资源预览内容
第1页 / 共15页
第2页 / 共15页
第3页 / 共15页
第4页 / 共15页
第5页 / 共15页
第6页 / 共15页
第7页 / 共15页
第8页 / 共15页
第9页 / 共15页
第10页 / 共15页
亲,该文档总共15页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
问题如下:也就是在一张照片里,已知有个长方形的物体,但是经过了透视投影,已经不再是规则的长方形,那么如何提取这个图形里的内容呢?这是个很常见的场景,比如在博物馆里看到一幅很喜欢的画,用手机找了下来,可是回家一看歪歪斜斜,脑补原画内容又觉得不对,那么就需要算法辅助来从原图里提取原来的内容了。不妨把应用的场景分为以下:纸张四角的坐标(图中红点) 已知的情况也就是上面的左图中 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:这个 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 方便得多。
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号