【问题标题】:Filling an outlined circle填充一个轮廓的圆圈
【发布时间】:2020-01-30 02:46:34
【问题描述】:

我有一组图像,其中一个圆圈绘制为白色轮廓。但是,我想用白色填充整个圆圈。什么是快速的方法?以下是图片示例:

我曾尝试使用嵌套循环来实现这一点,但这需要很长时间,而且我有大约 150 万张图像。以下是我的代码:

roundRobinIndex = 0
new_image = np.zeros((img_w, img_h))
for row in range(540):
    for column in range(800):
        if image[row,column] == 255:
            roundRobinIndex = (roundRobinIndex + 1) % 2
        if roundRobinIndex == 1:
            new_image[row, column] = 255

【问题讨论】:

  • 您遇到过实际的计时问题吗?你能显示出她有问题的代码吗?
  • 我很好奇 - 为什么你有 150 万个空椭圆? :-)
  • @MarkSetchell 实际上,这些是单独存储的肺结节位置。我想填充它而不是仅仅概述它。
  • 如图所示,它们都是纯白底黑字吗?您使用的是什么操作系统?
  • @MadPhysicist 代码已更新。但它会产生伪影。 nathancy 的答案非常有效。

标签: python image opencv image-processing python-imaging-library


【解决方案1】:

使用cv2.fillPoly()填充圆形轮廓

import cv2

image = cv2.imread('1.png', 0)
thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(image, cnts, [255,255,255])

cv2.imshow('image', image)
cv2.waitKey()

注意:由于输入图像已经是二值图像,可以去除 Otsu 的阈值以获得稍快的性能,您可以直接在灰度图像上找到轮廓

【讨论】:

    【解决方案2】:

    我尝试找到白色轮廓的边界框,并找到它的中心,然后从那里向外填充白色。

    #!/usr/bin/env python3
    
    import cv2
    
    def findfill(image):
        thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
        cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        cnts = cnts[0] if len(cnts) == 2 else cnts[1]
        cv2.fillPoly(image, cnts, [255,255,255])
    
    def me(image):
        x,y,w,h = cv2.boundingRect(image)
        cv2.floodFill(image,None,(int(x+w/2),int(y+h/2)),255)
        return image
    
    image = cv2.imread('BLYmz.png', 0)
    
    %timeit findfill(image)
    %timeit me(image)
    

    这似乎给出了相同的结果并且运行速度提高了 2.5 倍:

    findfill
    810 µs ± 2.94 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    me
    343 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    当然,如果你有 150 万个任务要做,我也会推荐一些并行处理 :-)

    【讨论】:

    • 当然做一个简单的边界框比搜索轮廓要快得多。这也让我对接受的答案感到困扰,但我对 opencv 的了解还不够多,无法说什么:)
    • 确实快多了。但是,我接受了上述答案,因为它是当时唯一的答案,而且它确实比我的代码运行得更快,没有任何伪影。
    【解决方案3】:

    对于真正任意的形状,我建议使用洪水填充。但是,由于您有保证的凸形,您可以进行一些优化。具体来说,图像的每一行/每一列都将遵循以下三种模式之一:

    1. 全黑
    2. 黑、白、黑
    3. 黑、白、黑、白、黑

    从技术上讲,还有更多选项,因为选项 2 和 3 中的一个或两个黑色边距可能会丢失。目标是填充选项 3 中的中间黑色区域。这可以通过一些简单的 numpy 掩码和花哨的索引来完成。

    基本算法是:

    1. 计算每个白色段的起始索引
    2. 制作包含两个起始索引的行的行掩码
    3. 制作一个包含原始数据的完整掩码,索引之间的元素也设置为True
    def fill_convex(image):
        mask = image.astype(np.bool)
        # mask out elements that are 1, but the previous is 0
        start = (mask[:, 1:] & ~mask[:, :-1])
        # find rows that have exactly two runs of True
        row_mask = (np.count_nonzero(start, axis=1) == 2)
        # get the pairs of column indices that correspond to the masked elements
        cols = np.nonzero(start[row_mask, :])[1].reshape(-1, 2)
        # create a row of column indices the same size as a row
        count = np.arange(image.shape[1])
        # fill in the elements between start and stop indices for each row
        # the None indices are used to trigger broadcasting
        to_fill = ((count[None, :] >= cols[:, 0, None]) & (count[None, :] <= cols[:, 1, None]))
        # update the mask
        mask[row_mask, :] |= to_fill
        # fill in the image
        image[mask] = 255
        return image
    

    时机

    这种方法的速度大约是@nathancy's 的两倍,比@MarkSetchell's 慢10 倍以上。在这一点上,我基本上把它留在这里。

    $ python -m timeit -s 'import q58174115' 'q58174115.nathancy(q58174115.image)'
    500 loops, best of 5: 437 usec per loop
    $ python -m timeit -s 'import q58174115' 'q58174115.MarkSetchell(q58174115.image.copy())'
    5000 loops, best of 5: 62.9 usec per loop
    $ python -m timeit -s 'import q58174115' 'q58174115.MadPhysicist(q58174115.image.copy())'
    500 loops, best of 5: 779 usec per loop
    

    这里,q58174115.py

    import cv2
    import numpy as np
    
    def nathancy(image):
        thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
        cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        cnts = cnts[0] if len(cnts) == 2 else cnts[1]
        cv2.fillPoly(image, cnts, [255,255,255])
        return image
    
    def MarkSetchell(image):
        x,y,w,h = cv2.boundingRect(image)
        cv2.floodFill(image,None,(int(x+w/2),int(y+h/2)),255)
        return image
    
    def MadPhysicist(image):
        mask = image.astype(np.bool)
        # mask out elements that are 1, but the previous is 0
        start = (mask[:, 1:] & ~mask[:, :-1])
        # find rows that have exactly two runs of True
        row_mask = (np.count_nonzero(start, axis=1) == 2)
        # get the pairs of column indices that correspond to the masked elements
        cols = np.nonzero(start[row_mask, :])[1].reshape(-1, 2)
        # create a row of column indices the same size as a row
        count = np.arange(image.shape[1])
        # fill in the elements between start and stop indices for each row
        # the None indices are used to trigger broadcasting
        to_fill = ((count[None, :] >= cols[:, 0, None]) & (count[None, :] <= cols[:, 1, None]))
        # update the mask
        mask[row_mask, :] |= to_fill
        # fill in the image
        image[mask] = 255
        return image
    
    image = cv2.imread('58174115.png', 0)
    

    【讨论】:

    • 这甚至比我的还要快,但我看不懂,甚至不知道答案在哪里检查结果 :-) 虽然它有我的投票 :-)
    • @马克。感谢您的信任。我在手机上写了这个,没有办法测试它。我会在使用桌面时进行解释和基准测试
    • @MarkSetchell。我不认为我的方式更快:(
    • @MadPhysicist,这是一些 Numpy 魔法。我相信如果移除阈值,我的方法会稍微快一些(因为 OP 的图像已经是二进制图像)
    • @nathancy。我怀疑与findContours 相比,阈值相当便宜。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-07
    • 2021-03-17
    • 1970-01-01
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多