সুডোকু স্কোয়ারে উত্তেজনা ত্রুটিগুলি কীভাবে সরিয়ে নেওয়া যায়?


193

আমি একটি মজাদার প্রকল্পটি করছিলাম: ওপেনসিভি (গুগল গগলস ইত্যাদিতে) ব্যবহার করে একটি ইনপুট চিত্র থেকে সুডোকু সমাধান করা। এবং আমি কাজটি শেষ করেছি, তবে শেষে আমি একটি সামান্য সমস্যা পেয়েছি যার জন্য আমি এখানে এসেছি।

আমি ওপেনসিভি 2.3.1 এর পাইথন এপিআই ব্যবহার করে প্রোগ্রামিং করেছি did

নীচে আমি যা করেছি তা হল:

  1. ছবিটি পড়ুন
  2. সূক্ষ্ম সন্ধান করুন
  3. সর্বাধিক অঞ্চল সহ একটি নির্বাচন করুন (এবং কিছুটা বর্গের সমতুল্য)।
  4. কোণার পয়েন্টগুলি সন্ধান করুন।

    যেমন নীচে দেওয়া:

    এখানে চিত্র বর্ণনা লিখুন

    ( এখানে লক্ষ করুন যে সবুজ লাইনটি সুডোকুর সত্য সীমানার সাথে সঠিকভাবে মিলে যায়, যাতে সুডোকু সঠিকভাবে রেপানো যায় next পরবর্তী চিত্র দেখুন)

  5. চিত্রটি নিখুঁত স্কোয়ারে রেপ করুন

    যেমন চিত্র:

    এখানে চিত্র বর্ণনা লিখুন

  6. ওসিআর সম্পাদন করুন (যার জন্য আমি ওপেনসিভি-পাইথনের সিম্পল ডিজিট রিকগনিশন ওসিআর-তে প্রদত্ত পদ্ধতিটি ব্যবহার করেছি )

এবং পদ্ধতিটি ভালভাবে কাজ করেছিল।

সমস্যা:

পরীক্ষা করে দেখুন এই ছবিটি।

এই চিত্রটিতে 4 পদক্ষেপটি সম্পাদন করা নীচের ফলাফল দেয়:

এখানে চিত্র বর্ণনা লিখুন

টানা লাল রেখাটি মূল কনট্যুর যা সুডোকু সীমার সত্যিকারের রূপরেখা।

আঁকা সবুজ রেখাটি আনুমানিক কনট্যুর যা রেপযুক্ত চিত্রটির রূপরেখা হবে।

অবশ্যই, সুডোকুর শীর্ষ প্রান্তে সবুজ রেখা এবং লাল রেখার মধ্যে পার্থক্য রয়েছে। সুতরাং ওয়ার্পিংয়ের সময়, আমি সুডোকুর আসল সীমানা পাচ্ছি না।

আমার প্রশ্ন :

আমি কীভাবে সুডোকুর সঠিক সীমানায় ইমেজটি গুটিয়ে রাখতে পারি, যেমন লাল রেখা বা আমি কীভাবে লাল রেখা এবং সবুজ রেখার পার্থক্যটি সরিয়ে ফেলতে পারি? ওপেনসিভিতে এর কোনও পদ্ধতি আছে?


1
আপনি কোণার পয়েন্টগুলির উপর ভিত্তি করে আপনার সনাক্তকরণটি করছেন যা লাল এবং সবুজ রেখাগুলিতে সম্মত। আমি ওপেনসিভি জানি না, তবে সম্ভবত আপনি সেই কোণার পয়েন্ট এবং তার ভিত্তিতে স্ট্র্যাপের মধ্যবর্তী লাইনগুলি সনাক্ত করতে চাইবেন।
ডগল

সম্ভবত কোণার পয়েন্টগুলিকে সংযুক্ত রেখাগুলি জোর করে চিত্রের ভারী কালো পিক্সেলের সাথে মিলিত হতে পারে। এটি হল, সবুজ রেখাগুলি কেবল কোণার পয়েন্টগুলির মধ্যে একটি সরল রেখা খুঁজে না দেওয়ার পরিবর্তে ভারী কালো পিক্সেল অতিক্রম করতে বাধ্য করুন। আমার ধারণা, এটি আপনার সমস্যাটিকে আরও জটিল করে তুলবে এবং আমি এমন কোনও ওপেনসিভি বিল্ট-ইনগুলি সম্পর্কে জানি না যা আপনার জন্য তাত্ক্ষণিকভাবে কার্যকর হবে।
ely

@ ডুগল: আমি মনে করি সবুজ রেখাটি লাল রেখার প্রায় সরলরেখা। সুতরাং এটি যে কোণার পয়েন্ট মধ্যে লাইন। আমি যখন সবুজ রেখা অনুযায়ী মোড়ান, আমি রেখাচিত চিত্রের শীর্ষে বাঁকা লাল রেখা পাই। (আমি আশা করি আপনি বুঝতে পেরেছেন, আমার ব্যাখ্যাটি কিছুটা খারাপ
লাগছে

@ ইএমএস: আমি মনে করি লাল রেখা আঁকা ঠিক সুডোকুর সীমানায়। তবে সমস্যাটি হ'ল, সুডোকুর সীমানায় ঠিক কীভাবে চিত্রটি গুটিয়ে নেওয়া যায়। (আমি বোঝাতে চাইছি, সমস্যাটি ওয়ারপিংয়ের সাথে, অর্থাত্ সেই বাঁকানো সীমানাকে একটি যথাযথ স্কোয়ারে রূপান্তর করা, যেমনটি আমি দ্বিতীয় চিত্রে দেখিয়েছি)
আবিদ রহমান কে

উত্তর:


252

আমার একটি সমাধান রয়েছে যা কার্যকর হয় তবে আপনাকে এটি ওপেনসিভিতে নিজেই অনুবাদ করতে হবে। এটি গাণিতিকায় লেখা হয়েছে।

প্রথম পদক্ষেপটি হ'ল ক্লোজিং অপারেশনের ফলাফলের সাথে প্রতিটি পিক্সেলকে ভাগ করে চিত্রের উজ্জ্বলতা সামঞ্জস্য করা:

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]

এখানে চিত্র বর্ণনা লিখুন

এখন, আমি দুটি পৃথক চিত্রের উল্লম্ব এবং অনুভূমিক রেখাগুলি খুঁজে পেতে একটি দ্বিতীয় অর্ডার ডেরিভেটিভ ফিল্টার ব্যবহার করতে পারি:

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
হে ভগবান !!!!!!!!! এটা ছিল দুর্দান্ত। এটি সত্যিই দুর্দান্ত। আমি এটি ওপেনসিভিতে করার চেষ্টা করব। আশা করি আপনি আমাকে কিছু নির্দিষ্ট ক্রিয়াকলাপ এবং পরিভাষা সম্পর্কিত বিশদ সম্পর্কে সহায়তা করবেন ... ধন্যবাদ
আবিদ রহমান কে

@ ইয়র্কিয়াজ: আমি ওপেনসিভি বিশেষজ্ঞ নই, তবে নিশ্চিত হয়ে থাকলে আমি সাহায্য করব।
নিকি

আপনি কি "ক্লোজিং" ফাংশনটির জন্য ব্যবহৃত তা ব্যাখ্যা করতে পারেন? আমার অর্থ হ'ল পটভূমিতে কী ঘটে? ডকুমেন্টেশনে, এটি বলে যে বন্ধ হওয়া লবণ এবং মরিচের গোলমাল দূর করে? লো পাস ফিল্টার বন্ধ হচ্ছে?
আবিদ রহমান কে

2
আশ্চর্য উত্তর! চিত্রটির উজ্জ্বলতা স্বাভাবিক করার জন্য আপনি ক্লোজিং দিয়ে ভাগ করার ধারণাটি কোথায় পেয়েছেন? আমি এই পদ্ধতির গতি উন্নত করার চেষ্টা করছি, যেহেতু ভাসমান-পয়েন্ট বিভাগটি মোবাইল ফোনে যন্ত্রণাদায়কভাবে ধীর হয় is আপনার কি কোন পরামর্শ আছে? @ আবিদরাহমানকে
1 ''

1
@ 1 *: আমি মনে করি একে "সাদা চিত্র সমন্বয়" বলা হয়। আমি এটি সম্পর্কে কোথায় পড়েছি তা আমাকে জিজ্ঞাসা করবেন না, এটি একটি স্ট্যান্ডার্ড ইমেজ প্রসেসিং সরঞ্জাম। ধারণার পিছনে মডেলটি সহজ: একটি (ল্যাম্বার্টিয়ান) পৃষ্ঠ থেকে প্রতিফলিত আলোর পরিমাণটি ঠিক একই পৃষ্ঠায় একটি সাদা দেহের আলোক পরিমাণের তুলনায় পৃষ্ঠের উজ্জ্বলতার দ্বিগুণ। একই স্থানে একটি সাদা দেহের আপাত উজ্জ্বলতা অনুমান করুন, এর দ্বারা প্রকৃত উজ্জ্বলতা ভাগ করুন এবং আপনি পৃষ্ঠের উজ্জ্বলতা পাবেন।
নিকি

209

নিকির উত্তরটি আমার সমস্যার সমাধান করেছিল, তবে তার উত্তরটি ছিল ম্যাথামেটিকায়। সুতরাং আমি ভেবেছিলাম আমার এটির ওপেনসিভি অভিযোজনটি দেওয়া উচিত। তবে প্রয়োগের পরে আমি দেখতে পেলাম যে ওপেনসিভি কোড নিকির গণিতের কোডের চেয়ে অনেক বড়। এবং এছাড়াও, আমি ওপেনসিভিতে নিকির দ্বারা করা ইন্টারপোলেশন পদ্ধতিটি খুঁজে পেলাম না (যদিও এটি স্কিপি ব্যবহার করে করা যেতে পারে, আমি সময় আসার পরে তা জানাব।)

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)

ফলাফল :

সমাপ্তির ফলাফল

২. সুডোকু স্কয়ার সন্ধান করা এবং মুখোশ চিত্র তৈরি করা

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

ফলাফল :

এখানে চিত্র বর্ণনা লিখুন

অনুভূমিক রেখা সন্ধান করা

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)

ফলাফল :

এখানে চিত্র বর্ণনা লিখুন

The. ত্রুটিগুলি সংশোধন করা

এখানে, নিকি একধরনের ইন্টারপোলেশন করে, যার সম্পর্কে আমার খুব বেশি জ্ঞান নেই। এবং আমি এই ওপেনসিভির জন্য কোনও সম্পর্কিত ফাংশন খুঁজে পাইনি। (এটি সেখানে থাকতে পারে, আমি জানি না)।

এই এসওএফটি পরীক্ষা করে দেখুন যা সায়পাই ব্যবহার করে এটি কীভাবে করবেন তা ব্যাখ্যা করে যা আমি ব্যবহার করতে চাই না: ওপেনসিভিতে চিত্র রূপান্তর

সুতরাং, আমি এখানে প্রতিটি উপ-বর্গক্ষেত্রের 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%
ভিক্টর সেহর

ধন্যবাদ, এর আসল উত্তরটি নিকি দিয়েছেন। তবে এটি ছিল গণিতে, তাই আমি কেবল ওপেনসিভিতে রূপান্তর করেছি। সুতরাং আসল উত্তরটি যথেষ্ট উপকার পেয়েছে, আমি মনে করি
আবিদ রহমান কে

আহ আপনার প্রশ্নটি পোস্ট করতে দেখেনি :)
ভিক্টর সেহর

হ্যাঁ। প্রশ্নও আমার। আমার এবং নিকির উত্তরগুলি কেবলমাত্র শেষে রয়েছে। তিনি গণিতের একধরণের ইন্টিপোলেশন ফাংশন পেয়েছেন যা নোংরা বা ওপেনসিভিতে নেই (তবে এটি স্কিপিতে রয়েছে তবে আমি এখানে স্কিপি ব্যবহার করতে চাইনি)
আবিদ রহমান কে

আমি ত্রুটি পাচ্ছি: আউটপুট [রি * 50: (রি + 1) * 50-1, সিআই * 50: (সিআই + 1) * 50-1] = ওয়ার্প [রি * 50: (রি + 1) * 50- 1, সিআই * 50: (সিআই + 1) * 50-1] কপি প্রকারের ত্রুটি: দীর্ঘ () যুক্তিটি অবশ্যই একটি স্ট্রিং বা একটি সংখ্যা হতে হবে, 'বিল্টিন_ফাংশন_অর_মোথড' নয়
ব্যবহারকারী 898678

6

আপনি নিজের ইচ্ছামতো ওয়ারপিংয়ের গ্রিড ভিত্তিক মডেলিং ব্যবহার করার চেষ্টা করতে পারেন। সুডোকু যেহেতু ইতিমধ্যে একটি গ্রিড, তাই খুব বেশি শক্ত হওয়া উচিত নয়।

সুতরাং আপনি প্রতিটি 3x3 উপমঞ্চের সীমানা সনাক্ত করার চেষ্টা করতে পারেন এবং তারপরে প্রতিটি অঞ্চলকে পৃথকভাবে মোড়ক দিতে পারেন। সনাক্তকরণটি সফল হলে এটি আপনাকে আরও ভাল অনুমান করতে পারে।


1

আমি যুক্ত করতে চাই যে উপরের পদ্ধতিটি কেবল তখনই কাজ করে যখন সুডোকু বোর্ড সোজা হয়ে যায়, অন্যথায় উচ্চতা / প্রস্থ (বা বিপরীতে) অনুপাত পরীক্ষাটি সম্ভবত ব্যর্থ হবে এবং আপনি সুডোকুর কিনারা সনাক্ত করতে সক্ষম হবেন না। (আমি আরও যোগ করতে চাই যে চিত্রগুলির সীমানায় লাইনগুলি লম্ব না হলে সোবল অপারেশনগুলি (ডেক্স এবং ডাই) এখনও কাজ করবে কারণ উভয় অক্ষের সাথে লাইনগুলি এখনও প্রান্তগুলিতে থাকবে have)

সরল রেখাগুলি সনাক্ত করতে সক্ষম হতে আপনার কনট্যুর বা পিক্সেল-ভিত্তিক বিশ্লেষণ যেমন কনট্যুরআরিয়া / বাউন্ডিংআরেক্টিয়ারিয়া, উপরের বাম এবং নীচের ডান পয়েন্টগুলিতে কাজ করা উচিত ...

সম্পাদনা: লিনিয়ার রিগ্রেশন প্রয়োগ করে এবং ত্রুটিটি যাচাই করে আমি সংক্ষিপ্তসারগুলির একটি সেট একটি লাইন তৈরি করে কিনা তা পরীক্ষা করতে সক্ষম হয়েছি। তবে লাইনটির slালু খুব বড় (যেমন> 1000) খুব কম হলে লাইনারি রিগ্রেশন খুব খারাপভাবে সঞ্চালিত হয় বা এটি 0 এর খুব কাছাকাছি থাকে সুতরাং লিনিয়ার রিগ্রেশন যুক্তির আগে উপরের অনুপাতের পরীক্ষার (সর্বাধিক উত্তরের উত্তরে) প্রয়োগ করা আমার পক্ষে কাজ করে for


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

আমি ভেবেছিলাম এটি একটি দুর্দান্ত পোস্ট এবং এটিআরके দ্বারা একটি দুর্দান্ত সমাধান; খুব ভাল স্থাপন এবং ব্যাখ্যা।

আমি একই ধরণের সমস্যা নিয়ে কাজ করছি এবং পুরো জিনিসটি তৈরি করেছি। কিছু পরিবর্তন হয়েছে (অর্থাত্ সিভি 2.ফাইন্ডকন্টরস-এ রেঞ্জের এক্সরেঞ্জ, আর্গুমেন্ট), তবে এটি বাক্সের বাইরে কাজ করা উচিত (পাইথন 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()
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.