填充轮廓但不填充包含区域。

3
我有一段Python代码,据说可以填充图像的轮廓,但是会留下其中包含的空洞不填充。这就是我想要的:

what I want (holes unfilled)

但这就是我得到的:

what I get (the whole thing filled)

我尝试使用cv2指定轮廓层次结构进行填充,但是我无法得到想要的结果。

这是我尝试过的:


import numpy as np
import cv2

# Load the PNG image
img = cv2.imread('slice.png')

# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)

# Find the contours in the binary image
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)

# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
    # Check if the contour has a parent
    if hierarchy[0][i][3] == -1:
        # If the contour doesn't have a parent, fill it with pixel value 255
        cv2.drawContours(filled_img, [contour], -1, 255, cv2.FILLED)

# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

我尝试修改“if hierarchy [0] [i] [3] == -1:”部分的-1、0、1值,但它要么填补较小的空洞,要么像我发布的第一张图片那样填满整个更大的轮廓。

更新

我还想用白色填充较小层次结构轮廓的内部,就像这样:

enter image description here enter image description here


泛洪算法可行吗? - fmw42
1个回答

2
问题在于,cv2.drawContours会填充封闭轮廓的整个内部区域,而不管是否存在内部轮廓。
我们可以使用白色轮廓开始,将没有子轮廓的轮廓填充为黑色,而不是用白色填充没有父轮廓的轮廓。

假设我们知道内部应该是黑色的,我们可以按照以下步骤进行:

  • 使用cv2.RETR_EXTERNAL查找轮廓,并用白色填充外轮廓。
  • 使用cv2.RETR_TREE查找轮廓。
  • 迭代轮廓层次结构,并仅填充没有子轮廓的轮廓(用黑色填充最内部的轮廓)。

代码示例:
import numpy as np
import cv2

# Load the PNG image
img = cv2.imread('slice.png')

# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)

# Find the outer contours in the binary image (using cv2.RETR_EXTERNAL)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)

# Fill the outer contour with white color
cv2.drawContours(filled_img, contours, -1, 255, cv2.FILLED)

# Find contours with hierarchy, this time use cv2.RETR_TREE
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
    # Check if the contour has no child
    if hierarchy[0][i][2] < 0:
        # If contour has no child, fill the contour with black color
        cv2.drawContours(filled_img, [contour], -1, 0, cv2.FILLED)

# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果 filled_img:
输入图像描述

注意:
如果我们不知道最内层轮廓的颜色,我们可以在黑色背景上绘制白色轮廓,并将结果用作掩码-使用掩码复制输入图像的原始内容。


更新:

支持没有子轮廓的轮廓:

为了支持既有子轮廓又没有子轮廓的轮廓,我们可以填充黑色颜色,只有符合以下两个条件的轮廓:

  • 轮廓没有子轮廓。
  • 轮廓有祖父轮廓(查找祖父轮廓而不是父轮廓,因为空轮廓有内部轮廓,其父轮廓是外部轮廓)。

代码示例:

import numpy as np
import cv2

# Load the PNG image
img = cv2.imread('slice.png')

# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)

# Find the outer contours in the binary image (using cv2.RETR_EXTERNAL)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)

# Fill the outer contour with white color
cv2.drawContours(filled_img, contours, -1, 255, cv2.FILLED)

# Find contours with hierarchy, this time use cv2.RETR_TREE
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
    has_grandparent = False
    has_parent = hierarchy[0][i][3] >= 0
    if has_parent:
        # Check if contour has a grandparent
        parent_idx = hierarchy[0][i][3]
        has_grandparent = hierarchy[0][parent_idx][3] >= 0

    # Check if the contour has no child
    if hierarchy[0][i][2] < 0 and has_grandparent:
        # If contour has no child, fill the contour with black color
        cv2.drawContours(filled_img, [contour], -1, 0, cv2.FILLED)

# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

更新:

填充较低层次轮廓内部的白色:

在用黑色填充轮廓之前,我们可以检查指定的轮廓是否有黑色像素。
仅当它没有子级、有祖父级且内部为黑色时才填充黑色。

为了测试是否有黑色像素,我们可以在临时图像上绘制轮廓(用白色),
然后检查最小值是否为0(绘制轮廓为白色的值)。

tmp = np.zeros_like(thresh)
cv2.drawContours(tmp, [contour], -1, 255, cv2.FILLED)
has_innder_black_pixels = (thresh[tmp==255].min() == 0)

代码示例:
import numpy as np
import cv2

# Load the PNG image
img = cv2.imread('slice.png')

# Convert the image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Threshold the image to create a binary image
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)

# Find the outer contours in the binary image (using cv2.RETR_EXTERNAL)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Create a blank image with the same dimensions as the original image
filled_img = np.zeros(img.shape[:2], dtype=np.uint8)

# Fill the outer contour with white color
cv2.drawContours(filled_img, contours, -1, 255, cv2.FILLED)

# Find contours with hierarchy, this time use cv2.RETR_TREE
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# Iterate over the contours and their hierarchies
for i, contour in enumerate(contours):
    has_grandparent = False
    has_parent = hierarchy[0][i][3] >= 0
    if has_parent:
        # Check if contour has a grandparent
        parent_idx = hierarchy[0][i][3]
        has_grandparent = hierarchy[0][parent_idx][3] >= 0

    # Draw the contour over temporary image first (for testing if it has black pixels inside).
    tmp = np.zeros_like(thresh)
    cv2.drawContours(tmp, [contour], -1, 255, cv2.FILLED)
    has_innder_black_pixels = (thresh[tmp==255].min() == 0)  # If the minimum value is 0 (value where draw contour is white) then the contour has black pixels inside

    if hierarchy[0][i][2] < 0 and has_grandparent and has_innder_black_pixels:
        # If contour has no child and has a grandparent and it has black inside, fill the contour with black color
        cv2.drawContours(filled_img, [contour], -1, 0, cv2.FILLED)

# Display the result
cv2.imshow('Original Image', img)
cv2.imshow('Filled Regions', filled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

谢谢!这似乎适用于具有子轮廓的轮廓,但我也想填充没有子轮廓的轮廓(例如填充一个正方形)。有办法两者都做到吗? - Nico
当然,我已经更新了答案。请注意,该答案可能并不涵盖所有可能的情况。 - Rotem
谢谢 :) 我稍后会尝试。如果打扰到您了,我很抱歉,只是我正在处理一个涉及扫描和切割一些3D物体的项目,并且遇到了这些问题。 - Nico
1
我想不出一个简单的解决方法... 我建议您发布一个新问题。一定要注明这是一个跟进的问题(引用此问题)。包括一些有关切片的背景,以便他人知道这不是练习。非常清楚地说明哪些轮廓应该被填充,哪些应该保持为空(这些东西非常令人困惑)。在发布带有红色标记的屏幕截图时,请不要忘记添加原始(清洁)图像。 - Rotem
好的,非常感谢你的帮助,我因此取得了很大进展 :) 你最后的代码能用吗(填充轮廓,然后取消填充,再次填充)?至少对于更大的轮廓是可以的,不像我的骨架化图像中的像素大小(这也有点不寻常)。 - Nico
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接