如何实现Python色块矩阵的精确检测器?
How to implement a precise detector for the matrix of color blocks in Python?
先举个例子说明问题
下面是一个示例输入图像,它可能被噪声和其他类型的仿射变换。
输出应该return一个二维array/matrix(用一个标准的颜色字符来表示每个色块)像
[[w, g, w, b],
[y, o, o, y],
[g, b, g, y],
[b, o, y, b]]
所以这个问题的目的是检测每个输入图像文件的色块矩阵,方向无关紧要。
解决这个问题的思路给我的直觉是像检测和解析一个二维码,但是不知道具体怎么解决
谁能给我一些建议,比如
- idea/procedure解决这个问题。
- 我应该探索和使用 Python 包中的哪些 API。
- 有什么类似的经典问题值得我去研究
- 一些正确的代码。
- ...
我们可以从找到黑点并“校正”图像开始
可以通过设置阈值和寻找“圆形”斑点来找到角点。可以在此处找到指标:https://learnopencv.com/blob-detection-using-opencv-python-c/
一旦我们有了角,我们就会“纠正”图像(图像可能会旋转 and/or 翻转,具体取决于角的顺序)
转换为 HSV
在“v”通道上再次遮罩以获得单个方块
然后我们计算每个方块的平均色调和饱和度以平滑噪声
(我们需要饱和度来区分白色方块)
我们可以将这些数字与我们的 pre-defined 颜色值进行匹配以打印出字符串:
['b', 'w', 'g', 'w']
['y', 'o', 'o', 'y']
['y', 'g', 'b', 'g']
['b', 'y', 'o', 'b']
完整代码
import cv2
import numpy as np
import math
# get average color of image within mask
# I think this can be done with some mix of numpy commands (it'd be much faster)
def maskedAverageColor(mask, img):
# convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
height,width = img.shape[:2];
count = 0;
hue_ave = 0;
sat_ave = 0;
# calculate average
for y in range(height):
for x in range(width):
if mask[y,x] == 255:
count += 1;
hue_ave += h[y,x];
sat_ave += s[y,x];
hue_ave /= count;
sat_ave /= count;
return [hue_ave, sat_ave];
# center of contour
def getCenter(contour):
M = cv2.moments(contour);
cx = int(M['m10']/M['m00']);
cy = int(M['m01']/M['m00']);
return [cx, cy];
# predefined colors
# white
# green
# blue
# orange
# yellow
# ??? missing one color (need 6 for a cube?)
color_map = {};
color_map['g'] = 53;
color_map['y'] = 27;
color_map['o'] = 10;
color_map['b'] = 120;
# load image
img = cv2.imread("cube.png");
# show
cv2.imshow("Image", img);
# grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
# threshold
mask = cv2.inRange(gray, 0, 50);
# close small black speckles and dilate
kernel = np.ones((3,3),np.uint8);
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel);
mask = cv2.dilate(mask, kernel, iterations = 1);
# find contours
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE);
# fill in black spaces
for con in contours:
cv2.drawContours(mask, [con], -1, 255, -1);
cv2.imshow("Mask", mask);
# get the contours again
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE);
# filter out little contours
filtered = [];
for con in contours:
area = cv2.contourArea(con);
if area > 25:
filtered.append(con);
contours = filtered;
# circularity
corners = [];
for con in contours:
area = cv2.contourArea(con);
perm = cv2.arcLength(con, True);
if area != 0 and perm != 0:
circ = (4 * math.pi * area) / (perm**2);
if circ > 0.5:
# get the corners
corners.append(getCenter(con));
# sort corners
# find point in the middle of the corners
cx = 0;
cy = 0;
for corner in corners:
x,y = corner;
cx += x;
cy += y;
cx /= 4;
cy /= 4;
# calculate angles
angles = []; # [angle, point]
for corner in corners:
x, y = corner;
dx = 1000;
dy = y - cy;
angle = math.atan2(dy, dx);
angles.append([angle, corner]);
# sort by angles (counter-clockwise)
angles = sorted(angles, key = lambda x : x[0]);
corners = [p[1] for p in angles];
cv2.destroyAllWindows();
# unmorph with corners
to_rect = [
[0, 0],
[500, 0],
[500, 500],
[0, 500]
]
corners = np.array(corners, dtype=np.float32);
to_rect = np.array(to_rect, dtype=np.float32);
warp_mat = cv2.getPerspectiveTransform(corners, to_rect);
rectified = cv2.warpPerspective(img, warp_mat, (500,500));
cv2.imshow("Rect", rectified);
# hsv and mask again
hsv = cv2.cvtColor(rectified, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
cv2.imshow("Hue", h);
cv2.imshow("Sat", s);
cv2.imshow("Val", v);
mask = cv2.inRange(v, 0, 150);
# dilate mask
mask = cv2.dilate(mask, kernel, iterations = 5);
mask = cv2.bitwise_not(mask);
# get contours (yes again)
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# get the squares
squares = [];
for con in contours:
area = cv2.contourArea(con);
if area < 100000:
squares.append(con);
# get the center of squares
drawn = np.copy(rectified);
for square in squares:
cx, cy = getCenter(square);
cv2.circle(drawn, (int(cx), int(cy)), 4, (255,0,0), -1);
# split into rows
rows = [];
index = -1;
for y in range(4):
row = [];
for x in range(4):
row.append(squares[index]);
index -= 1;
rows.append(row);
# draw rows
drawn = np.copy(rectified);
for row in rows:
for square in row:
cx, cy = getCenter(square);
cv2.circle(drawn, (int(cx), int(cy)), 4, (255,0,0), -1);
# get color of each square
color_rows = [];
for row in rows:
color_row = [];
for square in row:
# get mask
h,w = rectified.shape[:2];
mask = np.zeros((h,w), np.uint8);
mask = cv2.drawContours(mask, [square], -1, 255, -1);
# calculate average colors
hue, sat = maskedAverageColor(mask, rectified);
# convert to string colors
# white check
color_str = "NONE";
if sat < 50:
color_str = "w";
else:
margin = 5;
for key in color_map:
if color_map[key] - margin < hue and hue < color_map[key] + margin:
color_str = key;
if color_str == "NONE":
print("INCREASE MARGIN");
print("HUE SAT: " + str([hue, sat]));
color_row.append(color_str);
color_rows.append(color_row);
# print out results
for row in color_rows:
print(row);
cv2.waitKey(0);
先举个例子说明问题
下面是一个示例输入图像,它可能被噪声和其他类型的仿射变换。
输出应该return一个二维array/matrix(用一个标准的颜色字符来表示每个色块)像
[[w, g, w, b],
[y, o, o, y],
[g, b, g, y],
[b, o, y, b]]
所以这个问题的目的是检测每个输入图像文件的色块矩阵,方向无关紧要。
解决这个问题的思路给我的直觉是像检测和解析一个二维码,但是不知道具体怎么解决
谁能给我一些建议,比如
- idea/procedure解决这个问题。
- 我应该探索和使用 Python 包中的哪些 API。
- 有什么类似的经典问题值得我去研究
- 一些正确的代码。
- ...
我们可以从找到黑点并“校正”图像开始
可以通过设置阈值和寻找“圆形”斑点来找到角点。可以在此处找到指标:https://learnopencv.com/blob-detection-using-opencv-python-c/
一旦我们有了角,我们就会“纠正”图像(图像可能会旋转 and/or 翻转,具体取决于角的顺序)
转换为 HSV
在“v”通道上再次遮罩以获得单个方块
然后我们计算每个方块的平均色调和饱和度以平滑噪声 (我们需要饱和度来区分白色方块)
我们可以将这些数字与我们的 pre-defined 颜色值进行匹配以打印出字符串:
['b', 'w', 'g', 'w']
['y', 'o', 'o', 'y']
['y', 'g', 'b', 'g']
['b', 'y', 'o', 'b']
完整代码
import cv2
import numpy as np
import math
# get average color of image within mask
# I think this can be done with some mix of numpy commands (it'd be much faster)
def maskedAverageColor(mask, img):
# convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
height,width = img.shape[:2];
count = 0;
hue_ave = 0;
sat_ave = 0;
# calculate average
for y in range(height):
for x in range(width):
if mask[y,x] == 255:
count += 1;
hue_ave += h[y,x];
sat_ave += s[y,x];
hue_ave /= count;
sat_ave /= count;
return [hue_ave, sat_ave];
# center of contour
def getCenter(contour):
M = cv2.moments(contour);
cx = int(M['m10']/M['m00']);
cy = int(M['m01']/M['m00']);
return [cx, cy];
# predefined colors
# white
# green
# blue
# orange
# yellow
# ??? missing one color (need 6 for a cube?)
color_map = {};
color_map['g'] = 53;
color_map['y'] = 27;
color_map['o'] = 10;
color_map['b'] = 120;
# load image
img = cv2.imread("cube.png");
# show
cv2.imshow("Image", img);
# grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
# threshold
mask = cv2.inRange(gray, 0, 50);
# close small black speckles and dilate
kernel = np.ones((3,3),np.uint8);
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel);
mask = cv2.dilate(mask, kernel, iterations = 1);
# find contours
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE);
# fill in black spaces
for con in contours:
cv2.drawContours(mask, [con], -1, 255, -1);
cv2.imshow("Mask", mask);
# get the contours again
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE);
# filter out little contours
filtered = [];
for con in contours:
area = cv2.contourArea(con);
if area > 25:
filtered.append(con);
contours = filtered;
# circularity
corners = [];
for con in contours:
area = cv2.contourArea(con);
perm = cv2.arcLength(con, True);
if area != 0 and perm != 0:
circ = (4 * math.pi * area) / (perm**2);
if circ > 0.5:
# get the corners
corners.append(getCenter(con));
# sort corners
# find point in the middle of the corners
cx = 0;
cy = 0;
for corner in corners:
x,y = corner;
cx += x;
cy += y;
cx /= 4;
cy /= 4;
# calculate angles
angles = []; # [angle, point]
for corner in corners:
x, y = corner;
dx = 1000;
dy = y - cy;
angle = math.atan2(dy, dx);
angles.append([angle, corner]);
# sort by angles (counter-clockwise)
angles = sorted(angles, key = lambda x : x[0]);
corners = [p[1] for p in angles];
cv2.destroyAllWindows();
# unmorph with corners
to_rect = [
[0, 0],
[500, 0],
[500, 500],
[0, 500]
]
corners = np.array(corners, dtype=np.float32);
to_rect = np.array(to_rect, dtype=np.float32);
warp_mat = cv2.getPerspectiveTransform(corners, to_rect);
rectified = cv2.warpPerspective(img, warp_mat, (500,500));
cv2.imshow("Rect", rectified);
# hsv and mask again
hsv = cv2.cvtColor(rectified, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
cv2.imshow("Hue", h);
cv2.imshow("Sat", s);
cv2.imshow("Val", v);
mask = cv2.inRange(v, 0, 150);
# dilate mask
mask = cv2.dilate(mask, kernel, iterations = 5);
mask = cv2.bitwise_not(mask);
# get contours (yes again)
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# get the squares
squares = [];
for con in contours:
area = cv2.contourArea(con);
if area < 100000:
squares.append(con);
# get the center of squares
drawn = np.copy(rectified);
for square in squares:
cx, cy = getCenter(square);
cv2.circle(drawn, (int(cx), int(cy)), 4, (255,0,0), -1);
# split into rows
rows = [];
index = -1;
for y in range(4):
row = [];
for x in range(4):
row.append(squares[index]);
index -= 1;
rows.append(row);
# draw rows
drawn = np.copy(rectified);
for row in rows:
for square in row:
cx, cy = getCenter(square);
cv2.circle(drawn, (int(cx), int(cy)), 4, (255,0,0), -1);
# get color of each square
color_rows = [];
for row in rows:
color_row = [];
for square in row:
# get mask
h,w = rectified.shape[:2];
mask = np.zeros((h,w), np.uint8);
mask = cv2.drawContours(mask, [square], -1, 255, -1);
# calculate average colors
hue, sat = maskedAverageColor(mask, rectified);
# convert to string colors
# white check
color_str = "NONE";
if sat < 50:
color_str = "w";
else:
margin = 5;
for key in color_map:
if color_map[key] - margin < hue and hue < color_map[key] + margin:
color_str = key;
if color_str == "NONE":
print("INCREASE MARGIN");
print("HUE SAT: " + str([hue, sat]));
color_row.append(color_str);
color_rows.append(color_row);
# print out results
for row in color_rows:
print(row);
cv2.waitKey(0);