Estoy trabajando en el reconocimiento de varios dígitos impreso a mano Java
, utilizando la OpenCV
biblioteca para el preprocesamiento y la segmentación, y un Keras
modelo 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:
se convierte y se clasifica como 4
.
se convierte y se clasifica como 7
.
se convierte 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);
}
fuente
Respuestas:
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)
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
fuente
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);
. Aquí la relación de aspecto permanece igual, ¿dónde rompo las proporciones?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:
clausura:
Espero eso ayude.
fuente
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:
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.
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.
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.
fuente
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:
Como notó @ f4f, necesitaba recopilar mi propio conjunto de datos con datos del mundo real. Esto ya ayudó enormemente.
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
20x20
cuadro de píxeles (como estánMNIST
). Después de eso, centro el cuadro en el centro de la28x28
imagen 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.
fuente