Nuestro equipo de especialistas pasados algunos días de investigación y recopilación de de información, hallamos la solución, nuestro deseo es que resulte de gran utilidad en tu proyecto.
Solución:
Este es un enfoque potencial que utiliza operaciones morfológicas para filtrar los contornos que no son de texto. La idea es:
-
Obtenga una imagen binaria. Cargue la imagen, escala de grises, luego el umbral de Otsu
-
Elimina las líneas horizontales y verticales. Cree granos horizontales y verticales usando
cv2.getStructuringElement
luego elimine las líneas concv2.drawContours
-
Elimina líneas diagonales, objetos circulares y contornos curvos. Filtrar usando el área de contorno
cv2.contourArea
y aproximación de contornocv2.approxPolyDP
para aislar contornos que no sean de texto -
Extraiga ROI de texto y OCR. Encuentre contornos y filtre por ROI y luego OCR usando Pytesseract.
Se eliminaron las líneas horizontales resaltadas en verde.
Líneas verticales eliminadas
Se eliminaron varios contornos sin texto (líneas diagonales, objetos circulares y curvas)
Regiones de texto detectadas
import cv2
import numpy as np
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:Program FilesTesseract-OCRtesseract.exe"
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()
# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(clean, [c], -1, 0, 3)
# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(clean, [c], -1, 0, 3)
cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
# Remove diagonal lines
area = cv2.contourArea(c)
if area < 100:
cv2.drawContours(clean, [c], -1, 0, 3)
# Remove circle objects
elif area > 1000:
cv2.drawContours(clean, [c], -1, 0, -1)
# Remove curve stuff
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
x,y,w,h = cv2.boundingRect(c)
if len(approx) == 4:
cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)
open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = cv2.contourArea(c)
if area > 500:
ROI = image[y:y+h, x:x+w]
ROI = cv2.GaussianBlur(ROI, (3,3), 0)
data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
if data.isalnum():
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
print(data)
cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()
Muy bien, aquí tienes otra posible solución. Sé que trabajas con Python, yo trabajo con C ++. Le daré algunas ideas y, con suerte, si lo desea, podrá implementar esta respuesta.
La idea principal es no usar preprocesamiento en absoluto (al menos no en la etapa inicial) y, en cambio, concéntrese en cada personaje objetivo, obtenga algunos propiedades, y filtrar cada gota de acuerdo con estas propiedades.
Estoy tratando de no usar el preprocesamiento porque: 1) Los filtros y las etapas morfológicas podrían degradar la calidad de las manchas y 2) sus manchas objetivo parecen exhibir algunas características que podríamos explotar, principalmente: relación de aspecto y zona.
Compruébelo usted mismo, los números y las letras parecen ser más altos que anchos … además, parecen variar dentro de un determinado valor de área. Por ejemplo, quieres descartar objetos “demasiado ancho” o “demasiado grande”.
La idea es filtrar todo lo que no esté dentro de los valores precalculados. Examiné los caracteres (números y letras) y vine con valores de área mínimos y máximos y una relación de aspecto mínima (aquí, la relación entre alto y ancho).
Trabajemos en el algoritmo. Empiece por leer la imagen y cambiar su tamaño a la mitad de las dimensiones. Tu imagen es demasiado grande. Convierta a escala de grises y obtenga una imagen binaria a través de otsu, aquí está en pseudocódigo:
//Read input:
inputImage = imread( "diagram.png" );
//Resize Image;
resizeScale = 0.5;
inputResized = imresize( inputImage, resizeScale );
//Convert to grayscale;
inputGray = rgb2gray( inputResized );
//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );
Frio. Trabajaremos con esta imagen. Debe examinar cada mancha blanca y aplicar una “filtro de propiedades”. Estoy usando componentes conectados con estadísticas para recorrer cada blob y obtener su área y relación de aspecto, en C ++ esto se hace de la siguiente manera:
//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;
//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );
//Prepare a vector of colors – color the filtered blobs in black
std::vector colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.
//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ )
//get area:
auto blobArea = stats.at(i, cv::CC_STAT_AREA);
//get height, width and compute aspect ratio:
auto blobWidth = stats.at(i, cv::CC_STAT_WIDTH);
auto blobHeight = stats.at(i, cv::CC_STAT_HEIGHT);
float blobAspectRatio = (float)blobHeight/(float)blobWidth;
//Filter your blobs…
;
Ahora, aplicaremos el filtro de propiedades. Esto es solo una comparación con los umbrales precalculados. Usé los siguientes valores:
Minimum Area: 40 Maximum Area:400
MinimumAspectRatio: 1
Dentro de tu for
loop, compare las propiedades de blob actuales con estos valores. Si las pruebas son positivas, “pintas” la mancha de negro. Continuando dentro del for
círculo:
//Filter your blobs…
//Test the current properties against the thresholds:
bool areaTest = (blobArea > maxArea)||(blobArea < minArea);
bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!
//Paint the blob black:
if( areaTest || aspectRatioTest )
//filtered blobs are colored in black:
colors[i] = cv::Vec3b( 0, 0, 0 );
else
//unfiltered blobs are colored in white:
colors[i] = cv::Vec3b( 255, 255, 255 );
Después del ciclo, construya la imagen filtrada:
cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ )
for( int x = 0; x < filteredMat.cols; x++ )
int label = outputLabels.at(y, x);
filteredMat.at(y, x) = colors[label];
Y … eso es todo. Filtraste todos los elementos que no son similares a lo que estás buscando. Ejecutando el algoritmo obtienes este resultado:
Además, encontré los recuadros delimitadores de las manchas para visualizar mejor los resultados:
Como puede ver, algunos elementos no se detectan correctamente. Puede refinar el “filtro de propiedades” para identificar mejor los caracteres que está buscando. Una solución más profunda, que implica un poco de aprendizaje automático, requiere la construcción de un “vector de características ideal”, extrayendo características de los blobs y comparando ambos vectores mediante una medida de similitud. También puede aplicar algunos correo-procesamiento para mejorar los resultados …
Sea lo que sea, hombre, su problema no es trivial ni fácil de escalar, y solo le estoy dando ideas. Con suerte, podrá implementar su solución.
Un método es utilizar una ventana deslizante (es cara).
Determine el tamaño de los caracteres en la imagen (todos los caracteres son del mismo tamaño que se ven en la imagen) y establezca el tamaño de la ventana. Pruebe tesseract para la detección (la imagen de entrada requiere un procesamiento previo). Si una ventana detecta caracteres consecutivamente, almacene las coordenadas de la ventana. Fusiona las coordenadas y obtén la región de los personajes.