详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)


Posted in Python onJuly 01, 2019

前言

最近参加了大创项目,题目涉及到计算机视觉,学姐发了个修正图像的博客链接,于是打算用这个题目入门OpenCV。

分析问题

照片中的PPT区域总是沿着x,y,z三个轴都有倾斜(如下图),要想把照片翻转到平行位置,需要进行透视变换,而透视变换需要同一像素点变换前后的坐标。由此可以想到,提取矩形区域四个角的坐标作为变换前的坐标,变换后的坐标可以设为照片的四个角落,经过投影变换,矩形区域将会翻转并充满图像。

详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)

因此我们要解决的问题变为:提取矩形的四个角落、进行透视变换。

提取矩形角落坐标

矩形的检测主要是提取边缘,PPT显示部分的亮度通常高于周围环境,我们可以将图片阈值化,将PPT部分与周围环境明显的分别开来,这对后边的边缘检测非常有帮助。

检测矩形并提取坐标需要对图像进行预处理、边缘检测、提取轮廓、检测凸包、角点检测。

预处理

由于手机拍摄的照片像素可能会很高,为了加快处理速度,我们首先缩小图片,这里缩小了4倍。

pyrDown(srcPic,   shrinkedPic);    //减小尺寸 加快运算速度
pyrDown(shrinkedPic, shrinkedPic);

转化为灰度图

cvtColor(shrinkedPic, greyPic, COLOR_BGR2GRAY); //转化为灰度图

中值滤波

medianBlur(greyPic, greyPic, 7); //中值滤波

转为二值图片

threshold(greyPic, binPic, 80, 255, THRESH_BINARY); //阈值化为二值图片

此时图片已经变成了这个样子:

详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)

可见PPT部分已经与环境分离开来。

边缘检测与轮廓处理

进行Canny边缘检测

Canny(binPic, cannyPic, cannyThr, cannyThr*FACTOR); //Canny边缘检测

这里 cannyThr = 200, FACTOR = 2.5
可能由于边缘特征过于明显,系数在100-600范围(具体数字可能有出入,反正范围非常大)内产生的效果几乎相同。

提取轮廓

vector<vector<Point>> contours;  //储存轮廓
vector<Vec4i> hierarchy;
  
findContours(cannyPic, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);  //获取轮廓

findContour函数原型如下:

CV_EXPORTS_W void findContours( InputOutputArray image, OutputArrayOfArrays contours,
               OutputArray hierarchy, int mode,
              int method, Point offset = Point());

检测到的轮廓都存在contours里,每个轮廓保存为一个vector<Point>
hierarchy为可选的输出向量,包括图像的拓扑信息,这里可以选择不用。

我们可以反复调用drawContours函数将轮廓画出

linePic = Mat::zeros(cannyPic.rows, cannyPic.cols, CV_8UC3);
for (int index = 0; index < contours.size(); index++){    
    drawContours(linePic, contours, index, Scalar(rand() & 255, rand() & 255, rand() & 255), 1, 8/*, hierarchy*/);
}

drawContours函数原型:

CV_EXPORTS_W void drawContours( InputOutputArray image, InputArrayOfArrays contours,
              int contourIdx, const Scalar& color,
              int thickness = 1, int lineType = LINE_8,
              InputArray hierarchy = noArray(),
              int maxLevel = INT_MAX, Point offset = Point() );

作用是将contours中的第contourIdx条轮廓用color颜色绘制到image中,thickness为线条的粗细, contourIdx为负数时画出所有轮廓

这里要注意的是在绘制轮廓前要提前为输出矩阵分配空间,否则会出现以下错误

OpenCV(3.4.1) Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file C:\build\master_winpack-build-win64-vc15\opencv\modules\highgui\src\window.cpp, line 356

详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)

提取面积最大的轮廓并用多边形将轮廓包围

从上面的轮廓图中看出,PPT的矩形已经成为了图片的主要部分,接下来的思路是提取面积最大的轮廓,得到矩形轮廓。

vector<vector<Point>> polyContours(contours.size());
int maxArea = 0;
for (int index = 0; index < contours.size(); index++){    
    if (contourArea(contours[index]) > contourArea(contours[maxArea]))
      maxArea = index;    
    approxPolyDP(contours[index], polyContours[index], 10, true);
  }

contourArea用来计算轮廓的面积
approxPolyDP的作用是用多边形包围轮廓,可以得到严格的矩形,有助于找到角点

画出矩形,同样注意要提前为Mat分配空间

Mat polyPic = Mat::zeros(shrinkedPic.size(), CV_8UC3);
drawContours(polyPic, polyContours, maxArea, Scalar(0,0,255/*rand() & 255, rand() & 255, rand() & 255*/), 2);

详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)

如图,接下来我们只需提取到四个角的坐标

寻找凸包

vector<int> hull;
convexHull(polyContours[maxArea], hull, false);  //检测该轮廓的凸包

convexHull函数原型

CV_EXPORTS_W void convexHull( InputArray points, OutputArray hull,
              bool clockwise = false, bool returnPoints = true );

hull为输出参数, clockwise决定凸包顺逆时针方向, returnPoints为真时返回凸包的各个点,否则返回各点的指数
hull可以为vector<int>类型,此时返回的是凸包点在原图中的下标索引

我们可以把点和多边形添加到原图中查看效果

for (int i = 0; i < hull.size(); ++i){
    circle(polyPic, polyContours[maxArea][i], 10, Scalar(rand() & 255, rand() & 255, rand() & 255), 3);
  }
addWeighted(polyPic, 0.5, shrinkedPic, 0.5, 0, shrinkedPic);

详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)

现在我们已经比较准确地获得了需要的点,下面就要利用这些点进行坐标映射。

投影变换

投影变换需要像素在两个坐标系中的坐标一一对应,虽然我们已经有了四个坐标,但还没有区分它们的位置。

新建两个数组

Point2f srcPoints[4], dstPoints[4];
dstPoints[0] = Point2f(0, 0);
dstPoints[1] = Point2f(srcPic.cols, 0);
dstPoints[2] = Point2f(srcPic.cols, srcPic.rows);
dstPoints[3] = Point2f(0, srcPic.rows);

dstPoints储存的是变换后各点的坐标,依次为左上,右上,右下, 左下

srcPoints储存的是上面得到的四个角的坐标

下面对得到的四个点进行处理

for (int i = 0; i < 4; i++){
  polyContours[maxArea][i] = Point2f(polyContours[maxArea][i].x * 4, polyContours[maxArea][i].y * 4); //恢复坐标到原图
}
    //对四个点进行排序 分出左上 右上 右下 左下
bool sorted = false;
int n = 4;
while (!sorted){
  for (int i = 1; i < n; i++){
  sorted = true;
    if (polyContours[maxArea][i-1].x > polyContours[maxArea][i].x){
      swap(polyContours[maxArea][i-1], polyContours[maxArea][i]);
      sorted = false;
    }
  }
  n--;
}
if (polyContours[maxArea][0].y < polyContours[maxArea][1].y){
  srcPoints[0] = polyContours[maxArea][0];
  srcPoints[3] = polyContours[maxArea][1];
}
else{
  srcPoints[0] = polyContours[maxArea][1];
  srcPoints[3] = polyContours[maxArea][0];
}

if (polyContours[maxArea][9].y < polyContours[maxArea][10].y){
  srcPoints[1] = polyContours[maxArea][2];
  srcPoints[2] = polyContours[maxArea][3];
}
else{
  srcPoints[1] = polyContours[maxArea][3];
  srcPoints[2] = polyContours[maxArea][2];
}

即先对四个点的x坐标进行冒泡排序分出左右,再根据两对坐标的y值比较分出上下
(笔者试图通过凸包的顺逆时针顺序以及凸包点与原点的距离来活得位置信息,却均以失败告终)

坐标变换需要矩阵运算,OpenCV中给我们提供了getPerspectiveTransform函数用来得到矩阵

Mat transMat = getPerspectiveTransform(srcPoints, dstPoints); //得到变换矩阵

接下来进行坐标变换,网上查到的步骤都是通过perspectiveTransform函数变换,但尝试多次都出现了报错,Google了好长时间才知道原来这个函数的传入输入输出参数均为点集,我们这个场景用起来比较麻烦。

warpPerspective函数可以直接传入输入Mat类型数据,比较方便

warpPerspective(srcPic, outPic, transMat, srcPic.size()); //进行坐标变换

参数分别为输入输出图像、变换矩阵、大小。

坐标变换后就得到了我们要的最终图像。

详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)

总结

我们利用了屏幕亮度较高的特点,通过二值化突出轮廓提取坐标,进行透视变换。

但局限性在于,如果矩形的亮度与背景相差不大,就很难用这种方法检测到轮廓。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python脚本实现自动将数据库备份到 Dropbox
Feb 06 Python
Python生成数字图片代码分享
Oct 31 Python
Python一行代码实现快速排序的方法
Apr 30 Python
numpy.linspace函数具体使用详解
May 27 Python
django项目用higcharts统计最近七天文章点击量
Aug 17 Python
tensorflow之自定义神经网络层实例
Feb 07 Python
Django ForeignKey与数据库的FOREIGN KEY约束详解
May 20 Python
Python CSS选择器爬取京东网商品信息过程解析
Jun 01 Python
matplotlib基础绘图命令之bar的使用方法
Aug 13 Python
python实现梯度下降算法的实例详解
Aug 17 Python
python闭包与引用以及需要注意的陷阱
Sep 18 Python
Python超详细分步解析随机漫步
Mar 17 Python
用python打印1~20的整数实例讲解
Jul 01 #Python
python sklearn库实现简单逻辑回归的实例代码
Jul 01 #Python
python实现列表的排序方法分享
Jul 01 #Python
Apache,wsgi,django 程序部署配置方法详解
Jul 01 #Python
Python中字符串List按照长度排序
Jul 01 #Python
python opencv minAreaRect 生成最小外接矩形的方法
Jul 01 #Python
VPS CENTOS 上配置python,mysql,nginx,uwsgi,django的方法详解
Jul 01 #Python
You might like
PHP高级对象构建 多个构造函数的使用
2012/02/05 PHP
destoon调用自定义模板及样式的公告栏
2014/06/21 PHP
php禁止浏览器使用缓存页面的方法
2014/11/07 PHP
php微信公众号开发之二级菜单
2018/10/20 PHP
PHP7使用ODBC连接SQL Server2008 R2数据库示例【基于thinkPHP5.1框架】
2019/05/06 PHP
Laravel框架基础语法与知识点整理【模板变量、输出、include引入子视图等】
2019/12/03 PHP
解决PHP Opcache 缓存刷新、代码重载出现无法更新代码的问题
2020/08/24 PHP
jQuery EasyUI API 中文文档 - ValidateBox验证框
2011/10/06 Javascript
jQuery中bind,live,delegate与one方法的用法及区别解析
2013/12/30 Javascript
js支持键盘控制的左右切换立体式图片轮播效果代码分享
2015/08/26 Javascript
jquery实现页面加载效果
2017/02/21 Javascript
使用3D引擎threeJS实现星空粒子移动效果
2020/09/13 Javascript
基于vue.js的分页插件详解
2017/11/27 Javascript
mui back 返回刷新页面的实例
2017/12/06 Javascript
代码详解javascript模块加载器
2018/03/04 Javascript
浅谈在react中如何实现扫码枪输入
2018/07/04 Javascript
vue-router传递参数的几种方式实例详解
2018/11/13 Javascript
javascript之分片上传,断点续传的实际项目实现详解
2019/09/05 Javascript
vue props 单项数据流实例分享
2020/02/16 Javascript
jQuery实现颜色打字机的完整代码
2020/03/19 jQuery
[03:24]DOTA2超级联赛专访hao 大翻盘就是逆袭
2013/05/24 DOTA
python 内置函数filter
2017/06/01 Python
Windows下anaconda安装第三方包的方法小结(tensorflow、gensim为例)
2018/04/05 Python
Python3利用Dlib19.7实现摄像头人脸识别的方法
2018/05/11 Python
python使用matplotlib画柱状图、散点图
2019/03/18 Python
Python实现的登录验证系统完整案例【基于搭建的MVC框架】
2019/04/12 Python
关于Python形参打包与解包小技巧分享
2019/08/24 Python
python的json包位置及用法总结
2020/06/21 Python
python excel多行合并的方法
2020/12/09 Python
春节联欢晚会主持词范文
2014/03/24 职场文书
树转促学习心得体会
2014/09/10 职场文书
公司离职证明样本
2014/09/13 职场文书
房屋租赁合同解除协议书
2014/10/11 职场文书
优秀校长事迹材料
2014/12/24 职场文书
2015年校本培训工作总结
2015/07/24 职场文书
MySQL 十大常用字符串函数详解
2021/06/30 MySQL