una umbralizacion segmentacion otsu imagenes imagen histograma comparar binarizar algoritmo opencv image-processing threshold

opencv - umbralizacion - Umbralización de imagen rápida



umbralizacion opencv (5)

¿Cuál es una forma rápida y confiable de ajustar las imágenes con posible borrosidad y brillo no uniforme?

Ejemplo (desenfoque pero brillo uniforme):

Debido a que no se garantiza que la imagen tenga un brillo uniforme, no es factible usar un umbral fijo. Un umbral adaptativo funciona bien, pero debido a la falta de claridad crea roturas y distorsiones en las características (aquí, las características importantes son los dígitos del Sudoku):

También intenté usar la ecualización de histogramas (usando la función equalizeHist de OpenCV). Aumenta el contraste sin reducir las diferencias en el brillo.

La mejor solución que he encontrado es dividir la imagen por su cierre morfológico (crédito a esta publicación ) para uniformizar el brillo, luego renormalizar, luego usar un umbral fijo (usando el algoritmo de Otsu para elegir el nivel de umbral óptimo):

Aquí hay un código para esto en OpenCV para Android:

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(19,19)); Mat closed = new Mat(); // closed will have type CV_32F Imgproc.morphologyEx(image, closed, Imgproc.MORPH_CLOSE, kernel); Core.divide(image, closed, closed, 1, CvType.CV_32F); Core.normalize(closed, image, 0, 255, Core.NORM_MINMAX, CvType.CV_8U); Imgproc.threshold(image, image, -1, 255, Imgproc.THRESH_BINARY_INV +Imgproc.THRESH_OTSU);

Esto funciona bien, pero la operación de cierre es muy lenta. Reducir el tamaño del elemento estructurador aumenta la velocidad pero reduce la precisión.

Editar: de acuerdo con la sugerencia de DCS intenté usar un filtro de paso alto. Elegí el filtro Laplaciano, pero esperaría resultados similares con los filtros Sobel y Scharr. El filtro capta el ruido de alta frecuencia en las áreas que no contienen características y sufre una distorsión similar al umbral de adaptación debido a la borrosidad. también toma aproximadamente el tiempo que dura la operación de cierre. Aquí hay un ejemplo con un filtro de 15x15:

Edición 2: en base a la respuesta de AruniRC, utilicé Canny Edge Detection en la imagen con los parámetros sugeridos:

double mean = Core.mean(image).val[0]; Imgproc.Canny(image, image, 0.66*mean, 1.33*mean);

No estoy seguro de cómo ajustar de forma confiable y automática los parámetros para obtener los dígitos conectados.


Enfoque alternativo:

Suponiendo que su intención es tener los números claramente binarizados ... cambie su enfoque a los componentes en lugar de a la imagen completa.

Aquí hay un enfoque bastante fácil:

  1. Haz un mapa de bordes Canny en la imagen. Primero pruébelo con los parámetros de la función Canny en el rango del umbral bajo a 0.66 * [valor medio] y el umbral alto a 1.33 * [valor medio]. (es decir, la media de los valores de greylevel).
  2. Tendría que jugar un poco con los parámetros para obtener una imagen donde los componentes / números principales se vean claramente como componentes separados. Casi perfecto sería lo suficientemente bueno en esta etapa.
  3. Teniendo en cuenta cada Canny Edge como un componente conectado (es decir, use cvFindContours () o su contraparte de C ++, cualquiera) uno puede estimar los greylevels de primer plano y de fondo y alcanzar un umbral.

    Para el último bit, eche un vistazo a las secciones 2. y 3. de este documento . Saltarse la mayoría de las partes teóricas no esenciales no debería ser demasiado difícil para implementarlo en OpenCV.

    Espero que esto haya ayudado!

Editar 1:

Con base en los límites de Canny Edge, aquí hay una idea muy aproximada que basta para ajustar los valores. El high_threshold controla qué tan fuerte debe ser un borde antes de que se detecte. Básicamente, un borde debe tener una magnitud de gradiente mayor que high_threshold para ser detectado en primer lugar. Entonces esto hace la detección inicial de los bordes.

Ahora, el low_threshold trata de conectar bordes cercanos. Controla la cantidad de bordes desconectados cercanos que se combinarán en un solo borde. Para una mejor idea, lea "Paso 6" de esta página web . Intenta establecer un low_threshold muy pequeño y observa cómo ocurren las cosas. Podrías descartar esa cosa de 0.66 * [valor medio] si no funciona en estas imágenes, es solo una regla general de todos modos.


La forma de elipse es compleja de calcular si se compara con una forma plana. Trata de cambiar:

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(19,19));

a:

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(19,19));

puede acelerar su solución suficiente con bajo impacto a la precisión.


Puedes intentar trabajar por cada base si sabes que tienes una buena cosecha de la grilla. Trabajar en 9 subimágenes en lugar de toda la imagen probablemente conducirá a un brillo más uniforme en cada subimagen. Si su cultivo es perfecto, incluso podría intentar ir por cada celda de dígitos individualmente; pero todo depende de qué tan confiable es tu cultivo.


Utilizamos el algoritmo de Bradleys para un problema muy similar (para segmentar letras del fondo, con luz desigual y color de fondo desigual), que se describe aquí: http://people.scs.carleton.ca:8008/~roth/iit-publications-iti/docs/gerh-50002.pdf , código de C # aquí: http://code.google.com/p/aforge/source/browse/trunk/Sources/Imaging/Filters/Adaptive+Binarization/BradleyLocalThresholding.cs?r=1360 . Funciona en la imagen integral, que se puede calcular utilizando la función integral de OpenCV. Es muy confiable y rápido, pero no está implementado en OpenCV, pero es fácil de transportar.

Otra opción es el método adaptiveThreshold en openCV, pero no lo probamos: http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html#adaptivethreshold . La versión MEAN es la misma que la de los bradleys, excepto que usa una constante para modificar el valor medio en lugar de un porcentaje, que creo que es mejor.

Además, un buen artículo está aquí: https://dsp.stackexchange.com/a/2504


Utilizando las sugerencias de Vaughn Cato y Theraot, reduje la imagen antes de cerrarla, luego amplié la imagen cerrada hasta el tamaño normal. También reduje el tamaño del kernel proporcionalmente.

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5,5)); Mat temp = new Mat(); Imgproc.resize(image, temp, new Size(image.cols()/4, image.rows()/4)); Imgproc.morphologyEx(temp, temp, Imgproc.MORPH_CLOSE, kernel); Imgproc.resize(temp, temp, new Size(image.cols(), image.rows())); Core.divide(image, temp, temp, 1, CvType.CV_32F); // temp will now have type CV_32F Core.normalize(temp, image, 0, 255, Core.NORM_MINMAX, CvType.CV_8U); Imgproc.threshold(image, image, -1, 255, Imgproc.THRESH_BINARY_INV+Imgproc.THRESH_OTSU);

La imagen a continuación muestra los resultados uno al lado del otro para 3 métodos diferentes:

Izquierda: tamaño normal de cierre (432 píxeles), tamaño 19 kernel

Medio - cierre de mitad de tamaño (216 píxeles), núcleo de tamaño 9

Derecha: cierre de cuarto de tamaño (108 píxeles), tamaño 5 kernel

La calidad de la imagen se deteriora a medida que el tamaño de la imagen utilizada para cerrar se reduce, pero el deterioro no es lo suficientemente significativo como para afectar los algoritmos de reconocimiento de características. La velocidad aumenta ligeramente más de 16 veces para el cierre del tamaño de un cuarto, incluso con el cambio de tamaño, lo que sugiere que el tiempo de cierre es más o menos proporcional al número de píxeles en la imagen.

Cualquier sugerencia sobre cómo mejorar aún más esta idea (ya sea reduciendo aún más la velocidad o reduciendo el deterioro en la calidad de la imagen) es muy bienvenida.