Làm cách nào để loại bỏ khuyết điểm lồi trong hình vuông Sudoku?


192

Tôi đang thực hiện một dự án thú vị: Giải Sudoku từ hình ảnh đầu vào bằng OpenCV (như trong kính bảo hộ của Google, v.v.). Và tôi đã hoàn thành nhiệm vụ, nhưng cuối cùng tôi đã tìm thấy một vấn đề nhỏ mà tôi đến đây.

Tôi đã lập trình bằng API Python của OpenCV 2.3.1.

Dưới đây là những gì tôi đã làm:

  1. Đọc hình ảnh
  2. Tìm đường viền
  3. Chọn một cái có diện tích tối đa, (và cũng có phần tương đương với hình vuông).
  4. Tìm các điểm góc.

    ví dụ như được đưa ra dưới đây:

    nhập mô tả hình ảnh ở đây

    ( Lưu ý ở đây là đường màu xanh lá cây trùng khớp chính xác với ranh giới thực của Sudoku, vì vậy Sudoku có thể bị vênh chính xác . Kiểm tra hình ảnh tiếp theo)

  5. làm cong hình ảnh thành một hình vuông hoàn hảo

    ví dụ: hình ảnh:

    nhập mô tả hình ảnh ở đây

  6. Thực hiện OCR (mà tôi đã sử dụng phương pháp tôi đã đưa ra trong OCR nhận dạng chữ số đơn giản trong OpenCV-Python )

Và phương pháp làm việc tốt.

Vấn đề:

Kiểm tra hình ảnh này.

Thực hiện bước 4 trên hình ảnh này cho kết quả bên dưới:

nhập mô tả hình ảnh ở đây

Đường màu đỏ được vẽ là đường viền ban đầu là đường viền thực sự của ranh giới sudoku.

Đường màu xanh được vẽ là đường viền xấp xỉ sẽ là đường viền của hình ảnh bị cong vênh.

Tất nhiên, có sự khác biệt giữa đường màu xanh lá cây và đường màu đỏ ở cạnh trên của sudoku. Vì vậy, trong khi cong vênh, tôi không nhận được ranh giới ban đầu của Sudoku.

Câu hỏi của tôi :

Làm cách nào tôi có thể làm cong hình ảnh trên ranh giới chính xác của Sudoku, tức là đường màu đỏ HOẶC làm cách nào tôi có thể xóa sự khác biệt giữa đường màu đỏ và đường màu xanh lá cây? Có phương pháp nào cho việc này trong OpenCV không?


1
Bạn đang thực hiện phát hiện của mình dựa trên các điểm góc, mà các đường màu đỏ và màu xanh lá cây đồng ý. Tôi không biết OpenCV, nhưng có lẽ bạn sẽ muốn phát hiện các đường giữa các điểm góc và cong vênh dựa trên đó.
Dougal

Có lẽ buộc các đường nối các điểm góc trùng với các pixel đen nặng trong ảnh. Đó là, thay vì để các đường màu xanh lục chỉ tìm một đường thẳng giữa các điểm góc, buộc chúng phải đi qua các pixel đen nặng. Điều này sẽ làm cho vấn đề của bạn trở nên khó khăn hơn đáng kể, tôi nghĩ và tôi không biết bất kỳ công cụ tích hợp OpenCV nào sẽ hữu ích ngay lập tức cho bạn.
ely

@ Dougal: Tôi nghĩ rằng đường màu xanh lá cây được vẽ là đường thẳng gần đúng của đường màu đỏ. vì vậy nó là đường giữa các điểm góc. Khi tôi cong theo đường màu xanh lá cây, tôi nhận được đường màu đỏ cong ở đầu hình ảnh bị cong vênh. (tôi hy vọng bạn hiểu, lời giải thích của tôi có vẻ hơi tệ)
Abid Rahman K

@ EMS: Tôi nghĩ đường màu đỏ được vẽ chính xác trên đường viền của sudoku. Nhưng vấn đề là, làm thế nào để làm cong hình ảnh chính xác trên đường viền của sudoku. (I có nghĩa là, vấn đề là với cong vênh, tức là chuyển đổi những biên giới cong để một hình vuông chính xác, như tôi đã trình bày trong hình ảnh thứ hai)
Abid Rahman K

Câu trả lời:


251

Tôi có một giải pháp hiệu quả, nhưng bạn sẽ phải tự dịch nó sang OpenCV. Nó được viết bằng Mathicala.

Bước đầu tiên là điều chỉnh độ sáng trong ảnh, bằng cách chia từng pixel cho kết quả của thao tác đóng:

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

nhập mô tả hình ảnh ở đây

Bước tiếp theo là tìm khu vực sudoku, vì vậy tôi có thể bỏ qua (che đi) nền. Vì thế, tôi sử dụng phân tích thành phần được kết nối và chọn thành phần có diện tích lồi lớn nhất:

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

nhập mô tả hình ảnh ở đây

Bằng cách điền vào hình ảnh này, tôi nhận được một mặt nạ cho lưới sudoku:

mask = FillingTransform[largestComponent]

nhập mô tả hình ảnh ở đây

Bây giờ, tôi có thể sử dụng bộ lọc phái sinh bậc 2 để tìm các đường dọc và ngang trong hai hình ảnh riêng biệt:

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

nhập mô tả hình ảnh ở đây

Tôi sử dụng phân tích thành phần được kết nối một lần nữa để trích xuất các đường lưới từ những hình ảnh này. Các đường lưới dài hơn nhiều so với các chữ số, vì vậy tôi có thể sử dụng chiều dài caliper để chỉ chọn các thành phần được kết nối với các đường lưới. Sắp xếp chúng theo vị trí, tôi nhận được hình ảnh mặt nạ 2x10 cho mỗi đường lưới dọc / ngang trong hình ảnh:

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

nhập mô tả hình ảnh ở đây

Tiếp theo tôi lấy từng cặp đường lưới dọc / ngang, giãn chúng, tính toán giao điểm từng pixel và tính toán tâm của kết quả. Những điểm này là các giao điểm đường lưới:

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

nhập mô tả hình ảnh ở đây

Bước cuối cùng là xác định hai hàm nội suy cho ánh xạ X / Y thông qua các điểm này và biến đổi hình ảnh bằng các hàm nà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]

nhập mô tả hình ảnh ở đây

Tất cả các hoạt động là chức năng xử lý hình ảnh cơ bản, do đó, điều này cũng có thể có trong OpenCV. Việc chuyển đổi hình ảnh dựa trên spline có thể khó hơn, nhưng tôi không nghĩ bạn thực sự cần nó. Có lẽ sử dụng chuyển đổi phối cảnh mà bạn sử dụng bây giờ trên mỗi ô riêng lẻ sẽ cho kết quả đủ tốt.


3
Ôi chúa ơi !!!!!!!!! Điều đó thật tuyệt vời. Điều này thực sự thực sự tuyệt vời. Tôi sẽ cố gắng làm cho nó trong OpenCV. Hy vọng bạn sẽ giúp tôi với các chi tiết về các chức năng và thuật ngữ nhất định ... Cảm ơn bạn.
Abid Rahman K

@arkiaz: Tôi không phải là chuyên gia OpenCV, nhưng tôi sẽ giúp nếu tôi có thể, chắc chắn.
Niki

Bạn có thể giải thích chức năng "đóng" được sử dụng để làm gì không? những gì tôi có nghĩa là những gì xảy ra trong nền? Trong tài liệu, nó nói đóng cửa loại bỏ tiếng ồn muối và hạt tiêu? Là đóng bộ lọc thông thấp?
Abid Rahman K

2
Câu trả lời tuyệt vời! Trường hợp bạn có ý tưởng chia cho đóng cửa để bình thường hóa độ sáng của hình ảnh? Tôi đang cố gắng cải thiện tốc độ của phương pháp này, vì phân chia điểm nổi rất chậm trên điện thoại di động. Bạn có đề nghị nào không? @AbidRahmanK
1 ''

1
@ 1 *: Tôi nghĩ nó được gọi là "điều chỉnh hình ảnh trắng". Đừng hỏi tôi đã đọc về nó ở đâu, đó là một công cụ xử lý ảnh tiêu chuẩn. Mô hình đằng sau ý tưởng rất đơn giản: Lượng ánh sáng phản xạ từ bề mặt (Lambertian) chỉ bằng độ sáng bề mặt nhân với lượng ánh sáng mà một vật thể trắng ở cùng vị trí sẽ phản chiếu. Ước tính độ sáng rõ ràng của vật thể trắng ở cùng một vị trí, chia độ sáng thực tế cho đó và bạn có được độ sáng của bề mặt.
Niki

208

Câu trả lời của Nikie đã giải quyết vấn đề của tôi, nhưng câu trả lời của anh ấy là ở Mathicala. Vì vậy, tôi nghĩ rằng tôi nên cung cấp thích ứng OpenCV của nó ở đây. Nhưng sau khi triển khai tôi có thể thấy rằng mã OpenCV lớn hơn nhiều so với mã toán học của nikie. Ngoài ra, tôi không thể tìm thấy phương pháp nội suy được thực hiện bởi nikie trong OpenCV (mặc dù nó có thể được thực hiện bằng scipy, tôi sẽ nói với nó khi thời gian đến.)

1. Xử lý trước hình ảnh (thao tác đóng)

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)

Kết quả :

Kết quả đóng cửa

2. Tìm quảng trường Sudoku và tạo hình ảnh mặt nạ

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)

Kết quả :

nhập mô tả hình ảnh ở đây

3. Tìm đường dọc

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

Kết quả :

nhập mô tả hình ảnh ở đây

4. Tìm đường ngang

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

Kết quả :

nhập mô tả hình ảnh ở đây

Tất nhiên, cái này không tốt lắm.

5. Tìm điểm lưới

res = cv2.bitwise_and(closex,closey)

Kết quả :

nhập mô tả hình ảnh ở đây

6. Sửa lỗi

Ở đây, nikie thực hiện một số loại nội suy, mà tôi không có nhiều kiến ​​thức. Và tôi không thể tìm thấy bất kỳ chức năng tương ứng cho OpenCV này. (có thể là nó ở đó, tôi không biết).

Hãy xem SOF này giải thích cách thực hiện điều này bằng SciPy, cái mà tôi không muốn sử dụng: Chuyển đổi hình ảnh trong OpenCV

Vì vậy, ở đây tôi lấy 4 góc của mỗi hình vuông phụ và áp dụng phối cảnh dọc cho mỗi góc.

Đối với điều đó, đầu tiên chúng ta tìm thấy các trung tâm.

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

Nhưng kết quả là centroid sẽ không được sắp xếp. Kiểm tra hình ảnh dưới đây để xem thứ tự của họ:

nhập mô tả hình ảnh ở đây

Vì vậy, chúng tôi sắp xếp chúng từ trái sang phải, từ trên xuống dưới.

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

Bây giờ xem bên dưới thứ tự của họ:

nhập mô tả hình ảnh ở đây

Cuối cùng, chúng tôi áp dụng chuyển đổi và tạo một hình ảnh mới có kích thước 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()

Kết quả :

nhập mô tả hình ảnh ở đây

Kết quả gần giống như của nikie, nhưng độ dài mã lớn. Có thể, phương pháp tốt hơn có sẵn ngoài đó, nhưng cho đến lúc đó, điều này hoạt động tốt.

Trân trọng ARK.


4
"Tôi thích ứng dụng của tôi bị sập hơn là nhận được câu trả lời sai." <- Tôi cũng đồng ý với điều này 100%
Viktor Sehr

Cảm ơn, câu trả lời thực sự của nó được đưa ra bởi Nikie. Nhưng đó là trong mathicala, vì vậy tôi chỉ chuyển đổi nó thành OpenCV. Vì vậy, câu trả lời thực sự đã có đủ sự ủng hộ, tôi nghĩ
Abid Rahman K

Ah không thấy bạn cũng đăng câu hỏi :)
Viktor Sehr

Vâng. Câu hỏi cũng là của tôi. Câu trả lời của tôi và của nikie chỉ khác nhau ở cuối. Anh ta có một số loại chức năng giao thoa trong toán học không phải là numpy hay opencv (nhưng nó có ở Scipy, nhưng tôi không muốn sử dụng Scipy ở đây)
Abid Rahman K

Tôi đang gặp lỗi: 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 Lỗi Loại: dài () lập luận phải là một chuỗi hoặc một số, chứ không phải 'builtin_function_or_method'
user898678

6

Bạn có thể thử sử dụng một số loại mô hình dựa trên lưới của bạn cong vênh tùy ý. Và vì sudoku đã là một lưới, điều đó không quá khó.

Vì vậy, bạn có thể cố gắng phát hiện ranh giới của từng tiểu vùng 3x3 và sau đó làm cong từng vùng riêng lẻ. Nếu phát hiện thành công, nó sẽ cung cấp cho bạn một xấp xỉ tốt hơn.


1

Tôi muốn thêm rằng phương pháp trên chỉ hoạt động khi bảng sudoku đứng thẳng, nếu không, kiểm tra tỷ lệ chiều cao / chiều rộng (hoặc ngược lại) rất có thể sẽ thất bại và bạn sẽ không thể phát hiện các cạnh của sudoku. (Tôi cũng muốn thêm rằng nếu các đường không vuông góc với đường viền hình ảnh, các thao tác sobel (dx và dy) vẫn sẽ hoạt động vì các đường vẫn sẽ có các cạnh đối với cả hai trục.)

Để có thể phát hiện các đường thẳng, bạn nên làm việc trên phân tích đường viền hoặc pixel-khôn ngoan như contourArea / ràng buộcRectArea, các điểm trên cùng bên trái và dưới cùng bên phải ...

Chỉnh sửa: Tôi quản lý để kiểm tra xem một tập hợp các đường viền có tạo thành một dòng hay không bằng cách áp dụng hồi quy tuyến tính và kiểm tra lỗi. Tuy nhiên, hồi quy tuyến tính thực hiện kém khi độ dốc của đường quá lớn (tức là> 1000) hoặc rất gần với 0. Do đó, áp dụng thử nghiệm tỷ lệ ở trên (trong hầu hết câu trả lời nâng cao) trước khi hồi quy tuyến tính là hợp lý và đã làm việc với tôi.


1

Để loại bỏ các góc không bị xóa, tôi áp dụng hiệu chỉnh gamma với giá trị gamma là 0,8.

Trước khi sửa gamma

Vòng tròn màu đỏ được vẽ để hiển thị góc còn thiếu.

Sau khi điều chỉnh gamma

Mã này là:

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)

Đây là bổ sung cho câu trả lời của Abid Rahman nếu thiếu một số điểm góc.


0

Tôi nghĩ rằng đây là một bài viết tuyệt vời, và một giải pháp tuyệt vời của ARK; rất tốt đặt ra và giải thích.

Tôi đã làm việc với một vấn đề tương tự, và xây dựng toàn bộ. Có một số thay đổi (ví dụ xrange to phạm vi, đối số trong cv2.findContours), nhưng điều này sẽ hoạt động tốt (Python 3.5, Anaconda).

Đây là phần tổng hợp các phần tử ở trên, với một số mã bị thiếu được thêm vào (nghĩa là ghi nhãn các điểm).

'''

/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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.