也就是在一张照片里,已知有个长方形的物体,但是经过了透视投影,已经不再是规则的长方形,那么如何提取这个图形里的内容呢?这是个很常见的场景,比如在博物馆里看到一幅很喜欢的画,用手机找了下来,可是回家一看歪歪斜斜,脑补原画内容又觉得不对,那么就需要算法辅助来从原图里提取原来的内容了。不妨把应用的场景分为以下:
纸张四角的坐标(图中红点)已知的情况也就是上面的左图中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的变量里,那么提取纸张图案的代码如下:
1 M = cv2.getPerspectiveTransform(corners, canvas) 2 result = cv2.warpPerspective(image, M, (0, 0))
把左图剪裁出来,去掉红点后试了试,结果如下:
当然,其实这一步用Photoshop就可以了。。
纸张四角的坐标未知或难以准确标注的情况这种场景可能是小屏幕应用,或是原始图像就很小,比如我这里用的这个300x400例子,点坐标很难精确标注。这种情况下一个思路是,用边缘检测提取纸张四边,然后求出四角坐标,再做Perspective Transform。
1) 图像预处理
一般而言即使做普通的边缘检测也需要提前对图像进行降噪避免误测,比如最常见的办法是先对图像进行高斯滤波,然而这样也会导致图像变得模糊,当待检测图形边缘不明显,或是图像本身分辨率不高的情况下(比如本文用的例子),会在降噪的同时把待检测的边缘强度也给牺牲了。具体到本文的例子,纸张是白色,背景是浅黄带纹路,如果进行高斯滤波是显然不行的,这时候一个替代方案是可以考虑使用Mean Shift,Mean Shift的优点就在于如果是像背景桌面的浅色纹理,图像分割的过程中相当于将这些小的浮动过滤掉,并且保留相对明显的纸张边缘,结果如下:
原图
处理后
Meanshift的代码:
1 image = cv2.pyrMeanShiftFiltering(image, 25, 10)
因为主要目的是预处理降噪,windows size和color distance都不用太大,避免浪费计算时间还有过度降噪。降噪后可以看到桌面上的纹理都被抹去了,纸张边缘附近干净了很多。然而这还远远不够,图案本身,和图像里的其他物体都有很多明显的边缘,而且都是直线边缘。
2) 纸张边缘检测
虽然降噪了,可是图像里还是有很多边缘明显的元素。怎么尽量只保留纸张的边缘呢,这时候可以考虑用分割算法,把图像分为纸张部分和其他部分,这样分割的mask边缘就和纸张边缘应该是差不多重合的。在这里可以考虑用GrabCut,这样对于简单的情况,比如纸张或画布和背景对比强烈的,直接把图像边缘的像素作为bounding box就可以实现自动分割。当自动分割不精确的情况下再引入手动辅助分割,具体到我这里用的例子,背景和画面接近,所以需要手动辅助:
结果如下:
可以看到,分割后的结果虽然能基本区分纸张形状了,可是边缘并不准确,另外键盘和部分桌面没能区分开来。这时可以继续用GrabCut+手动标注得到只有纸张的分割。或者为了用户友好的话,尽量少引入手动辅助,那么可以考虑先继续到下一步检测边缘,再做后期处理。假设我们考虑后者,那么我们得到的是如下的mask: