前言

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

分析问题

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

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

提取矩形角落坐标

矩形的检测主要是提取边缘,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); //阈值化为二值图片

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

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

边缘检测与轮廓处理

进行Canny边缘检测

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

这里 cannyThr = 200, FACTOR = 2.5

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

提取轮廓

vector> contours; //储存轮廓

vector 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

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

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

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

vector> 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);

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

寻找凸包

vector 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类型,此时返回的是凸包点在原图中的下标索引

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

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);

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

投影变换

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

新建两个数组

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屏幕等)

本文地址: http://www.cppcns.com/jiaoben/python/264136.html

python检测图像中的矩形_详解利用OpenCV提取图像中的矩形区域(PPT屏幕等)相关推荐

  1. php中yii的controller,详解PHP的Yii框架中的Controller控制器,yiicontroller

    详解PHP的Yii框架中的Controller控制器,yiicontroller 控制器是 MVC 模式中的一部分, 是继承yii\base\Controller类的对象,负责处理请求和生成响应. 具 ...

  2. python zxing 识别条码_详解利用python识别图片中的条码(pyzbar)及条码图片矫正和增强...

    前言 这周和大家分享如何用python识别图像里的条码.用到的库可以是zbar.希望西瓜6辛苦码的代码不要被盗了.(zxing的话,我一直没有装好,等装好之后再写一篇) 具体步骤 前期准备 用open ...

  3. python middleware模块_详解利用django中间件django.middleware.csrf.CsrfViewMiddleware防止csrf攻击...

    一.在django后台处理 1.将django的setting中的加入django.contrib.messages.middleware.MessageMiddleware,一般新建的django项 ...

  4. python中index函数_详解python中的index函数用法

    1.函数的创建 def fun(): #定义 print('hellow') #函数的执行代码 retrun 1 #返回值 fun() #执行函数 2.函数的参数 普通参数 :要按照顺序输入参数 de ...

  5. python中 的用法_详解python中@的用法

    python中@的用法 @是一个装饰器,针对函数,起调用传参的作用. 有修饰和被修饰的区别,@function作为一个装饰器,用来修饰紧跟着的函数(可以是另一个装饰器,也可以是函数定义). 代码1 结 ...

  6. python正则匹配空格+数字+空格_详解Python中正则匹配TAB及空格的小技巧

    详解Python中正则匹配TAB及空格的小技巧 发布时间:2020-10-15 08:38:48 来源:脚本之家 阅读:94 作者:杰瑞26 在正则中,使用.*可以匹配所有字符,其中.代表除\n外的任 ...

  7. python中squeeze函数_详解pytorch中squeeze()和unsqueeze()函数介绍

    squeeze的用法主要就是对数据的维度进行压缩或者解压. 先看torch.squeeze() 这个函数主要对数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列这种,一个一行三列(1,3)的 ...

  8. python中递归函数写法_详解python中递归函数

    函数执行流程 def foo1(b,b1=3): print("foo1 called",b,b1) def foo2(c): foo3(c) print("foo2 c ...

  9. python中setattr用法_详解Python的hasattr() getattr() setattr() 函数使用方法

    hasattr(object, name) 判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False. 需要注意的是name要用括号括起来 ...

  10. 详解python中的用法_详解python中@的用法

    python中@的用法 @是一个装饰器,针对函数,起调用传参的作用. 有修饰和被修饰的区别,作为一个装饰器,用来修饰紧跟着的函数(可以是另一个装饰器,也可以是函数定义). 代码1 def funa(d ...

最新文章

  1. 学习javascript 非常好的博客
  2. prefetch 和preload_前端preload, prefetch,dns-prefetch,defer,async了解一下
  3. MVC系列框架之Struts存在的意义是什么?
  4. css-padding
  5. linux主节点启动nfs,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  6. WebService大讲堂之Axis2(5):会话(Session)管理
  7. Go 1.9 新特性
  8. PHP多线程的实现(PHP多线程类)
  9. TreeSet集合为什么要实现Comparable?
  10. Linux基础急速入门:用 TCPDUMP 抓包
  11. python 随机请求头_为了爬虫换个头,我用python实现三种随机请求头方式!
  12. 为什么我们要使用图嵌入?
  13. 计算机毕业论文乐谱播放器,单片机音乐播放器毕业论文
  14. 称重仪表显示ol怎么解决_称重仪表显示Erd和数字是怎么回事?
  15. 软件授权文件.lic文件
  16. Python 实现分离GIF图片,-pillow
  17. 产品经理--无人岛的项目开发与推广
  18. 如何合理确定线程池的大小
  19. 系统架构设计 2.1 管道-过滤器风格
  20. 2021沭阳中学高考成绩查询,2021年沐阳县高考状元名单资料,今年沐阳县高考状元多少分...

热门文章

  1. UT2012学习笔记
  2. vbs脚本实现qq定时发消息(初级)
  3. 将数组分成两部分,使得 |sum1 - sum2| 最小. LeetCode - 1049
  4. 【供应链架构day4】途牛进销存架构的演进之路 - 从诞生到发展
  5. 【HZNUOJ】【C系列3.16】征战的Loy
  6. 【C1认证任务】C1-4
  7. 视频教程-微信公众号二维码签到和抽奖软件-微信开发
  8. 【新书推荐】【2017.07】创新的雷达技术与应用第一卷:实孔径阵列雷达、成像雷达与无源多基地雷达
  9. jdk官网下载与安装
  10. 商务统计_4 用图表演示数据 - 频数分布