web-dev-qa-db-fra.com

Comment supprimer par programmation toutes les lignes et bordures de l'image (conserver les textes)?

J'essaie d'extraire du texte d'une image à l'aide de Tesseract OCR. Actuellement, avec l'image d'entrée d'origine (comme ci-dessous), la qualité de sortie est très mauvaise (environ 50%). Mais lorsque j'essaie de supprimer toutes les lignes et les bordures de l'image d'entrée (à l'aide de Photoshop), la sortie s'améliore beaucoup (~ 90%). Existe-t-il un moyen de supprimer toutes les lignes et bordures de l'image (conserver les textes) par programme (en utilisant OpenCV, Image magick, ..)?

Image originale: Original Image

Attendre l'image: Expect Image

19
wind

Ne pas utiliser OpenCV, mais juste une ligne d'ImageMagick dans le terminal, mais cela peut vous donner une idée de la façon de le faire dans OpenCV. ImageMagick est installé sur la plupart des distributions Linux et est disponible pour OSX et Windows.

Le nœud du concept est de créer une nouvelle image où chaque pixel est réglé sur la médiane des 100 pixels voisins à sa gauche et des 100 pixels voisins à sa droite. De cette façon, les pixels qui ont beaucoup de voisins horizontaux qui sont noirs (c'est-à-dire des lignes noires horizontales) seront blancs dans l'image de sortie. Ensuite, le même traitement est appliqué dans le sens vertical pour supprimer les lignes verticales.

La commande que vous saisissez dans le terminal sera:

convert input.png                                                 \
   \( -clone 0 -threshold 50% -negate -statistic median 200x1 \)  \
   -compose lighten -composite                                    \
   \( -clone 0 -threshold 50% -negate -statistic median 1x200 \)  \
   -composite result.png

La première ligne indique de charger votre image d'origine.

La deuxième ligne commence certains "traitement de côté" qui copie l'image originale, la seuil et l'inverse, puis la médiane de tous les pixels voisins 100 de chaque côté est calculé.

La troisième ligne prend ensuite le résultat de la deuxième ligne et la compose sur l'image d'origine, en choisissant le plus clair des pixels à chaque emplacement - c'est-à-dire ceux que mon masque de ligne horizontale a blanchis.

Les deux lignes suivantes font à nouveau la même chose mais orientées verticalement pour les lignes verticales.

Le résultat est comme ceci:

enter image description here

Si je fais une différence avec votre image d'origine, comme celle-ci, je peux voir ce qu'elle a fait:

convert input.png result.png -compose difference -composite diff.png

enter image description here

Je suppose que si vous vouliez supprimer un peu plus de lignes, vous pourriez en fait flouter un peu l'image de différence et l'appliquer à l'original. Bien sûr, vous pouvez jouer avec les longueurs de filtre et les seuils et tout ça aussi.

14
Mark Setchell

Il existe une meilleure façon de le faire avec ImageMagick.

Identifier la forme de la ligne et la supprimer

ImageMagick a une fonctionnalité intéressante, appelée Morphologie des formes. Vous pouvez l'utiliser pour identifier des formes comme des lignes de table et les supprimer.

Bon mot

convert in.png                              \
-type Grayscale                             \
-negate                                     \
-define morphology:compose=darken           \
-morphology Thinning 'Rectangle:1x80+0+0<'  \
-negate                                     \
out.png

Explication

  • convertir in.png : charger l'image.
  • - tapez Niveaux de gris : assurez-vous qu'ImageMagick sait qu'il s'agit d'une image en niveaux de gris.
  • - nier : inverser les couches de couleur de l'image (déjà correctement ajustées en configurant les niveaux de gris). Les lignes et les caractères seront blancs et l'arrière-plan noir.
  • - définir la morphologie: composer = assombrir : définir que les zones identifiées par la morphologie seront assombries.
  • - morphologie Thinning 'Rectangle: 1x80 + 0 + 0 <' définit un noyau de rectangle de 1px par 80px qui sera utilisé pour identifier les formes des lignes. Ce n'est que si ce noyau s'inscrit dans une forme blanche (rappelez-vous que nous niez couleurs) si grand ou plus grand, il sera assombri. Le drapeau < lui permet de tourner.
  • - nier : inverser les couleurs une deuxième fois. Les caractères seront à nouveau noirs et l'arrière-plan sera blanc.
  • out.png : Le fichier de sortie à générer.

Image résultante

Après avoir postulé

convert in.png -type Grayscale -negate -define morphology:compose=darken -morphology Thinning 'Rectangle:1x80+0+0<' -negate out.png

c'était l'image de sortie:

Output image after one liner was applied

Observations

  • Vous devez choisir une taille de noyau de rectangle plus grande que la taille de votre plus grand caractère, pour vous assurer que le rectangle ne tient pas à l'intérieur d'un caractère.
  • Il reste encore de petites lignes pointillées et de petites divisions de cellules de tableau, mais c'est parce qu'elles sont inférieures à 80 pixels.
  • Le mérite de cette technique est qu'elle préserve mieux les caractères que l'approche de différence de couleur de pixel médiane proposée ici par d'autres utilisateurs, et malgré le peu d'encombrement, elle a toujours un résultat vraiment meilleur en supprimant les lignes du tableau.
13
luizv

Face au même problème. Et je pense qu'une solution plus logique pourrait être (Référence: Extraire les bordures du tablea )

//assuming, b_w is the binary image
inv = 255 - b_w    
horizontal_img = new_img
vertical_img = new_img

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (100,1))
horizontal_img = cv2.erode(horizontal_img, kernel, iterations=1)
horizontal_img = cv2.dilate(horizontal_img, kernel, iterations=1)


kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,100))
vertical_img = cv2.erode(vertical_img, kernel, iterations=1)
vertical_img = cv2.dilate(vertical_img, kernel, iterations=1)

mask_img = horizontal_img + vertical_img
no_border = np.bitwise_or(b_w, mask_img)
7

Puisque personne n'a publié de solution OpenCV complète, voici une approche simple

  1. Obtenez une image binaire. Chargez l'image, convertissez-la en niveaux de gris et seuil d'Ots

  2. Supprimer les lignes horizontales. Nous créons un noyau de forme horizontale avec cv2.getStructuringElement puis trouver les contours et supprimez les lignes avec cv2.drawContours

  3. Supprimer les lignes verticales. Nous faisons la même opération mais avec un noyau de forme verticale


Charger l'image, convertir en niveaux de gris, puis seuil d'Ots pour obtenir une image binaire

image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

enter image description here

Maintenant, nous créons un noyau horizontal pour détecter les lignes horizontales avec cv2.getStructuringElement() et trouver les contours avec cv2.findContours() . Pour supprimer les lignes horizontales, nous utilisons cv2.drawContours() et remplissons chaque contour horizontal de blanc. Cela "efface" effectivement la ligne horizontale. Voici les lignes horizontales détectées en vert

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

enter image description here

De même, nous créons un noyau vertical pour supprimer les lignes verticales, trouver les contours et remplir chaque contour vertical de blanc. Voici les lignes verticales détectées surlignées en vert

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,40))
remove_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

enter image description here

Après avoir rempli les lignes horizontales et verticales avec du blanc, voici notre résultat

enter image description here


Remarque: Selon l'image, vous devrez peut-être modifier la taille du noyau. Par exemple, pour capturer des lignes horizontales plus longues, il peut être nécessaire d'augmenter le noyau horizontal de (40, 1) Pour dire (80, 1). Si vous vouliez détecter des lignes horizontales plus épaisses, vous pourriez augmenter la largeur du noyau pour dire (80, 2). De plus, vous pouvez augmenter le nombre d'itérations lors de l'exécution de cv2.morphologyEx(). De même, vous pouvez modifier les noyaux verticaux pour détecter des lignes plus ou moins verticales. Il y a un compromis lorsque vous augmentez ou diminuez la taille du noyau car vous pouvez capturer plus ou moins de lignes. Encore une fois, tout varie en fonction de l'image d'entrée

Code complet pour l'exhaustivité

import cv2

image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,40))
remove_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.imwrite('result.png', result)
cv2.waitKey()
6
nathancy

Vous pouvez utiliser un algorithme de détection de bord de Sobel/Laplacian/Canny et utiliser la transformation de Hough pour identifier les lignes dans OpenCV et les colorer en blanc pour supprimer les lignes:

laplacian = cv2.Laplacian(img,cv2.CV_8UC1) # Laplacian OR
edges = cv2.Canny(img,80,10,apertureSize = 3) # canny Edge OR
# Output dtype = cv2.CV_8U # Sobel
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)
# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

# Hough's Probabilistic Line Transform 
minLineLength = 900
maxLineGap = 100
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
    for x1,y1,x2,y2 in line:
        cv2.line(img,(x1,y1),(x2,y2),(255,255,255),2)

cv2.imwrite('houghlines.jpg',img)
4
Anindita Bhowmik

Ce dont vous avez besoin est Leptonica et Lept4j .

Il y a un exemple sur la façon d'accomplir cela dans le code source du projet, dans les tests ici: LineRemovalTest.Java

Contribution:

enter image description here

production:

enter image description here

2
delkant

J'ai une idée. Mais cela ne fonctionnera que si vous avez des lignes absolument horizontales et verticales. Vous pouvez d'abord faire une binarisation sur cette image (si ce n'est pas déjà fait). Ensuite, écrivez du code qui itère à travers chaque ligne de l'image en même temps en vérifiant s'il y a une séquence de pixels noirs contenant plus d'un certain seuil. Par exemple, s'il y a une séquence continue de points noirs sur une ligne à partir du 100e pixel au 150e pixel, rendez ces pixels blancs. Après avoir trouvé toutes les lignes horizontales, vous pouvez faire de même pour vous débarrasser des lignes verticales.

Ici, dans mon exemple, je considère que la séquence de pixels noirs commence exactement à partir du 100e pixel et se termine au 150e parce que s'il y a un autre pixel noir au 151e pixel, je dois également ajouter ce pixel. En d'autres termes, essayez de trouver les lignes complètement.

Si vous résolvez cette question, veuillez me le faire savoir)

1
Kanat