OpenCV实战1——图像矫正技术

当我们遇到一些歪歪扭扭的照片,比如下面这些照片:

  

这些图片让人看得真不舒服!我们可以用PS来处理?但如果有1000张图,我们只能交给计算机去做!

对于图像矫正的问题,在图像处理领域很多,比如人民币的矫正、文本的矫正、车牌的矫正、身份证矫正等等。这些都是因为拍摄者总不可能100%正确地拍摄好图片,这就要求我们通过后期的图像处理技术将图片还原好,才能进一步做后面的处理,比如数字分割啊数字识别啊,不然歪歪扭扭的文字数字,想识别出来估计就很难了。

为了将图片尽可能地矫正过来,可以使用OpenCV。

算法步骤(基于轮廓提取的矫正算法):

  1. 图片灰度化
  2. 阈值二值化
  3. 检测轮廓
  4. 寻找轮廓的包围矩阵,并且获取角度
  5. 根据角度进行旋转矫正

废话不多说,直接上代码:

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

using namespace std;
using namespace cv;

int main()
{
	Mat srcImage = imread("D:\\Program Files\\OpenCV\\opencv\\sources\\samples\\data\\imageTextR.png");

	if (srcImage.cols>1000 || srcImage.rows>800) {//图片过大,进行降采样
		pyrDown(srcImage, srcImage);
		pyrDown(srcImage, srcImage);
		pyrDown(srcImage, srcImage);
	}

	Mat grayImage, binaryImage;
	cvtColor(srcImage, grayImage, CV_BGR2GRAY);//转化灰度图
	adaptiveThreshold(grayImage, binaryImage, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 0);//自适应滤波

	vector<vector<Point> > contours;
	//RETR_EXTERNAL:表示只检测最外层轮廓
	//CHAIN_APPROX_NONE:获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1 
	findContours(binaryImage, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	//获得矩形包围框,之所以先用boundRect,是为了使用它的area方法求面积,而RotatedRect类不具备该方法
	float area = boundingRect(contours[0]).area(); 
	int index = 0;
	for (int i = 1; i<contours.size(); i++)
	{

		if (boundingRect(contours[i]).area()>area)
		{
			area = boundingRect(contours[i]).area();
			index = i;
		}
	}
	Rect maxRect = boundingRect(contours[index]);//找出最大的那个矩形框(即最大轮廓)
	Mat ROI = binaryImage(maxRect);
	imshow("maxROI", ROI);

	RotatedRect rect = minAreaRect(contours[index]);//获取对应的最小矩形框,这个长方形是倾斜的
	Point2f rectPoint[4];
	rect.points(rectPoint);//获取四个顶点坐标,这是RotatedRect类定义的方法
	double angle = rect.angle;
	//angle += 90;
	Point2f center = rect.center;


	drawContours(binaryImage, contours, -1, Scalar(255), CV_FILLED);
	// srcImage.copyTo(RoiSrcImg,binaryImage);
	Mat RoiSrcImg = Mat::zeros(srcImage.size(), srcImage.type());
	srcImage.copyTo(RoiSrcImg);

	Mat Matrix = getRotationMatrix2D(center, angle, 0.8);//得到旋转矩阵算子,0.8缩放因子
	warpAffine(RoiSrcImg, RoiSrcImg, Matrix, RoiSrcImg.size(), 1, 0, Scalar(0, 0, 0));

	imshow("src Image", srcImage);
	imshow("contours", binaryImage);
	imshow("recorrected", RoiSrcImg);

	while (waitKey() != 'q') {}
	return 0;
}

 结果:

 

函数说明:

  OpenCV3学习(9.3)图像轮廓的多边形逼近approxPolyDP、轮廓包围框\圆\椭圆、凸包

OpenCV3学习(5.1)——图像变换之缩放、金字塔、仿射、透射

OpenCV3学习(9.1)图像轮廓查找与绘制函数findContours()与drawContours()

 

对于人民币图像和发票图像他们有明显的的边界轮廓,采用基于轮廓方法很有效。而文本图像没有。文本图像的背景是白色的,所以我们有时可能没有办法像人民币发票那类有明显边界的矩形物体那样,提取出轮廓并旋转矫正。

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

对于这种情况,我们采用另一种方法:基于直线探测的矫正算法

算法步骤:

  1. 用霍夫线变换探测出图像中的所有直线
  2. 计算出每条直线的倾斜角,求他们的平均值
  3. 根据倾斜角旋转矫正
// 基于直线探测的矫正算法
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

#define ERROR 1234

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


//逆时针旋转图像degree角度(原尺寸)    
void 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 CalcDegree(const Mat &srcImage, Mat &dst)
{
	Mat midImage, dstImage;

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

	//通过霍夫变换检测直线
	vector<Vec2f> lines;
	HoughLines(midImage, lines, 1, CV_PI / 180, 300, 0, 0);//第5个参数就是阈值,阈值越大,检测精度越高
														   //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;
	}

	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;

		line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色

		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 ImageRecify(const char* pInFileName, const char* pOutFileName)
{
	double degree;
	Mat src = imread(pInFileName);
	imshow("原始图", src);
	Mat dst;
	//倾斜角度矫正
	degree = CalcDegree(src, dst);
	if (degree == ERROR)
	{
		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);
}


int main()
{
	ImageRecify("D:\\Program Files\\OpenCV\\opencv\\sources\\samples\\data\\imageTextR.png", "FinalImage.png");
	waitKey();
	return 0;
}

结果:

 

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

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

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

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

 转载自:https://www.cnblogs.com/skyfsm/p/6902524.html#undefined

代码地址:https://github.com/liuzheCSDN/OpenCV

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值