Eliminar pequeñas islas espurias de ruido en una imagen-Python OpenCV


Estoy tratando de deshacerme del ruido de fondo de algunas de mis imágenes. Esta es la imagen sin filtrar.

Para filtrar, utilicé este código para generar una máscara de lo que debería permanecer en la imagen:

 element = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
 mask = cv2.erode(mask, element, iterations = 1)
 mask = cv2.dilate(mask, element, iterations = 1)
 mask = cv2.erode(mask, element)

Con este código y cuando enmascare los píxeles no deseados de la imagen original, lo que obtengo es:

Como pueden ver, todos los puntos diminutos en el área central se han ido, pero muchos de los que vienen del área más densa también se han ido. Para reducir el filtrado, He intentado cambiar el segundo parámetro de getStructuringElement() para ser (1,1), pero haciendo esto me da la primera imagen como si nada se ha filtrado.

¿Hay alguna manera en la que pueda aplicar algún filtro que esté entre estos 2 extremos?

Además, ¿puede alguien explicarme qué hace exactamente getStructuringElement()? ¿Qué es un "elemento estructurante"? ¿Qué hace y cómo afecta su tamaño (el segundo parámetro) al nivel de filtrado?

Author: rayryeng, 2015-05-21

1 answers

Muchas de sus preguntas provienen del hecho de que no está seguro de cómo funciona el procesamiento morfológico de imágenes, pero podemos disipar sus dudas. Puede interpretar el elemento estructurante como la "forma base" con la que comparar. 1 en el elemento estructurante corresponde a un píxel que desea ver en esta forma y 0 es uno que desea ignorar. Hay diferentes formas, tales como rectangular (como usted ha descubierto con MORPH_RECT), elipse, circular, etc.

Como tal, cv2.getStructuringElement devuelve un elemento estructurante para usted. El primer parámetro especifica el tipo que desea y el segundo parámetro especifica el tamaño que desee. En su caso, desea un "rectángulo"de 2 x 2... que es realmente un cuadrado, pero está bien.

En un sentido más bastardizado, se usa el elemento de estructuración y se escanea de izquierda a derecha y de arriba a abajo de la imagen y se toman los barrios de píxeles. Cada vecindario de píxeles tiene su centro exactamente en el píxel de interés que estás mirando. El tamaño de cada vecindario de píxeles es del mismo tamaño que el elemento estructurante.

Erosión

Para una erosión, se examinan todos los píxeles en un vecindario de píxeles que están tocando el elemento estructurante. Si cada píxel distinto de cero está tocando un píxel de elemento estructurante que es 1, entonces el píxel de salida en la posición central correspondiente con respecto a la entrada es 1. Si hay al menos un píxel distinto de cero que no toque una estructuración píxel que es 1, entonces la salida es 0.

En términos del elemento de estructuración rectangular, debe asegurarse de que cada píxel en el elemento de estructuración esté tocando un píxel distinto de cero en su imagen para un vecindario de píxeles. Si no lo es, entonces la salida es 0, de lo contrario 1. Esto elimina eficazmente pequeñas áreas espurias de ruido y también disminuye ligeramente el área de los objetos.

Los factores de tamaño en donde cuanto más grande es el rectángulo, más encogimiento se realiza. Tamaño del elemento de estructuración es una línea de base donde los objetos que son más pequeños que este elemento de estructuración rectangular, puede considerarlos como filtrados y no aparecen en la salida. Básicamente, elegir un elemento de estructuración rectangular de 1 x 1 es lo mismo que la imagen de entrada en sí porque ese elemento de estructuración se ajusta a todos los píxeles dentro de él, ya que el píxel es la representación más pequeña de información posible en una imagen.

Dilatación

La dilatación es lo contrario de erosión. Si hay al menos un píxel distinto de cero que toca un píxel en el elemento estructurante que es 1, entonces la salida es 1, de lo contrario la salida es 0. Puedes pensar en esto como agrandar ligeramente las áreas de objetos y hacer que las islas pequeñas sean más grandes.

Las implicaciones con el tamaño aquí es que cuanto mayor sea el elemento estructurante, mayores serán las áreas de los objetos y mayores serán las islas aisladas.


Lo que estás haciendo es una erosión primero seguida de un dilatación. Esto es lo que se conoce como una operación de apertura . El propósito de esta operación es eliminar pequeñas islas de ruido mientras (intenta) mantener las áreas de los objetos más grandes en su imagen. La erosión elimina esas islas mientras que la dilatación hace que los objetos más grandes vuelvan a sus tamaños originales.

Sigues esto con una erosión de nuevo por alguna razón, que no puedo entender del todo, pero eso está bien.


Lo que yo personalmente haría es realizar un cierre operación primero que es una dilatación seguida de una erosión. El cierre ayuda a agrupar áreas que están juntas en un solo objeto. Como tal, ves que hay algunas áreas más grandes que están cerca unas de otras que probablemente deberían unirse antes de hacer cualquier otra cosa. Como tal, primero haría un cierre, luego haría una apertura después para que podamos eliminar las áreas ruidosas aisladas. Tome nota de que voy a hacer que el tamaño del elemento de estructuración de cierre más grandeya que quiero asegurarme de obtener píxeles cercanos y el tamaño del elemento de estructuración de apertura más pequeño para que no quiera eliminar por error ninguna de las áreas más grandes.

Una vez que hagas esto, ocultaría cualquier información adicional con la imagen original para que dejes las áreas más grandes intactas mientras las pequeñas islas desaparecen.

En Lugar de encadenar una erosión seguida de una dilatación o una dilatación seguida de una erosión, uso cv2.morphologyEx, donde puede especificar MORPH_OPEN y MORPH_CLOSE como banderas.

Como tal, yo personalmente haría esto, asumiendo que su imagen se llama spots.png:

import cv2
import numpy as np

img = cv2.imread('spots.png')
img_bw = 255*(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) > 5).astype('uint8')

se1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
se2 = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
mask = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, se1)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, se2)

mask = np.dstack([mask, mask, mask]) / 255
out = img * mask

cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out)

El código anterior se explica por sí mismo. Primero, leo en la imagen y luego convierto la imagen a escala de grises y umbral con una intensidad de 5 para crear una máscara de lo que se considera píxeles de objeto. Esta es una imagen bastante limpia y por lo que cualquier cosa más grande que 5 parece haber funcionado. Para las rutinas de morfología, necesito convertir la imagen a uint8 y escala la máscara a 255. A continuación, creamos dos elementos estructurantes: uno que es un rectángulo de 5 x 5 para la operación de cierre y otro que es de 2 x 2 para la operación de apertura. Corro cv2.morphologyEx dos veces para las operaciones de apertura y cierre respectivamente en la imagen umbral.

Una vez que hago eso, apilo la máscara para que se convierta en una matriz 3D y divido por 255 para que se convierta en una máscara de [0,1] y luego multiplicamos esta máscara con la imagen original para que podamos agarrar el píxeles originales de la imagen de nuevo y mantener lo que se considera un objeto verdadero de la salida de la máscara.

El resto es solo para ilustrar. Muestro la imagen en una ventana, y también guardo la imagen en un archivo llamado output.png, y su propósito es mostrarte cómo se ve la imagen en este post.

Entiendo esto: {[14]]}

introduzca la descripción de la imagen aquí

Ten en cuenta que no es perfecto, pero es mucho mejor que como lo tenías antes. Tendrás que jugar con la estructuración tamaños de elementos para obtener algo que considere como una buena salida, pero esto es ciertamente suficiente para comenzar. ¡Buena suerte!


C++ version

Ha habido algunas solicitudes para traducir el código que escribí anteriormente a la versión de C++ usando OpenCV. Finalmente he llegado a escribir una versión en C++ del código y esto ha sido probado en OpenCV 3.1.0. El código para esto está abajo. Como puedes ver, el código es muy similar al que se ve en la versión de Python. Sin embargo, usé cv::Mat::setTo en una copia de la imagen original y establecer lo que no era parte de la máscara final a 0. Esto es lo mismo que realizar una multiplicación de elementos en Python.

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char *argv[])
{
    // Read in the image
    Mat img = imread("spots.png", CV_LOAD_IMAGE_COLOR);

    // Convert to black and white
    Mat img_bw;
    cvtColor(img, img_bw, COLOR_BGR2GRAY);
    img_bw = img_bw > 5;

    // Define the structuring elements
    Mat se1 = getStructuringElement(MORPH_RECT, Size(5, 5));
    Mat se2 = getStructuringElement(MORPH_RECT, Size(2, 2));

    // Perform closing then opening
    Mat mask;
    morphologyEx(img_bw, mask, MORPH_CLOSE, se1);
    morphologyEx(mask, mask, MORPH_OPEN, se2);

    // Filter the output
    Mat out = img.clone();
    out.setTo(Scalar(0), mask == 0);

    // Show image and save
    namedWindow("Output", WINDOW_NORMAL);
    imshow("Output", out);
    waitKey(0);
    destroyWindow("Output");
    imwrite("output.png", out);
}

Los resultados deben ser los mismos que se obtienen en la versión de Python.

 58
Author: rayryeng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-26 21:53:10