OpenCV4学习笔记(30)——透视变换(投影变换)

今天要整理的笔记是关于图像的透视变换的内容。

首先需要了解什么是透视变换,这里引用百度百科上的内容:

透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。

emmmmmm从严谨的定义来看的话,可能会有一点点抽象,那么具体到图像方面的透视变换呢,其实可以理解成将同一物体在某一视角所成的像,变换到另一个视角所成的像。

假设我们使用多个摄像头在不同的视角对目标物体进行拍摄成像,那么这里形成的多张图像虽然包含的是同一个目标物体的信息,但是它们具有不同的视平面。那么将某一幅图像中的内容变换到另一幅图像中的内容,也就是将目标物体从一个摄像头的视平面转换到另一个摄像头的视平面,这就是对于图像的透视变换。
我们也可以说是将图像投影到一个新的视平面,所以透视变换也被称为投影变换。

透视变换经常用在图像矫正、全景拼接等场景,尤其是对于很多的文本扫描图像,有时候会因为放置的原因、或者是拍摄角度的原因而导致文本区域倾斜,这就影响了后续的文本分析识别工作,因此需要把类似这样不合视角要求的图像矫正为正确的、合适的视角,以方便下一步的布局分析与文字识别等工作。

对于图像的透视变换,其实说到底就是矩阵的变换,因为对于计算机而言,图像就是一个矩阵,只不过其数据具有一定的规范而已。对于两个矩阵之间的变换,我们需要找到他们之间具有的联系,也就是第三个矩阵——变换矩阵(单应矩阵)。我们通过这个变换矩阵,对输入的矩阵进行计算,得到的就是输出的变换完成后的矩阵。

那么具体到两张图像,输入图像是原始视角图像,输出图像就是透视变换后的另一视角图像。首先我们需要找到在这两张图像上的对应点集,然后计算这些对应点之间的联系,也就是一个变换矩阵,再使用这个变换矩阵去对输入的整幅图像进行计算,得到矫正后的图像。这就完成了一次图像的透视变换。

这个过程可以总结为:
1、寻找对应点集,也就是需要变换的区域;
2、计算对应点集之间的变换矩阵;
3、将对应点集之间的变换矩阵应用到两幅图像中;
4、完成图像的整体变换。

那么,其中非常重要的就是如何计算对应点集之间的变换矩阵,在OpenCV中提供了一个API来计算两个点集之间的3x3变换矩阵(单应矩阵),那就是findHomography(),其主要参数如下:
第一个参数src_point:原视平面的点集
第二个参数dst_point:目标视平面的点集
第三个参数method:计算变换矩阵的方式,有以下几种选择(在这里选用默认值即可,但是RANSAC和RHO相对来说很更常用一些):
(1)0 - 利用所有点的常规方法
(2)RANSAC - RANSAC - 基于RANSAC的鲁棒算法
(3)LMEDS - 最小中值鲁棒算法
(4)RHO - PROSAC - 基于PROSAC的鲁棒算法
后面的参数是和RANSAC与RHO这两种变换方法相关的,使用默认值即可。

通过这个API我们就能得到对应点集之间变换矩阵(单应矩阵),然后再通过warpPerspective()这个API完成将图像从一个视平面映射到另一个视平面的透视变换操作。
warpPerspective()这个API可以说是非常的见名知义了,函数名warpPerspective这个单词本身就是“透视”的意思。其参数如下:
第一个参数src:输入的需要透视变换的图像;
第二个参数dst:输出的透视变换后的图像;
第三个参数M: 先前得到的3x3 变换矩阵(单应矩阵);
第四个参数dsize:输出图像的尺寸;
第五个参数flsg:插值方式,默认值即可;
第六个参数borderMode:边界填充方式,默认值即可;
第七个参数borderValue:常量边界时的填充值,默认值即可。

下面是具体代码:

	//读取图像并进行预处理,将要矫正的区域提取出来
	Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\case1r.png");
	imshow("test_image", test_image);
	Mat binary_image, gray_image;
	cvtColor(test_image, gray_image, COLOR_BGR2GRAY);
	threshold(gray_image, binary_image, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	imshow("binary_image", binary_image);
	//轮廓发现
	vector<vector<Point>> contours;
	findContours(binary_image, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//寻找最大面积轮廓,也就是要进行矫正的区域轮廓
	vector<float> area;
	for (int i = 0; i < contours.size(); i++)
	{
		area.push_back(contourArea(contours[i]));				//包含所有轮廓面积的集合
	}
	//获得最大面积轮廓的索引,获得的索引必须为二维数组[ col , row ]
	double minVal, maxVal;
	int minIdx[2], maxIdx[2];		      //maxIdx中的row坐标即为最大面积轮廓的索引
	minMaxIdx(area, &minVal, &maxVal, minIdx, maxIdx);			
	RotatedRect min_rect = minAreaRect(contours[maxIdx[1]]);			//面积最大轮廓的最小外接旋转矩形
	//获取该矩形的尺寸
	int width = min_rect.size.width;
	int height = min_rect.size.height;
	//获取该矩形的四个点,获取顺序为:从右下角的点开始,顺时针获取各点
	Point2f min_rect_point[4];
	min_rect.points(min_rect_point);
	//将获取得到的四个Point2f类型的顶点转换为Point类型后存放到一个点向量中
	vector<Point> src_point;
	for (int j = 0; j < 4; j++)
	{
		int x = min_rect_point[j].x;
		int y = min_rect_point[j].y;
		src_point.push_back(Point(x, y));
	}
	//定义要透视变换到的视平面,由四个顶点确定;存放顶点顺序决定透视变换时的方向
	vector<Point> dst_point;
	dst_point.push_back(Point(0, height));
	dst_point.push_back(Point(0, 0));
	dst_point.push_back(Point(width, 0));
	dst_point.push_back(Point(width, height));

	//计算对应点的 3x3 变换矩阵
	Mat M = findHomography(src_point, dst_point);
	//进行整幅图像的透视变换
	Mat result;
	warpPerspective(test_image, result, M, Size(width, height));
	imshow("result", result);

在上述代码中,使用了一张倾斜的文档图像,如下图:
在这里插入图片描述
我们要完成的效果就是将这份文档给矫正到正常的视角上来,所以需要先找到定位到这份文档的区域。我们使用二值分析来把最外层的边框给提取出来,然后再通过轮廓分析,来获取最大面积轮廓以及这个最大轮廓的最小外接旋转矩形的四个顶点。其二值图像如下:
在这里插入图片描述

接着将得到的这四个顶点,与我们需要转换到的视平面上的四个顶点进行对应,通过这两个视平面上的对应点集来获得变换矩阵(单应矩阵)。

最后将这个变换矩阵(单应矩阵)应用到整幅图像上,得到我们的最终结果图,效果如下图所示:
在这里插入图片描述
可以看到,我们成功的把文档该矫正到了正常视角上了,需要注意的是,我们进行透视变换时的输出尺寸选择的是最大轮廓的最小外接矩的宽和高,如果直接使用整幅图像的高和宽,就会将图像中的不感兴趣区域也进行透视变换了,输出结果中除了矫正后的文档,还会有其他的干扰元素。

今天的笔记整理到此结束,谢谢阅读~

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

发布了36 篇原创文章 · 获赞 43 · 访问量 1755

猜你喜欢

转载自blog.csdn.net/weixin_45224869/article/details/104994445
今日推荐