Maison >développement back-end >Tutoriel Python >OpenCV : Rechercher des colonnes dans des revues arabes (Python)
Je suis nouveau sur opencv et nouveau sur python. J'ai essayé de reconstituer le code que j'ai trouvé en ligne pour résoudre mon problème de recherche. J'ai un journal arabe de 1870 qui compte des centaines de pages, chaque page contient deux colonnes et a une épaisse bordure noire. Je souhaite extraire deux colonnes sous forme de fichiers image afin de pouvoir exécuter ocr dessus individuellement tout en ignorant l'en-tête et le pied de page. Voici un exemple de page :
Page 3
J’ai dix pages de l’impression originale sous forme de fichiers png séparés. J'ai écrit le script suivant pour gérer chacun d'entre eux. Cela fonctionne comme prévu dans 2 des 10 pages, mais ne parvient pas à générer les colonnes dans les 8 autres pages. Je ne comprends pas assez bien toutes les fonctions pour savoir où je pourrais utiliser ces valeurs, ou si toute mon approche est erronée - je pense que la meilleure façon d'apprendre est de demander à la communauté comment vous résoudriez ce problème.
import cv2 def cutpage(fname, pnum): image = cv2.imread(fname) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (7,7), 0) thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 13)) dilate = cv2.dilate(thresh, kernel, iterations=1) dilatename = "temp/dilate" + str(pnum) + ".png" cv2.imwrite(dilatename, dilate) cnts = cv2.findContours(dilate, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cnts = cnts[0] if len(cnts) == 2 else cnts[1] cnts = sorted(cnts, key=lambda x: cv2.boundingRect(x)[0]) fullpage=1 column=1 for c in cnts: x, y, w, h = cv2.boundingRect(c) if h > 300 and w > 20: if (h/w)<2.5: print("Found full page: ", x, y, w, h) filename = "temp/p" + str(pnum) + "-full" + str(fullpage) + ".png" fullpage+=1 else: print("Found column: ", x, y, w, h) filename = "temp/p" + str(pnum) + "-col" + str(column) + ".png" column+=1 roi = image[y:y+h, x:x+w] cv2.imwrite(filename, roi) return (column-1) for nr in range(10): filename = "p"+str(nr)+".png" print("Checking page", nr) diditwork = cutpage(filename, nr) print("Found", diditwork, "columns")
En suivant le tutoriel, j'ai créé une inversion binaire floue et dilatée afin qu'elle puisse identifier les différentes zones rectangulaires par la grande zone blanche. J'ai également enregistré une copie de chaque version étendue pour pouvoir voir à quoi elle ressemble, voici la page ci-dessus après traitement :
La page 3 a été agrandie
La boucle "for c in cnts" doit trouver de grandes zones rectangulaires dans l'image. Si le rapport hauteur/largeur est inférieur à 2,5, j'obtiens une page complète (sans en-tête ni pied de page, ce qui fonctionne bien), si le rapport hauteur/largeur est supérieur à cela, je sais que c'est une colonne et cela enregistre, par exemple temp/ p2-col2.png
.J'ai obtenu de belles pages complètes sans en-têtes ni pieds de page, c'est-à-dire juste de grandes bordures noires, mais non découpées en colonnes. En 2 pages sur 10 j'ai obtenu ce que je voulais, à savoir :
Chronique du succès à la page 2
Étant donné que j'obtiens parfois les résultats souhaités, il doit y avoir quelque chose qui fonctionne, mais je ne sais pas comment l'améliorer davantage.
Éditeur :
Voici d'autres exemples de pages :
p0
p1
p5
J'ai essayé quelque chose sans aucune expansion parce que je voulais voir si je pouvais simplement utiliser la ligne médiane comme "séparateur". Voici le code :
im = cv2.cvtcolor(cv2.imread("arabic.png"), cv2.color_bgr2rgb) # read im as rgb for better plots gray = cv2.cvtcolor(im, cv2.color_rgb2gray) # convert to gray _, threshold = cv2.threshold(gray, 250, 255, cv2.thresh_binary_inv) # inverse thresholding contours, _ = cv2.findcontours(threshold, cv2.retr_external, cv2.chain_approx_none) # find contours sortedcontours = sorted(contours, key = cv2.contourarea, reverse=true) # sort according to area, descending bigbox = sortedcontours[0] # get the contour of the big box middleline = sortedcontours[1] # get the contour of the vertical line xmiddleline, _, _, _ = cv2.boundingrect(middleline) # get x coordinate of middleline leftboxcontour = np.array([point for point in bigbox if point[0, 0] < xmiddleline]) # assign left of line as points from the big contour rightboxcontour = np.array([point for point in bigbox if point[0, 0] >= xmiddleline]) # assigh right of line as points from the big contour leftboxx, leftboxy, leftboxw, leftboxh = cv2.boundingrect(leftboxcontour) # get properties of box on left rightboxx, rightboxy, rightboxw, rightboxh = cv2.boundingrect(rightboxcontour) # get properties of box on right leftboxcrop = im[leftboxy:leftboxy + leftboxh, leftboxx:leftboxx + leftboxw] # crop left rightboxcrop = im[rightboxy:rightboxy + rightboxh, rightboxx:rightboxx + rightboxw] # crop right # maybe do you assertations about aspect ratio?? cv2.imwrite("right.png", rightboxcrop) # save image cv2.imwrite("left.png", leftboxcrop) # save image
Je n'utilise aucune affirmation sur les proportions, alors peut-être que c'est toujours quelque chose que vous devez faire..
Fondamentalement, les lignes les plus importantes de cette méthode génèrent les contours gauche et droit en fonction de la coordonnée x. Voici le résultat final que j'ai obtenu :
Il y a encore quelques parties noires sur les bords, mais cela ne devrait pas poser de problème pour l'ocr.
Pour information : j'utilise les packages suivants dans jupyter :
import cv2 import numpy as np %matplotlib notebook import matplotlib.pyplot as plt
v2.0 : Implémenté en utilisant uniquement la détection de grandes boîtes :
J'ai donc fait une dilatation et la grosse boîte était facilement détectable. J'utilise un noyau horizontal pour m'assurer que les lignes verticales de la grande boîte sont toujours suffisamment épaisses pour être détectées. Cependant, je n'arrive pas à résoudre le problème de la ligne médiane car elle est très fine... Voici néanmoins le code de la méthode ci-dessus :
im = cv2.cvtcolor(cv2.imread("1.png"), cv2.color_bgr2rgb) # read im as rgb for better plots gray = cv2.cvtcolor(im, cv2.color_rgb2gray) # convert to gray gray[gray<255] = 0 # added some contrast to make it either completly black or white _, threshold = cv2.threshold(gray, 250, 255, cv2.thresh_binary_inv) # inverse thresholding thresholddilated = cv2.dilate(threshold, np.ones((1,10)), iterations = 1) # dilate horizontally contours, _ = cv2.findcontours(thresholddilated, cv2.retr_external, cv2.chain_approx_none) # find contours sortedcontours = sorted(contours, key = cv2.contourarea, reverse=true) # sort according to area, descending x, y, w, h = cv2.boundingrect(sortedcontours[0]) # get the bounding rect properties of the contour left = im[y:y+h, x:x+int(w/2)+10].copy() # generate left, i included 10 pix from the right just in case right = im[y:y+h, int(w/2)-10:w].copy() # and right, i included 10 pix from the left just in case fig, ax = plt.subplots(nrows = 2, ncols = 3) # plotting... ax[0,0].axis("off") ax[0,1].imshow(im) ax[0,1].axis("off") ax[0,2].axis("off") ax[1,0].imshow(left) ax[1,0].axis("off") ax[1,1].axis("off") ax[1,2].imshow(right) ax[1,2].axis("off")
Voici les résultats, vous pouvez remarquer que ce n'est pas parfait, mais encore une fois, puisque votre objectif est ocr, cela ne devrait pas poser de problème.
S'il vous plaît, dites-moi si cela fonctionne, sinon je vais me creuser la tête pour trouver une meilleure solution...
v3.0 : Une meilleure façon d'obtenir des images plus droites, ce qui améliorera la qualité de l'ocr.
Inspiré de mon autre réponse ici : answer. Il est logique de redresser l'image pour que l'ocr donne de meilleurs résultats. Par conséquent, j'ai utilisé une transformation en quatre points sur le cadre extérieur détecté. Cela redressera légèrement l'image et rendra le texte plus horizontal. Voici le code :
im = cv2.cvtcolor(cv2.imread("2.png"), cv2.color_bgr2rgb) # read im as rgb for better plots gray = cv2.cvtcolor(im, cv2.color_rgb2gray) # convert to gray gray[gray<255] = 0 # added some contrast to make it either completly black or white _, threshold = cv2.threshold(gray, 250, 255, cv2.thresh_binary_inv) # inverse thresholding thresholddilated = cv2.dilate(threshold, np.ones((1,10)), iterations = 1) # dilate horizontally contours, _ = cv2.findcontours(thresholddilated, cv2.retr_external, cv2.chain_approx_none) # find contours largest_contour = max(contours, key = cv2.contourarea) # get largest contour hull = cv2.convexhull(largest_contour) # get the hull epsilon = 0.02 * cv2.arclength(largest_contour, true) # epsilon pts1 = np.float32(cv2.approxpolydp(hull, epsilon, true).reshape(-1, 2)) # get the points result = four_point_transform(im, pts1) # using imutils height, width = result.shape[:2] # get the dimensions of the transformed image left = result[:, 0:int(width/2)].copy() # from the beginning to half the width right = result[:, int(width/2): width].copy() # from half the width till the end fig, ax = plt.subplots(nrows = 2, ncols = 3) # plotting... ax[0,0].axis("off") ax[0,1].imshow(result) ax[0,1].axvline(width/2) ax[0,1].axis("off") ax[0,2].axis("off") ax[1,0].imshow(left) ax[1,0].axis("off") ax[1,1].axis("off") ax[1,2].imshow(right) ax[1,2].axis("off")
Comprend les forfaits suivants :
import cv2 import numpy as np %matplotlib notebook import matplotlib.pyplot as plt from imutils.perspective import four_point_transform
Comme vous pouvez le voir sur le code, c'est une meilleure approche, vous pouvez forcer l'image à être centrée et horizontale grâce à la transformation en quatre points. De plus, il n’est pas nécessaire d’inclure un certain chevauchement puisque les images sont bien séparées. Voici un exemple pour votre référence :
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!