详解利用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标准库内置函数complex介绍
Nov 25 Python
使用python调用zxing库生成二维码图片详解
Jan 10 Python
Python tkinter模块中类继承的三种方式分析
Aug 08 Python
Python爬虫包BeautifulSoup学习实例(五)
Jun 17 Python
python读取图片并修改格式与大小的方法
Jul 24 Python
Python 中字符串拼接的多种方法
Jul 30 Python
python中map的基本用法示例
Sep 10 Python
python中时间模块的基本使用教程
May 14 Python
使用python-pptx包批量修改ppt格式的实现
Feb 14 Python
Python 的 __str__ 和 __repr__ 方法对比
Sep 02 Python
用Python selenium实现淘宝抢单机器人
Jun 18 Python
python画条形图的具体代码
Apr 20 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目录管理函数小结
2008/09/10 PHP
Zend Framework动作助手Redirector用法实例详解
2016/03/05 PHP
PHP 构造函数和析构函数原理与用法分析
2020/04/21 PHP
JavaScript中exec函数用法实例分析
2015/06/08 Javascript
jQuery实现form表单基于ajax无刷新提交方法详解
2015/12/08 Javascript
jQuery ajax分页插件实例代码
2016/01/27 Javascript
底部悬浮通栏可以关闭广告位的实现方法
2016/06/01 Javascript
angular.js分页代码的实例
2016/07/27 Javascript
JavaScript之json_动力节点Java学院整理
2017/06/29 Javascript
关于Vue.nextTick()的正确使用方法浅析
2017/08/25 Javascript
node.js环境搭建图文详解
2018/09/19 Javascript
javascript中call()、apply()的区别
2019/03/21 Javascript
小程序绑定用户方案优化小结
2019/05/15 Javascript
javascript History对象原理解析
2020/02/17 Javascript
解决element-ui里的下拉多选框 el-select 时,默认值不可删除问题
2020/08/14 Javascript
详解JavaScript类型判断的四种方法
2020/10/21 Javascript
Python ORM框架SQLAlchemy学习笔记之映射类使用实例和Session会话介绍
2014/06/10 Python
windows10系统中安装python3.x+scrapy教程
2016/11/08 Python
使用Python对SQLite数据库操作
2017/04/06 Python
Python实现的中国剩余定理算法示例
2017/08/05 Python
Python通过matplotlib画双层饼图及环形图简单示例
2017/12/15 Python
Python3中关于cookie的创建与保存
2018/10/21 Python
详解Python基础random模块随机数的生成
2019/03/23 Python
win10安装python3.6的常见问题
2020/07/01 Python
python操作ini类型配置文件的实例教程
2020/10/30 Python
百丽国际旗下购物网站:优购
2017/02/28 全球购物
Desigual美国官方网站:西班牙服装品牌
2019/03/29 全球购物
护理自荐信
2013/10/22 职场文书
幼儿教师师德承诺书
2014/05/23 职场文书
跑操口号
2014/06/12 职场文书
爱护公物演讲稿
2014/09/09 职场文书
幼儿园迎国庆65周年活动策划方案
2014/09/16 职场文书
2015年个人思想总结
2015/03/09 职场文书
现货白银电话营销话术
2015/05/29 职场文书
2019商业计划书格式、范文
2019/04/24 职场文书
MySQL之MyISAM存储引擎的非聚簇索引详解
2022/03/03 MySQL