在填充轮廓圆形

8

我有一组带白色轮廓的图像集合,但我想把整个圆填充为白色。有什么快速的方法可以做到吗?以下是图像示例:

Sample image

我尝试使用嵌套循环来实现此目的,但这需要很长时间,而且我有约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

你遇到了实际的时间问题吗?能否展示一下导致问题的代码? - Mad Physicist
2
我很好奇 - 你为什么有150万个空椭圆? :-) - Mark Setchell
@MarkSetchell,实际上这些是单独存储的肺结节位置。我想要填充它而不仅仅是勾画轮廓。 - Haroon S.
它们是否都像所示一样纯白底黑色?你使用的操作系统是什么? - Mark Setchell
@MadPhysicist 代码已更新。但是它会产生伪影。nathancy的答案完美地解决了这个问题。 - Haroon S.
3个回答

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

enter image description here

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阈值以获得稍微快一点的性能,你可以在灰度图像上直接查找轮廓。

5

我试图找到白色轮廓的边界框并获取其中心,然后从那里向外部进行白色填充。

#!/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不够了解,所以什么也没说 :) - Mad Physicist
确实快得多。然而,由于当时它是唯一的答案,并且没有任何伪像,它比我的代码更快,因此我接受了上面的答案。 - Haroon S.

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的方法慢大约两倍,比@MarkSetchell的方法慢超过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)

这比我的还要快,但我无法阅读它,甚至不知道答案在哪里以检查结果 :-) 不过我支持它 :-) - Mark Setchell
@Mark。感谢你的信任。我是在手机上编写的,无法进行测试。等我到桌面电脑时,我会解释并进行基准测试。 - Mad Physicist
@MarkSetchell。我不认为我的方法更快 :( - Mad Physicist
@MadPhysicist,这是一些Numpy的魔法。我相信如果删除阈值(因为OP的图像已经是二进制图像),我的方法会稍微快一点。 - nathancy
@nathancy。我怀疑相对于findContours,阈值处理是比较便宜的。 - Mad Physicist

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