Image de nettoyage pour OCR

9

J'ai essayé d'effacer les images pour l'OCR: (les lignes)

entrez la description de l'image ici

J'ai besoin de supprimer ces lignes pour parfois traiter davantage l'image et je me rapproche assez, mais la plupart du temps, le seuil enlève trop du texte:

    copy = img.copy()
    blur = cv2.GaussianBlur(copy, (9,9), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,30)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    dilate = cv2.dilate(thresh, kernel, iterations=2)

    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 > 300:
            x,y,w,h = cv2.boundingRect(c)
            cv2.rectangle(copy, (x, y), (x + w, y + h), (36,255,12), 3)

Modifier: De plus, l'utilisation de nombres constants ne fonctionnera pas si la police change. Existe-t-il un moyen générique de procéder?

K41F4r
la source
2
Certaines de ces lignes, ou des fragments d'entre elles, ont les mêmes caractéristiques que le texte légal, et il sera difficile de s'en débarrasser sans gâcher un texte valide. Si cela s'applique, vous pouvez vous concentrer sur le fait qu'ils sont plus longs que les caractères et quelque peu isolés. Une première étape pourrait donc consister à estimer la taille et la proximité des caractères.
Yves Daoust
@YvesDaoust Comment procéder pour trouver la proximité des personnages? (puisque le filtrage purement sur la taille se mélange
souvent
1
Vous pouvez trouver, pour chaque goutte, la distance à son plus proche voisin. Ensuite, par analyse histographique des distances, vous trouveriez un seuil entre "proche" et "à part" (quelque chose comme le mode de distribution), ou entre "entouré" et "isolé".
Yves Daoust
En cas de plusieurs petites lignes proches les unes des autres, leur voisin le plus proche ne serait-il pas l'autre petite ligne? Le calcul de la distance moyenne à tous les autres blobs serait-il trop coûteux?
K41F4r
"leur voisin le plus proche ne serait-il pas l'autre petite ligne?": bonne objection, votre Honneur. En fait, un tas de segments courts proches ne diffèrent pas du texte légitime, bien que dans un arrangement complètement improbable. Vous devrez peut-être regrouper les fragments de lignes brisées. Je ne suis pas sûr que la distance moyenne à tous vous sauverait.
Yves Daoust

Réponses:

14

Voici une idée. Nous décomposons ce problème en plusieurs étapes:

  1. Déterminer l'aire de contour rectangulaire moyenne. Nous seuil puis trouver les contours et filtrer en utilisant la zone rectangle englobante du contour. La raison pour laquelle nous le faisons est à cause de l'observation que tout caractère typique ne sera que si grand alors que le grand bruit s'étendra sur une plus grande zone rectangulaire. Nous déterminons ensuite la surface moyenne.

  2. Supprimez les grands contours aberrants. Nous réitérons les contours et supprimons les grands contours s'ils sont 5xplus grands que la zone de contour moyenne en remplissant le contour. Au lieu d'utiliser une zone à seuil fixe, nous utilisons ce seuil dynamique pour plus de robustesse.

  3. Dilatez avec un noyau vertical pour connecter les caractères . L'idée est de profiter de l'observation que les caractères sont alignés en colonnes. En dilatant avec un noyau vertical, nous connectons le texte ensemble afin que le bruit ne soit pas inclus dans ce contour combiné.

  4. Supprimez les petits bruits . Maintenant que le texte à conserver est connecté, nous trouvons les contours et supprimons tous les contours plus petits que 4xla zone de contour moyenne.

  5. Au niveau du bit et pour reconstruire l'image . Puisque nous avons seulement souhaité que les contours restent sur notre masque, nous les avons au niveau du bit et pour conserver le texte et obtenir notre résultat.


Voici une visualisation du processus:

Nous avons le seuil d' Otsu pour obtenir une image binaire puis trouver des contours pour déterminer l'aire de contour rectangulaire moyenne. De là, nous supprimons les grands contours aberrants surlignés en vert en remplissant les contours

entrez la description de l'image ici entrez la description de l'image ici

Ensuite, nous construisons un noyau vertical et dilatons pour connecter les caractères. Cette étape connecte tout le texte souhaité pour conserver et isoler le bruit en blobs individuels.

entrez la description de l'image ici

Maintenant, nous trouvons les contours et filtrons en utilisant la zone de contour pour éliminer le petit bruit

entrez la description de l'image ici

Voici toutes les particules de bruit supprimées surlignées en vert

entrez la description de l'image ici

Résultat

entrez la description de l'image ici

Code

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()

Remarque: Le traitement d'image traditionnel se limite au seuillage, aux opérations morphologiques et au filtrage des contours (approximation des contours, zone, rapport d'aspect ou détection de taches). Étant donné que les images d'entrée peuvent varier en fonction de la taille du texte, il est assez difficile de trouver une solution singulière. Vous voudrez peut-être étudier la formation de votre propre classificateur avec machine / deep learning pour une solution dynamique.

nathancy
la source
1
Dans le cas d'une police plus grande, cela ne supprimerait-il pas aussi le texte?
K41F4r
Oui, c'est possible, vous devrez donc ajuster la valeur de la zone de seuil. Pour une approche plus dynamique, une idée est de déterminer la zone de caractère moyenne puis de l'utiliser comme seuil
nathancy
Semble être trop spécifique à l'exemple, l'utilisation de la zone moyenne supprimera toujours le texte la plupart du temps, ce qui aggrave le résultat de l'OCR
K41F4r
Avez-vous un autre exemple d'image d'entrée que vous pourriez ajouter au message?
nathancy
1
Trouver une solution qui fonctionne dans toutes les situations en utilisant les techniques traditionnelles de traitement d'image est assez difficile. Vous voudrez peut-être étudier la formation de votre propre classificateur à l'aide de l'apprentissage en profondeur. Bonne chance!
nathancy