segmentacion - Elimina pequeñas islas espurias de ruido en una imagen-Python OpenCV
gradiente de una imagen python (1)
Muchas de sus preguntas se derivan del hecho de que no está seguro de cómo funciona el procesamiento de imágenes morfológicas, pero podemos poner sus dudas a descansar. Puede interpretar el elemento estructurador como la "forma base" para comparar. 1 en el elemento de estructura corresponde a un píxel que desea ver en esta forma y 0 es uno que desea ignorar. Hay diferentes formas, como rectangular (como se ha descubierto con MORPH_RECT
), elipse, circular, etc.
Como tal, cv2.getStructuringElement
devuelve un elemento de estructuración. El primer parámetro especifica el tipo que desea y el segundo parámetro especifica el tamaño que desea. En tu caso, quieres un "rectángulo" de 2 x 2 ... que en realidad es un cuadrado, pero está bien.
En un sentido más bastardo, utiliza el elemento de estructuración y escanea de izquierda a derecha y de arriba a abajo de la imagen y capta vecindades de píxeles. Cada vecindad de píxeles tiene su centro exactamente en el píxel de interés que está viendo. El tamaño de cada vecindad de píxeles es del mismo tamaño que el elemento estructurador.
Erosión
Para una erosión, examina todos los píxeles en una vecindad de píxeles que están tocando el elemento de estructuración. 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 un píxel estructurante eso es 1, entonces la salida es 0.
En términos del elemento de estructura 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 una vecindad de píxeles. Si no lo es, entonces la salida es 0, sino 1. Esto elimina efectivamente las áreas de ruido pequeñas y también reduce el área de los objetos ligeramente.
Los factores de tamaño en donde cuanto más grande es el rectángulo, más contracción se realiza. El 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 estructura rectangular, puede considerarlos como filtrados y no aparecen en el resultado. Básicamente, elegir un elemento de estructura rectangular de 1 x 1 es lo mismo que la imagen de entrada en sí misma 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 opuesto a la erosión. Si hay al menos un píxel distinto de cero que toca un píxel en el elemento de estructuración que es 1, entonces la salida es 1; de lo contrario, la salida es 0. Puede pensar en esto como áreas de objetos que se agrandan ligeramente y agrandar las islas pequeñas.
Las implicaciones con el tamaño aquí son que cuanto mayor sea el elemento estructurante, mayores serán las áreas de los objetos y mayor será el tamaño de las islas aisladas.
Lo que estás haciendo es una erosión primero seguida de una 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 vuelve a crecer los objetos más grandes a sus tamaños originales.
Sigues esto con una erosión de nuevo por alguna razón, que no puedo entender del todo, pero está bien.
Lo que personalmente haría es realizar primero una operación de cierre , que es una dilatación seguida de una erosión. El cierre ayuda a agrupar áreas que están muy 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, cerraría primero, luego abriría después para poder eliminar las áreas ruidosas aisladas. Tenga en cuenta que voy a aumentar el tamaño del elemento de estructuración de cierre ya que quiero asegurarme de que obtenga 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 haga esto, ocultaría cualquier información adicional con la imagen original para que deje intactas las áreas más grandes mientras las islas pequeñas desaparecen.
En lugar de encadenar una erosión seguida de una dilatación o una dilatación seguida de una erosión, use cv2.morphologyEx
, donde puede especificar MORPH_OPEN
y MORPH_CLOSE
como las banderas.
Como tal, yo personalmente haría esto, suponiendo que tu imagen se llame 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 es bastante autoexplicativo. 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 del objeto. Esta es una imagen bastante limpia y, por lo tanto, cualquier cosa más grande que 5 parece haber funcionado. Para las rutinas de morfología, necesito convertir la imagen a uint8
y escalar 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 2 x 2 para la apertura operación. cv2.morphologyEx
dos veces para las operaciones de apertura y cierre respectivamente en la imagen con umbral.
Una vez que hago eso, apilo la máscara para que se convierta en una matriz 3D y se divida 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 tomar los píxeles originales de la imagen hacia atrás y manteniendo lo que se considera un objeto verdadero a partir de la salida de la máscara.
El resto es solo para ilustrar. output.png
la imagen en una ventana, y también output.png
la imagen en un archivo llamado output.png
, y su propósito es mostrarle cómo se ve la imagen en esta publicación.
Entiendo esto:
Tenga en cuenta que no es perfecto, pero es mucho mejor que cómo lo tenía antes. Tendrá que jugar con los tamaños de elementos de estructuración para obtener algo que considere como un buen resultado, pero esto es ciertamente suficiente para comenzar. ¡Buena suerte!
Versión C ++
Ha habido algunas solicitudes para traducir el código que escribí arriba en la versión de C ++ usando OpenCV. Finalmente me puse a escribir una versión C ++ del código y esto se ha probado en OpenCV 3.1.0. El código para esto está abajo. Como puede ver, el código es muy similar al que se ve en la versión de Python. Sin embargo, utilicé cv::Mat::setTo
en una copia de la imagen original y establecí lo que no era parte de la máscara final en 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 obtiene en la versión de Python.
Estoy tratando de eliminar el ruido de fondo de algunas de mis imágenes. Esta es la imagen sin filtro.
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 puede ver, todos los pequeños puntos 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, intenté cambiar el segundo parámetro de getStructuringElement()
a ser (1,1) pero al hacer esto me da la primera imagen como si nada hubiera sido filtrado.
¿Hay alguna manera en que pueda aplicar algún filtro que esté entre estos 2 extremos?
Además, ¿alguien puede 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?