วิธีลบข้อบกพร่องที่นูนออกมาในจตุรัส Sudoku?


193

ฉันกำลังทำโครงการสนุก: แก้ปัญหา Sudoku จากภาพอินพุตโดยใช้ OpenCV (เช่นใน Google goggles และอื่น ๆ ) และฉันก็ทำงานให้เสร็จ แต่ในที่สุดฉันก็พบปัญหาเล็กน้อยซึ่งมาที่นี่

ฉันเขียนโปรแกรมโดยใช้ Python API ของ OpenCV 2.3.1

ด้านล่างเป็นสิ่งที่ฉันทำ:

  1. อ่านภาพ
  2. ค้นหารูปทรง
  3. เลือกรายการที่มีพื้นที่สูงสุด (และยังเท่ากับสี่เหลี่ยม)
  4. ค้นหาจุดมุม

    เช่นรับด้านล่าง:

    ป้อนคำอธิบายรูปภาพที่นี่

    ( โปรดสังเกตที่นี่ว่าเส้นสีเขียวนั้นสอดคล้องกับขอบเขตที่แท้จริงของ Sudoku อย่างถูกต้องดังนั้น Sudoku จึงสามารถวาร์ปได้อย่างถูกต้องตรวจสอบภาพถัดไป)

  5. บิดภาพเป็นสี่เหลี่ยมจัตุรัสที่สมบูรณ์แบบ

    เช่นรูปภาพ:

    ป้อนคำอธิบายรูปภาพที่นี่

  6. ดำเนินการ OCR (ซึ่งฉันใช้วิธีที่ฉันให้ไว้ในSimple Digit OCR Recognition ใน OpenCV-Python )

และวิธีการทำงานได้ดี

ปัญหา:

ลองดูภาพนี้

การดำเนินการตามขั้นตอนที่ 4 ในภาพนี้จะให้ผลลัพธ์ด้านล่าง:

ป้อนคำอธิบายรูปภาพที่นี่

เส้นสีแดงที่ลากเป็นเส้นชั้นความสูงดั้งเดิมซึ่งเป็นโครงร่างที่แท้จริงของขอบเขตซูโดกุ

วาดเส้นสีเขียวเป็นรูปร่างโดยประมาณซึ่งจะเป็นโครงร่างของภาพที่บิดเบี้ยว

ซึ่งแน่นอนว่ามีความแตกต่างระหว่างเส้นสีเขียวและเส้นสีแดงที่ขอบด้านบนของซูโดกุ ดังนั้นในขณะที่แปรปรวนฉันไม่ได้รับขอบเขตดั้งเดิมของ Sudoku

คำถามของฉัน :

ฉันจะบิดภาพบนขอบเขตที่ถูกต้องของ Sudoku ได้อย่างไรเช่นเส้นสีแดงหรือฉันจะลบความแตกต่างระหว่างเส้นสีแดงและเส้นสีเขียวได้อย่างไร มีวิธีการนี้ใน OpenCV หรือไม่?


1
คุณกำลังทำการตรวจจับของคุณตามจุดที่เกิดขึ้นซึ่งเส้นสีแดงและสีเขียวเห็นด้วย ฉันไม่รู้ OpenCV แต่คุณน่าจะต้องการตรวจจับเส้นระหว่างจุดมุมกับเส้นโค้งตามนั้น
Dougal

อาจบังคับให้เส้นที่เชื่อมต่อจุดมุมตรงกับพิกเซลสีดำขนาดใหญ่ในภาพ นั่นคือแทนที่จะปล่อยให้เส้นสีเขียวเพียงแค่หาเส้นตรงระหว่างจุดมุม, บังคับให้พวกเขาผ่านพิกเซลสีดำหนัก นี่จะทำให้ปัญหาของคุณยากขึ้นอย่างมากฉันคิดว่าและฉันไม่ทราบว่ามีบิวด์อิน OpenCV ใด ๆ ที่จะเป็นประโยชน์สำหรับคุณทันที
ely

@ Dougal: ฉันคิดว่าการลากเส้นสีเขียวเป็นเส้นตรงโดยประมาณของเส้นสีแดง ดังนั้นมันคือเส้นแบ่งระหว่างจุดมุมเหล่านั้น เมื่อฉันแปรปรวนตามเส้นสีเขียวฉันจะได้เส้นสีแดงโค้งที่ส่วนบนของภาพที่บิดเบี้ยว (ฉันหวังว่าคุณจะเข้าใจคำอธิบายของฉันดูเหมือนจะแย่ไปหน่อย)
Abid Rahman K

@ EMS: ฉันคิดว่าการลากเส้นสีแดงนั้นอยู่ที่ชายแดนของ Sudoku แต่ปัญหาคือวิธีการบิดภาพให้ตรงกับขอบของซูโดกุ (ฉันหมายถึงปัญหาเกี่ยวกับการแปรปรวนคือการแปลงเส้นโค้งเหล่านั้นเป็นสี่เหลี่ยมจัตุรัสที่แน่นอนตามที่ฉันได้แสดงในภาพที่สอง)
Abid Rahman K

คำตอบ:


252

ฉันมีวิธีแก้ปัญหาที่ใช้งานได้ แต่คุณจะต้องแปลเป็น OpenCV ด้วยตัวคุณเอง มันเขียนใน Mathematica

ขั้นตอนแรกคือการปรับความสว่างในภาพโดยการหารแต่ละพิกเซลด้วยผลลัพธ์ของการดำเนินการปิด:

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

ป้อนคำอธิบายรูปภาพที่นี่

ขั้นตอนสุดท้ายคือการกำหนดฟังก์ชั่นการแก้ไขสองฟังก์ชันสำหรับการทำแผนที่ X / Y ผ่านจุดเหล่านี้และแปลงภาพโดยใช้ฟังก์ชั่นเหล่านี้:

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]

ป้อนคำอธิบายรูปภาพที่นี่

การดำเนินการทั้งหมดเป็นฟังก์ชั่นการประมวลผลภาพขั้นพื้นฐานดังนั้นสิ่งนี้ควรเป็นไปได้ใน OpenCV ด้วย การแปลงภาพที่เป็นเส้นโค้งอาจทำได้ยากขึ้น แต่ฉันไม่คิดว่าคุณต้องการมันจริงๆ อาจใช้การแปลงเปอร์สเปคทีฟที่คุณใช้ในแต่ละเซลล์ตอนนี้จะให้ผลลัพธ์ที่ดีพอ


3
โอ้พระเจ้า !!!!!!!!! นั่นยอดเยี่ยมมาก มันยอดเยี่ยมจริงๆ ฉันจะพยายามทำมันใน OpenCV หวังว่าคุณจะช่วยฉันด้วยรายละเอียดเกี่ยวกับฟังก์ชั่นและคำศัพท์บางอย่าง ... ขอบคุณ
Abid Rahman K

@arkiaz: ฉันไม่ใช่ผู้เชี่ยวชาญ OpenCV แต่ฉันจะช่วยถ้าฉันสามารถทำได้แน่นอน
Niki

คุณช่วยอธิบายฟังก์ชั่น "ปิด" ที่ใช้เพื่ออะไรได้บ้าง? สิ่งที่ฉันหมายถึงสิ่งที่เกิดขึ้นในพื้นหลัง? ในเอกสารประกอบกล่าวว่าการปิดเสียงจะขจัดเสียงรบกวนของเกลือและพริกไทย กำลังปิดตัวกรอง Low Pass หรือไม่
Abid Rahman K

2
คำตอบที่น่าทึ่ง! คุณได้ความคิดในการหารด้วยการปิดเพื่อปรับความสว่างของภาพให้เป็นมาตรฐานที่ใด ฉันกำลังพยายามปรับปรุงความเร็วของวิธีนี้เนื่องจากการแบ่งจุดลอยตัวนั้นช้าบนโทรศัพท์มือถือ คุณมีข้อเสนอแนะใด? @AbidRahmanK
1 ''

1
@ 1 *: ฉันคิดว่าเรียกว่า "การปรับภาพสีขาว" อย่าถามฉันว่าฉันอ่านที่ไหนมันเป็นเครื่องมือประมวลผลภาพมาตรฐาน โมเดลที่อยู่เบื้องหลังแนวคิดนั้นเรียบง่าย: ปริมาณแสงที่สะท้อนจากพื้นผิว (Lambertian) เป็นเพียงความสว่างของพื้นผิวคูณกับปริมาณของแสงที่วัตถุสีขาวในตำแหน่งเดียวกันจะสะท้อนออกมา ประเมินความสว่างที่ชัดเจนของวัตถุสีขาวในตำแหน่งเดียวกันโดยแบ่งความสว่างที่แท้จริงตามนั้นและคุณจะได้ความสว่างของพื้นผิว
Niki

209

คำตอบของ Nikie แก้ปัญหาของฉันได้ แต่คำตอบของเขาอยู่ใน Mathematica ดังนั้นฉันคิดว่าฉันควรจะปรับ OpenCV ที่นี่ แต่หลังจากนำไปใช้ฉันจะเห็นว่าโค้ด OpenCV นั้นใหญ่กว่าโค้ด mathematica ของ nikie มาก และฉันไม่สามารถหาวิธีการแก้ไขที่ทำโดย nikie ใน OpenCV (แม้ว่าจะสามารถทำได้โดยใช้ scipy ฉันจะบอกได้ว่าเมื่อถึงเวลา)

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. ค้นหาจัตุรัส Sudoku และสร้างรูปหน้ากาก

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. การแก้ไขข้อบกพร่อง

นี่นิกกี้ทำการแก้ไขบางอย่างซึ่งฉันไม่มีความรู้มากนัก และฉันไม่พบฟังก์ชันที่เกี่ยวข้องสำหรับ 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()

ผลลัพธ์ :

ป้อนคำอธิบายรูปภาพที่นี่

ผลลัพธ์ใกล้เคียงกับของ nikie แต่ความยาวโค้ดมีขนาดใหญ่ อาจเป็นไปได้ว่ามีวิธีการที่ดีกว่า แต่ก่อนหน้านี้ก็ใช้งานได้ดี

ขอแสดงความนับถือ ARK


4
"ฉันต้องการให้แอปพลิเคชันของฉันหยุดทำงานแทนที่จะได้รับคำตอบที่ผิด" <- ฉันเห็นด้วยกับสิ่งนี้ 100%
Viktor Sehr

ขอขอบคุณคำตอบที่แท้จริงของ Nikie แต่นั่นคือในวิชาคณิตศาสตร์ดังนั้นฉันเพิ่งแปลงเป็น OpenCV ดังนั้นคำตอบที่แท้จริงมี upvotes เพียงพอฉันคิดว่า
Abid Rahman K

อาไม่ได้เห็นคุณยังโพสต์คำถาม :)
วิคเตอร์ Sehr

ใช่. คำถามก็เป็นของฉันเช่นกัน คำตอบของ Mine และ nikie นั้นแตกต่างกันในตอนท้าย เขามีฟังก์ชัน intepolation บางอย่างใน mathematica ซึ่งไม่ได้อยู่ใน numpy หรือ opencv (แต่มีอยู่ใน Scipy แต่ฉันไม่ต้องการใช้ Scipy ที่นี่)
Abid Rahman K

ฉันได้รับข้อผิดพลาด: เอาต์พุต [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: long () อาร์กิวเมนต์ต้องเป็นสตริงหรือตัวเลขไม่ใช่ 'builtin_function_or_method'
user898678

6

คุณสามารถลองใช้การสร้างแบบจำลองตารางตามชนิดของการแปรปรวนโดยพลการ และเนื่องจากซูโดกุนั้นเป็นกริดอยู่แล้วจึงไม่ควรยากเกินไป

ดังนั้นคุณสามารถลองตรวจจับขอบเขตของแต่ละภูมิภาคย่อย 3x3 จากนั้นบิดแต่ละภูมิภาค หากการตรวจจับสำเร็จก็จะทำให้การประมาณที่ดีขึ้น


1

ฉันต้องการที่จะเพิ่มวิธีการข้างต้นจะทำงานเฉพาะเมื่อบอร์ดซูโดกุยืนตรงมิฉะนั้นการทดสอบอัตราส่วนความสูง / ความกว้าง (หรือกลับกัน) จะล้มเหลวมากที่สุดและคุณจะไม่สามารถตรวจจับขอบของซูโดกุ (ฉันต้องการเพิ่มด้วยว่าหากเส้นที่ไม่ได้ตั้งฉากกับเส้นขอบของภาพการทำงานของ sobel (dx และ dy) จะยังคงทำงานได้เนื่องจากเส้นจะยังคงมีขอบตามแกนทั้งสอง)

เพื่อให้สามารถตรวจจับเส้นตรงคุณควรทำงานเกี่ยวกับการวิเคราะห์รูปร่างหรือพิกเซลเช่น contourArea / boundingRectArea จุดบนซ้ายและขวาล่าง ...

แก้ไข: ฉันจัดการเพื่อตรวจสอบว่าชุดของรูปทรงเป็นเส้นหรือไม่โดยใช้การถดถอยเชิงเส้นและตรวจสอบข้อผิดพลาด อย่างไรก็ตามการถดถอยเชิงเส้นทำได้ไม่ดีเมื่อความชันของเส้นใหญ่เกินไป (เช่น> 1,000) หรือใกล้เคียงกับ 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)

นี่คือนอกเหนือจากคำตอบของ Abid Rahman หากบางจุดขาดหายไป


0

ฉันคิดว่านี่เป็นโพสต์ที่ยอดเยี่ยมและโซลูชั่นที่ยอดเยี่ยมโดย ARK; วางและอธิบายได้ดีมาก

ฉันกำลังทำงานกับปัญหาที่คล้ายกันและสร้างสิ่งทั้งหมด มีการเปลี่ยนแปลงบางอย่าง (เช่น xrange ถึง range, อาร์กิวเมนต์ใน cv2.findContours) แต่สิ่งนี้ควรจะออกนอกกรอบ (Python 3.5, Anaconda)

นี่เป็นการรวบรวมองค์ประกอบข้างต้นโดยมีการเพิ่มรหัสที่หายไปบางส่วน (เช่นการติดฉลากคะแนน)

'''

/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.