如何使用Python OpenCV在图像中获得泛光区域及其边界?

2024-04-25 07:55:30 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个像这样的图像,只有黑白两种颜色:

Image of a Maze

我只想用cv2.floodfill获得图像中边界为的淹没区域,就像这样(请原谅我的绘画技巧):

enter image description here

这是我目前的代码:

    # Copy the image.
    im_floodfill = cv2.resize(actual_map_image, (500, 500)).copy()

    # Floodfill from point (X, Y)
    cv2.floodFill(im_floodfill, None, (X, Y), (255, 255, 255))

    # Display images.
    cv2.imshow("Floodfilled Image", im_floodfill)
    cv2.waitKey(0)

我得到的输出等于原始图像。我怎样才能只得到有边界的淹没区?你知道吗

编辑:我想从“竞技场”内的任何白色点进行泛光填充,如图像中的红点(X,Y)。我只希望竞技场内小圆圈的外缘和外墙的内缘。你知道吗

EDIT2:我已经完成一半了:

# Resize for test purposes 
    actual_map_image = cv2.resize(actual_map_image, (1000, 1000))
    actual_map_image = cv2.cvtColor(actual_map_image, cv2.COLOR_BGR2GRAY)

    h, w = actual_map_image.shape[:2]
    flood_mask = np.zeros((h+2, w+2), dtype=np.uint8)
    connectivity = 8
    flood_fill_flags = (connectivity | cv2.FLOODFILL_FIXED_RANGE | cv2.FLOODFILL_MASK_ONLY | 255 << 8) 

    # Copy the image.
    im_floodfill = actual_map_image.copy()

    # Floodfill from point inside arena, not inside a black dot
    cv2.floodFill(im_floodfill, flood_mask, (h/2 + 20, w/2 + 20), 255, None, None, flood_fill_flags)

    borders = []
    for i in range(len(actual_map_image)):
        borders.append([B-A for A,B in zip(actual_map_image[i], flood_mask[i])])

    borders = np.asarray(borders)
    borders = cv2.bitwise_not(borders)

    # Display images.
    cv2.imshow("Original Image", cv2.resize(actual_map_image, (500, 500)))
    cv2.imshow("Floodfilled Image", cv2.resize(flood_mask, (500, 500)))
    cv2.imshow("Borders", cv2.resize(borders, (500, 500)))

    cv2.waitKey(0)

我明白了:

enter image description here

然而,我觉得这是一个错误的方式获得边界,他们是不完整的。你知道吗


Tags: 图像imagenonemapmaskcv2边界imshow
3条回答

我必须创建自己的Flood-Fill实现来获得我想要的。我以this one为基础。你知道吗

def fill(data, start_coords, fill_value, border_value, connectivity=8):
    """
    Flood fill algorithm

    Parameters
         
    data : (M, N) ndarray of uint8 type
        Image with flood to be filled. Modified inplace.
    start_coords : tuple
        Length-2 tuple of ints defining (row, col) start coordinates.
    fill_value : int
        Value the flooded area will take after the fill.
    border_value: int
        Value of the color to paint the borders of the filled area with.
    connectivity: 4 or 8
        Connectivity which we use for the flood fill algorithm (4-way or 8-way).

    Returns
       -
    filled_data: ndarray
        The data with the filled area.
    borders: ndarray
        The borders of the filled area painted with border_value color.
    """
    assert connectivity in [4,8]

    filled_data = data.copy()

    xsize, ysize = filled_data.shape
    orig_value = filled_data[start_coords[0], start_coords[1]]

    stack = set(((start_coords[0], start_coords[1]),))
    if fill_value == orig_value:
        raise ValueError("Filling region with same value already present is unsupported. Did you already fill this region?")

    border_points = []

    while stack:
        x, y = stack.pop()

        if filled_data[x, y] == orig_value:
            filled_data[x, y] = fill_value
            if x > 0:
                stack.add((x - 1, y))
            if x < (xsize - 1):
                stack.add((x + 1, y))
            if y > 0:
                stack.add((x, y - 1))
            if y < (ysize - 1):
                stack.add((x, y + 1))

            if connectivity == 8:
                if x > 0 and y > 0:
                    stack.add((x - 1, y - 1))
                if x > 0 and y < (ysize - 1):
                    stack.add((x - 1, y + 1))
                if x < (xsize - 1) and y > 0:
                    stack.add((x + 1, y - 1))
                if x < (xsize - 1) and y < (ysize - 1):
                    stack.add((x + 1, y + 1))
        else:
            if filled_data[x, y] != fill_value:
                border_points.append([x,y])

    # Fill all image with white
    borders = filled_data.copy()
    borders.fill(255)

    # Paint borders
    for x,y in border_points:
        borders[x, y] = border_value

    return filled_data, borders

我做的唯一一件事就是添加else条件。如果点的值不等于orig_valuefill_value,则它是一个边框,因此我将其附加到包含所有边框点的列表中。然后我只画边界。你知道吗

我可以用这个代码得到以下图像:

    # Resize for test purposes 
    actual_map_image = cv2.resize(actual_map_image, (500, 500))
    actual_map_image = cv2.cvtColor(actual_map_image, cv2.COLOR_BGR2GRAY)

    h, w = actual_map_image.shape[:2]

    filled_data, borders = fill(actual_map_image, [h/2 + 20, w/2 + 20], 127, 0, connectivity=8)

    cv2.imshow("Original Image", actual_map_image)
    cv2.imshow("Filled Image", filled_data)
    cv2.imshow("Borders", borders)

enter image description here

右边那个就是我的目标。谢谢大家!你知道吗

扩张和异或如何

kernel = np.ones((3,3), np.uint8)
dilated = cv2.dilate(actual_map_image, kernel, iterations = 1)
borders = cv2.bitwise_xor(dilated, actual_map_image)

这将只给你的边界,我不清楚,如果你想要的只是圆边界还是内部边界,你应该能够删除边界,你不想要的大小为基础。你知道吗


可以使用大小阈值移除外部边界,定义如下函数:

def size_threshold(bw, minimum, maximum):
    retval, labels, stats, centroids = cv.connectedComponentsWithStats(bw)
    for val in np.where((stats[:, 4] < minimum) + (stats[:, 4] > maximum))[0]:
      labels[labels==val] = 0
    return (labels > 0).astype(np.uint8) * 255

result = size_threshold(borders, 0, 500)

将500替换为大于要保留的边界且小于要丢失的边界的数字。你知道吗

我认为最简单、最快的方法就是用中灰色填满竞技场。然后只提取灰度像素并找到它们的边缘。看起来是这样的,但是记住一半以上的行是注释和调试语句:-)

#!/usr/bin/env python3

import cv2

# Load image as greyscale to use 1/3 of the memory and processing time
im = cv2.imread('arena.png', cv2.IMREAD_GRAYSCALE)

# Floodfill arena area with value 128, i.e. mid-grey
floodval = 128
cv2.floodFill(im, None, (150,370), floodval)
# DEBUG cv2.imwrite('result-1.png', im)

# Extract filled area alone
arena = ((im==floodval) * 255).astype(np.uint8)
# DEBUG cv2.imwrite('result-2.png', arena)

# Find edges and save
edges = cv2.Canny(arena,100,200)
# DEBUG cv2.imwrite('result-3.png',edges)

以下是调试输出的3个步骤,向您展示了处理顺序:

result-1.png如下所示:

enter image description here

result-2.png如下所示:

enter image description here

result-3.png如下所示:

enter image description here


顺便说一句,您不必编写任何Python代码来实现这一点,因为您只需在终端中使用ImageMagick即可完成,它包含在大多数Linux发行版中,可用于macOS和Windows。这里使用的方法与我在上面Python中使用的方法完全对应:

magick arena.png -colorspace gray               \
   -fill gray -draw "color 370,150 floodfill"   \
   -fill white +opaque gray -canny 0x1+10%+30% result.png

相关问题 更多 >