如何从 cv2.findContours 结果中重新创建带有孔的原始图像?
Posted
技术标签:
【中文标题】如何从 cv2.findContours 结果中重新创建带有孔的原始图像?【英文标题】:How can I recreate original image from cv2.findContours result with holes? 【发布时间】:2022-01-09 10:45:31 【问题描述】:我正在尝试使用 OpenCV 检测二进制图像中的轮廓,然后绘制生成的轮廓多边形以重新创建输入图像。但是,返回 OpenCV 轮廓多边形的表示方式并不容易。
首先,让我们设置数据:
import cv2
import numpy as np
def a_small_hole_with_diagonal_border() -> np.ndarray:
bitmask = np.zeros((10, 10), dtype=np.uint8) + 255
indices = [(2, 2), (2, 3), (2, 4), (2, 5), (2, 6),
(3, 4), (3, 5), (3, 6),
(4, 4), (4, 5), (4, 6)]
row_indices, col_indices = zip(*indices)
bitmask[row_indices, col_indices] = 0
bitmask[5:, 7:] = 0
return bitmask
bitmask = a_small_hole_with_diagonal_border()
padded_bitmask = np.zeros((bitmask.shape[0] + 2, bitmask.shape[1] + 2), dtype=bitmask.dtype)
padded_bitmask[1:-1, 1:-1] = bitmask
这张图是这样的(这些只是matplotlib的截图):
现在我正在运行 findContours 并绘制结果轮廓:
contours, hierarchy = cv2.findContours(image=padded_bitmask,
mode=cv2.RETR_TREE,
method=cv2.CHAIN_APPROX_SIMPLE)
def get_bitmask(*polygons: np.ndarray, width: int, height: int) -> np.ndarray:
image = np.zeros((width, height, 3), dtype=np.uint8)
for polygon in polygons:
polygon = polygon.reshape((-1, 1, 2)).astype(np.int32)
cv2.fillPoly(image, pts=[polygon], color=(0, 0, 255))
return image[..., -1]
get_bitmask(contours[0], 10, 10)
get_bitmask(contours[1], 10, 10)
我得到以下输出:
外围区域的轮廓正是我想要的, 但是对于中间的洞,我想以某种方式勾勒坐标 不代表轮廓的最内边界,而是代表孔的最外边界。 这就是我的意思:
(我手动使用 gimp 将第二个轮廓覆盖在原始图像上)
我想要的是描述孔的这些像素的轮廓(请原谅我的绘画技巧不佳):
所以本质上,如果我在第一个轮廓上绘制第二个轮廓(我正在为此解析树层次结构),我想重新创建输入图像。 我怎样才能做到这一点? 如果我只是按照here 的建议绘制孔的多边形,我会得到错误的结果:
img = np.zeros((10, 10, 3), dtype=np.uint8)
polygon = contours[1].reshape((-1, 1, 2)).astype(np.int32)
cv2.fillPoly(img, polygon, color=(255, 0, 0),)
cv2.CHAIN_APPROX_SIMPLE
和 cv2.CHAIN_APPROX_NONE
我尝试在图像及其逆图像上运行 findContours,然后合并结果,但必须有一种更简单的方法,因为我无法让它在所有情况下都能正常工作,而只需获得正确的表示即可容易很多。
【问题讨论】:
【参考方案1】:这是一个解决方案,但可能会有更好的解决方案。 反转整个图像很棘手,但您可以反转每个孔的遮罩,然后再次运行 findContours。 然后用该输出替换孔的轮廓。 这是一个从 blob 边界计算孔边界的函数。 对来自问题的数据运行它,如果您放入第二个轮廓,您将获得描述您绘制的数据的多边形。
def _get_hole_contour_from_outer_boundary_contour(boundary_contour: np.ndarray,
padded_bitmask: np.ndarray) -> np.ndarray:
# We have the outer boundary of the toplevel blob, but we need the outer boundary of the hole
# which is the pixels that are enclosed by the boundary_contour
col, row, width, height = cv2.boundingRect(boundary_contour)
cutout = padded_bitmask[row:row + height, col:col + width]
# we invert the pixels so that the black hole now becomes a white blob
# this way we get the representation we want when we extract contours again
inverse_cutout = np.abs(255 - cutout)
# We zero out the first and last row and column, as these are part of the
# bounding box and not part of what is inside the hole
# This way we don't have to worry about any other holes potentially overlapping
# into the bounding box
inverse_cutout[:, 0] = 0
inverse_cutout[:, -1] = 0
inverse_cutout[0, :] = 0
inverse_cutout[-1, :] = 0
# Now we can use RETR_EXTERNAL, which only gives us the outer boundaries and does not care about holes
contours_hole, _ = cv2.findContours(image=inverse_cutout,
mode=cv2.RETR_EXTERNAL,
method=cv2.CHAIN_APPROX_SIMPLE,
offset=(col, row))
# Since we cut out the area exactly around the hole, the one with the biggest bounding box is
# always the one we want
return sorted(contours_hole, key=lambda contour: _area_of_bounding_react(contour=contour), reverse=True)[0]
def _area_of_bounding_react(contour: np.ndarray) -> int:
col, row, width, height = cv2.boundingRect(contour)
return (col + width) * (row + height)
【讨论】:
以上是关于如何从 cv2.findContours 结果中重新创建带有孔的原始图像?的主要内容,如果未能解决你的问题,请参考以下文章
【python】opencv库中cv2.findContours()和cv2.drawContours()函数
使用cv2.findContours函数传入参数的数据类型问题
Opencv-python 找到图像轮廓并绘制,cv2.findContours()函数,求轮廓外接矩形,cv2.boundingrect()
python调用cv2.findContours时报错:ValueError: not enough values to unpack (expected 3, got 2)