¿Cómo mejorar el reconocimiento de dígitos de un modelo capacitado en MNIST?

12

Estoy trabajando en el reconocimiento de varios dígitos impreso a mano Java, utilizando la OpenCVbiblioteca para el preprocesamiento y la segmentación, y un Kerasmodelo capacitado en MNIST (con una precisión de 0,98) para el reconocimiento.

El reconocimiento parece funcionar bastante bien, aparte de una cosa. La red a menudo falla al reconocer los (número "uno"). No puedo entender si sucede debido al preprocesamiento / implementación incorrecta de la segmentación, o si una red capacitada en MNIST estándar simplemente no ha visto el número uno que se parece a mis casos de prueba.

Así es como se ven los dígitos problemáticos después del preprocesamiento y segmentación:

ingrese la descripción de la imagen aquíse convierte ingrese la descripción de la imagen aquíy se clasifica como 4.

ingrese la descripción de la imagen aquíse convierte ingrese la descripción de la imagen aquíy se clasifica como 7.

ingrese la descripción de la imagen aquíse convierte ingrese la descripción de la imagen aquíy se clasifica como 4. Y así...

¿Es esto algo que podría solucionarse mejorando el proceso de segmentación? ¿O más bien mejorando el conjunto de entrenamiento?

Editar: Mejorar el conjunto de entrenamiento (aumento de datos) definitivamente ayudaría, lo que ya estoy probando, la cuestión del preprocesamiento correcto aún permanece.

Mi preprocesamiento consiste en redimensionar, convertir a escala de grises, binarización, inversión y dilatación. Aquí está el código:

Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);

Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);

Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);

Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);

Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);

La imagen preprocesada se segmenta en dígitos individuales de la siguiente manera:

List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

// code to sort contours
// code to check that contour is a valid char

List rects = new ArrayList<>();

for (MatOfPoint contour : contours) {
     Rect boundingBox = Imgproc.boundingRect(contour);
     Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);

     rects.add(rectCrop);
}

for (int i = 0; i < rects.size(); i++) {
    Rect x = (Rect) rects.get(i);
    Mat digit = new Mat(preprocessed, x);

    int border = 50;
    Mat result = digit.clone();
    Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));

    Imgproc.resize(result, result, new Size(28, 28));
    digits.add(result);
}
youngpanda
fuente
1
¿Está utilizando la máscara o los píxeles de escala de grises originales (enmascarados) como entrada para su clasificación?
Micka
@Micka Estoy usando la versión preprocesada (binarizada, invertida, dilatada). Los que coinciden con el conjunto de entrenamiento MNIST. Hay ejemplos del número "1" después del preprocesamiento en mi publicación.
youngpanda

Respuestas:

5

Creo que tu problema es el proceso de dilatación. Entiendo que desea normalizar los tamaños de imagen, pero no debe romper las proporciones, debe cambiar el tamaño al máximo deseado por un eje (el que permite una reescala más grande sin permitir que otra dimensión del eje exceda el tamaño máximo) y rellene con color de fondo el resto de la imagen. No es que "el MNIST estándar simplemente no haya visto el número uno que se parece a sus casos de prueba", hace que sus imágenes se vean como diferentes números entrenados (los que son reconocidos)

Superposición de la fuente y las imágenes procesadas

Si mantuvo la relación de aspecto correcta de sus imágenes (fuente y postprocesado), puede ver que no solo redimensionó la imagen sino que la "distorsionó". Puede ser el resultado de dilatación no homogénea o cambio de tamaño incorrecto

Señor
fuente
Creo que @SiR tiene algo de peso. Intente no cambiar la relación de aspecto de los literales numéricos.
ZdaR
Lo siento, no te sigo. ¿Crees que mi proceso de dilatación o cambio de tamaño es el problema? Solo cambio el tamaño de la imagen al principio con esta línea Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);. Aquí la relación de aspecto permanece igual, ¿dónde rompo las proporciones?
youngpanda
@SiR en respuesta a sus ediciones anteriores: sí, no solo cambio el tamaño de la imagen, aplico diferentes operaciones, una de ellas es la dilatación, que es morfológica, lo que causa una ligera "distorsión" ya que causa regiones brillantes dentro de un la imagen de “crecer" O Qué quiere decir el cambio de tamaño en el final, donde hago las imágenes 28x28.?
youngpanda
@youngpanda, puede encontrar interesante la discusión aquí stackoverflow.com/questions/28525436/… . Podría darle una pista de por qué su enfoque no brinda buenos resultados
SiR
@SiR gracias por el enlace, estoy familiarizado con LeNet, pero es bueno leer de nuevo
youngpanda
5

Ya hay algunas respuestas publicadas, pero ninguna de ellas responde a su pregunta real sobre el preprocesamiento de imágenes .

A mi vez, no veo ningún problema significativo con su implementación, siempre y cuando sea un proyecto de estudio, bien hecho.

Pero una cosa para notar que puede perderse. Existen operaciones básicas en la morfología matemática: erosión y dilatación (utilizada por usted). Y hay operaciones complejas: varias combinaciones de las básicas (por ejemplo, apertura y cierre). El enlace de Wikipedia no es la mejor referencia de CV, pero puede comenzar con él para tener una idea.

Por lo general, es mejor usar la apertura en lugar de la erosión y el cierre en lugar de la dilatación, ya que en este caso la imagen binaria original cambia mucho menos (pero se alcanza el efecto deseado de limpiar bordes afilados o rellenar huecos). Entonces, en su caso, debe verificar el cierre (dilatación de la imagen seguida de erosión con el mismo núcleo). En caso de que la imagen extra pequeña 8 * 8 se modifique mucho cuando se dilata incluso con 1 * 1 kernel (1 píxel es más del 16% de la imagen), que es menor en imágenes más grandes).

Para visualizar la idea, vea las siguientes fotos (de los tutoriales de OpenCV: 1 , 2 ):

dilatación: símbolo original y dilatado

clausura: símbolo original y cerrado

Espero eso ayude.

f4f
fuente
¡Gracias por la aportación! En realidad se trata no de un proyecto de estudio, por lo cual sería el problema entonces? .. Mi imagen es bastante grande cuando solicito la dilatación, 8x8 no es el tamaño de la imagen, es el factor de cambio de tamaño para la altura y la anchura. Pero aún puede ser una opción de mejora para probar diferentes operaciones matemáticas. No sabía acerca de abrir y cerrar, ¡lo probaré! Gracias.
youngpanda
Mi culpa, leí mal el tamaño de la llamada como estaba con 8 * 8 como nuevo tamaño. En caso de que desee utilizar OCR en el mundo real, deberá considerar una opción de transferencia de aprendizaje de su red original en datos típicos de su área de uso. Al menos verifique si mejora la precisión, generalmente debería hacerlo.
f4f
Otra cosa a verificar es el orden de preprocesamiento: escala de grises-> binario-> inverso-> redimensionar. Cambiar el tamaño es una operación costosa y no veo la necesidad de aplicarlo a la imagen en color. Y la segmentación de símbolos se puede hacer sin detección de contorno (con algo menos costoso) si tiene algún formato de entrada específico, pero puede ser difícil de implementar.
f4f
Si tuviera otro conjunto de datos aparte de MNIST, podría intentar transferir el aprendizaje :) Trataré de cambiar el orden de preprocesamiento y responderle. ¡Gracias! Todavía no he encontrado ninguna opción más fácil que la detección del contorno por mi problema ...
youngpanda
1
Okay. Puede recopilar el conjunto de datos usted mismo a partir de las imágenes en las que usará OCR en una práctica común.
f4f
4

Por lo tanto, necesita un enfoque complejo porque cada paso de su cascada informática se basa en los resultados anteriores. En su algoritmo tiene las siguientes características:

  1. Preprocesamiento de imagen

Como se mencionó anteriormente, si aplica el cambio de tamaño, pierde información sobre las relaciones de aspecto de la imagen. Debe hacer el mismo reprocesamiento de imágenes de dígitos para obtener los mismos resultados que el proceso de capacitación.

Mejor manera si solo recorta la imagen por imágenes de tamaño fijo. En esa variante, no necesitará en contornos para encontrar y cambiar el tamaño de la imagen de dígitos antes del proceso de entrenamiento. Entonces podría hacer un pequeño cambio en su algoritmo de recorte para un mejor reconocimiento: simplemente encuentre el contorno y coloque su dígito sin cambiar el tamaño en el centro del marco de imagen relevante para el reconocimiento.

También debe prestar más atención al algoritmo de binarización. He tenido experiencia estudiando el efecto de los valores de umbral de binarización en el error de aprendizaje: puedo decir que este es un factor muy significativo. Puede probar otros algoritmos de binarización para verificar esta idea. Por ejemplo, puede usar esta biblioteca para probar algoritmos de binarización alternativos.

  1. Algoritmo de aprendizaje

Para mejorar la calidad del reconocimiento, utiliza la validación cruzada en el proceso de capacitación. Esto te ayuda a evitar el problema de sobreajustar tus datos de entrenamiento. Por ejemplo, puede leer este artículo donde se explica cómo usarlo con Keras.

A veces, las tasas más altas de medición de precisión no dicen nada acerca de la calidad de reconocimiento real porque ANN entrenada no encontró el patrón en los datos de entrenamiento. Puede estar conectado con el proceso de capacitación o el conjunto de datos de entrada como se explicó anteriormente, o puede causar la elección de la arquitectura ANN.

  1. Arquitectura ANN

Es un gran problema ¿Cómo definir la mejor arquitectura ANN para resolver la tarea? No hay formas comunes de hacer eso. Pero hay algunas maneras de acercarse al ideal. Por ejemplo, podrías leer este libro . Le ayuda a tener una mejor visión de su problema. También puede encontrar aquí algunas fórmulas heurísticas para ajustarse al número de capas / elementos ocultos para su ANN. También aquí encontrará una pequeña descripción de esto.

Espero que esto ayude.

Egor Zamotaev
fuente
1. Si te entiendo correctamente, no puedo recortar a un tamaño fijo, es una imagen de un número de varios dígitos, y todos los casos son diferentes en tamaño / lugar, etc. ¿O quisiste decir algo diferente? Sí, probé diferentes métodos de binarización y parámetros ajustados, si eso es lo que quieres decir. 2. En realidad, el reconocimiento en MNIST es excelente, no se produce un sobreajuste, la precisión que mencioné es la precisión de la prueba. Ni la red ni su capacitación son el problema. 3. Gracias por todos los enlaces, estoy bastante contento con mi arquitectura, por supuesto, siempre hay margen de mejora.
youngpanda
Sí, lo entiendes. Pero siempre tiene la posibilidad de unificar su conjunto de datos. En su caso, es mejor recortar imágenes de dígitos por los contornos como ya lo hace. Pero después de eso, será mejor expandir sus imágenes de dígitos a un tamaño unificado de acuerdo con el tamaño máximo de una imagen de dígitos por escala x e y. Podría preferir el centro de la región de contorno de dígitos para hacer eso. Le dará datos de entrada más limpios para su algoritmo de entrenamiento.
Egor Zamotaev
¿Quieres decir que tengo que saltarme la dilatación? Al final ya centro la imagen, cuando aplico el borde (50 px en cada lado). Después de eso, redimensionaré cada dígito a 28x28, ya que este es el tamaño que necesitamos para MNIST. ¿Quiere decir que puedo cambiar el tamaño a 28x28 de manera diferente?
youngpanda
1
Sí, la dilatación es indeseable. Sus contornos pueden tener diferentes proporciones por altura y ancho, por eso es necesario que mejore su algoritmo aquí. Al menos debes hacer tamaños de imágenes con las mismas proporciones. Como tiene tamaños de imagen de entrada de 28x28, debe preparar imágenes con la misma relación 1: 1 en escalas x e y. No debería obtener un borde de 50 px para cada lado de la imagen, sino bordes X, Y px que satisfagan la condición: contourSizeX + borderSizeX == contourSizeY + borderSizeY. Eso es todo.
Egor Zamotaev
Ya lo intenté sin dilatación (olvidé mencionarlo en la publicación). No cambió ningún resultado ... Mi número de borde fue experimental. Idealmente, necesitaría mis dígitos para caber en una caja de 20x20 (tamaño normalizado como tal en el conjunto de datos) y después de eso cambiarlo usando el centro de masa ...
youngpanda
1

Después de algunas investigaciones y experimentos, llegué a la conclusión de que el preprocesamiento de la imagen en sí no era el problema (cambié algunos parámetros sugeridos, como por ejemplo, el tamaño y la forma de la dilatación, pero no fueron cruciales para los resultados). Lo que sí ayudó, sin embargo, fueron 2 cosas siguientes:

  1. Como notó @ f4f, necesitaba recopilar mi propio conjunto de datos con datos del mundo real. Esto ya ayudó enormemente.

  2. Hice cambios importantes en mi preprocesamiento de segmentación. Después de obtener contornos individuales, primero normalizo el tamaño de las imágenes para que quepan en un 20x20cuadro de píxeles (como están MNIST). Después de eso, centro el cuadro en el centro de la 28x28imagen usando el centro de masa (que para las imágenes binarias es el valor medio en ambas dimensiones).

Por supuesto, todavía hay casos de segmentación difíciles, como dígitos superpuestos o conectados, pero los cambios anteriores respondieron a mi pregunta inicial y mejoraron mi rendimiento de clasificación.

youngpanda
fuente