对于真正任意的形状,我建议使用洪水填充。但是,由于您有保证的凸形,您可以进行一些优化。具体来说,图像的每一行/每一列都将遵循以下三种模式之一:
- 全黑
- 黑、白、黑
- 黑、白、黑、白、黑
从技术上讲,还有更多选项,因为选项 2 和 3 中的一个或两个黑色边距可能会丢失。目标是填充选项 3 中的中间黑色区域。这可以通过一些简单的 numpy 掩码和花哨的索引来完成。
基本算法是:
- 计算每个白色段的起始索引
- 制作包含两个起始索引的行的行掩码
- 制作一个包含原始数据的完整掩码,索引之间的元素也设置为
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)