OpenCV - 原样绘制轮廓

0 投票
3 回答
6631 浏览
提问于 2025-04-18 02:21

我有一个看起来很简单的问题——我有一张图片,我用下面的代码提取轮廓。

import numpy as np
import cv2

def findAndColorShapes(inputFile):
    # Find contours in the image
    im = cv2.imread(inputFile)
    imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    ret,thresh = cv2.threshold(imgray,127,255,0)
    contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

这段代码能很好地找到图片中的轮廓,然后我用以下代码来绘制它们。

cv2.drawContours(fullIm, [con], -1, (0,255,0), 2)

有些形状是空心的(比如一个轮廓圆),而有些是实心的。我想把轮廓画得和原图里一样。比如,如果轮廓是一个实心圆,它就应该被填充颜色;如果只是一个轮廓线,那就只画轮廓线。

我尝试了很多方法(其中包括把findContours的模式改成CHAIN_APPROX_NONE,而不是CHAIN_APPROX_SIMPLE),还有改变drawContours的第五个参数,但都没有成功。

编辑:我加了一张示例图片——左边的圆应该是空心的,而右边的方块应该是实心的。

左边的圆应该是空心的,而右边的方块应该是实心的

你知道有什么方法可以做到吗?

谢谢!

3 个回答

0

这是创建一个遮罩图像的方法(也就是填充的轮廓),然后用这个遮罩去“过滤”源图像,从而得到结果。

在这个代码片段中,“th”是阈值图像(单通道)。

#np comes from import numpy as np
mask = np.zeros(th.shape, np.uint8)  # create a black base 'image'
mask = cv2.drawContours(mask, contours, -1, 255, cv2.FILLED)  # set everything to white inside all contours

result = np.zeros(th.shape, np.uint8)  
result = np.where(mask == 0, result, th)  # set everything where the mask is white to the value of th

注意:findContours会直接修改给定的图像!如果你想在其他地方使用这个阈值图像,最好传递一个副本(np.copy(th))给它。

0

你可以查看层级参数来判断一个轮廓是否有子轮廓(没有填充),或者没有子轮廓(已填充)。

举个例子,

vector< Vec4i > hierarchy

对于第 i 个轮廓,

hierarchy[i][0] = next contour at the same hierarchical level
hierarchy[i][1] = previous contour at the same hierarchical level
hierarchy[i][2] = denotes its first child contour
hierarchy[i][3] = denotes index of its parent contour

如果对于轮廓 i 没有下一个、上一个、父轮廓或嵌套轮廓,那么 hierarchy[i] 中对应的元素会是负数。所以你需要检查每个轮廓是否有子轮廓。

然后,如果有子轮廓,就用厚度为 1 的线条来绘制轮廓;
如果没有子轮廓,就用填充的方式来绘制轮廓。

我觉得这个方法适合你发布的那种图像。

另外,看看这个回答 这里 可能会对你有帮助。

2

如果将来有人需要做类似的事情,这就是我最终使用的代码。虽然效率不是很高,但它运行得很好,而且在这个项目中时间不是问题(注意我在cv2.threshold(imgray,220,255,0)中使用了红色和绿色来设定轮廓的阈值。你可能想要更改一下这个设置)-

def contour_to_image(con, original_image):
    # Get the rect coordinates of the contour
    lm, tm, rm, bm = rect_for_contour(con)

    con_im = original_image.crop((lm, tm, rm, bm))

    if con_im.size[0] == 0 or con_im.size[1] == 0:
        return None

    con_pixels = con_im.load()

    for x in range(0, con_im .size[0]):
        for y in range(0, con_im.size[1]):
            # If the pixel is already white, don't bother checking it
            if con_im.getpixel((x, y)) == (255, 255, 255):
                continue

            # Check if the pixel is outside the contour. If so, clear it
            if cv2.pointPolygonTest(con, (x + lm, y + tm), False) < 0:
                con_pixels[x, y] = (255, 255, 255)

    return con_im

def findAndColorShapes(input_file, shapes_dest_path):
    im = cv2.imread(input_file)
    imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(imgray, 220, 255, 0)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

    i = 0

    for con in contours:
        con_im = contour_to_image(con, Image.open(input_file))
        if con_im is not None:
            con_im.save(shapes_dest_path + "%d.png"%i)
            i += 1

其中np_to_int()rect_for_contour()是两个简单的辅助函数 -

def np_to_int(np_val):
    return np.asscalar(np.int16(np_val))

def rect_for_contour(con):
    # Get coordinates of a rectangle around the contour
    leftmost = tuple(con[con[:,:,0].argmin()][0])
    topmost = tuple(con[con[:,:,1].argmin()][0])
    bottommost = tuple(con[con[:,:,1].argmax()][0])
    rightmost = tuple(con[con[:,:,0].argmax()][0])

    return leftmost[0], topmost[1], rightmost[0], bottommost[1]

撰写回答