Wie entferne ich Konvexitätsfehler in einem Sudoku-Quadrat?

192

Ich habe ein lustiges Projekt gemacht: Ein Sudoku aus einem Eingabebild mit OpenCV lösen (wie bei Google-Brillen usw.). Und ich habe die Aufgabe erledigt, aber am Ende habe ich ein kleines Problem gefunden, für das ich hierher gekommen bin.

Ich habe die Programmierung mit der Python-API von OpenCV 2.3.1 durchgeführt.

Folgendes habe ich getan:

  1. Lesen Sie das Bild
  2. Finde die Konturen
  3. Wählen Sie die mit der maximalen Fläche (und auch etwas gleich dem Quadrat).
  4. Finde die Eckpunkte.

    zB unten angegeben:

    Geben Sie hier die Bildbeschreibung ein

    ( Beachten Sie hier, dass die grüne Linie korrekt mit der tatsächlichen Grenze des Sudoku übereinstimmt, sodass das Sudoku korrekt verzogen werden kann . Überprüfen Sie das nächste Bild.)

  5. Verzerren Sie das Bild zu einem perfekten Quadrat

    zB Bild:

    Geben Sie hier die Bildbeschreibung ein

  6. OCR durchführen (für die ich die Methode verwendet habe, die ich in OCR für einfache Ziffernerkennung in OpenCV-Python angegeben habe )

Und die Methode hat gut funktioniert.

Problem:

Schauen Sie sich dieses Bild an.

Wenn Sie Schritt 4 für dieses Bild ausführen, erhalten Sie das folgende Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Die gezeichnete rote Linie ist die ursprüngliche Kontur, die den wahren Umriss der Sudoku-Grenze darstellt.

Die gezeichnete grüne Linie ist eine ungefähre Kontur, die den Umriss des verzerrten Bildes darstellt.

Was natürlich einen Unterschied zwischen der grünen und der roten Linie am oberen Rand des Sudoku gibt. Während ich mich verziehe, erhalte ich nicht die ursprüngliche Grenze des Sudoku.

Meine Frage :

Wie kann ich das Bild an der richtigen Grenze des Sudoku verziehen, dh an der roten Linie, ODER wie kann ich den Unterschied zwischen roter und grüner Linie beseitigen? Gibt es dafür eine Methode in OpenCV?

Abid Rahman K.
quelle
1
Sie erkennen anhand von Eckpunkten, auf die sich die roten und grünen Linien einigen. Ich kenne OpenCV nicht, aber vermutlich möchten Sie die Linien zwischen diesen Eckpunkten und dem Warp basierend darauf erkennen.
Dougal
Erzwingen Sie möglicherweise, dass die Linien, die die Eckpunkte verbinden, mit schweren schwarzen Pixeln im Bild übereinstimmen. Das heißt, anstatt die grünen Linien nur eine gerade Linie zwischen den Eckpunkten finden zu lassen, zwingen Sie sie, schwere schwarze Pixel zu durchlaufen. Dies wird Ihr Problem erheblich erschweren, denke ich, und ich kenne keine OpenCV-Integrationen, die für Sie sofort nützlich sein werden.
ely
@ Dougal: Ich denke, die gezeichnete grüne Linie ist die ungefähre gerade Linie der roten Linie. Es ist also die Linie zwischen diesen Eckpunkten. Wenn ich mich gemäß der grünen Linie verziehe, erhalte ich oben im verzogenen Bild eine gekrümmte rote Linie. (Ich hoffe du verstehst, meine Erklärung scheint ein wenig schlecht zu sein)
Abid Rahman K
@ EMS: Ich denke, die rote Linie ist genau an der Grenze von Sudoku. Das Problem ist jedoch, wie das Bild genau an der Grenze von Sudoku verzogen werden kann. (Ich meine, das Problem ist das Verziehen, dh das Konvertieren dieser gekrümmten Grenze in ein genaues Quadrat, wie ich im zweiten Bild gezeigt habe)
Abid Rahman K

Antworten:

251

Ich habe eine Lösung, die funktioniert, aber Sie müssen sie selbst in OpenCV übersetzen. Es ist in Mathematica geschrieben.

Der erste Schritt besteht darin, die Helligkeit im Bild anzupassen, indem jedes Pixel mit dem Ergebnis einer Schließoperation geteilt wird:

src = ColorConvert[Import["http://davemark.com/images/sudoku.jpg"], "Grayscale"];
white = Closing[src, DiskMatrix[5]];
srcAdjusted = Image[ImageData[src]/ImageData[white]]

Geben Sie hier die Bildbeschreibung ein

Der nächste Schritt besteht darin, den Sudoku-Bereich zu finden, damit ich den Hintergrund ignorieren (maskieren) kann. Dazu verwende ich die Analyse verbundener Komponenten und wähle die Komponente mit der größten konvexen Fläche aus:

components = 
  ComponentMeasurements[
    ColorNegate@Binarize[srcAdjusted], {"ConvexArea", "Mask"}][[All, 
    2]];
largestComponent = Image[SortBy[components, First][[-1, 2]]]

Geben Sie hier die Bildbeschreibung ein

Durch das Ausfüllen dieses Bildes erhalte ich eine Maske für das Sudoku-Gitter:

mask = FillingTransform[largestComponent]

Geben Sie hier die Bildbeschreibung ein

Jetzt kann ich einen Ableitungsfilter 2. Ordnung verwenden, um die vertikalen und horizontalen Linien in zwei separaten Bildern zu finden:

lY = ImageMultiply[MorphologicalBinarize[GaussianFilter[srcAdjusted, 3, {2, 0}], {0.02, 0.05}], mask];
lX = ImageMultiply[MorphologicalBinarize[GaussianFilter[srcAdjusted, 3, {0, 2}], {0.02, 0.05}], mask];

Geben Sie hier die Bildbeschreibung ein

Ich verwende wieder die Analyse verbundener Komponenten, um die Gitterlinien aus diesen Bildern zu extrahieren. Die Gitterlinien sind viel länger als die Ziffern, daher kann ich die Dicke des Messschiebers verwenden, um nur die mit den Gitterlinien verbundenen Komponenten auszuwählen. Wenn ich sie nach Position sortiere, erhalte ich 2x10 Maskenbilder für jede der vertikalen / horizontalen Gitterlinien im Bild:

verticalGridLineMasks = 
  SortBy[ComponentMeasurements[
      lX, {"CaliperLength", "Centroid", "Mask"}, # > 100 &][[All, 
      2]], #[[2, 1]] &][[All, 3]];
horizontalGridLineMasks = 
  SortBy[ComponentMeasurements[
      lY, {"CaliperLength", "Centroid", "Mask"}, # > 100 &][[All, 
      2]], #[[2, 2]] &][[All, 3]];

Geben Sie hier die Bildbeschreibung ein

Als nächstes nehme ich jedes Paar vertikaler / horizontaler Gitterlinien, erweitere sie, berechne den Pixel-für-Pixel-Schnittpunkt und berechne die Mitte des Ergebnisses. Diese Punkte sind die Schnittpunkte der Gitterlinien:

centerOfGravity[l_] := 
 ComponentMeasurements[Image[l], "Centroid"][[1, 2]]
gridCenters = 
  Table[centerOfGravity[
    ImageData[Dilation[Image[h], DiskMatrix[2]]]*
     ImageData[Dilation[Image[v], DiskMatrix[2]]]], {h, 
    horizontalGridLineMasks}, {v, verticalGridLineMasks}];

Geben Sie hier die Bildbeschreibung ein

Der letzte Schritt besteht darin, zwei Interpolationsfunktionen für die X / Y-Abbildung durch diese Punkte zu definieren und das Bild mithilfe dieser Funktionen zu transformieren:

fnX = ListInterpolation[gridCenters[[All, All, 1]]];
fnY = ListInterpolation[gridCenters[[All, All, 2]]];
transformed = 
 ImageTransformation[
  srcAdjusted, {fnX @@ Reverse[#], fnY @@ Reverse[#]} &, {9*50, 9*50},
   PlotRange -> {{1, 10}, {1, 10}}, DataRange -> Full]

Geben Sie hier die Bildbeschreibung ein

Alle Vorgänge sind grundlegende Bildverarbeitungsfunktionen, daher sollte dies auch in OpenCV möglich sein. Die Spline-basierte Bildtransformation ist möglicherweise schwieriger, aber ich glaube nicht, dass Sie sie wirklich brauchen. Wenn Sie die Perspektiventransformation verwenden, die Sie jetzt für jede einzelne Zelle verwenden, erhalten Sie wahrscheinlich ausreichend gute Ergebnisse.

Niki
quelle
3
Oh mein Gott !!!!!!!!! Das war wunderbar. Das ist wirklich sehr, sehr gut. Ich werde versuchen, es in OpenCV zu machen. Ich hoffe, Sie helfen mir mit Details zu bestimmten Funktionen und Terminologie ... Vielen Dank.
Abid Rahman K
@arkiaz: Ich bin kein OpenCV-Experte, aber ich werde helfen, wenn ich kann, klar.
Niki
Können Sie bitte erklären, wofür die Funktion "Schließen" verwendet wird? Was ich meine ist, was im Hintergrund passiert? In der Dokumentation heißt es, dass das Schließen Salz- und Pfeffergeräusche entfernt? Wird der Tiefpassfilter geschlossen?
Abid Rahman K
2
Erstaunliche Antwort! Woher kam die Idee, durch das Schließen zu teilen, um die Bildhelligkeit zu normalisieren? Ich versuche, die Geschwindigkeit dieser Methode zu verbessern, da die Gleitkommadivision auf Mobiltelefonen schmerzhaft langsam ist. Hast du irgendwelche Vorschläge? @AbidRahmanK
1 ''
1
@ 1 *: Ich denke, es heißt "Weißbildanpassung". Fragen Sie mich nicht, wo ich darüber gelesen habe, es ist ein Standard-Bildverarbeitungswerkzeug. Das Modell hinter der Idee ist einfach: Die von einer (Lambertschen) Oberfläche reflektierte Lichtmenge ist nur die Oberflächenhelligkeit multipliziert mit der Lichtmenge, die ein weißer Körper an derselben Position reflektieren würde. Schätzen Sie die scheinbare Helligkeit eines weißen Körpers an derselben Position, dividieren Sie die tatsächliche Helligkeit durch diese und Sie erhalten die Helligkeit der Oberfläche.
Niki
208

Nikies Antwort löste mein Problem, aber seine Antwort war in Mathematica. Also dachte ich, ich sollte hier seine OpenCV-Anpassung geben. Aber nach der Implementierung konnte ich sehen, dass OpenCV-Code viel größer ist als Nikies Mathematica-Code. Außerdem konnte ich in OpenCV keine von Nikie durchgeführte Interpolationsmethode finden (obwohl dies mit scipy möglich ist, werde ich es zu gegebener Zeit mitteilen.)

1. Bildvorverarbeitung (Schließvorgang)

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)

Ergebnis:

Ergebnis des Schließens

2. Sudoku-Quadrat finden und Maskenbild erstellen

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)

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

3. Vertikale Linien finden

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

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

4. Horizontale Linien finden

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

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Natürlich ist dieser nicht so gut.

5. Gitterpunkte finden

res = cv2.bitwise_and(closex,closey)

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

6. Behebung der Mängel

Hier macht Nikie eine Art Interpolation, über die ich nicht viel weiß. Und ich konnte keine entsprechende Funktion für dieses OpenCV finden. (Vielleicht ist es da, ich weiß es nicht).

Schauen Sie sich dieses SOF an, in dem erklärt wird, wie dies mit SciPy gemacht wird, das ich nicht verwenden möchte: Bildtransformation in OpenCV

Also habe ich hier 4 Ecken von jedem Unterquadrat genommen und auf jedes eine Warp-Perspektive angewendet.

Dafür finden wir zuerst die Zentroide.

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

Die resultierenden Zentroide werden jedoch nicht sortiert. Schauen Sie sich das Bild unten an, um die Reihenfolge zu sehen:

Geben Sie hier die Bildbeschreibung ein

Also sortieren wir sie von links nach rechts, von oben nach unten.

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

Nun siehe unten ihre Reihenfolge:

Geben Sie hier die Bildbeschreibung ein

Schließlich wenden wir die Transformation an und erstellen ein neues Bild der Größe 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()

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Das Ergebnis ist fast das gleiche wie bei Nikie, aber die Codelänge ist groß. Möglicherweise gibt es bessere Methoden, aber bis dahin funktioniert dies in Ordnung.

Grüße ARK.

Abid Rahman K.
quelle
4
"Ich ziehe es vor, dass meine Anwendung abstürzt, als falsche Antworten zu erhalten." <- Ich stimme auch diesem 100% zu
Viktor Sehr
Danke, die wahre Antwort gibt Nikie. Aber das war in Mathematica, also habe ich es einfach in OpenCV konvertiert. Die wirkliche Antwort hat also genug positive Stimmen, denke ich
Abid Rahman K
Ich habe nicht gesehen, dass du auch die Frage gepostet hast :)
Viktor Sehr
Ja. Frage ist auch meine. Meine und Nikies Antwort sind nur am Ende unterschiedlich. Er hat eine Art Intepolationsfunktion in Mathematica, die nicht in Numpy oder OpenCV ist (aber sie ist in Scipy vorhanden, aber ich wollte Scipy hier nicht verwenden)
Abid Rahman K
Ich erhalte die Fehlermeldung: Ausgabe [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 TypeError: Das Argument long () muss eine Zeichenfolge oder eine Zahl sein, nicht 'builtin_function_or_method'
user898678
6

Sie könnten versuchen, eine Art gitterbasierte Modellierung Ihrer willkürlichen Verzerrung zu verwenden. Und da das Sudoku bereits ein Gitter ist, sollte das nicht zu schwer sein.

Sie können also versuchen, die Grenzen jeder 3x3-Subregion zu erkennen und dann jede Region einzeln zu verzerren. Wenn die Erkennung erfolgreich ist, erhalten Sie eine bessere Annäherung.

sietschie
quelle
1

Ich möchte hinzufügen, dass die obige Methode nur funktioniert, wenn das Sudoku-Brett gerade steht. Andernfalls schlägt der Verhältnis-Test für Höhe / Breite (oder umgekehrt) höchstwahrscheinlich fehl und Sie können keine Sudoku-Kanten erkennen. (Ich möchte auch hinzufügen, dass Sobel-Operationen (dx und dy) weiterhin funktionieren, wenn Linien, die nicht senkrecht zu den Bildrändern stehen, weiterhin Kanten in Bezug auf beide Achsen haben.)

Um gerade Linien erkennen zu können, sollten Sie an kontur- oder pixelweisen Analysen wie contourArea / boundingRectArea, Punkten oben links und unten rechts arbeiten ...

Bearbeiten: Ich konnte überprüfen, ob eine Reihe von Konturen eine Linie bildet oder nicht, indem ich eine lineare Regression anwendete und den Fehler überprüfte. Die lineare Regression zeigte jedoch eine schlechte Leistung, wenn die Steigung der Linie zu groß ist (dh> 1000) oder sehr nahe bei 0 liegt. Daher ist es logisch, den obigen Ratio-Test (in der am häufigsten bewerteten Antwort) vor der linearen Regression anzuwenden, und hat bei mir funktioniert.

Ali Eren Çelik
quelle
1

Um nicht abgeleitete Ecken zu entfernen, habe ich eine Gammakorrektur mit einem Gammawert von 0,8 angewendet.

Vor der Gammakorrektur

Der rote Kreis zeigt die fehlende Ecke.

Nach der Gammakorrektur

Der Code lautet:

gamma = 0.8
invGamma = 1/gamma
table = np.array([((i / 255.0) ** invGamma) * 255
                  for i in np.arange(0, 256)]).astype("uint8")
cv2.LUT(img, table, img)

Dies ist zusätzlich zu Abid Rahmans Antwort, wenn einige Eckpunkte fehlen.

Vardan Agarwal
quelle
0

Ich dachte, dies sei ein großartiger Beitrag und eine großartige Lösung von ARK. sehr gut angelegt und erklärt.

Ich habe an einem ähnlichen Problem gearbeitet und das Ganze gebaut. Es gab einige Änderungen (z. B. xrange to range, Argumente in cv2.findContours), die jedoch sofort funktionieren sollten (Python 3.5, Anaconda).

Dies ist eine Zusammenstellung der obigen Elemente, wobei ein Teil des fehlenden Codes hinzugefügt wurde (dh Beschriftung von Punkten).

'''

/programming/10196198/how-to-remove-convexity-defects-in-a-sudoku-square

'''

import cv2
import numpy as np

img = cv2.imread('test.png')

winname="raw image"
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.moveWindow(winname, 100,100)


img = cv2.GaussianBlur(img,(5,5),0)

winname="blurred"
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.moveWindow(winname, 100,150)

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
mask = np.zeros((gray.shape),np.uint8)
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))

winname="gray"
cv2.namedWindow(winname)
cv2.imshow(winname, gray)
cv2.moveWindow(winname, 100,200)

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)

winname="res2"
cv2.namedWindow(winname)
cv2.imshow(winname, res2)
cv2.moveWindow(winname, 100,250)

 #find elements
thresh = cv2.adaptiveThreshold(res,255,0,1,19,2)
img_c, 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)

winname="puzzle only"
cv2.namedWindow(winname)
cv2.imshow(winname, res)
cv2.moveWindow(winname, 100,300)

# vertical lines
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)

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

winname="vertical lines"
cv2.namedWindow(winname)
cv2.imshow(winname, img_d)
cv2.moveWindow(winname, 100,350)

# find horizontal lines
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)

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

winname="horizontal lines"
cv2.namedWindow(winname)
cv2.imshow(winname, img_e)
cv2.moveWindow(winname, 100,400)


# intersection of these two gives dots
res = cv2.bitwise_and(closex,closey)

winname="intersections"
cv2.namedWindow(winname)
cv2.imshow(winname, res)
cv2.moveWindow(winname, 100,450)

# text blue
textcolor=(0,255,0)
# points green
pointcolor=(255,0,0)

# find centroids and sort
img_f, 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))

# sorting
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 range(10)])
bm = b.reshape((10,10,2))

# make copy
labeled_in_order=res2.copy()

for index, pt in enumerate(b):
    cv2.putText(labeled_in_order,str(index),tuple(pt),cv2.FONT_HERSHEY_DUPLEX, 0.75, textcolor)
    cv2.circle(labeled_in_order, tuple(pt), 5, pointcolor)

winname="labeled in order"
cv2.namedWindow(winname)
cv2.imshow(winname, labeled_in_order)
cv2.moveWindow(winname, 100,500)

# create final

output = np.zeros((450,450,3),np.uint8)
for i,j in enumerate(b):
    ri = int(i/10) # row index
    ci = i%10 # column index
    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()

winname="final"
cv2.namedWindow(winname)
cv2.imshow(winname, output)
cv2.moveWindow(winname, 600,100)

cv2.waitKey(0)
cv2.destroyAllWindows()
Asylumax
quelle