检测的步骤相信大家很熟悉了,我今天在这里是要说一个大坑。
首先,常见的步骤如下:
edge = cv2.Canny(img, low, high)
contours, hier = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
但是,这样会丢边缘!
这是因为cv2.RETR_EXTERNAL
只会检测最外层的边缘。也就是说,一旦你用cv2.Canny
检测到的边缘有嵌套,那么内层的边缘都会丢失!
解决方案是使用cv2.RETR_CCOMP
然后手动筛选。
当使用cv2.RETR_CCOMP
时,hier
中包含每个边缘的相对关系,以“后前子父”的索引值表示(-1
表示无),且只会保留一层相对关系。因此,我们只要筛选出“父”索引值为-1的项即可。
实现如下:
def outer_contours(img):
contours, hier = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
return [contours[i] for i in range(len(hier[0])) if hier[0][i][3]==-1]
edge = cv2.Canny(img, low, high)
contours = outer_contours(edge)
注
详细的说,cv2.Canny
得到的只是“反映边缘位置的二值图”。对cv2.Canny
的结果使用cv2.findContours
,一个Canny边缘会检测出内外两个contour。
举个栗子,假设cv2.Canny
检测出来的边缘包含甲、乙、丙
三条线,那么cv2.findContours
会找到甲内、甲外、乙内、乙外、丙内、丙外
6个contour。如果甲、乙、丙
无嵌套关系,使用cv2.RETR_EXTERNAL
时,筛选出的contour就是甲外、乙外、丙外
这3个,正常工作。
但是,如果甲、乙
有着甲>乙
的嵌套关系,那么它们检测出的contours就有甲外>甲内>乙外>乙内
的关系,此时使用cv2.RETR_EXTERNAL
,就只会返回甲外
,乙外
就丢失了。
彩蛋
这个方法9年前在StackOverflow上就有了:
https://stackoverflow.com/a/15867297/16599029
可惜很多教程还是在用会丢边缘的方法。