Aquí hay una idea. Dividimos este problema en varios pasos:
Determinar el área de contorno rectangular promedio. Luego umbralamos los contornos y los filtramos usando área del rectángulo delimitador del contorno. La razón por la que hacemos esto es por la observación de que cualquier personaje típico solo será tan grande, mientras que el ruido grande abarcará un área rectangular más grande. Luego determinamos el área promedio.
Eliminar grandes contornos atípicos. Repetimos los contornos nuevamente y eliminamos los contornos grandes si están5x
más grandes que el área de contorno promedio rellenando el contorno. En lugar de usar un área de umbral fijo, usamos este umbral dinámico para una mayor solidez.
Dilatar con un núcleo vertical para conectar personajes . La idea es aprovechar la observación de que los personajes están alineados en columnas. Al dilatar con un núcleo vertical conectamos el texto para que el ruido no se incluya en este contorno combinado.
Eliminar pequeños ruidos . Ahora que el texto a guardar está conectado, encontramos contornos y eliminamos los contornos más pequeños que 4x
el área de contorno promedio.
A nivel de bits y para reconstruir la imagen . Como solo hemos deseado contornos para mantener nuestra máscara, lo hacemos a nivel de bits y para preservar el texto y obtener nuestro resultado.
Aquí hay una visualización del proceso:
Tenemos el umbral de Otsu para obtener una imagen binaria y luego encontramos contornos para determinar el área de contorno rectangular promedio. Desde aquí eliminamos los grandes contornos atípicos resaltados en verde rellenando contornos
A continuación, construimos un núcleo vertical y lo dilatamos para conectar los caracteres. Este paso conecta todo el texto deseado para mantener y aísla el ruido en blobs individuales.
Ahora encontramos contornos y filtros usando el área de contorno para eliminar el pequeño ruido
Aquí están todas las partículas de ruido eliminadas resaltadas en verde
Resultado
Código
import cv2
# Load image, grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Determine average contour area
average_area = []
cnts = cv2.findContours(thresh, 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 = w * h
average_area.append(area)
average = sum(average_area) / len(average_area)
# Remove large lines if contour area is 5x bigger then average contour area
cnts = cv2.findContours(thresh, 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 = w * h
if area > average * 5:
cv2.drawContours(thresh, [c], -1, (0,0,0), -1)
# Dilate with vertical kernel to connect characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
dilate = cv2.dilate(thresh, kernel, iterations=3)
# Remove small noise if contour area is smaller than 4x average
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area < average * 4:
cv2.drawContours(dilate, [c], -1, (0,0,0), -1)
# Bitwise mask with input image
result = cv2.bitwise_and(image, image, mask=dilate)
result[dilate==0] = (255,255,255)
cv2.imshow('result', result)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()
Nota: El procesamiento tradicional de imágenes se limita a los umbrales, las operaciones morfológicas y el filtrado de contornos (aproximación de contornos, área, relación de aspecto o detección de manchas). Dado que las imágenes de entrada pueden variar según el tamaño del texto de los caracteres, encontrar una solución singular es bastante difícil. Es posible que desee estudiar la capacitación de su propio clasificador con aprendizaje automático / profundo para una solución dinámica.