openCV4图像矫正实现方案

不想你离开。 提交于 2020-01-16 11:00:25

需求:

         在图像处理时,有时候会因为拍照的角度不一致导致得到的图像出现了一定程度的偏移,需要对图像进行矫正,如下面两张图片,

图1

图2

比如图一,我要想将标签矫正,并且把标签整个抠出来保存,该怎么做?那就涉及到了图像的矫正和感兴趣区域提取两大技术了。

 

总的来说,要进行进行图像矫正,至少有以下几项知识储备:

  • 轮廓提取技术
  • 霍夫变换知识
  • ROI感兴趣区域知识

下面以标签矫正、发票矫正、文本矫正为例,一步步剖析如何实现图像矫正。

首先分析如何矫正标签。

比如我们要矫正这张标签,思路应该是怎么样?

 

首先分析这张图的特点。

在这张图里,标签有一定的倾斜角度,但是角度不大;标签的背景是黑色的,而且标签的边缘应该比较明显。

没错,我们就抓住标签的的边缘比较明显来做文章!我们是不是可以先把标签的轮廓找出来(找出来的轮廓当然就是一个大大的矩形),然后用矩形去包围它,得到他的旋转角度,然后根据得到的角度进行旋转,那样不就可以实现矫正了吗!

再详细地总结处理步骤:

图片灰度化

阈值二值化

检测轮廓

寻找轮廓的包围矩阵,并且获取角度

根据角度进行旋转矫正

对旋转后的图像进行轮廓提取

对轮廓内的图像区域抠出来,成为一张独立图像

我把该矫正算法命名为基于轮廓提取的矫正算法,因为其关键技术就是通过轮廓来获取旋转角度。

 

代码实现参考如下示例代码,

void GetContoursPic(Mat srcImg, Mat dstImg)
{
	imshow("原始图", srcImg);
	Mat gray, binImg;
	//灰度化
	cvtColor(srcImg, gray, COLOR_RGB2GRAY);
	//imshow("灰度图", gray);
	//二值化
	threshold(gray, binImg, 60, 200, CV_THRESH_BINARY);	
	///////////////////////////////////////////
	imshow("二值化", binImg);
	waitKey(0);

	vector<vector<Point> > contours;
	vector<Rect> boundRect(contours.size());
	//注意第5个参数为CV_RETR_EXTERNAL,只检索外框  
	findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	///////////////////////////////////////////////////
	cout << contours.size() << endl;

	for (int i = 0; i < contours.size(); i++)
	{
		//需要获取的坐标  
		CvPoint2D32f rectpoint[4];
		CvBox2D rect = minAreaRect(Mat(contours[i]));
		//获取4个顶点坐标  
		cvBoxPoints(rect, rectpoint); 
		//与水平线的角度  
		float angle = rect.angle;
		//cout << angle << endl;
		int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
		int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
		////rectangle(binImg, rectpoint[0], rectpoint[3], Scalar(255), 2);
		////面积太小的直接pass
		//if (line1 * line2 < 600)
		//{
		//	continue;
		//}

		double area = contourArea(contours[i]);
		if (area<3000)
		{
			continue;
		}

		//为了让正方形横着放,所以旋转角度是不一样的。竖放的,给他加90度,翻过来  
		if (line1 > line2)
		{
			angle = 90 + angle;
		}

		//新建一个感兴趣的区域图,大小跟原图一样大  
		Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3
		RoiSrcImg.setTo(0); //颜色都设置为黑色  
		//imshow("新建的ROI", RoiSrcImg);
		//对得到的轮廓填充一下  
		drawContours(binImg, contours, -1, Scalar(255), FILLED);

		//抠图到RoiSrcImg
		srcImg.copyTo(RoiSrcImg, binImg);

		//再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了  
		//namedWindow("RoiSrcImg", 1);
		//imshow("RoiSrcImg", RoiSrcImg);

		//创建一个旋转后的图像  
		Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
		RatationedImg.setTo(0);
		//对RoiSrcImg进行旋转  
		Point2f center = rect.center;  //中心点  
		Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵 
		warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射变换 
		//imshow("旋转之后", RatationedImg);
		imwrite("r.jpg", RatationedImg); //将矫正后的图片保存下来
		//waitKey(0);
		//destroyAllWindows();

	}

上面的算法可以很好的处理标签和发票两种情况的倾斜矫正,但是对文本矫正效果并不理想,原因就在于,标签图像和发票图像他们有明显的的边界轮廓,而文本图像没有。文本图像的背景是白色的,所以我们没有办法像标签及发票那类有明显边界的矩形物体那样,提取出轮廓并旋转矫正。

经过深入分析可以看出,虽然文本类图像没有明显的边缘轮廓,但是他们有一个很重要的特征,那就是每一行文字都是呈现一条直线形状,而且这些直线都是平行的!

对于这种情况,我想到了另一种方法:基于直线探测的矫正算法

首先介绍一下我的算法思路:

用霍夫线变换探测出图像中的所有直线

计算出每条直线的倾斜角,求他们的平均值

根据倾斜角旋转矫正

最后根据文本尺寸裁剪图片

然后给出OpenCV的实现算法,参考示例代码的 void txtImgRecify(Mat src,Mat& dst) 函数。

//度数转换
double CImageOrientationCorrection::DegreeTrans(double theta)
{
	double res = theta / CV_PI * 180;
	return res;
}

//逆时针旋转图像degree角度(原尺寸)
void CImageOrientationCorrection::rotateImage(Mat src, Mat& img_rotate, double degree)
{
	//旋转中心为图像中心    
	Point2f center;
	center.x = float(src.cols / 2.0);
	center.y = float(src.rows / 2.0);
	int length = 0;
	length = sqrt(src.cols*src.cols + src.rows*src.rows);
	//计算二维旋转的仿射变换矩阵  
	Mat M = getRotationMatrix2D(center, degree, 1);
	warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色  
}

//通过霍夫变换计算角度
double CImageOrientationCorrection::CalcDegree(const Mat &srcImage, Mat &dst)
{
	Mat midImage, dstImage;

	Canny(srcImage, midImage, 50, 200, 3);
	cvtColor(midImage, dstImage, CV_GRAY2BGR);

	//通过霍夫变换检测直线
	vector<Vec2f> lines;
	//第5个参数就是阈值,阈值越大,检测精度越高
	HoughLines(midImage, lines, 1, CV_PI / 180, 300, 0, 0);
	//cout << lines.size() << endl;
	//由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
	//所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值
	if (!lines.size())
	{
		HoughLines(midImage, lines, 1, CV_PI / 180, 200, 0, 0);
	}
	//cout << lines.size() << endl;
	if (!lines.size())
	{
		HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
	}
	//cout << lines.size() << endl;
	if (!lines.size())
	{
		cout << "没有检测到直线!" << endl;
		return ERROR_RETURNS;
	}
	float sum = 0;
	//依次画出每条线段
	for (size_t i = 0; i < lines.size(); i++)
	{
		float rho = lines[i][0];
		float theta = lines[i][1];
		Point pt1, pt2;
		//cout << theta << endl;
		double a = cos(theta), b = sin(theta);
		double x0 = a * rho, y0 = b * rho;
		pt1.x = cvRound(x0 + 1000 * (-b));
		pt1.y = cvRound(y0 + 1000 * (a));
		pt2.x = cvRound(x0 - 1000 * (-b));
		pt2.y = cvRound(y0 - 1000 * (a));
		//只选角度最小的作为旋转角度
		sum += theta;
		//Scalar函数用于调节线段颜色
		line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA);

		imshow("直线探测效果图", dstImage);
	}
	//对所有角度求平均,这样做旋转效果会更好
	float average = sum / lines.size();
	cout << "average theta:" << average << endl;
	double angle = DegreeTrans(average) - 90;
	rotateImage(dstImage, dst, angle);
	//imshow("直线探测效果图2", dstImage);
	return angle;
}

void CImageOrientationCorrection::txtImgRecify(Mat src, Mat& dst)
{
	double degree;	
	imshow("原始图", src);	
	//倾斜角度矫正
	degree = CalcDegree(src, dst);
	if (degree == ERROR_RETURNS)
	{
		cout << "矫正失败!" << endl;
		return;
	}
	rotateImage(src, dst, degree);
	cout << "angle:" << degree << endl;
	imshow("旋转调整后", dst);
	//根据先验知识,估计好文本的长宽,再裁剪下来
	Mat resulyImage = dst(Rect(0, 0, dst.cols, 500));
	imshow("裁剪之后", resulyImage);
	imwrite("recified.jpg", resulyImage);
	waitKey(0);
	destroyAllWindows();
}

我们发现矫正之后的图像有较多留白,影响观看,所以需要进一步裁剪,保留文字区域。

可以看出,基于直线探测的矫正算法在文本处理上效果真的很不错!

最后总结一下两个算法的应用场景:

基于轮廓提取的矫正算法更适用于车牌、身份证、人民币、书本、标签、发票一类矩形形状而且边界明显的物体矫正。

基于直线探测的矫正算法更适用于文本类的矫正。

完整的示例代码获取地址:https://download.csdn.net/download/shufac/12041413

参考:https://www.cnblogs.com/skyfsm/p/6902524.html

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!