Skip to content

Commit

Permalink
* optimize tic tac toe
Browse files Browse the repository at this point in the history
  • Loading branch information
lxowalle committed Jul 31, 2024
1 parent 6cc9e58 commit cd0404f
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 50 deletions.
133 changes: 83 additions & 50 deletions projects/demo_tic_tac_toe/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import cv2
import numpy as np
import gc
from utils import rgb888_to_lab

# 80 fps or 60fps
disp = display.Display()
Expand Down Expand Up @@ -233,61 +234,93 @@ def find_qipan():
del cam
gc.collect()

def blob_is_valid(b):
is_valid = True
if b.area() > 3000: # 认为面积大于3000时无效,受距离影响
is_valid = False
if b.w() > 55 or b.h() > 55: # 过滤过大的宽高
is_valid = False
return is_valid

def find_qizi():
debug = True
black_area_max_th = 6000
cam = camera.Camera(320, 320, fps = 60) # fps can set to 80
area_threshold = 300
pixels_threshold = 300
threshold_red = [[40, 60, 33, 53, -10, 10]]
threshold_black = [[0, 20, -128, 127, -128, 127]]
threshold_white = [[60, 100, -11, 21, -43, -13]]

cam = camera.Camera(320, 320, fps = 60) # fps can set to 80
# cam.constrast(50)
# cam.saturation(0)
last_pointer = [0, 0]
last_string = ""
# 1. 点击触摸屏获取棋子的LAB值,根据LAB值获取一个合适的阈值
# 2. 在white_thresholds(白棋阈值)或white_thresholds(黑棋阈值)中增加、或者合并新的阈值。阈值越多执行越慢,也可以自行编写逻辑记录上次的阈值来提高速度
# 3. 设置area_threshold和pixels_threshold过滤一些较小的色块
# 4. 自定义blob_is_valid来添加其他过滤规则
# 5. 添加roi来过滤不需要检测的部分,降低环境复杂程度
# 注意:环境太暗时无法识别黑棋有可能是因为棋子离黑线过近,棋子的影子和黑线连在一起被认为时一个色块,导致该棋子被过滤了。建议保证室内有均匀的光线
white_thresholds = [[63,96,-13,17,8,56], [60,94,-16,14,-25,23]]
black_thresholds = [[9,45,-15,18,-20,19], [16,46,3,33,3,33], [14,44,-16,25,-13,36], [0,33,-15,16,-19,13]]
white_blobs = None
black_blobs = None
roi = [5, 5, cam.width() - 10, cam.height() - 10] # 配置roi范围
while mode == 3:
img = cam.read()

# 软件畸变矫正,速度比较慢,建议直接买无畸变摄像头(Sipeed 官方淘宝点询问)
# img = img.lens_corr(strength=1.5)

blobs = img.find_blobs(threshold_red, roi=[1,1,img.width()-1, img.height()-1], x_stride=2, y_stride=1, area_threshold=area_threshold, pixels_threshold=pixels_threshold)
for b in blobs:
corners = b.mini_corners()
for i in range(4):
img.draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image.COLOR_YELLOW, 2)
blobs = img.find_blobs(threshold_black, roi=[1,1,img.width()-1, img.height()-1], x_stride=2, y_stride=1, area_threshold=area_threshold, pixels_threshold=pixels_threshold)
for b in blobs:
corners = b.mini_corners()
if b.area() < black_area_max_th: # 过滤掉棋盘,认为area大于800时是棋盘,根据实际值调节
enclosing_circle = b.enclosing_circle()
img.draw_circle(enclosing_circle[0], enclosing_circle[1], enclosing_circle[2], image.COLOR_GREEN, 2)
else:
print("black area:", b.area())
blobs = img.find_blobs(threshold_white, roi=[1,1,img.width()-1, img.height()-1], x_stride=2, y_stride=1, area_threshold=area_threshold, pixels_threshold=pixels_threshold)
for b in blobs:
corners = b.mini_corners()
enclosing_circle = b.enclosing_circle()
img.draw_circle(enclosing_circle[0], enclosing_circle[1], enclosing_circle[2], image.COLOR_RED, 2)

if debug:
# 左下画红色二值化图
binary = img.binary(threshold_red, copy=True)
binary1 = binary.resize(img.width() // 4, img.height() // 4)

# 右下画黑色二值化图
binary = img.binary(threshold_black, copy=True)
binary2 = binary.resize(img.width() // 4, img.height() // 4)


# 右上画白色二值化图
binary = img.binary(threshold_white, copy=True)
binary3 = binary.resize(img.width() // 4, img.height() // 4)

img.draw_image(0, img.height() - binary1.height(), binary1)
img.draw_image(img.width() - binary2.width(), img.height() - binary2.height(), binary2)
img.draw_image(img.width() - binary3.width(), 0, binary3)

# 检测黑/白棋子
for threshold in black_thresholds:
black_blobs = img.find_blobs([threshold], roi = roi, area_threshold = area_threshold, pixels_threshold = pixels_threshold)
valid_cnt = 0
# 检查有效数量大于等于5,则认为阈值有效,否则使用下一个阈值
for b in black_blobs:
if blob_is_valid(b):
valid_cnt += 1
if valid_cnt >= 5:
break

for threshold in white_thresholds:
white_blobs = img.find_blobs([threshold], area_threshold = area_threshold, pixels_threshold = pixels_threshold)
valid_cnt = 0
# 检查有效数量大于等于5,则认为阈值有效,否则使用下一个阈值
for b in white_blobs:
if blob_is_valid(b):
valid_cnt += 1
if valid_cnt >= 5:
break

# 检测完成后打印黑/白棋子位置,在这里做逻辑处理
if black_blobs:
for b in black_blobs:
if blob_is_valid(b):
corners = b.mini_corners()
for i in range(4):
img.draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image.COLOR_GREEN, 2)

if white_blobs:
for b in white_blobs:
if blob_is_valid(b):
corners = b.mini_corners()
for i in range(4):
img.draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image.COLOR_BLUE, 2)

# 点击屏幕,获取当前lab值,并给出一个预测值。TODO:坐标映射有BUG,以屏幕绿点位置为准
t = ts.read()
x, y, touch = t[0], t[1], t[2]
if touch:
cam_w = cam.width()
cam_h = cam.height()
disp_w = disp.width()
disp_h = disp.height()
x += ((cam_w - img.width()) / 2)
y += ((cam_h - img.height()) / 2)
real_x = int(x * cam_w / disp_w)
real_y = int(y * cam_h / disp_h)
last_pointer[0] = real_x
last_pointer[1] = real_y
rgb = img.get_pixel(real_x, real_y, True)
l, a, b = rgb888_to_lab(rgb[0], rgb[1], rgb[2])
last_string = f"l:{l}, a:{a}, b:{b}, try [{max(l-15, 0)},{min(l+15, 100)},{max(a-15, -128)},{min(a+15, 127)},{max(b-15, -128)},{min(b+15, 127)}]"
print(last_string)
img.draw_circle(last_pointer[0], last_pointer[1], 3, image.COLOR_GREEN, -1)
img.draw_string(0, 10, last_string, image.COLOR_RED)

# 画出roi区域
img.draw_rect(roi[0], roi[1], roi[2], roi[3], image.COLOR_YELLOW, 2)
disp.show(img)
check_mode_switch(img, disp.width(), disp.height())
disp.show(img)
del cam
Expand Down
118 changes: 118 additions & 0 deletions projects/demo_tic_tac_toe/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import math

COLOR_L_MAX = 100
COLOR_L_MIN = 0
COLOR_A_MAX = 127
COLOR_A_MIN = -128
COLOR_B_MAX = 127
COLOR_B_MIN = -128

xyz_table = [
0.000000, 0.030353, 0.060705, 0.091058, 0.121411, 0.151763, 0.182116, 0.212469,
0.242822, 0.273174, 0.303527, 0.334654, 0.367651, 0.402472, 0.439144, 0.477695,
0.518152, 0.560539, 0.604883, 0.651209, 0.699541, 0.749903, 0.802319, 0.856813,
0.913406, 0.972122, 1.032982, 1.096009, 1.161225, 1.228649, 1.298303, 1.370208,
1.444384, 1.520851, 1.599629, 1.680738, 1.764195, 1.850022, 1.938236, 2.028856,
2.121901, 2.217388, 2.315337, 2.415763, 2.518686, 2.624122, 2.732089, 2.842604,
2.955683, 3.071344, 3.189603, 3.310477, 3.433981, 3.560131, 3.688945, 3.820437,
3.954624, 4.091520, 4.231141, 4.373503, 4.518620, 4.666509, 4.817182, 4.970657,
5.126946, 5.286065, 5.448028, 5.612849, 5.780543, 5.951124, 6.124605, 6.301002,
6.480327, 6.662594, 6.847817, 7.036010, 7.227185, 7.421357, 7.618538, 7.818742,
8.021982, 8.228271, 8.437621, 8.650046, 8.865559, 9.084171, 9.305896, 9.530747,
9.758735, 9.989873, 10.224173, 10.461648, 10.702310, 10.946171, 11.193243, 11.443537,
11.697067, 11.953843, 12.213877, 12.477182, 12.743768, 13.013648, 13.286832, 13.563333,
13.843162, 14.126329, 14.412847, 14.702727, 14.995979, 15.292615, 15.592646, 15.896084,
16.202938, 16.513219, 16.826940, 17.144110, 17.464740, 17.788842, 18.116424, 18.447499,
18.782077, 19.120168, 19.461783, 19.806932, 20.155625, 20.507874, 20.863687, 21.223076,
21.586050, 21.952620, 22.322796, 22.696587, 23.074005, 23.455058, 23.839757, 24.228112,
24.620133, 25.015828, 25.415209, 25.818285, 26.225066, 26.635560, 27.049779, 27.467731,
27.889426, 28.314874, 28.744084, 29.177065, 29.613827, 30.054379, 30.498731, 30.946892,
31.398871, 31.854678, 32.314321, 32.777810, 33.245154, 33.716362, 34.191442, 34.670406,
35.153260, 35.640014, 36.130678, 36.625260, 37.123768, 37.626212, 38.132601, 38.642943,
39.157248, 39.675523, 40.197778, 40.724021, 41.254261, 41.788507, 42.326767, 42.869050,
43.415364, 43.965717, 44.520119, 45.078578, 45.641102, 46.207700, 46.778380, 47.353150,
47.932018, 48.514994, 49.102085, 49.693300, 50.288646, 50.888132, 51.491767, 52.099557,
52.711513, 53.327640, 53.947949, 54.572446, 55.201140, 55.834039, 56.471151, 57.112483,
57.758044, 58.407842, 59.061884, 59.720179, 60.382734, 61.049557, 61.720656, 62.396039,
63.075714, 63.759687, 64.447968, 65.140564, 65.837482, 66.538730, 67.244316, 67.954247,
68.668531, 69.387176, 70.110189, 70.837578, 71.569350, 72.305513, 73.046074, 73.791041,
74.540421, 75.294222, 76.052450, 76.815115, 77.582222, 78.353779, 79.129794, 79.910274,
80.695226, 81.484657, 82.278575, 83.076988, 83.879901, 84.687323, 85.499261, 86.315721,
87.136712, 87.962240, 88.792312, 89.626935, 90.466117, 91.309865, 92.158186, 93.011086,
93.868573, 94.730654, 95.597335, 96.468625, 97.344529, 98.225055, 99.110210, 100.000000
]

def fast_cbrtf(x):
return x ** (1.0 / 3.0)

def fast_floorf(x):
return math.floor(x)

def rgb888_to_l(r, g, b):
r_lin = xyz_table[r]
g_lin = xyz_table[g]
b_lin = xyz_table[b]

y = ((r_lin * 0.2126) + (g_lin * 0.7152) + (b_lin * 0.0722)) * (1.0 / 100.0)

if y > 0.008856:
y = fast_cbrtf(y)
else:
y = (y * 7.787037) + 0.137931

L = fast_floorf(116 * y) - 16
L = max(min(L, COLOR_L_MAX), COLOR_L_MIN)

return int(L)

def rgb888_to_a(r, g, b):
r_lin = xyz_table[r]
g_lin = xyz_table[g]
b_lin = xyz_table[b]

x = ((r_lin * 0.4124) + (g_lin * 0.3576) + (b_lin * 0.1805)) * (1.0 / 95.047)
y = ((r_lin * 0.2126) + (g_lin * 0.7152) + (b_lin * 0.0722)) * (1.0 / 100.0)

if x > 0.008856:
x = fast_cbrtf(x)
else:
x = (x * 7.787037) + 0.137931

if y > 0.008856:
y = fast_cbrtf(y)
else:
y = (y * 7.787037) + 0.137931

a = fast_floorf(500 * (x - y))
a = max(min(a, COLOR_A_MAX), COLOR_A_MIN)

return int(a)

def rgb888_to_b(r, g, b):
r_lin = xyz_table[r]
g_lin = xyz_table[g]
b_lin = xyz_table[b]

y = ((r_lin * 0.2126) + (g_lin * 0.7152) + (b_lin * 0.0722)) * (1.0 / 100.0)
z = ((r_lin * 0.0193) + (g_lin * 0.1192) + (b_lin * 0.9505)) * (1.0 / 108.883)

if y > 0.008856:
y = fast_cbrtf(y)
else:
y = (y * 7.787037) + 0.137931

if z > 0.008856:
z = fast_cbrtf(z)
else:
z = (z * 7.787037) + 0.137931

b = fast_floorf(200 * (y - z))
b = max(min(b, COLOR_B_MAX), COLOR_B_MIN)

return int(b)

def rgb888_to_lab(r, g, b):
l = rgb888_to_l(r, g, b)
a = rgb888_to_a(r, g, b)
b = rgb888_to_b(r, g, b)
return l, a, b

0 comments on commit cd0404f

Please sign in to comment.