opencv---透視校正

問題描述:將一個變形的圖像還原至正常視角下的形狀。

如圖

思路:二值化處理+形態學操作+輪廓尋找+檢測直線+尋找四個交點+透視變換

結果

直線檢測和角點尋找的結果

代碼實現

#include<iostream>
#include<opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
	//加載圖像
	Mat src = imread("1.jpg");
	if (src.empty())
	{
		cout << "no image!" << endl;
		return -1;
	}
	imshow("src", src);
	//二值化
	Mat gray, binary;
	cvtColor(src, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	imshow("binary", binary);
	//形態學處理,消除微小顆粒
	Mat closeImg;
	
	Mat kern1 = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
	morphologyEx(binary, closeImg, MORPH_CLOSE, kern1, Point(-1, -1));
	imshow("close", closeImg);
	bitwise_not(closeImg, closeImg);
	imshow("not", closeImg);
	//尋找輪廓
	int width = src.cols;
	int height = src.rows;
	vector<vector<Point>>contours;
	vector<Vec4i>hie;
	Mat mask = Mat::zeros(src.size(), CV_8UC3);
	findContours(closeImg, contours, hie, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
	for (size_t i = 0; i < contours.size(); i++)
	{
		Rect rect = boundingRect(contours[i]);
		if (rect.width > width/2 && rect.height > height/2 && rect.width < src.cols - 5)
		{
			drawContours(mask, contours, static_cast<int>(i), Scalar(0, 0, 255), 2, 8, hie, 0, Point());
		}
	}
	imshow("mask", mask);
	//檢測直線
	Mat gray_mask;
	int accu = min(0.5*width, 0.5*height);
	cvtColor(mask, gray_mask, COLOR_BGR2GRAY);
	imshow("gray_mask", gray_mask);
	vector<Vec4i>lines;
	HoughLinesP(gray_mask, lines, 1, 3.1415 / 180.0, accu, accu, 0);
	Mat mask_line = Mat::zeros(mask.size(), CV_8UC3);
	for (size_t i = 0; i < lines.size(); i++)
	{
		line(mask_line, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(0, 0, 255), 2, 8,0);
	}
	imshow("lines", mask_line);
	cout << "直線數量:" << lines.size() << endl;
	//尋找並定位上下左右四條直線
	int deltaH = 0;//定義高度差
	int deltaW = 0;//寬度差
	Vec4i topLine, bottomLine, leftLine, rightLine;
	for (int i = 0; i < lines.size(); i++)
	{
		Vec4i ln = lines[i];
		deltaH = abs(ln[3] - ln[1]);
		deltaW = abs(ln[2] - ln[0]);
		//double slope = (ln[3] - ln[1]) / (ln[2] - ln[0] + 0.00001);
		if (ln[1]<height / 2.0 && ln[3] < height / 2.0 && deltaH < accu - 1)
		{
			topLine = lines[i];
		}
		if (ln[1]>height / 2.0 && ln[3] > height / 2.0 && deltaH < accu - 1)
		{
			bottomLine = lines[i];
		}
		if (ln[0] < width / 2.0 &&  ln[2]<width / 2.0 && deltaW < accu - 1)
		{
			leftLine = lines[i];
		}
		if (ln[0] > width / 2.0  && ln[2]>width / 2.0 && deltaW < accu - 1)
		{
			rightLine = lines[i];
		}
	}
	cout << "topLine:" << topLine[0] << "," << topLine[1] << ";" << topLine[2] << "," << topLine[3] << endl;
	cout << "bottomLine:" << bottomLine[0] << "," << bottomLine[1] << ";" << bottomLine[2] << "," << bottomLine[3] << endl;
	cout << "leftLine:" << leftLine[0] << "," << leftLine[1] << ";" << leftLine[2] << "," << leftLine[3] << endl;
	cout << "rightLine:" << rightLine[0] << "," << rightLine[1] << ";" << rightLine[2] << "," << rightLine[3] << endl;
	//擬合四條直線方程
	//top
	float k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
	float c1 = topLine[1] - k1 * topLine[0];
	cout << "k1=" << k1 << ",c1=" << c1 << endl;
	//bottom
	float k2 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
	float c2 = bottomLine[1] - k2 * bottomLine[0];
	cout << "k2=" << k2 << ",c2=" << c2 << endl;
	//left
	float k3 = float(leftLine[3] - leftLine[1]) /float (leftLine[2] - leftLine[0]);
	float c3 = leftLine[1] - k3 * leftLine[0];
	cout << "k3=" << k3 << ",c3=" << c3 << endl;
	//right
	float k4 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
	float c4 = rightLine[1] - k4 * rightLine[0];
	cout << "k4=" << k4 << ",c4=" << c4 << endl;
	//計算四角
	Point pt1, pt2, pt3, pt4;
	//左上
	pt1.x = static_cast<int>((c1 - c3) / (k3 - k1));
	pt1.y = static_cast<int>(k1*pt1.x + c1);
	//右上
	pt2.x = static_cast<int>((c4 - c1) / (k1 - k4));
	pt2.y = static_cast<int>(k1*pt2.x + c1);
	//左下
	pt3.x = static_cast<int>((c2 - c3) / (k3 - k2));
	pt3.y = static_cast<int>(k2*pt3.x + c2);
	//右下
	pt4.x = static_cast<int>((c4 - c2) / (k2 - k4));
	pt4.y = static_cast<int>(k2*pt4.x + c2);
	circle(mask_line, pt1, 2, Scalar(0, 255, 0), -1, 8);
	circle(mask_line, pt2, 2, Scalar(0, 255, 0), -1, 8);
	circle(mask_line, pt3, 2, Scalar(0, 255, 0), -1, 8);
	circle(mask_line, pt4, 2, Scalar(0, 255, 0), -1, 8);
	imshow("mask_line_point", mask_line);
	cout << "pt1(x, y)=" << pt1.x << "," << pt1.y << endl;
	cout << "pt2(x, y)=" << pt2.x << "," << pt2.y << endl;
	cout << "pt3(x, y)=" << pt3.x << "," << pt3.y << endl;
	cout << "pt4(x, y)=" << pt4.x << "," << pt4.y << endl;
	//透視變換
	//輸入點
	Point2f src_corners[4];
	src_corners[0] = pt1;
	src_corners[1] = pt2;
	src_corners[2] = pt3;
	src_corners[3] = pt4;
	//輸出點
	Point2f dst_corners[4];
	dst_corners[0] = Point2f(static_cast<float>(0), static_cast<float>(0));
	dst_corners[1] = Point2f(static_cast<float>(width), static_cast<float>(0));
	dst_corners[2] = Point2f(static_cast<float>(0), static_cast<float>(height));
	dst_corners[3] = Point2f(static_cast<float>(width), static_cast<float>(height));
	Mat resultImg = Mat::zeros(src.size(), CV_8UC3);
	//Mat warpMat(3,3,CV_32F);
	Mat warpMat = getPerspectiveTransform(src_corners, dst_corners);
	cout << warpMat.type() << endl;
	warpPerspective(src, resultImg, warpMat, resultImg.size());
	//resultImg.convertTo(resultImg, CV_8U);
	imshow("result", resultImg);
	

	waitKey(0);
	return 0;
}

代碼解釋:

(1)爲什麼進行二值化而不是canny邊緣檢測?

答:因爲我們要提取的是卡片的整體邊界,而進行canny邊緣檢測處理後,卡片中的文字邊緣也會對後續的邊界輪廓尋找產生干擾,因此不如使用二值化操作過濾文字干擾。

(2)爲什麼進行形態學閉操作?

答:因爲經過二值化處理後,二值圖像中會出現許多微小的顆粒,經過閉操作可以消除這些顆粒的影響。

(3)爲什麼要進行直線檢測?

答:通過直線檢測可以確定邊緣輪廓的上下左右四條直線,再通過直線擬合,解二元一次方程組後便可計算出四個交點的座標。

(4)關於透視變換。

在opencv提供的函數裏有getPerspectiveTransform,他需要提供原圖像的四個角點座標,和輸出圖像的四個角點座標,這也是上面爲什麼要進行直線檢測的原因。

注意:warpPerspective而不是warpAffine,真的不要太蠢,用成了warpAffine,找了半天bug才發現是自己用錯了函數,簡直不要太蠢!而給出的錯誤提示也很有意思,是這樣的

Assertion failed ((M0.type() == 5 || M0.type() == 6) && M0.rows == 2 && M0.cols == 3) in warpAffine

這是說生成的變換矩陣有問題,到底什麼問題呢?驢脣不對馬嘴能沒問題嘛。

所以說,遇到bug不要急着找解決方案,大罵什麼太垃圾,百分之九十九的問題是你自己的代碼有問題,還是仔細審覈一下代碼爲上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章