这个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方便得多。。
--------------------------------------分割线 --------------------------------------