java - source - Elimina el ruido de fondo de la imagen para que el texto sea más claro para OCR
tesseract ocr python opencv (5)
He escrito una aplicación que segmenta una imagen basada en las regiones de texto dentro de ella, y extrae esas regiones como mejor me parezca. Lo que intento hacer es limpiar la imagen para que el OCR (Tesseract) arroje un resultado preciso. Tengo la siguiente imagen como ejemplo:
Ejecutar esto a través de tesseract da un resultado ampliamente inexacto. Sin embargo, limpiar la imagen (usando Photoshop) para obtener la imagen de la siguiente manera:
Da exactamente el resultado que esperaría. La primera imagen ya se está ejecutando a través del siguiente método para limpiarlo hasta ese punto:
public Mat cleanImage (Mat srcImage) {
Core.normalize(srcImage, srcImage, 0, 255, Core.NORM_MINMAX);
Imgproc.threshold(srcImage, srcImage, 0, 255, Imgproc.THRESH_OTSU);
Imgproc.erode(srcImage, srcImage, new Mat());
Imgproc.dilate(srcImage, srcImage, new Mat(), new Point(0, 0), 9);
return srcImage;
}
¿Qué más puedo hacer para limpiar la primera imagen para que se asemeje a la segunda imagen?
Editar: Esta es la imagen original antes de que se ejecute a través de la función cleanImage
.
¿Te ayudaría esa imagen?
El algoritmo que produce esa imagen sería fácil de implementar. Estoy seguro de que si modificas algunos de sus parámetros, puedes obtener muy buenos resultados para ese tipo de imágenes.
Probé todas las imágenes con tesseract:
- Imagen original: nada detectado
- Imagen procesada n. ° 1: nada detectado
- Imagen procesada n. ° 2: 12-14 (coincidencia exacta)
- Mi imagen procesada: y''1''2-14 / j
Creo que debes trabajar más en la parte de preprocesamiento para preparar la imagen para que sea lo más clara posible antes de llamar al tesseract.
¿Cuáles son mis ideas para hacer que son las siguientes:
1- Extrae los contornos de la imagen y encuentra los contornos en la imagen (mira this ) y this
2- Cada contorno tiene ancho, alto y área, por lo que puedes filtrar los contornos según el ancho, la altura y su área (mira this y this ), además puedes usar alguna parte del código de análisis de contorno aquí para filtrar los contornos y más puede eliminar los contornos que no son similares a un contorno de "letra o número" usando una coincidencia de contorno de plantilla.
3- Después de filtrar el contorno, puedes verificar dónde están las letras y los números en esta imagen, por lo que es posible que necesites utilizar algunos métodos de detección de texto, como here
4- Todo lo que necesita ahora si desea eliminar el área que no es de texto y los contornos que no son buenos a partir de la imagen
5- Ahora puedes crear tu método de binirización o puedes usar tesseract para hacer la binirización de la imagen y luego llamar al OCR en la imagen.
Seguro que estos son los mejores pasos para hacer esto, puede usar algunos de ellos y puede ser suficiente para usted.
Otras ideas:
Puede usar diferentes formas de hacer esto, la mejor idea es encontrar una forma de detectar la ubicación de dígitos y caracteres usando diferentes métodos como la coincidencia de plantillas o funciones basadas en HOG.
Primero puede hacer binarización en su imagen y obtener la imagen binaria, luego debe aplicar la apertura con línea estructural para la horizontal y la vertical, y esto le ayudará a detectar los bordes después de eso y hacer la segmentación en la imagen, luego la OCR .
Después de detectar todos los contornos en la imagen, también puede usar la
Hough transformation
para detectar cualquier tipo de línea y curva definida como esta, y de esta manera puede detectar los caracteres que están alineados para que pueda segmentar la imagen y hacer el OCR después de eso.
Manera mucho más fácil:
2- Alguna operación de morfología para separar los contornos:
3- Invertir el color en la imagen (esto puede ser antes del paso 2)
4- Encuentra todos los contornos en la imagen
5- Borre todos los contornos que tengan un ancho mayor que su altura, borre los contornos muy pequeños, los muy grandes y los contornos no rectangulares
Nota: puede usar los métodos de detección de texto (o usar HOG o detección de bordes) en lugar de los pasos 4 y 5.
6- Encuentra el rectángulo grande que contiene todos los contornos restantes en la imagen
7- Puede hacer un preprocesamiento adicional para mejorar la entrada del tesseract y luego puede llamar al OCR ahora. (Te aconsejo recortar la imagen y hacerla como una entrada al OCR [me refiero a recortar el rectángulo amarillo y no hacer que toda la imagen sea solo una entrada del rectángulo amarillo y eso mejorará los resultados también])
El tamaño de la fuente no debe ser tan grande o pequeño, aproximadamente debe estar en el rango de 10-12 pt (es decir, altura del carácter aproximadamente superior a 20 y menor de 80). puedes muestrear la imagen y probar con tesseract. Y pocas fuentes no están capacitadas en tesseract, el problema puede surgir si no está en las fuentes formadas.
Mi respuesta se basa en las siguientes suposiciones. Es posible que ninguno de ellos sea válido en su caso.
- Es posible que imponga un umbral para delimitar las alturas de cuadro en la región segmentada. Entonces debería poder filtrar otros componentes.
- Usted sabe el ancho promedio de trazo de los dígitos. Use esta información para minimizar la posibilidad de que los dígitos estén conectados a otras regiones. Puede usar transformaciones de distancia y operaciones morfológicas para esto.
Este es mi procedimiento para extraer los dígitos:
- Aplicar el umbral Otsu a la imagen
- Toma la transformación de distancia
Umbral de la imagen transformada de distancia utilizando la restricción de ancho de carrera (= 8)
Filtra las alturas del cuadro delimitador y adivina dónde están los dígitos
stroke-width = 8 stroke-width = 10
EDITAR
Prepare una máscara utilizando el convexo de los contornos de los dígitos encontrados
Copie la región de los dígitos a una imagen limpia usando la máscara
Mi conocimiento de Tesseract está un poco oxidado. Como recuerdo, puedes obtener un nivel de confianza para los personajes. Es posible que pueda filtrar el ruido con esta información si detecta regiones ruidosas como cuadros delimitadores de caracteres.
Código C ++
Mat im = imread("aRh8C.png", 0);
// apply Otsu threshold
Mat bw;
threshold(im, bw, 0, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
// take the distance transform
Mat dist;
distanceTransform(bw, dist, CV_DIST_L2, CV_DIST_MASK_PRECISE);
Mat dibw;
// threshold the distance transformed image
double SWTHRESH = 8; // stroke width threshold
threshold(dist, dibw, SWTHRESH/2, 255, CV_THRESH_BINARY);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
// perform opening, in case digits are still connected
Mat morph;
morphologyEx(dibw, morph, CV_MOP_OPEN, kernel);
dibw.convertTo(dibw, CV_8U);
// find contours and filter
Mat cont;
morph.convertTo(cont, CV_8U);
Mat binary;
cvtColor(dibw, binary, CV_GRAY2BGR);
const double HTHRESH = im.rows * .5; // height threshold
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
vector<Point> digits; // points corresponding to digit contours
findContours(cont, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
Rect rect = boundingRect(contours[idx]);
if (rect.height > HTHRESH)
{
// append the points of this contour to digit points
digits.insert(digits.end(), contours[idx].begin(), contours[idx].end());
rectangle(binary,
Point(rect.x, rect.y), Point(rect.x + rect.width - 1, rect.y + rect.height - 1),
Scalar(0, 0, 255), 1);
}
}
// take the convexhull of the digit contours
vector<Point> digitsHull;
convexHull(digits, digitsHull);
// prepare a mask
vector<vector<Point>> digitsRegion;
digitsRegion.push_back(digitsHull);
Mat digitsMask = Mat::zeros(im.rows, im.cols, CV_8U);
drawContours(digitsMask, digitsRegion, 0, Scalar(255, 255, 255), -1);
// expand the mask to include any information we lost in earlier morphological opening
morphologyEx(digitsMask, digitsMask, CV_MOP_DILATE, kernel);
// copy the region to get a cleaned image
Mat cleaned = Mat::zeros(im.rows, im.cols, CV_8U);
dibw.copyTo(cleaned, digitsMask);
EDITAR
Código Java
Mat im = Highgui.imread("aRh8C.png", 0);
// apply Otsu threshold
Mat bw = new Mat(im.size(), CvType.CV_8U);
Imgproc.threshold(im, bw, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
// take the distance transform
Mat dist = new Mat(im.size(), CvType.CV_32F);
Imgproc.distanceTransform(bw, dist, Imgproc.CV_DIST_L2, Imgproc.CV_DIST_MASK_PRECISE);
// threshold the distance transform
Mat dibw32f = new Mat(im.size(), CvType.CV_32F);
final double SWTHRESH = 8.0; // stroke width threshold
Imgproc.threshold(dist, dibw32f, SWTHRESH/2.0, 255, Imgproc.THRESH_BINARY);
Mat dibw8u = new Mat(im.size(), CvType.CV_8U);
dibw32f.convertTo(dibw8u, CvType.CV_8U);
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
// open to remove connections to stray elements
Mat cont = new Mat(im.size(), CvType.CV_8U);
Imgproc.morphologyEx(dibw8u, cont, Imgproc.MORPH_OPEN, kernel);
// find contours and filter based on bounding-box height
final double HTHRESH = im.rows() * 0.5; // bounding-box height threshold
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
List<Point> digits = new ArrayList<Point>(); // contours of the possible digits
Imgproc.findContours(cont, contours, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
if (Imgproc.boundingRect(contours.get(i)).height > HTHRESH)
{
// this contour passed the bounding-box height threshold. add it to digits
digits.addAll(contours.get(i).toList());
}
}
// find the convexhull of the digit contours
MatOfInt digitsHullIdx = new MatOfInt();
MatOfPoint hullPoints = new MatOfPoint();
hullPoints.fromList(digits);
Imgproc.convexHull(hullPoints, digitsHullIdx);
// convert hull index to hull points
List<Point> digitsHullPointsList = new ArrayList<Point>();
List<Point> points = hullPoints.toList();
for (Integer i: digitsHullIdx.toList())
{
digitsHullPointsList.add(points.get(i));
}
MatOfPoint digitsHullPoints = new MatOfPoint();
digitsHullPoints.fromList(digitsHullPointsList);
// create the mask for digits
List<MatOfPoint> digitRegions = new ArrayList<MatOfPoint>();
digitRegions.add(digitsHullPoints);
Mat digitsMask = Mat.zeros(im.size(), CvType.CV_8U);
Imgproc.drawContours(digitsMask, digitRegions, 0, new Scalar(255, 255, 255), -1);
// dilate the mask to capture any info we lost in earlier opening
Imgproc.morphologyEx(digitsMask, digitsMask, Imgproc.MORPH_DILATE, kernel);
// cleaned image ready for OCR
Mat cleaned = Mat.zeros(im.size(), CvType.CV_8U);
dibw8u.copyTo(cleaned, digitsMask);
// feed cleaned to Tesseract
Solo un poco de pensamiento fuera de la caja:
Puedo ver en su imagen original que es un documento preformateado de manera bastante rigurosa, parece una credencial de impuesto de circulación o algo así, ¿no?
Si la suposición anterior es correcta, entonces podría implementar una solución menos genérica: el ruido del que está tratando de deshacerse se debe a las características de la plantilla del documento específico, que ocurre en regiones específicas y conocidas de su imagen. De hecho, también lo hace el texto.
En ese caso, una de las formas de hacerlo es definir los límites de las regiones donde sabe que hay tal "ruido", y simplemente blanquearlas.
Luego, siga el resto de los pasos que ya está siguiendo: Haga la reducción de ruido que eliminará los detalles más finos (es decir, el patrón de fondo que se parece a la marca de agua de seguridad o al holograma en la insignia). El resultado debe ser lo suficientemente claro para que Tesseract procese sin problemas.
Sólo un pensamiento de todos modos. No es una solución genérica, lo reconozco, por lo que depende de cuáles sean sus requisitos reales.