Skip to content

Commit 329dbdc

Browse files
author
howlclat
committed
首次提交
0 parents  commit 329dbdc

28 files changed

+714
-0
lines changed

.idea/vcs.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

correction.py

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# -*- coding: utf-8 -*-
2+
import cv2
3+
import numpy as np
4+
import plotCVImg
5+
6+
IMAGE_WIDTH = 40
7+
IMAGE_HEIGHT = 40
8+
SUDOKU_SIZE = 9
9+
N_MIN_ACTIVE_PIXELS = 30
10+
SIZE_PUZZLE = IMAGE_WIDTH * SUDOKU_SIZE
11+
DEBUG = 0
12+
13+
14+
def correct(img_original):
15+
# 灰度化
16+
img_gray = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)
17+
if DEBUG:
18+
plotCVImg.plotImg(img_gray, "gray")
19+
20+
# 中值滤波
21+
img_blur = cv2.medianBlur(img_gray, 1)
22+
if DEBUG:
23+
plotCVImg.plotImg(img_blur, "median Blur")
24+
25+
# 高斯滤波
26+
img_blur = cv2.GaussianBlur(img_blur, (3, 3), 0)
27+
if DEBUG:
28+
plotCVImg.plotImg(img_blur, "Gaussian Blur")
29+
30+
# 将每个像素除以闭操作后的像素,可以调整图像亮度
31+
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11))
32+
close = cv2.morphologyEx(img_blur, cv2.MORPH_CLOSE, kernel)
33+
div = np.float32(img_blur) / close
34+
img_brightness_adjust = np.uint8(cv2.normalize(div, div, 0, 255, cv2.NORM_MINMAX))
35+
if DEBUG:
36+
plotCVImg.plotImg(img_brightness_adjust, "brightness adjust")
37+
38+
# 自适应阈值二值化,注意其返回值只有一个
39+
img_thresh = cv2.adaptiveThreshold(img_brightness_adjust, 255,
40+
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
41+
cv2.THRESH_BINARY_INV, 11, 7)
42+
if DEBUG:
43+
img_thresh = cv2.medianBlur(img_thresh, 3)
44+
plotCVImg.plotImg(img_thresh, "adaptive Threshold")
45+
46+
# 寻找轮廓
47+
binary, contours, hierarchy = cv2.findContours(img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
48+
if DEBUG:
49+
img_contours = img_original.copy()
50+
cv2.drawContours(img_contours, contours, -1, (0, 0, 255), 2)
51+
plotCVImg.plotImg(img_contours, "contours")
52+
53+
# 找到最大轮廓
54+
max_area = 0
55+
biggest_contour = None
56+
for cnt in contours:
57+
area = cv2.contourArea(cnt)
58+
if area > max_area:
59+
max_area = area
60+
biggest_contour = cnt
61+
62+
# mask操作
63+
mask = np.zeros(img_brightness_adjust.shape, np.uint8)
64+
cv2.drawContours(mask, [biggest_contour], 0, 255, cv2.FILLED)
65+
cv2.drawContours(mask, [biggest_contour], 0, 0, 2)
66+
image_with_mask = cv2.bitwise_and(img_brightness_adjust, mask)
67+
if DEBUG:
68+
plotCVImg.plotImg(image_with_mask, "image_with_mask")
69+
70+
# 角点检测
71+
dst = cv2.cornerHarris(image_with_mask, 2, 3, 0.04)
72+
if DEBUG:
73+
plotCVImg.plotImg(dst, "image_cornerHarris")
74+
75+
# x方向Sobel算子,膨胀操作连接断线,边缘检测找出竖线
76+
dx = cv2.Sobel(image_with_mask, cv2.CV_16S, 1, 0)
77+
dx = cv2.convertScaleAbs(dx)
78+
cv2.normalize(dx, dx, 0, 255, cv2.NORM_MINMAX)
79+
ret, close = cv2.threshold(dx, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
80+
kernelx = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 10))
81+
close = cv2.morphologyEx(close, cv2.MORPH_DILATE, kernelx, iterations=1)
82+
83+
binary, contour, hierarchy = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
84+
for cnt in contour:
85+
x, y, w, h = cv2.boundingRect(cnt)
86+
if h / w > 5:
87+
cv2.drawContours(close, [cnt], 0, 255, -1)
88+
else:
89+
cv2.drawContours(close, [cnt], 0, 0, -1)
90+
91+
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, None, iterations=2)
92+
closex = close.copy()
93+
94+
# Y方向,找出横线
95+
dy = cv2.Sobel(image_with_mask, cv2.CV_16S, 0, 2)
96+
dy = cv2.convertScaleAbs(dy)
97+
cv2.normalize(dy, dy, 0, 255, cv2.NORM_MINMAX)
98+
retVal, close = cv2.threshold(dy, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
99+
kernely = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 2))
100+
close = cv2.morphologyEx(close, cv2.MORPH_DILATE, kernely)
101+
102+
binary, contour, hierarchy = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
103+
for cnt in contour:
104+
x, y, w, h = cv2.boundingRect(cnt)
105+
if w / h > 5:
106+
cv2.drawContours(close, [cnt], 0, 255, -1)
107+
else:
108+
cv2.drawContours(close, [cnt], 0, 0, -1)
109+
110+
close = cv2.morphologyEx(close, cv2.MORPH_DILATE, None, iterations=2)
111+
closey = close.copy()
112+
113+
# 求x,y交点
114+
res = cv2.bitwise_and(closex, closey)
115+
if DEBUG:
116+
plotCVImg.plotImg(res, "dots")
117+
118+
# 查找轮廓,求每个轮廓的质心centroids
119+
img_dots = cv2.cvtColor(img_brightness_adjust, cv2.COLOR_GRAY2BGR)
120+
binary, contour, hierarchy = cv2.findContours(res, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
121+
centroids = []
122+
for cnt in contour:
123+
if cv2.contourArea(cnt) > 20:
124+
mom = cv2.moments(cnt)
125+
(x, y) = int(mom['m10'] / mom['m00']), int(mom['m01'] / mom['m00'])
126+
cv2.circle(img_dots, (x, y), 4, (0, 255, 0), -1)
127+
centroids.append((x, y))
128+
centroids = np.array(centroids, dtype=np.float32)
129+
c = centroids.reshape((100, 2))
130+
c2 = c[np.argsort(c[:, 1])]
131+
132+
b = np.vstack([c2[i * 10:(i + 1) * 10][np.argsort(c2[i * 10:(i + 1) * 10, 0])] for i in range(10)])
133+
bm = b.reshape((10, 10, 2))
134+
135+
res2 = cv2.cvtColor(img_brightness_adjust, cv2.COLOR_GRAY2BGR)
136+
output = np.zeros((450, 450, 3), np.uint8)
137+
for i, j in enumerate(b):
138+
ri = i // 10
139+
ci = i % 10
140+
if ci != 9 and ri != 9:
141+
src = bm[ri:ri + 2, ci:ci + 2, :].reshape((4, 2))
142+
dst = np.array([[ci * 50, ri * 50], [(ci + 1) * 50 - 1, ri * 50], [ci * 50, (ri + 1) * 50 - 1],
143+
[(ci + 1) * 50 - 1, (ri + 1) * 50 - 1]], np.float32)
144+
retval = cv2.getPerspectiveTransform(src, dst)
145+
warp = cv2.warpPerspective(res2, retval, (450, 450))
146+
output[ri * 50:(ri + 1) * 50 - 1, ci * 50:(ci + 1) * 50 - 1] = warp[ri * 50:(ri + 1) * 50 - 1,
147+
ci * 50:(ci + 1) * 50 - 1].copy()
148+
img_correct = cv2.cvtColor(output, cv2.COLOR_BGR2GRAY)
149+
img_puzzle = cv2.adaptiveThreshold(img_correct, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 5, 7)
150+
img_puzzle = cv2.resize(img_puzzle, (SIZE_PUZZLE, SIZE_PUZZLE), interpolation=cv2.INTER_LINEAR)
151+
return img_puzzle
152+
153+
154+
def correct2(img_original):
155+
156+
if DEBUG:
157+
plotCVImg.plotImg(img_original, "original")
158+
159+
# gray image
160+
img_gray = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)
161+
if DEBUG:
162+
plotCVImg.plotImg(img_gray, "gray")
163+
164+
# median Blur
165+
img_Blur = cv2.medianBlur(img_gray, 5)
166+
if DEBUG:
167+
plotCVImg.plotImg(img_Blur, "median Blur")
168+
169+
# Gaussian Blur
170+
img_Blur = cv2.GaussianBlur(img_gray, (3, 3), 0)
171+
if DEBUG:
172+
plotCVImg.plotImg(img_Blur, "GaussianBlur")
173+
174+
# adaptive threshold
175+
img_thresh = cv2.adaptiveThreshold(img_Blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
176+
if DEBUG:
177+
plotCVImg.plotImg(img_thresh, "adaptiveThreshold")
178+
179+
# find the contours RETR_EXTERNAL
180+
binary, contours, hierarchy = cv2.findContours(img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
181+
if DEBUG:
182+
img_contours = img_original.copy()
183+
cv2.drawContours(img_contours, contours, -1, (0, 0, 255), 2)
184+
plotCVImg.plotImg(img_contours, "contours")
185+
186+
# find the biggest contours
187+
size_rectangle_max = 0
188+
index_biggest = 0
189+
for i in range(len(contours)):
190+
size_rectangle = cv2.contourArea(contours[i])
191+
# store the index of the biggest
192+
if size_rectangle > size_rectangle_max:
193+
size_rectangle_max = size_rectangle
194+
index_biggest = i
195+
196+
# 多边形拟合
197+
epsilon = 0.1 * cv2.arcLength(contours[index_biggest], True)
198+
biggest_rectangle = cv2.approxPolyDP(contours[index_biggest], epsilon, True)
199+
200+
if DEBUG:
201+
# copy the original image to show the border
202+
img_border = img_original.copy()
203+
# 画出数独方格的边界
204+
for x in range(len(biggest_rectangle)):
205+
cv2.line(img_border,
206+
(biggest_rectangle[(x % 4)][0][0], biggest_rectangle[(x % 4)][0][1]),
207+
(biggest_rectangle[((x + 1) % 4)][0][0], biggest_rectangle[((x + 1) % 4)][0][1]),
208+
(255, 0, 0), 2)
209+
plotCVImg.plotImg(img_border, "border")
210+
211+
# sort the corners to remap the image
212+
def sortCornerPoints(rcCorners):
213+
point = rcCorners.reshape((4, 2))
214+
mean = rcCorners.sum() / 8
215+
cornerPoint = np.zeros((4, 2), dtype=np.float32)
216+
for i in range(len(point)):
217+
if point[i][0] < mean:
218+
if point[i][1] < mean:
219+
cornerPoint[0] = point[i]
220+
else:
221+
cornerPoint[2] = point[i]
222+
else:
223+
if point[i][1] < mean:
224+
cornerPoint[1] = point[i]
225+
else:
226+
cornerPoint[3] = point[i]
227+
return cornerPoint
228+
229+
# 透视变换
230+
cornerPoints = sortCornerPoints(biggest_rectangle)
231+
puzzlePoints = np.float32([[0, 0], [SIZE_PUZZLE, 0], [0, SIZE_PUZZLE], [SIZE_PUZZLE, SIZE_PUZZLE]])
232+
PerspectiveMatrix = cv2.getPerspectiveTransform(cornerPoints, puzzlePoints)
233+
img_puzzle = cv2.warpPerspective(img_thresh, PerspectiveMatrix, (SIZE_PUZZLE, SIZE_PUZZLE))
234+
if DEBUG:
235+
plotCVImg.plotImg(img_puzzle, "puzzle")
236+
237+
return img_puzzle

extractNumber.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# -*- coding: utf-8 -*-
2+
import cv2
3+
import numpy as np
4+
5+
# 标准方格大小
6+
GRID_WIDTH = 40
7+
GRID_HEIGHT = 40
8+
# 标准数字大小
9+
NUM_WIDTH = 20
10+
NUM_HEIGHT = 20
11+
# 判定非零像素书最小阈值
12+
N_MIN_ACTIVE_PIXELS = 30
13+
14+
15+
def extract_grid(im_number):
16+
"""
17+
将校正后图像划分为9x9的棋盘,取出小方格;二值化;去除离中心较远的像素(排除边框干扰);统计非零像素数(判断方格中是否有数字)
18+
:param im_number: 方格图像
19+
:param x: 方格横坐标 (0~9)
20+
:param y: 方格纵坐标 (0~9)
21+
:return: im_number_thresh: 二值化及处理后图像
22+
n_active_pixels: 非零像素数
23+
"""
24+
25+
# 二值化
26+
retVal, im_number_thresh = cv2.threshold(im_number, 150, 255, cv2.THRESH_BINARY)
27+
28+
# 去除离中心较远的像素点(排除边框干扰)
29+
for i in range(im_number.shape[0]):
30+
for j in range(im_number.shape[1]):
31+
dist_center = np.sqrt(np.square(GRID_WIDTH // 2 - i) + np.square(GRID_HEIGHT // 2 - j))
32+
if dist_center > GRID_WIDTH // 2 - 2:
33+
im_number_thresh[i, j] = 0
34+
35+
# 统计非零像素数,以判断方格中是否有数字
36+
n_active_pixels = cv2.countNonZero(im_number_thresh)
37+
38+
return [im_number_thresh, n_active_pixels]
39+
40+
41+
def find_biggest_bounding_box(im_number_thresh):
42+
"""
43+
找出小方格中外接矩形面积最大的轮廓,返回其外接矩形参数
44+
:param im_number_thresh: 当前方格的二值化及处理后图像
45+
:return: 外接矩形参数(左上坐标及长和宽)
46+
"""
47+
# 轮廓检测
48+
b, contour, hierarchy1 = cv2.findContours(im_number_thresh.copy(),
49+
cv2.RETR_EXTERNAL,
50+
cv2.CHAIN_APPROX_SIMPLE)
51+
# 找出外接矩形面积最大的轮廓
52+
biggest_bound_rect = []
53+
bound_rect_max_size = 0
54+
for i in range(len(contour)):
55+
bound_rect = cv2.boundingRect(contour[i])
56+
size_bound_rect = bound_rect[2] * bound_rect[3]
57+
if size_bound_rect > bound_rect_max_size:
58+
bound_rect_max_size = size_bound_rect
59+
biggest_bound_rect = bound_rect
60+
61+
# 将外接矩形扩大一个像素
62+
x_b, y_b, w, h = biggest_bound_rect
63+
x_b = x_b - 1
64+
y_b = y_b - 1
65+
w = w + 2
66+
h = h + 2
67+
return [x_b, y_b, w, h]
68+
69+
70+
def recognize_number(im_number, x, y):
71+
"""
72+
判断当前方格是否存在数字并存储该数字
73+
:param im_number: 方格图像
74+
:param x: 方格横坐标 (0~9)
75+
:param y: 方格纵坐标 (0~9)
76+
:return: 数字的一维数组
77+
"""
78+
# 提取并处理方格图像
79+
[im_number_thresh, n_active_pixels] = extract_grid(im_number)
80+
81+
# 条件1:非零像素大于设定的最小值
82+
if n_active_pixels > N_MIN_ACTIVE_PIXELS:
83+
84+
# 找出外接矩形
85+
[x_b, y_b, w, h] = find_biggest_bounding_box(im_number_thresh)
86+
87+
# 计算矩形中心与方格中心距离
88+
cX = x_b + w // 2
89+
cY = y_b + h // 2
90+
d = np.sqrt(np.square(cX - GRID_WIDTH // 2) + np.square(cY - GRID_HEIGHT // 2))
91+
92+
# 条件2: 外接矩形中心与方格中心距离足够小
93+
if d < GRID_WIDTH // 4:
94+
95+
# 取出方格中数字
96+
number_roi = im_number[y_b:y_b + h, x_b:x_b + w]
97+
98+
# 扩充数字图像为正方形,边长取长宽较大者
99+
h1, w1 = np.shape(number_roi)
100+
if h1 > w1:
101+
number = np.zeros(shape=(h1, h1))
102+
number[:, (h1 - w1) // 2:(h1 - w1) // 2 + w1] = number_roi
103+
else:
104+
number = np.zeros(shape=(w1, w1))
105+
number[(w1 - h1) // 2:(w1 - h1) // 2 + h1, :] = number_roi
106+
107+
# 将数字缩放为标准大小
108+
number = cv2.resize(number, (NUM_WIDTH, NUM_HEIGHT), interpolation=cv2.INTER_LINEAR)
109+
110+
retVal, number = cv2.threshold(number, 50, 255, cv2.THRESH_BINARY)
111+
112+
# 转换为1维数组并返回
113+
return True, number.reshape(1, NUM_WIDTH * NUM_HEIGHT)
114+
115+
# 没有数字,则返回全零1维数组
116+
return False, np.zeros(shape=(1, NUM_WIDTH * NUM_HEIGHT))
117+

images/a1.png

28.5 KB
Loading

images/a2.png

28.3 KB
Loading

images/c2.png

1.39 MB
Loading

images/c3.png

245 KB
Loading

images/c4.png

423 KB
Loading

images/c5.png

431 KB
Loading

images/digits.png

704 KB
Loading

images/h1.png

214 KB
Loading

images/h2.png

274 KB
Loading

images/h3.png

746 KB
Loading

images/h5.png

292 KB
Loading

images/h6.png

296 KB
Loading

images/p1.png

26.8 KB
Loading

images/p2.png

23.1 KB
Loading

images/p3.png

68.3 KB
Loading

images/pe.png

30.2 KB
Loading

0 commit comments

Comments
 (0)