基于知乎上的一个答案。问题如下:
也就是在一张照片里,已知有个长方形的物体,但是经过了透视投影,已经不再是规则的长方形,那么如何提取这个图形里的内容呢?这是个很常见的场景,比如在博物馆里看到一幅很喜欢的画,用手机找了下来,可是回家一看歪歪斜斜,脑补原画内容又觉得不对,那么就需要算法辅助来从原图里提取原来的内容了。不妨把应用的场景分为以下:

纸张四角的坐标(图中红点)已知的情况

也就是上面的左图中4个红点是可以准确获取,比如手动标注,那么就简单了:用OpenCV的Perspective Transform就可以。具体步骤如下:
1) 将标注好的四个点坐标存入一个叫corner的变量里,比如上面的例子中,原图的分辨率是300x400,定义x和y的方向如下:
那么纸张的四角对应的坐标分别是:
左上:157.6, 71.5

右上:295.6, 118.4

右下:172.4, 311.3

左下:2.4, 202.4

 
把这四个坐标按如上顺序放到一个叫corner的变量里。如果我们打算把这幅图案恢复到一个300x400的图像里,那么按照对应的顺序把下面四个坐标放到一个叫canvas的变量里:
左上:0, 0

右上:300, 0

右下:300, 400

左下:0, 400

 
假设原图已经用OpenCV读取到一个叫image的变量里,那么提取纸张图案的代码如下:
 M = cv2.getPerspectiveTransform(corners, canvas)
result = cv2.warpPerspective(image, M, (0, 0))
把左图剪裁出来,去掉红点后试了试,结果如下:
当然,其实这一步用Photoshop就可以了。。

纸张四角的坐标未知或难以准确标注的情况

这种场景可能是小屏幕应用,或是原始图像就很小,比如我这里用的这个300x400例子,点坐标很难精确标注。这种情况下一个思路是,用边缘检测提取纸张四边,然后求出四角坐标,再做Perspective Transform。
 
1) 图像预处理
一般而言即使做普通的边缘检测也需要提前对图像进行降噪避免误测,比如最常见的办法是先对图像进行高斯滤波,然而这样也会导致图像变得模糊,当待检测图形边缘不明显,或是图像本身分辨率不高的情况下(比如本文用的例子),会在降噪的同时把待检测的边缘强度也给牺牲了。具体到本文的例子,纸张是白色,背景是浅黄带纹路,如果进行高斯滤波是显然不行的,这时候一个替代方案是可以考虑使用Mean Shift,Mean Shift的优点就在于如果是像背景桌面的浅色纹理,图像分割的过程中相当于将这些小的浮动过滤掉,并且保留相对明显的纸张边缘,结果如下:
原图
处理后
Meanshift的代码:
 image = cv2.pyrMeanShiftFiltering(image, 25, 10)

因为主要目的是预处理降噪,windows size和color distance都不用太大,避免浪费计算时间还有过度降噪。降噪后可以看到桌面上的纹理都被抹去了,纸张边缘附近干净了很多。然而这还远远不够,图案本身,和图像里的其他物体都有很多明显的边缘,而且都是直线边缘。

2) 纸张边缘检测

虽然降噪了,可是图像里还是有很多边缘明显的元素。怎么尽量只保留纸张的边缘呢,这时候可以考虑用分割算法,把图像分为纸张部分和其他部分,这样分割的mask边缘就和纸张边缘应该是差不多重合的。在这里可以考虑用GrabCut,这样对于简单的情况,比如纸张或画布和背景对比强烈的,直接把图像边缘的像素作为bounding box就可以实现自动分割。当自动分割不精确的情况下再引入手动辅助分割,具体到我这里用的例子,背景和画面接近,所以需要手动辅助:
结果如下:
可以看到,分割后的结果虽然能基本区分纸张形状了,可是边缘并不准确,另外键盘和部分桌面没能区分开来。这时可以继续用GrabCut+手动标注得到只有纸张的分割。或者为了用户友好的话,尽量少引入手动辅助,那么可以考虑先继续到下一步检测边缘,再做后期处理。假设我们考虑后者,那么我们得到的是如下的mask:
这个mask并不精确,所以不能直接用于边缘检测,但是它大致标出了图片里最明显的边缘位置所在,所以可以考虑下面的思路:保留降噪后位于mask边缘附近的信息用于真正的边缘检测,而把其他部分都模糊处理,也就是说基于上面得到的mask做出下面的mask用于模糊处理:
基于这个mask得到的用于边缘检测的图像如下:
用canny算子检测出边缘如下:

3) 直线检测

对检测到的边缘使用Hough变换检测直线,我例子里用的是cv2.HoughLinesP,分辨率1像素和1°,可以根据图像大小设置检测的阈值和minLineLength去除大部分误检测。特别提一下的是如果使用OpenCV的Python binding,OpenCV 2和OpenCV 3的结果结构是不一样的,如果进行代码移植需要相应的修改。检测到的结果如下:
可以看到,有些线几乎重合在一起了,这是难以避免的,上图中一共检测到9条线,其中两对(下、右边缘)重合。可以通过距离判断和直线相对角度来判断并把重合线段合为一条:
剩下的都是没有重合的线了。

4) 判断纸张边缘

那么如何选取纸张边缘的四条线呢(即使图像分割步骤非常好得分开了纸张和其他部分,这在有些情况下还是难以避免的,比如图案里有和边缘平行的线条),可以沿着提取线段的两边采样像素的灰度:
在线段的两个端点之间平均采样左右两边像素的值,因为一般来说如果是纸张或者画布,边缘和背景的颜色在四边上应该都是类似的。然而这样做的话引入另外一个问题是需要区分线段的“左”和“右”,对于线段本身而言就是要区分前后。所以需要对画面里所有的线段端点进行排序,而这个排序的基准就是相对画布。
具体到本文的例子就是把图像中心定义为所有线段的“左”边,如上图。而决定线段端点“前”和“后”可以用如下办法:
先假设线段的前后端点,将两个端点坐标分别减去中心点(红点)的坐标,然后将得到的两个向量a和b求叉积,如果叉积大于0则说明假设正确,如果<0则交换假设的前后端点。线段端点的顺序确定后就可以进行采样了,简单起见可以分别采样左右两侧的像素灰度值,如果希望更准确可以采样RGB通道的值进行综合比较,下面是7条线段对应的两侧像素灰度的中值分布:
可以看到其中有4个点距离非常近(红色),说明他们的像素灰度分布也很接近,把这4条选出来,结果如下:

正是要的结果。

5) 计算四角的坐标

接下来计算四条线的交点,方法点这里。因为有4条线,会得到6个结果,因为在这种应用场景中,方形的物体在透视变换下不会出现凹角,所以直接舍弃离纸张中心最远的两个交点就得到了四个角的坐标,结果如下:

这样就回到了一开始四角坐标已经得到的情况,直接进行透视变换就行了。

Camera Calibration?

写了这么多,其实有一条至关重要的假设,甚至可以说是最关键的步骤之一我一直没提,那就是Camera Calibration,如果有相机的情况下,meta data都知道,那么需要先坐Camera Calibration才能知道纸张或者画布的原始尺寸。我这里试的例子当然是没有的,也可以有,相应的算法OpenCV里也有现成的,但准确度未必够,而且还是非常麻烦,所以我的所有流程都是默认原始尺寸已经获得了。再说了,就算没有,变换回方形之后使用者凭感觉进行简单轴缩放都比Camera Calibration方便得多。。

印象派

我用的例子算是略微有些极端的,因为背景和图案非常接近,另一方面分辨率还巨低。在网上搜搜,我找了幅少年画家的印象派作品来试试:

原图

手动标注

GrabCut

检测到的边缘

结果

看上去还不错~

利用OpenCV检测图像中的长方形画布或纸张并提取图像内容的更多相关文章

  1. matlab在图像中画长方形(框)

    function [state,result]=draw_rect(data,pointAll,windSize,showOrNot) % 函数调用:[state,result]=draw_rect( ...

  2. 在图像中隐藏数据:用 Python 来实现图像隐写术

    什么是“隐写术”? 隐写术是将机密信息隐藏在更大的信息中,使别人无法知道隐藏信息的存在以及隐藏信息内容的过程.隐写术的目的是保证双方之间的机密交流.与隐藏机密信息内容的密码学不同,隐写术隐瞒了传达消息 ...

  3. 利用OpenCV检测手掌(palm)和拳头(fist)

    思路:利用训练好的palm.xml和fist.xml文件,用OpenCV的CascadeClassifier对每一帧图像检测palm和fist,之后对多帧中检测到的palm和fist进行聚类分组,满足 ...

  4. 图像中的掩膜(Mask)是什么

    在图像处理中,经常会碰到掩膜(Mask)这个词.那么这个词到底是什么意思呢?下面来简单解释一下. 1.什么是掩膜 首先我们从物理的角度来看看mask到底是什么过程. 在半导体制造中,许多芯片工艺步骤采 ...

  5. 从单一图像中提取文档图像:ICCV2019论文解读

    从单一图像中提取文档图像:ICCV2019论文解读 DewarpNet: Single-Image Document Unwarping With Stacked 3D and 2D Regressi ...

  6. python+opencv选出视频中一帧再利用鼠标回调实现图像上画矩形框

    最近因为要实现模板匹配,需要在视频中选中一个目标,然后框出(即作为模板),对其利用模板匹配的方法进行检测.于是需要首先选出视频中的一帧,但是在利用摄像头读视频的过程中我唯一能想到的方法就是: 1.在视 ...

  7. (转)使用Python和OpenCV检测图像中的物体并将物体裁剪下来

    原文链接:https://blog.csdn.net/liqiancao/article/details/55670749 介绍 硕士阶段的毕设是关于昆虫图像分类的,代码写到一半,上周五导师又给我新的 ...

  8. 访问图像中的像素[OpenCV 笔记16]

    再更一发好久没更过的OpenCV,不过其实写到这个部分对计算机视觉算法有所了解的应该可以做到用什么查什么了,所以后面可能会更的慢一点吧,既然开了新坑,还是机器学习更有研究价值吧... 图像在内存中的存 ...

  9. 利用OpenCV给图像添加中文标注

    利用OpenCV给图像添加中文标注 : 参考:http://blog.sina.com.cn/s/blog_6bbd2dd101012dbh.html  和https://blog.csdn.net/ ...

随机推荐

  1. 【Asphyre引擎】Asphyre时隔3年,更名为PXL,全平台支持!

    ps:回忆日志 新版本10月初就推出了,我第一时间(10.2日更新,我当天就看到了)下载下来.发现部分Demo需要XE8才能编译通过,又去下载了一个XE8.折腾完已经深夜,只是粗粗的把Demo都编译了 ...

  2. js注入,黑客之路必备!

    最近刚出了新闻,阿里四名网络安全部门员工利用网页漏洞写js脚本抢月饼,于是兴致来了,想了解一下这个js脚本到底怎么写,各种刷单各种抢枪抢又是怎么实现的. 什么是javascript注入攻击? 1.每当 ...

  3. CentOS常用指令

    创建文件: 如touch a.txt 创建文件夹: mkdir -p 文件夹名,当文件夹不存在时候,创建这个文件夹 文件重命名: 把文件text.php得命名为index.php,可以是rename ...

  4. 多准则决策模型-TOPSIS评价方法-源码

    ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ...

  5. 【iOS】Quartz2D练习-动态改变属性值

    一.通过slider控制圆的缩放 1.实现过程 新建一个项目,新建一个继承自UIview的类,并和storyboard中自定义的view进行关联.代码示例:SLViewController.m文件 # ...

  6. linux实现rdp访问

    远程访问linux系统,我们常用的有终端ssh.图形界面VNC,但是VNC安全性不够,并且不够流畅.相比之下,我认为rdp更好,并且还能利用windows的远程桌面访问,非常方便. 1. 安装xRDP ...

  7. 一些js小题(一)

    一些js小题,掌握这些对于一些常见的面试.笔试题应该很有帮助: var a=10; function aa(){ alert(a); } function bb(){ aa(); } bb();//1 ...

  8. Microsoft SQL Server,附加数据库 错误:Error 916解决方法

    错误信息:错误提示:标题: Microsoft SQL Server Management Studio Express ——————————  无法为此请求检索数据. (Microsoft.SqlS ...

  9. 去掉 Android工程中让人很不爽的“黄色警告”

    一:问题       二:解决方法 (1)选择android工程,右键Android Tools —> Clear Lint Markers 这种方式能够清除android工程里面的所有警告信息 ...

  10. iOS 视频播放 - YVideoPlayer - UIView

    这是一个使用简便的视频播放框架,它基于UIView,它可以是一个小窗口,也可以是一个全屏的窗口 简单的方式加载Video框架: 一行代码加载! 一行代码更新! 下载链接 : https://githu ...