Bir Sudoku meydanında dışbükeylik kusurları nasıl kaldırılır?


193

Eğlenceli bir proje yapıyordum: OpenCV kullanarak giriş resminden Sudoku çözme (Google gözlüklerinde olduğu gibi). Ve görevi tamamladım, ama sonunda buraya geldiğim küçük bir sorun buldum.

Programlamayı OpenCV 2.3.1'in Python API'sini kullanarak yaptım.

Aşağıda yaptığım şey:

  1. Resmi okuyun
  2. Kontürleri bulun
  3. Maksimum alana sahip olanı seçin (ve ayrıca kareye eşdeğer).
  4. Köşe noktalarını bulun.

    örneğin aşağıda verilmiştir:

    resim açıklamasını buraya girin

    ( Yeşil çizginin Sudoku'nun gerçek sınırıyla doğru bir şekilde çakıştığına dikkat edin, böylece Sudoku doğru bir şekilde bükülebilir . Sonraki görüntüyü kontrol edin)

  5. görüntüyü mükemmel bir kareye dönüştür

    örneğin resim:

    resim açıklamasını buraya girin

  6. OCR gerçekleştirin ( OpenCV-Python'da Basit Rakam Tanıma OCR'de verdiğim yöntemi kullandığım )

Ve yöntem iyi çalıştı.

Sorun:

Check out Bu resmi.

Bu görüntü üzerinde 4. adımı uygulamak aşağıdaki sonucu verir:

resim açıklamasını buraya girin

Çizilen kırmızı çizgi, sudoku sınırının gerçek anahattı olan orijinal kontur.

Çizilen yeşil çizgi, çarpık görüntünün ana hatları olacak yaklaşık kontur.

Hangi tabii ki, sudoku üst kenarında yeşil çizgi ve kırmızı çizgi arasında fark var. Çözgü yaparken, Sudoku'nun orijinal sınırını elde edemiyorum.

Benim sorum :

Resmi Sudoku'nun doğru sınırında, yani kırmızı çizgide nasıl bükebilirim? VEYA kırmızı çizgi ile yeşil çizgi arasındaki farkı nasıl kaldırabilirim? OpenCV'de bunun için herhangi bir yöntem var mı?


1
Tespitinizi kırmızı ve yeşil çizgilerin üzerinde anlaştığı köşe noktalarına göre yapıyorsunuz. OpenCV'yi bilmiyorum, ancak muhtemelen bu köşe noktaları ile çözgü arasındaki çizgileri tespit etmek isteyeceksiniz.
Dougal

Belki de köşe noktalarını birbirine bağlayan çizgileri görüntüdeki ağır siyah piksellerle çakışmaya zorlar. Yani, yeşil çizgilerin sadece köşe noktaları arasında düz bir çizgi bulmasını sağlamak yerine, onları ağır siyah pikselleri geçmeye zorlayın. Bu, sorununuzu önemli ölçüde daha zor hale getireceğini düşünüyorum ve sizin için hemen yararlı olacak herhangi bir OpenCV yerleşikini bilmiyorum.
ely

@ Dougal: Bence çizilen yeşil çizgi, kırmızı çizginin yaklaşık düz çizgisidir. bu köşe noktaları arasındaki çizgidir. Yeşil çizgiye göre çözgü yaptığımda, çarpık görüntünün üst kısmında eğri kırmızı çizgi oluşuyor. (Umarım anlarsın, açıklamam biraz kötü görünüyor)
Abid Rahman K

@ Ems: sanırım çizilmiş kırmızı çizgi tam sudoku sınırında. Ama sorun şu ki, görüntünün tam olarak sudoku sınırında nasıl çözüleceği. (yani, çarpıtma ile ilgili sorun, yani ikinci görüntüde gösterildiği gibi, bu kavisli sınırın tam bir kareye dönüştürülmesi)
Abid Rahman K

Yanıtlar:


252

İşe yarayan bir çözümüm var, ancak bunu OpenCV'ye çevirmeniz gerekecek. Mathematica'da yazılmıştır.

İlk adım, her pikseli bir kapatma işleminin sonucuna bölerek görüntüdeki parlaklığı ayarlamaktır:

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

resim açıklamasını buraya girin

Bir sonraki adım sudoku alanını bulmaktır, böylece arka planı görmezden gelebilirim (maskeleyebilirim). Bunun için, bağlı bileşen analizini kullanıyorum ve en büyük dışbükey alana sahip bileşeni seçiyorum:

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

resim açıklamasını buraya girin

Bu görüntüyü doldurarak, sudoku ızgarası için bir maske alıyorum:

mask = FillingTransform[largestComponent]

resim açıklamasını buraya girin

Şimdi, iki ayrı görüntüde dikey ve yatay çizgileri bulmak için 2. dereceden bir türev filtre kullanabilirim:

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];

resim açıklamasını buraya girin

Bu görüntülerden ızgara çizgilerini çıkarmak için tekrar bağlı bileşen analizini kullanıyorum. Izgara çizgileri rakamlardan çok daha uzun, bu yüzden sadece ızgara çizgilerine bağlı bileşenleri seçmek için kaliper uzunluğunu kullanabilirim. Onları konuma göre sıralayarak, görüntüdeki dikey / yatay ızgara çizgilerinin her biri için 2x10 maske görüntüsü alıyorum:

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]];

resim açıklamasını buraya girin

Daha sonra, her bir dikey / yatay ızgara çizgisini alıyorum, onları genişletiyorum, piksel piksel kesişimini ve sonucun merkezini hesaplıyorum. Bu noktalar ızgara çizgisi kavşaklarıdır:

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}];

resim açıklamasını buraya girin

Son adım, bu noktalardan X / Y eşlemesi için iki enterpolasyon işlevi tanımlamak ve bu işlevleri kullanarak görüntüyü dönüştürmektir:

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]

resim açıklamasını buraya girin

Tüm işlemler temel görüntü işleme işlevidir, bu nedenle bu OpenCV'de de mümkün olmalıdır. Kama tabanlı görüntü dönüşümü daha zor olabilir, ancak buna gerçekten ihtiyacınız olduğunu düşünmüyorum. Muhtemelen şu anda her bir hücrede kullandığınız perspektif dönüşümünü kullanmak yeterince iyi sonuç verecektir.


3
Aman Tanrım !!!!!!!!! Bu harikaydı. Bu gerçekten harika. Bunu OpenCV'de yapmaya çalışacağım. Umarım bazı fonksiyonlar ve terminoloji hakkında bana yardımcı olursun ... Teşekkürler.
Abid Rahman K

@arkiaz: Ben bir OpenCV uzmanı değilim ama yapabilirsem yardımcı olacağım.
Niki

"Kapanış" fonksiyonunun ne için kullanıldığını açıklayabilir misiniz? Demek istediğim arka planda ne oluyor? Dokümantasyonda, kapanmanın tuz ve karabiber gürültüsünü ortadan kaldırdığını mı söylüyor? Alçak geçiren filtre kapatılıyor mu?
Abid Rahman K

2
Şaşırtıcı cevap! Görüntü parlaklığını normalleştirmek için kapanışa bölme fikrini nereden buldunuz? Kayan nokta bölümü cep telefonlarında ağrılı yavaş olduğundan, bu yöntemin hızını artırmaya çalışıyorum. Önerin var mı? @AbidRahmanK
1 ''

1
@ 1 *: Sanırım buna "beyaz görüntü ayarı" deniyor. Bana nereden okuduğumu sorma, bu standart bir görüntü işleme aracı. Fikrin arkasındaki model basittir: (Lambertian) bir yüzeyden yansıyan ışık miktarı, sadece yüzey parlaklığı, aynı pozisyonda beyaz bir cismin yansıtacağı ışık miktarıdır. Beyaz bir gövdenin görünür parlaklığını aynı konumda tahmin edin, gerçek parlaklığı buna bölün ve yüzeyin parlaklığını elde edin.
Niki

209

Nikie'nin cevabı sorunumu çözdü, ama cevabı Mathematica'daydı. Bu yüzden OpenCV adaptasyonunu burada yapmam gerektiğini düşündüm. Ancak uyguladıktan sonra OpenCV kodunun nikie'nin mathematica kodundan çok daha büyük olduğunu görebiliyordum. Ve ayrıca, OpenCV'de nikie tarafından yapılan enterpolasyon yöntemini bulamadım (scipy kullanılarak yapılabilir, ancak zaman geldiğinde söyleyeceğim.)

1. Görüntü Önişleme (kapanma işlemi)

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)

Sonuç:

Kapanış sonucu

2. Sudoku Meydanı Bulma ve Maske Görüntüsü Oluşturma

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)

Sonuç:

resim açıklamasını buraya girin

3. Dikey çizgiler bulma

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

Sonuç:

resim açıklamasını buraya girin

4. Yatay Çizgileri Bulmak

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

Sonuç:

resim açıklamasını buraya girin

Tabii ki, bu o kadar iyi değil.

5. Izgara Noktalarını Bulma

res = cv2.bitwise_and(closex,closey)

Sonuç:

resim açıklamasını buraya girin

6. Kusurları düzeltme

Burada nikie, hakkında fazla bilgim olmayan bir tür enterpolasyon yapıyor. Ve bu OpenCV için karşılık gelen bir işlev bulamadım. (orada olabilir, bilmiyorum).

Kullanmak istemediğim SciPy kullanarak bunu nasıl yapacağınızı açıklayan bu SOF'a göz atın: OpenCV'de görüntü dönüşümü

Burada, her bir alt karenin 4 köşesini aldım ve her birine çözgü perspektifi uyguladım.

Bunun için önce sentroidleri buluyoruz.

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

Ancak ortaya çıkan sentroidler sıralanmaz. Siparişlerini görmek için aşağıdaki resme bakın:

resim açıklamasını buraya girin

Bu yüzden onları soldan sağa, yukarıdan aşağıya doğru sıralarız.

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

Şimdi siparişlerine bakın:

resim açıklamasını buraya girin

Sonunda dönüşümü uyguluyoruz ve 450x450 boyutunda yeni bir görüntü oluşturuyoruz.

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

Sonuç:

resim açıklamasını buraya girin

Sonuç neredeyse nikie ile aynıdır, ancak kod uzunluğu büyüktür. Belki daha iyi yöntemler var ama o zamana kadar bu işe yarıyor.

Saygılarımızla, ARK.


4
"Uygulamamın yanlış yanıtlar almaktan çökmesini tercih ediyorum." <- Bu% 100'ü de kabul ediyorum
Viktor Sehr

Teşekkürler, Gerçek cevabı Nikie tarafından verilir. Ama bu mathematica, bu yüzden ben sadece OpenCV dönüştürdü. Yani gerçek cevabın yeterli oyu var, sanırım
Abid Rahman K

Ah da soruyu gönderdiğini görmedim :)
Viktor Sehr

Evet. Soru da benim. Mine ve nikie'nin cevabı sadece sonunda farklı. Mathematica'da numpy veya opencv'de olmayan bir çeşit enterpolasyon işlevi var (ama Scipy'de var, ancak Scipy'yi burada kullanmak istemedim)
Abid Rahman K

Hata alıyorum: çıktı [ri * 50: (ri + 1) * 50-1, ci * 50: (ci + 1) * 50-1] = çözgü [ri * 50: (ri + 1) * 50- 1, ci * 50: (CI + 1) * 50-1] .copy TypeError: uzun () bağımsız değişken bir dize veya bir sayı değil, 'builtin_function_or_method' olmalıdır
user898678

6

Rasgele çözgü için bir tür ızgara tabanlı modellemeyi kullanmaya çalışabilirsiniz. Ve sudoku zaten bir ızgara olduğundan, bu çok zor olmamalı.

Böylece her 3x3 alt bölgesinin sınırlarını tespit etmeye ve sonra her bölgeyi ayrı ayrı çarpıtmaya çalışabilirsiniz. Tespit başarılı olursa size daha iyi bir yaklaşım sağlar.


1

Yukarıdaki yöntemin yalnızca sudoku panosu düz durduğunda işe yaradığını eklemek istiyorum, aksi takdirde yükseklik / genişlik (veya tersi) oran testi büyük olasılıkla başarısız olacak ve sudoku kenarlarını tespit edemeyeceksiniz. (Görüntü sınırlarına dik olmayan çizgiler varsa, sobel işlemlerinin (dx ve dy) hala çizgilerin her iki eksene göre de kenarları olacağından hala çalışacağını eklemek istiyorum.)

Düz çizgileri algılayabilmek için, kontur veya konturAre / sınırlamaRectArea, sol üst ve sağ alt noktalar gibi piksel bazlı analiz üzerinde çalışmalısınız ...

Düzenleme: Doğrusal regresyon uygulayarak ve hatayı kontrol ederek bir kontur kümesinin bir çizgi oluşturup oluşturmadığını kontrol etmeyi başardım. Bununla birlikte, çizginin eğimi çok büyük olduğunda (yani> 1000) ya da 0'a çok yakın olduğunda doğrusal regresyon kötü performans gösterdi. Bu nedenle doğrusal regresyon mantıklı olmadan ve benim için işe yaramadan önce yukarıdaki oran testini uygulamak (en çok oylanan cevapta).


1

Sınırsız köşeleri çıkarmak için 0.8 gama değerine sahip gama düzeltmesi uyguladım.

Gama düzeltmesinden önce

Kırmızı daire eksik köşeyi göstermek için çizilir.

Gama düzeltmesinden sonra

Kod:

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)

Bu, bazı köşe noktaları eksikse Abid Rahman'ın cevabına ek olarak.


0

Bunun harika bir gönderi olduğunu ve ARK tarafından harika bir çözüm olduğunu düşündüm; çok iyi ortaya koydu ve açıkladı.

Benzer bir sorun üzerinde çalışıyordum ve her şeyi inşa ettim. Bazı değişiklikler vardı (yani aralıklı xrange, cv2.findContours içinde argümanlar), ancak bu kutunun dışında çalışmalıdır (Python 3.5, Anaconda).

Bu, bazı eksik kodların eklenmesi (yani, noktaların etiketlenmesi) ile yukarıdaki öğelerin bir derlemesidir.

'''

/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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.