La réponse de Nikie a résolu mon problème, mais sa réponse était en Mathematica. J'ai donc pensé que je devais donner son adaptation OpenCV ici. Mais après l'implémentation, j'ai pu voir que le code OpenCV est beaucoup plus gros que le code Mathematica de Nikie. Et aussi, je n'ai pas pu trouver la méthode d'interpolation faite par nikie dans OpenCV ( bien qu'elle puisse être faite en utilisant scipy, je le dirai en temps voulu).
1. Prétraitement de l'image (opération de fermeture)
import cv2
import numpy as np
img = cv2.imread('dave.jpg')
img = cv2.GaussianBlur(img,(5,5),0)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
mask = np.zeros((gray.shape),np.uint8)
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))
close = cv2.morphologyEx(gray,cv2.MORPH_CLOSE,kernel1)
div = np.float32(gray)/(close)
res = np.uint8(cv2.normalize(div,div,0,255,cv2.NORM_MINMAX))
res2 = cv2.cvtColor(res,cv2.COLOR_GRAY2BGR)
Résultat :
2. Trouver le carré Sudoku et créer une image de masque
thresh = cv2.adaptiveThreshold(res,255,0,1,19,2)
contour,hier = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
max_area = 0
best_cnt = None
for cnt in contour:
area = cv2.contourArea(cnt)
if area > 1000:
if area > max_area:
max_area = area
best_cnt = cnt
cv2.drawContours(mask,[best_cnt],0,255,-1)
cv2.drawContours(mask,[best_cnt],0,0,2)
res = cv2.bitwise_and(res,mask)
Résultat :
3. Trouver des lignes verticales
kernelx = cv2.getStructuringElement(cv2.MORPH_RECT,(2,10))
dx = cv2.Sobel(res,cv2.CV_16S,1,0)
dx = cv2.convertScaleAbs(dx)
cv2.normalize(dx,dx,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dx,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernelx,iterations = 1)
contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
x,y,w,h = cv2.boundingRect(cnt)
if h/w > 5:
cv2.drawContours(close,[cnt],0,255,-1)
else:
cv2.drawContours(close,[cnt],0,0,-1)
close = cv2.morphologyEx(close,cv2.MORPH_CLOSE,None,iterations = 2)
closex = close.copy()
Résultat :
4. Trouver des lignes horizontales
kernely = cv2.getStructuringElement(cv2.MORPH_RECT,(10,2))
dy = cv2.Sobel(res,cv2.CV_16S,0,2)
dy = cv2.convertScaleAbs(dy)
cv2.normalize(dy,dy,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dy,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernely)
contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
x,y,w,h = cv2.boundingRect(cnt)
if w/h > 5:
cv2.drawContours(close,[cnt],0,255,-1)
else:
cv2.drawContours(close,[cnt],0,0,-1)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,None,iterations = 2)
closey = close.copy()
Résultat :
Bien sûr, celui-ci n'est pas si bon.
5. Trouver des points de grille
res = cv2.bitwise_and(closex,closey)
Résultat :
6. Corriger les défauts
Ici, nikie fait une sorte d'interpolation, sur laquelle je n'ai pas beaucoup de connaissances. Et je n'ai pas pu trouver de fonction correspondante pour cet OpenCV. (peut-être qu'elle existe, je ne sais pas).
Jetez un coup d'œil à ce SOF qui explique comment faire cela en utilisant SciPy, que je ne veux pas utiliser : Transformation d'images dans OpenCV
Donc, ici, j'ai pris 4 coins de chaque sous-carré et j'ai appliqué la Perspective de la chaîne sur chacun d'eux.
Pour cela, il faut d'abord trouver les centroïdes.
contour, hier = cv2.findContours(res,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
centroids = []
for cnt in contour:
mom = cv2.moments(cnt)
(x,y) = int(mom['m10']/mom['m00']), int(mom['m01']/mom['m00'])
cv2.circle(img,(x,y),4,(0,255,0),-1)
centroids.append((x,y))
Mais les centroïdes résultants ne seront pas triés. Regardez l'image ci-dessous pour voir leur ordre :
Nous les trions donc de gauche à droite, de haut en bas.
centroids = np.array(centroids,dtype = np.float32)
c = centroids.reshape((100,2))
c2 = c[np.argsort(c[:,1])]
b = np.vstack([c2[i*10:(i+1)*10][np.argsort(c2[i*10:(i+1)*10,0])] for i in xrange(10)])
bm = b.reshape((10,10,2))
Maintenant voir ci-dessous leur ordre :
Enfin, nous appliquons la transformation et créons une nouvelle image de taille 450x450.
output = np.zeros((450,450,3),np.uint8)
for i,j in enumerate(b):
ri = i/10
ci = i%10
if ci != 9 and ri!=9:
src = bm[ri:ri+2, ci:ci+2 , :].reshape((4,2))
dst = np.array( [ [ci*50,ri*50],[(ci+1)*50-1,ri*50],[ci*50,(ri+1)*50-1],[(ci+1)*50-1,(ri+1)*50-1] ], np.float32)
retval = cv2.getPerspectiveTransform(src,dst)
warp = cv2.warpPerspective(res2,retval,(450,450))
output[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1] = warp[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1].copy()
Résultat :
Le résultat est presque le même que celui de Nikie, mais la longueur du code est grande. Peut-être que de meilleures méthodes sont disponibles, mais en attendant, cela fonctionne bien.
Salutations ARK.
1 votes
Vous faites votre détection en vous basant sur les points d'angle, sur lesquels les lignes rouges et vertes sont d'accord. Je ne connais pas OpenCV, mais on peut supposer que vous voulez détecter les lignes entre ces points d'angle et déformer en fonction de cela.
0 votes
Peut-être faut-il forcer les lignes qui relient les points d'angle à coïncider avec les pixels noirs lourds de l'image. En d'autres termes, au lieu de laisser les lignes vertes trouver une ligne droite entre les points d'angle, forcez-les à traverser de gros pixels noirs. Cela rendra votre problème sensiblement plus difficile, je pense, et je ne connais pas de build-ins OpenCV qui vous seraient immédiatement utiles.
0 votes
Dougal : Je pense que la ligne verte dessinée est la ligne droite approximative de la ligne rouge. C'est donc la ligne entre ces points d'angle. Quand je déforme selon la ligne verte, j'obtiens une ligne rouge courbée en haut de l'image déformée. ( j'espère que vous comprenez, mon explication semble un peu mauvaise)
0 votes
@ EMS : je pense que la ligne rouge dessinée est exactement sur la frontière du sudoku. Mais le problème est, comment déformer l'image exactement sur le bord du sudoku. ( je veux dire, le problème est avec la déformation, c'est à dire convertir ces bords courbés en un carré exact, comme je l'ai montré dans la deuxième image).