सुडोकू वर्ग में उत्तलता दोष को कैसे दूर करें?


193

मैं एक मजेदार प्रोजेक्ट कर रहा था: ओपनसीवी (जैसे गूगल गॉगल्स आदि) का उपयोग करके इनपुट छवि से एक सुडोकू को हल करना। और मैंने कार्य पूरा कर लिया है, लेकिन अंत में मुझे थोड़ी समस्या हुई जिसके लिए मैं यहां आया था।

मैंने ओपनसीवी 2.3.1 के पायथन एपीआई का उपयोग करके प्रोग्रामिंग की।

नीचे मैंने क्या किया है:

  1. छवि पढ़ें
  2. आकृति ज्ञात कीजिए
  3. अधिकतम क्षेत्र के साथ एक का चयन करें, (और कुछ हद तक वर्ग के बराबर)।
  4. कोने के बिंदु खोजें।

    नीचे दिया गया उदाहरण:

    यहां छवि विवरण दर्ज करें

    ( यहां ध्यान दें कि ग्रीन लाइन सही ढंग से सुडोकू की वास्तविक सीमा से मेल खाती है, इसलिए सुडोकू को सही ढंग से विकृत किया जा सकता है

  5. एक पूर्ण वर्ग के लिए छवि ताना

    उदाहरण के लिए चित्र:

    यहां छवि विवरण दर्ज करें

  6. OCR प्रदर्शन करें (जिसके लिए मैंने OpenCV-Python में साधारण अंक मान्यता OCR में दी गई विधि का उपयोग किया है )

और विधि अच्छी तरह से काम किया।

संकट:

की जाँच करें इस छवि।

इस छवि पर चरण 4 का प्रदर्शन करने से नीचे परिणाम मिलता है:

यहां छवि विवरण दर्ज करें

लाल रेखा खींची गई मूल समोच्च है जो सुडोकू सीमा की सही रूपरेखा है।

खींची गई हरे रंग की रेखा समोच्च समोच्च है जो विकृत छवि की रूपरेखा होगी।

निश्चित रूप से, सुडोकू के शीर्ष पर हरे रंग की रेखा और लाल रेखा के बीच अंतर है। इसलिए युद्ध करते समय, मुझे सुडोकू की मूल सीमा नहीं मिल रही है।

मेरा प्रश्न :

मैं सुडोकू की सही सीमा, यानी लाल रेखा पर छवि को कैसे ताना जा सकता हूं या मैं लाल रेखा और हरी रेखा के बीच के अंतर को कैसे दूर कर सकता हूं? क्या OpenCV में इसके लिए कोई विधि है?


1
आप कोने के बिंदुओं के आधार पर अपनी पहचान कर रहे हैं, जिस पर लाल और हरी रेखाएं सहमत हैं। मैं OpenCV नहीं जानता, लेकिन संभवतः आप उन कोने बिंदुओं और उस पर आधारित ताना के बीच की रेखाओं का पता लगाना चाहते हैं।
डगल १

शायद उन रेखाओं को मजबूर करें जो कोने के बिंदुओं को जोड़ने के लिए छवि में भारी काले पिक्सेल के साथ मेल खाती हैं। यही कारण है कि, हरे रंग की लाइनों को कोने के बिंदुओं के बीच एक सीधी रेखा को खोजने देने के बजाय, उन्हें भारी काले पिक्सेल को पार करने के लिए मजबूर करें। यह आपकी समस्या को और अधिक कठिन बना देगा, मुझे लगता है, और मुझे किसी भी OpenCV बिल्ट-इन का पता नहीं है जो आपके लिए तुरंत उपयोगी होगा।
ईवी

@ डगल: मुझे लगता है कि खींची गई हरे रंग की रेखा लाल रेखा की अनुमानित सीधी रेखा है। तो यह उन कोने बिंदुओं के बीच की रेखा है। जब मैं हरे रंग की रेखा के अनुसार ताना जाता हूं, तो मैं विकृत छवि के शीर्ष पर घुमावदार लाल रेखा प्राप्त करता हूं। (मुझे आशा है कि आप समझ गए होंगे, मेरा स्पष्टीकरण थोड़ा बुरा लगता है)
आबिद रहमान के

@ ईएमएस: मुझे लगता है कि लाल रेखा खींची बिल्कुल सुडोकू की सीमा पर है। लेकिन समस्या यह है कि, सुडोकू की सीमा पर छवि को कैसे ताना जाए। (मेरा मतलब है, समस्या युद्ध के साथ है, यानी उन घुमावदार सीमा को एक सटीक वर्ग में बदलना, जैसा कि मैंने दूसरी छवि में दिखाया है)
आबिद रहमान के

जवाबों:


252

मेरे पास एक समाधान है जो काम करता है, लेकिन आपको इसे खुद OpenCV में अनुवाद करना होगा। यह मैथेमेटिका में लिखा गया है।

पहला कदम एक समापन ऑपरेशन के परिणाम के साथ प्रत्येक पिक्सेल को विभाजित करके, छवि में चमक को समायोजित करना है:

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

यहां छवि विवरण दर्ज करें

अगला कदम सुडोकू क्षेत्र को ढूंढना है, इसलिए मैं पृष्ठभूमि को अनदेखा कर सकता हूं। उसके लिए, मैं जुड़े घटक विश्लेषण का उपयोग करता हूं, और उस घटक का चयन करता हूं जिसे सबसे बड़ा उत्तल क्षेत्र मिला है:

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

यहां छवि विवरण दर्ज करें

इस छवि को भरने से, मुझे सुडोकू ग्रिड के लिए एक मुखौटा मिलता है:

mask = FillingTransform[largestComponent]

यहां छवि विवरण दर्ज करें

अब, मैं दो अलग-अलग छवियों में ऊर्ध्वाधर और क्षैतिज रेखाओं को खोजने के लिए 2 क्रम के व्युत्पन्न फिल्टर का उपयोग कर सकता हूं:

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

यहां छवि विवरण दर्ज करें

मैं इन चित्रों से ग्रिड लाइनों को निकालने के लिए फिर से जुड़े घटक विश्लेषण का उपयोग करता हूं। ग्रिड लाइनें अंकों की तुलना में अधिक लंबी होती हैं, इसलिए मैं केवल ग्रिड लाइनों से जुड़े घटकों का चयन करने के लिए कैलिपर की लंबाई का उपयोग कर सकता हूं। स्थिति के अनुसार उन्हें क्रमबद्ध करते हुए, मुझे छवि में ऊर्ध्वाधर / क्षैतिज ग्रिड लाइनों में से प्रत्येक के लिए 2x10 मास्क छवियां मिलती हैं:

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

यहां छवि विवरण दर्ज करें

अगले मैं ऊर्ध्वाधर / क्षैतिज ग्रिड लाइनों की प्रत्येक जोड़ी लेता हूं, उन्हें पतला करता हूं, पिक्सेल-बाय-पिक्सेल चौराहे की गणना करता है, और परिणाम के केंद्र की गणना करता है। ये बिंदु ग्रिड लाइन चौराहों हैं:

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

यहां छवि विवरण दर्ज करें

अंतिम चरण इन बिंदुओं के माध्यम से एक्स / वाई मैपिंग के लिए दो प्रक्षेप कार्यों को परिभाषित करना है, और इन कार्यों का उपयोग करके छवि को बदलना है:

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]

यहां छवि विवरण दर्ज करें

सभी ऑपरेशन बेसिक इमेज प्रोसेसिंग फंक्शन हैं, इसलिए ओपनसीवी में भी यह संभव होना चाहिए। तख़्त-आधारित छवि परिवर्तन कठिन हो सकता है, लेकिन मुझे नहीं लगता कि आपको वास्तव में इसकी आवश्यकता है। संभवतः प्रत्येक व्यक्तिगत सेल पर आपके द्वारा उपयोग किए जाने वाले परिप्रेक्ष्य परिवर्तन का उपयोग करके पर्याप्त अच्छे परिणाम मिलेंगे।


3
हे भगवान !!!!!!!!! वह अद्भुत था। यह वास्तव में वास्तव में बहुत अच्छा है। मैं इसे OpenCV में बनाने की कोशिश करूंगा। आशा है कि आप मुझे कुछ कार्यों और शब्दावली के विवरण के साथ मदद करेंगे ... धन्यवाद।
आबिद रहमान के

@arkiaz: मैं एक OpenCV विशेषज्ञ नहीं हूं, लेकिन अगर मुझे यकीन है कि मैं मदद करूंगा।
निकी

क्या आप बता सकते हैं कि "क्लोजिंग" फंक्शन का उपयोग किस लिए किया जाता है? मेरा मतलब है कि पृष्ठभूमि में क्या होता है? प्रलेखन में, यह कहता है कि बंद करने से नमक और काली मिर्च का शोर दूर हो जाता है? कम पास फिल्टर बंद कर रहा है?
आबिद रहमान के

2
अद्भुत जवाब! छवि चमक को सामान्य करने के लिए आपको समापन द्वारा विभाजित करने का विचार कहां से मिला? मैं इस पद्धति की गति में सुधार करने की कोशिश कर रहा हूं, क्योंकि फ्लोटिंग-पॉइंट डिवीजन मोबाइल फोन पर दर्द से धीमा है। क्या तुम्हारे पास कोई सुझाव है? @AbidRahmanK
1 ''

1
@ 1 *: मुझे लगता है कि इसे "सफेद छवि समायोजन" कहा जाता है। मुझसे यह न पूछें कि मैंने इसके बारे में कहां पढ़ा है, यह एक मानक इमेज प्रोसेसिंग टूल है। विचार के पीछे का मॉडल सरल है: एक (लैम्बर्टियन) सतह से परावर्तित प्रकाश की मात्रा, सतह की चमक का एक ही स्थिति में सफेद शरीर के प्रकाश की मात्रा को दर्शाती है। एक ही स्थिति में एक सफेद शरीर की स्पष्ट चमक का अनुमान लगाएं, वास्तविक चमक को उसी से विभाजित करें, और आपको सतह की चमक मिलती है।
निकी

209

निकी के जवाब से मेरी समस्या हल हो गई, लेकिन उनका जवाब गणितज्ञ में था। इसलिए मैंने सोचा कि मुझे इसका OpenCV रूपांतरण यहाँ देना चाहिए। लेकिन लागू करने के बाद मैं देख सकता था कि ओपनसीवी कोड निक्की के गणित कोड से बहुत बड़ा है। और इसके अलावा, मैं ओपनसीवी में निक्की द्वारा किया गया इंटरपोलेशन विधि नहीं ढूंढ सका (हालांकि यह स्कैपी का उपयोग करके किया जा सकता है, मैं इसे समय आने पर बताऊंगा।)

1. छवि प्रीप्रोसेसिंग (संचालन बंद)

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)

परिणाम :

समापन का परिणाम

2. सुडोकू स्क्वायर ढूंढना और मास्क इमेज बनाना

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)

परिणाम :

यहां छवि विवरण दर्ज करें

3. लंबवत रेखाओं का पता लगाना

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

परिणाम :

यहां छवि विवरण दर्ज करें

4. क्षैतिज रेखाएँ ढूँढना

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

परिणाम :

यहां छवि विवरण दर्ज करें

बेशक, यह इतना अच्छा नहीं है।

5. ग्रिड पॉइंट खोजना

res = cv2.bitwise_and(closex,closey)

परिणाम :

यहां छवि विवरण दर्ज करें

6. दोषों को ठीक करना

यहाँ, nikie किसी प्रकार का प्रक्षेप करता है, जिसके बारे में मुझे अधिक ज्ञान नहीं है। और मुझे इस OpenCV के लिए कोई भी संबंधित कार्य नहीं मिला। (यह हो सकता है, मुझे पता नहीं है)।

इस SOF को देखें जो बताता है कि SciPy का उपयोग करके यह कैसे किया जाए, जिसका मैं उपयोग नहीं करना चाहता: OpenCV में छवि परिवर्तन

इसलिए, यहां मैंने प्रत्येक उप-वर्ग के 4 कोनों को लिया और प्रत्येक के लिए ताना परिप्रेक्ष्य लागू किया।

उसके लिए, पहले हम केन्द्रक पाते हैं।

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

लेकिन परिणामस्वरूप केन्द्रक को छांटा नहीं जाएगा। उनके आदेश को देखने के लिए नीचे दी गई छवि देखें:

यहां छवि विवरण दर्ज करें

इसलिए हम उन्हें बाएं से दाएं, ऊपर से नीचे तक क्रमबद्ध करते हैं।

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

अब उनके आदेश के नीचे देखें:

यहां छवि विवरण दर्ज करें

अंत में हम परिवर्तन लागू करते हैं और 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()

परिणाम :

यहां छवि विवरण दर्ज करें

परिणाम लगभग निक्की के समान है, लेकिन कोड की लंबाई बड़ी है। हो सकता है, वहां बेहतर तरीके उपलब्ध हों, लेकिन तब तक, यह ठीक है।

सादर ए.आर.के।


4
"मुझे गलत उत्तर मिलने की तुलना में मेरा आवेदन दुर्घटनाग्रस्त होना पसंद है।" <- मैं इस 100% पर सहमत हूं
विक्टर सेहर

धन्यवाद, इसका असली जवाब निक्की ने दिया है। लेकिन यह गणित में था, इसलिए मैंने इसे OpenCV में बदल दिया। तो असली जवाब में पर्याप्त उत्थान हुआ है, मुझे लगता है
आबिद रहमान के

आह को देखने के फ्लॉप आप भी सवाल :) पोस्ट
विक्टर Sehr

हाँ। प्रश्न भी मेरा है। मेरा और निक्की का जवाब केवल अंत में अलग हैं। उन्हें गणित में कुछ प्रकार के अंतर्ग्रहण समारोह मिले हैं, जो सुपी या ऑप्केनव में नहीं है (लेकिन यह स्किपी में है, लेकिन मैं यहाँ स्कैपी का उपयोग नहीं करना चाहता था)
आबिद रहमान के

मुझे त्रुटि मिल रही है: आउटपुट [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 प्रकार: लंबा () तर्क एक स्ट्रिंग या संख्या होना चाहिए, न कि 'buildin_function_or_method'
user898674

6

आप किसी तरह के ग्रिड आधारित मॉडलिंग का उपयोग करने की कोशिश कर सकते हैं जो आपको मनमाने ढंग से ताना-बाना दे। और चूंकि सुडोकू पहले से ही एक ग्रिड है, इसलिए इसे बहुत मुश्किल नहीं होना चाहिए।

इसलिए आप प्रत्येक 3x3 उप-सीमा की सीमाओं का पता लगाने की कोशिश कर सकते हैं और फिर प्रत्येक क्षेत्र को व्यक्तिगत रूप से ताना दे सकते हैं। यदि पहचान सफल होती है तो यह आपको एक बेहतर सन्निकटन देगा।


1

मैं यह जोड़ना चाहता हूं कि उपरोक्त विधि तभी काम करती है जब सुडोकू बोर्ड सीधा खड़ा होता है, अन्यथा ऊंचाई / चौड़ाई (या इसके विपरीत) अनुपात परीक्षण सबसे अधिक विफल हो जाएगा और आप सुडोकू के किनारों का पता लगाने में सक्षम नहीं होंगे। (मैं यह भी जोड़ना चाहता हूं कि यदि रेखाएं जो छवि सीमाओं के लंबवत नहीं हैं, तो सोबेल ऑपरेशन (डीएक्स और डाई) अभी भी काम करेंगे क्योंकि लाइनों में अभी भी दोनों अक्षों के संबंध में किनारे होंगे।)

सीधी रेखाओं का पता लगाने में सक्षम होने के लिए आपको समोच्च या पिक्सेल-वार विश्लेषण पर काम करना चाहिए जैसे कि समोआरे / बाउंड्रीरेक्टआरे, टॉप लेफ्ट और बॉटम राइट पॉइंट ...

संपादित करें: मैं यह जांचने में कामयाब रहा कि क्या समोच्चों का एक सेट लीनियर रिग्रेशन लागू करने और त्रुटि की जांच करके लाइन बनाता है या नहीं। हालाँकि रेखीय प्रतिगमन खराब तरीके से किया जाता है जब रेखा का ढलान बहुत बड़ा होता है (अर्थात> 1000) या यह 0. के बहुत करीब होता है। इसलिए रेखीय प्रतिगमन तार्किक होने से पहले ऊपर (सबसे उत्तोलन उत्तर में) अनुपात अनुपात को लागू करना मेरे लिए काम किया है।


1

अवांछित कोनों को हटाने के लिए मैंने 0.8 के गामा मूल्य के साथ गामा सुधार लागू किया।

गामा सुधार से पहले

लापता कोने को दिखाने के लिए लाल घेरा बनाया गया है।

गामा सुधार के बाद

कोड है:

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)

यह आबिद रहमान के जवाब के अलावा है अगर कुछ कोने अंक गायब हैं।


0

मैंने सोचा कि यह एक महान पद था, और एआरके द्वारा एक महान समाधान; बहुत अच्छी तरह से समझाया और समझाया।

मैं इसी तरह की समस्या पर काम कर रहा था, और पूरी चीज का निर्माण किया। कुछ परिवर्तन हुए (यानी रेंज में व्यवस्थित करने के लिए, cv2.findContours में तर्क), लेकिन यह बॉक्स से बाहर काम करना चाहिए (पायथन 3.5, एनाकोंडा)।

यह ऊपर दिए गए तत्वों का एक संकलन है, जिसमें कुछ लापता कोड जोड़े गए हैं (यानी, अंकों का लेबलिंग)।

'''

/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()
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.