Tengo una colección de imágenes con un círculo dibujado como un contorno blanco. Sin embargo, quiero llenar el círculo completo con color blanco. ¿Cuál es una forma rápida de hacerlo? La siguiente es la muestra de la imagen:

Sample image

He intentado usar bucles anidados para lograr esto, pero lleva mucho tiempo, y tengo alrededor de 1,5 millones de imágenes. El siguiente es mi código:

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
7
Haroon S. 30 sep. 2019 a las 22:27

3 respuestas

La mejor respuesta

Traté de encontrar el cuadro delimitador del contorno blanco, y obtener su centro, luego llenándome de blanco desde allí hacia afuera.

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

Esto parece dar los mismos resultados y ejecuta 2.5 veces más rápido:

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)

Por supuesto, si tiene 1,5 millones para hacer, recomendaría también un procesamiento paralelo :-)

4
Mark Setchell 1 oct. 2019 a las 00:07

Para una forma verdaderamente arbitraria, recomendaría el relleno de inundación. Sin embargo, dado que tiene una forma convexa garantizada, puede hacer algunas optimizaciones. Específicamente, cada fila / columna de la imagen seguirá uno de tres patrones:

  1. Toda negra
  2. Negra, blanca, negra
  3. Negra, blanca, negra, blanca, negra

Técnicamente hay más opciones, ya que puede faltar uno o ambos márgenes negros en las opciones 2 y 3. El objetivo es completar la región negra del medio en la opción 3. Esto se puede hacer con un simple enmascarado numpy e indexación elegante.

El algoritmo básico es:

  1. Calcule el índice de inicio de cada segmento blanco
  2. Haga una máscara de fila de filas que contengan dos índices iniciales
  3. Haga una máscara completa que contenga los datos originales, con elementos entre los índices establecidos también en 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

Tiempo

Este método es aproximadamente el doble de lento que @ nathancy's y más de 10 veces más lento que @MarkSetchell's . Básicamente, lo dejo aquí por diversión en este momento.

$ 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

Aquí, q58174115.py es

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)
3
Mad Physicist 1 oct. 2019 a las 03:36

Use cv2.fillPoly() para llenar el círculo contorno

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()

Nota: El umbral de Otsu podría eliminarse para obtener un rendimiento ligeramente más rápido, ya que la imagen de entrada ya es una imagen binaria, puede encontrar contornos directamente en la imagen en escala de grises

4
nathancy 1 oct. 2019 a las 03:51
58174115