OpenCv-C++-小案例实战-透视变换

版权声明:本文为博主原创文章,转载请附上博文链接! https://blog.csdn.net/Daker_Huang/article/details/85397624

现在有这么一张图片:
在这里插入图片描述
如果说我们要做OCR文字识别,识别卡片中的文字,如果直接检测并识别文字,对于这张图片,也许能够识别,但对于其余像这样的图片,成功率肯定会比较低,如果这张图片是正的,而没有任何偏转,识别率肯定会很高。

不管识别什么,在识别前肯定都要进行图像预处理,接下来就采用OpenCv C++的图像处理方法进行透视变换,将这样一张图片还原为“正”的。

解决思路:
在进行透视变换前,我们需要知道原图目标的四个角点的坐标点,所以,需要进行霍夫直线检测,轮廓查找等操作获取坐标点,之后才可以进行透视变换的操作。

解决方法:
1、图像二值化;
2、形态学操作;
3、轮廓发现与绘制;
4、霍夫直线检测;
5、寻找4条直线两端坐标;
6、方程拟合;
7、求卡片4角点的坐标;
8、透视变换。

1)二值化操作

Mat binary;
	threshold(gray_src, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("binary Image", binary);

运行结果:
在这里插入图片描述

2)形态学操作
这里主要是填充中间的黑色区域,所以用了腐蚀操作。

Mat dst;
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	erode(binary, dst, kernel, Point(-1, -1), 1);//1表示迭代1次
	//bitwise_not(dst, dst);
	imshow("erode Image", dst);

运行结果:
在这里插入图片描述

3)轮廓发现与绘制

//---------------轮廓发现----------------
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(dst, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));

	//---------------轮廓绘制-----------------
	int height = src.rows;
	int width = src.cols;
	Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
	for (size_t i = 0; i < contours.size(); i++)
	{
		Rect rect = boundingRect(contours[i]); //获取最小外接矩形

		if (rect.width > width / 2 && rect.width < width - 5)
		{
			drawContours(drawImg, contours, static_cast<int>(i),
				Scalar(0, 0, 255), 2, 8, hierarchy, 0, Point());
		}
	}
	imshow("drawContours Image", drawImg);

运行结果:
在这里插入图片描述
进行到这一步,我么就可以进行霍夫直线检测了。

4)霍夫直线检测

//霍夫直线检测
	Mat houghImg;
	cvtColor(drawImg, houghImg, CV_BGR2GRAY);
	vector<Vec4i> lines;
	//int accu = min(width/2,height/2);
	HoughLinesP(houghImg, lines, 1, CV_PI / 180.0, 30, 100, 0);
	Mat linesImg = Mat::zeros(src.size(), CV_8UC3);
	for (size_t i = 0; i < lines.size(); i++)
	{
		Vec4i ln = lines[i];
		line(linesImg, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);

	}
	cout << "直线检测的数量:" << lines.size() << endl;
	imshow("HoughLines Detect Image", linesImg);

运行结果:
在这里插入图片描述
在这之后就可以算出上、下、左、右四条边所在的直线方程,然后两条直线的交点就能求出来。

5)寻找4条直线两端坐标

//寻找与定位上下左右4条线
	int theata = 0;
	Vec4i topLine, bottomLine;
	Vec4i leftLine, rightLine;
	for (size_t i = 0; i < lines.size(); i++)
	{
		Vec4i ln = lines[i];
		theata = abs(ln[3] - ln[1]);
		if (ln[3] < height / 2 && ln[1] < height / 2 && theata < (height / 2) - 1)
		{
			topLine = lines[i];
		}
		if (ln[3] > height / 2 && ln[1] > height / 2 && theata < (height / 2) - 1)
		{
			bottomLine = lines[i];
		}
		if (ln[2] < width / 2 && ln[0] < width / 2)
		{
			leftLine = lines[i];
		}
		if (ln[2] > width / 2 && ln[0] > width / 2)
		{
			rightLine = lines[i];
		}

	}
	//打印直线两端坐标
	cout << "topLine Point(x1,y1):" << topLine[0] << "," << topLine[1] << "  " << "topLine Point(x2,y2):" << topLine[2] << "," << topLine[3] << endl;
	cout << "bottomLine Point(x1,y1):" << bottomLine[0] << "," << bottomLine[1] << "  " << "bottomLine Point(x2,y2):" << bottomLine[2] << "," << bottomLine[3] << endl;
	cout << "leftLine Point(x1,y1):" << leftLine[0] << "," << leftLine[1] << "  " << "leftLine Point(x2,y2):" << leftLine[2] << "," << leftLine[3] << endl;
	cout << "rightLine Point(x1,y1):" << rightLine[0] << "," << rightLine[1] << "  " << "rightLine Point(x2,y2):" << rightLine[2] << "," << rightLine[3] << endl;
	//绘制找出来的4条线
	line(linesImg, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	line(linesImg, Point(bottomLine[0], bottomLine[1]), Point(bottomLine[2], bottomLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	line(linesImg, Point(leftLine[0], leftLine[1]), Point(leftLine[2], leftLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	line(linesImg, Point(rightLine[0], rightLine[1]), Point(rightLine[2], rightLine[3]), Scalar(255, 0, 0), 2, 8, 0);

运行结果:
在这里插入图片描述

6)方程拟合
因为都是直线,所以用y=kx+b就可以求出来。
k=(y1-y2)/(x1-x2);
b=y-kx
而(x1,y1),(x2,y2)之前我们也有求出来。

//上端方程函数
	float k1, b1;
	k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
	b1 = float(topLine[1] - k1 * (topLine[0]));

	//右端方程函数
	float k2, b2;
	k2 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
	b2 = float(rightLine[1] - k2 * (rightLine[0]));

	//下端方程函数
	float k3, b3;
	k3 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
	b3 = float(bottomLine[1] - k3 * (bottomLine[0]));

	//左端方程函数
	float k4, b4;
	k4 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
	b4 = float(leftLine[1] - k4 * (leftLine[0]));

7)求卡片4角点的坐标
两条函数的交点就是坐标点,所以这里求出4条直线两两相交的坐标点。

//左上角交点坐标
	Point p1;
	p1.x = float((b4 - b1) / (k1 - k4));
	p1.y = float(k1 * p1.x + b1);

	//右上角交点坐标
	Point p2;
	p2.x = float((b2 - b1) / (k1 - k2));
	p2.y = float(k1 * p2.x + b1);

	//右下角交点坐标
	Point p3;
	p3.x = float((b3 - b2) / (k2 - k3));
	p3.y = float(k3 * p3.x + b3);

	//左下角交点坐标
	Point p4;
	p4.x = float((b4 - b3) / (k3 - k4));
	p4.y = float(k3 * p4.x + b3);

	circle(linesImg, p1, 2, Scalar(0, 255, 0), 2, 8, 0);
	circle(linesImg, p2, 2, Scalar(0, 255, 0), 2, 8, 0);
	circle(linesImg, p3, 2, Scalar(0, 255, 0), 2, 8, 0);
	circle(linesImg, p4, 2, Scalar(0, 255, 0), 2, 8, 0);
	cout << "--------------------交点坐标----------------" << endl;
	cout << "左上角交点坐标:" << p1.x << "," << p1.y << endl;
	cout << "右上角交点坐标:" << p2.x << "," << p2.y << endl;
	cout << "右下角交点坐标:" << p3.x << "," << p3.y << endl;
	cout << "左下角交点坐标:" << p4.x << "," << p4.y << endl;
	
	imshow("four contours Image", linesImg);

运行结果:
在这里插入图片描述
上图中,4个绿色的点就是相交的坐标点,坐标点数值为:
在这里插入图片描述

8)透视变换
求出来原图的4个坐标点,现在就可以进行透视变换了。

//透视变换前的四个点所在坐标
	vector<Point2f> src_corner(4);
	src_corner[0] = p1;
	src_corner[1] = p2;
	src_corner[2] = p3;
	src_corner[3] = p4;

	//透视变换后的四个点所在坐标
	vector<Point2f> dst_corner(4);
	dst_corner[0] = Point(0,0);
	dst_corner[1] = Point(src.cols,0);
	dst_corner[2] = Point(src.cols,src.rows);
	dst_corner[3] = Point(0,src.rows);

	Mat resultImg;
	Mat M = getPerspectiveTransform(src_corner, dst_corner);
	warpPerspective(src, resultImg, M, resultImg.size(),INTER_LINEAR);
	imshow("result Image", resultImg);

运行结果:
在这里插入图片描述
那么,如果以这样子去进行OCR文字识别的话,成功率应该会有所提升。

附上所有代码:

#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>

using namespace cv;
using namespace std;

Mat src, gray_src;
int main(int argc, char** argv)
{
	src = imread("D:/test/card.png");
	if (src.empty())
	{
		cout << "图像未找到" << endl;
		return -1;
	}
	imshow("input Image", src);

	cvtColor(src, gray_src, CV_BGR2GRAY);
	//imshow("gray Image", gray_src);

	//------------二值化操作-------------
	Mat binary;
	threshold(gray_src, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("binary Image", binary);

	//---------------形态学操作-------------
	Mat dst;
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	erode(binary, dst, kernel, Point(-1, -1), 1);//1表示迭代1次
	//bitwise_not(dst, dst);
	imshow("erode Image", dst);

	//---------------轮廓发现----------------
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(dst, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));

	//---------------轮廓绘制-----------------
	int height = src.rows;
	int width = src.cols;
	Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
	for (size_t i = 0; i < contours.size(); i++)
	{
		Rect rect = boundingRect(contours[i]); //获取最小外接矩形

		if (rect.width > width / 2 && rect.width < width - 5)
		{
			drawContours(drawImg, contours, static_cast<int>(i),
				Scalar(0, 0, 255), 2, 8, hierarchy, 0, Point());
		}
	}
	imshow("drawContours Image", drawImg);

	//霍夫直线检测
	Mat houghImg;
	cvtColor(drawImg, houghImg, CV_BGR2GRAY);
	vector<Vec4i> lines;
	//int accu = min(width/2,height/2);
	HoughLinesP(houghImg, lines, 1, CV_PI / 180.0, 30, 100, 0);
	Mat linesImg = Mat::zeros(src.size(), CV_8UC3);
	for (size_t i = 0; i < lines.size(); i++)
	{
		Vec4i ln = lines[i];
		line(linesImg, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);

	}
	cout << "直线检测的数量:" << lines.size() << endl;
	imshow("HoughLines Detect Image", linesImg);


	//寻找与定位上下左右4条线
	int theata = 0;
	Vec4i topLine, bottomLine;
	Vec4i leftLine, rightLine;
	for (size_t i = 0; i < lines.size(); i++)
	{
		Vec4i ln = lines[i];
		theata = abs(ln[3] - ln[1]);
		if (ln[3] < height / 2 && ln[1] < height / 2 && theata < (height / 2) - 1)
		{
			topLine = lines[i];
		}
		if (ln[3] > height / 2 && ln[1] > height / 2 && theata < (height / 2) - 1)
		{
			bottomLine = lines[i];
		}
		if (ln[2] < width / 2 && ln[0] < width / 2)
		{
			leftLine = lines[i];
		}
		if (ln[2] > width / 2 && ln[0] > width / 2)
		{
			rightLine = lines[i];
		}

	}
	//打印直线两端坐标
	cout << "topLine Point(x1,y1):" << topLine[0] << "," << topLine[1] << "  " << "topLine Point(x2,y2):" << topLine[2] << "," << topLine[3] << endl;
	cout << "bottomLine Point(x1,y1):" << bottomLine[0] << "," << bottomLine[1] << "  " << "bottomLine Point(x2,y2):" << bottomLine[2] << "," << bottomLine[3] << endl;
	cout << "leftLine Point(x1,y1):" << leftLine[0] << "," << leftLine[1] << "  " << "leftLine Point(x2,y2):" << leftLine[2] << "," << leftLine[3] << endl;
	cout << "rightLine Point(x1,y1):" << rightLine[0] << "," << rightLine[1] << "  " << "rightLine Point(x2,y2):" << rightLine[2] << "," << rightLine[3] << endl;
	//绘制找出来的4条线
	line(linesImg, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	line(linesImg, Point(bottomLine[0], bottomLine[1]), Point(bottomLine[2], bottomLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	line(linesImg, Point(leftLine[0], leftLine[1]), Point(leftLine[2], leftLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	line(linesImg, Point(rightLine[0], rightLine[1]), Point(rightLine[2], rightLine[3]), Scalar(255, 0, 0), 2, 8, 0);
	//imshow("four contours Image", linesImg);
	
	//--------------------拟合方程,求方程-----------------
	//y=kx+b

	//上端方程函数
	float k1, b1;
	k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
	b1 = float(topLine[1] - k1 * (topLine[0]));

	//右端方程函数
	float k2, b2;
	k2 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
	b2 = float(rightLine[1] - k2 * (rightLine[0]));

	//下端方程函数
	float k3, b3;
	k3 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
	b3 = float(bottomLine[1] - k3 * (bottomLine[0]));

	//左端方程函数
	float k4, b4;
	k4 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
	b4 = float(leftLine[1] - k4 * (leftLine[0]));


	//-------------求交点坐标-------------
	//左上角交点坐标
	Point p1;
	p1.x = float((b4 - b1) / (k1 - k4));
	p1.y = float(k1 * p1.x + b1);

	//右上角交点坐标
	Point p2;
	p2.x = float((b2 - b1) / (k1 - k2));
	p2.y = float(k1 * p2.x + b1);

	//右下角交点坐标
	Point p3;
	p3.x = float((b3 - b2) / (k2 - k3));
	p3.y = float(k3 * p3.x + b3);

	//左下角交点坐标
	Point p4;
	p4.x = float((b4 - b3) / (k3 - k4));
	p4.y = float(k3 * p4.x + b3);

	circle(linesImg, p1, 2, Scalar(0, 255, 0), 2, 8, 0);
	circle(linesImg, p2, 2, Scalar(0, 255, 0), 2, 8, 0);
	circle(linesImg, p3, 2, Scalar(0, 255, 0), 2, 8, 0);
	circle(linesImg, p4, 2, Scalar(0, 255, 0), 2, 8, 0);
	cout << "--------------------交点坐标----------------" << endl;
	cout << "左上角交点坐标:" << p1.x << "," << p1.y << endl;
	cout << "右上角交点坐标:" << p2.x << "," << p2.y << endl;
	cout << "右下角交点坐标:" << p3.x << "," << p3.y << endl;
	cout << "左下角交点坐标:" << p4.x << "," << p4.y << endl;
	
	imshow("four contours Image", linesImg);


	//----------------透视变换---------------------
	//透视变换前的四个点所在坐标
	vector<Point2f> src_corner(4);
	src_corner[0] = p1;
	src_corner[1] = p2;
	src_corner[2] = p3;
	src_corner[3] = p4;

	//透视变换后的四个点所在坐标
	vector<Point2f> dst_corner(4);
	dst_corner[0] = Point(0,0);
	dst_corner[1] = Point(src.cols,0);
	dst_corner[2] = Point(src.cols,src.rows);
	dst_corner[3] = Point(0,src.rows);

	Mat resultImg;
	Mat M = getPerspectiveTransform(src_corner, dst_corner);
	warpPerspective(src, resultImg, M, resultImg.size(),INTER_LINEAR);
	imshow("result Image", resultImg);

	waitKey(0);
	return 0;


}

猜你喜欢

转载自blog.csdn.net/Daker_Huang/article/details/85397624