Заполнение контура - PullRequest
       32

Заполнение контура

7 голосов
/ 30 сентября 2019

У меня есть коллекция изображений с кругом, нарисованным в виде белого контура. Тем не менее, я хочу заполнить весь круг белым цветом. Какой быстрый способ сделать это? Ниже приведен пример изображения:

Sample image

Я пытался использовать вложенные циклы для достижения этой цели, но это занимает много времени, и у меня есть около 1,5 миллиона изображений. Вот мой код:

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

Ответы [ 3 ]

4 голосов
/ 01 октября 2019

Я попытался найти ограничивающий прямоугольник белого контура и получить его центр, а затем залить белым оттуда наружу.

#!/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)

Конечно, если у вас есть 1,5 миллиона, я бы порекомендовал также параллельную обработку: -)

4 голосов
/ 30 сентября 2019

Используйте 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()

Примечание: порог Оцу может быть удалендля немного более высокой производительности, поскольку входное изображение уже является двоичным, вы можете непосредственно найти контуры на изображении в градациях серого

3 голосов
/ 01 октября 2019

Для действительно произвольной формы, я бы порекомендовал заливку. Однако, поскольку у вас есть гарантированная выпуклая форма, вы можете сделать некоторые оптимизации. В частности, каждая строка / столбец изображения будет следовать одному из трех шаблонов:

  1. Весь черный
  2. Черный, белый, черный
  3. Черный, белый, черный,белый, черный

Технически существует больше опций, поскольку один или оба черных поля в опциях 2 и 3 могут отсутствовать. Цель состоит в том, чтобы заполнить среднюю черную область в варианте 3. Это можно сделать с помощью простой маскировки и необычной индексации.

Основной алгоритм:

  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 , и более чем в 10 раз медленнее, чем @ MarkSetchell в . Я просто оставляю это здесь для развлечения.

$ 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)
...