cv2.findContours提取简单二值图轮廓效果不佳,如何正确提取?
二值图像轮廓提取问题
问题背景
给定如下二值图像:
import numpy as np thresh = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]]).astype('uint8')
尝试用以下代码提取轮廓:
import cv2 contours, hierarchy = cv2.findContours( thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE )
绘制轮廓的代码:
canvas = np.zeros_like(thresh) for ct in contours: cv2.drawContours(canvas, ct,-1, 1, 1)
直观来看,二值图像中值为1的区域是箭头形状的完整区域,但绘制后得到零散的轮廓点,需解决如何正确提取轮廓的问题。
问题原因
该二值图像存在多个内部孔洞(如第14行的(0,8)位置、第16行的(0,14)位置均为0值孔洞),结合使用的cv2.RETR_CCOMP模式,会同时提取所有外层轮廓和内层孔洞的轮廓,观察到的零散轮廓实际是这些孔洞的边界。
解决方案
1. 提取最外层轮廓
改用cv2.RETR_EXTERNAL模式,该模式仅提取最外层轮廓,忽略所有内层孔洞的轮廓,直接得到完整的箭头外层轮廓:
contours, hierarchy = cv2.findContours( thresh, cv2.RETR_EXTERNAL, # 仅保留最外层轮廓 cv2.CHAIN_APPROX_SIMPLE )
2. 填充内部孔洞(可选)
若需要将箭头区域变为实心后再提取轮廓,可使用floodFill填充孔洞:
# 复制原图像并初始化填充掩码 filled = thresh.copy() h, w = filled.shape mask = np.zeros((h+2, w+2), np.uint8) # 从左上角0区域填充,反转后得到实心箭头区域 cv2.floodFill(filled, mask, (0,0), 1) filled = cv2.bitwise_not(filled) # 提取实心区域的轮廓 contours, hierarchy = cv2.findContours(filled, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
3. 正确绘制轮廓
确保绘制参数正确:thickness设为正数时绘制轮廓线,设为-1时填充轮廓内部。使用RETR_EXTERNAL模式后,contours列表仅包含一个大轮廓,可直接绘制:
canvas = np.zeros_like(thresh) # 绘制轮廓线(厚度为2) cv2.drawContours(canvas, contours, -1, 1, thickness=2) # 或者填充轮廓内部 # cv2.drawContours(canvas, contours, -1, 1, thickness=-1)
内容的提问来源于stack exchange,提问作者Sandeep Thapa
相关产品推荐
相关产品推荐

