Files
archery/test/test_laser_center_point.py

542 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
激光中心点检测单元测试(单文件,无项目依赖)
直接使用 maix 标准库,实现红色激光点坐标检测
运行方式:
python3 test/test_laser_center_point.py
Ctrl+C 退出,按 s 保存截图
"""
from maix import camera, display, image, time, app, uart, pinmap
import os
import struct
import select
_USE_CV = False
try:
import cv2
import numpy as np
_USE_CV = True
except ImportError:
pass
WIDTH = 640
HEIGHT = 480
THRESHOLD = 140
SEARCH_RADIUS = 50
def read_key_ev():
"""非阻塞读取 /dev/input/event0 按键(返回 key_code 或 -1"""
try:
r, _, _ = select.select([_key_fd], [], [], 0)
if r:
event = _key_fd.read(16)
if len(event) == 16:
_, _, etype, code, value = struct.unpack("IIHHI", event)
if etype == 1 and value == 1:
return code
except Exception:
pass
return -1
def find_ellipse(img_cv, cx, cy, roi_r, th):
x1 = max(0, cx - roi_r)
x2 = min(WIDTH, cx + roi_r)
y1 = max(0, cy - roi_r)
y2 = min(HEIGHT, cy + roi_r)
roi = img_cv[y1:y2, x1:x2]
if roi.size == 0:
return None
r = roi[:, :, 0].astype(np.int32)
g = roi[:, :, 1].astype(np.int32)
b = roi[:, :, 2].astype(np.int32)
mask = (r > th) & (r > g * 1.5) & (r > b * 1.5)
oe = (r > 200) & (g > 200) & (b > 200) & (r >= g) & (r >= b) & ((r - g) > 10) & ((r - b) > 10)
combined = (mask | oe).astype(np.uint8) * 255
contours, _ = cv2.findContours(combined, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None
largest = max(contours, key=cv2.contourArea)
if cv2.contourArea(largest) < 5:
return None
cnt = largest.copy()
for pt in cnt:
pt[0][0] += x1
pt[0][1] += y1
if len(cnt) >= 5:
(ex, ey), (ew, eh), ang = cv2.fitEllipse(cnt)
mask_ellipse = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
cv2.ellipse(mask_ellipse, (int(ex), int(ey)), (int(ew / 2), int(eh / 2)), ang, 0, 360, 255, -1)
brightness = img_cv[:, :, 0].astype(np.int32) + img_cv[:, :, 1].astype(np.int32) + img_cv[:, :, 2].astype(np.int32)
masked = np.where(mask_ellipse > 0, brightness, 0)
vals = masked[masked > 0]
if len(vals) > 0:
bth = np.percentile(vals, 90)
bmask = (masked >= bth).astype(np.uint8) * 255
bcontours, _ = cv2.findContours(bmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if bcontours:
blargest = max(bcontours, key=cv2.contourArea)
if cv2.contourArea(blargest) >= 3 and len(blargest) >= 5:
(ix, iy), _, _ = cv2.fitEllipse(blargest)
return (float(ix), float(iy))
M = cv2.moments(blargest)
if M["m00"] > 0:
return (float(M["m10"] / M["m00"]), float(M["m01"] / M["m00"]))
return (float(ex), float(ey))
M = cv2.moments(cnt)
if M["m00"] > 0:
return (float(M["m10"] / M["m00"]), float(M["m01"] / M["m00"]))
return None
def find_brightest(img_cv, cx, cy, roi_r, th):
x1 = max(0, cx - roi_r)
x2 = min(WIDTH, cx + roi_r)
y1 = max(0, cy - roi_r)
y2 = min(HEIGHT, cy + roi_r)
best_score = 0
best_pos = None
for y in range(y1, y2):
for x in range(x1, x2):
r, g, b = int(img_cv[y, x, 0]), int(img_cv[y, x, 1]), int(img_cv[y, x, 2])
is_red = (r > th and r > g * 1.5 and r > b * 1.5)
is_oe = (r > 200 and g > 200 and b > 200 and r >= g and r >= b and (r - g) > 10 and (r - b) > 10)
if is_red or is_oe:
score = r + g + b
dx, dy = x - cx, y - cy
dist = (dx * dx + dy * dy) ** 0.5
score *= max(0.5, 1.0 - (dist / roi_r) * 0.5)
if score > best_score:
best_score = score
best_pos = (float(x), float(y))
return best_pos
# 打开键盘输入设备
_key_fd = None
try:
_key_fd = open("/dev/input/event0", "rb")
except Exception:
try:
_key_fd = open("/dev/input/event1", "rb")
except Exception:
_key_fd = None
print("=" * 50)
print("激光中心点检测单元测试")
print("=" * 50)
print()
cam = camera.Camera(WIDTH, HEIGHT)
disp = display.Display()
print("[OK] 摄像头和显示初始化完成")
# 初始化激光串口
_laser_on = False
_laser_uart = None
try:
pinmap.set_pin_function("A18", "UART1_RX")
pinmap.set_pin_function("A19", "UART1_TX")
_laser_uart = uart.UART("/dev/ttyS1", 9600)
_laser_uart.read(-1)
print("[OK] 激光串口初始化完成")
except Exception as e:
print(f"[WARN] 激光串口初始化失败: {e}")
LASER_ON = bytes([0xAA, 0x00, 0x01, 0xBE, 0x00, 0x01, 0x00, 0x01, 0xC1])
LASER_OFF = bytes([0xAA, 0x00, 0x01, 0xBE, 0x00, 0x01, 0x00, 0x00, 0xC0])
# 默认开启激光
if _laser_uart:
try:
_laser_uart.write(LASER_ON)
time.sleep_ms(50)
_laser_uart.read(-1)
_laser_on = True
print("[OK] 激光已开启")
except Exception as e:
print(f"[WARN] 开启激光失败: {e}")
print()
pos_ellipse = None
pos_bright = None
frame_count = 0
use_ellipse = True
while not app.need_exit():
frame = cam.read()
if frame is None:
time.sleep_ms(10)
continue
frame_count += 1
if _USE_CV:
img_cv = image.image2cv(frame, False, False)
cx, cy = WIDTH // 2, HEIGHT // 2
t0 = time.ticks_ms()
pos_ellipse = find_ellipse(img_cv, cx, cy, SEARCH_RADIUS, THRESHOLD)
t1 = time.ticks_ms()
pos_bright = find_brightest(img_cv, cx, cy, SEARCH_RADIUS, THRESHOLD)
t2 = time.ticks_ms()
dt_e = abs(time.ticks_diff(t0, t1))
dt_b = abs(time.ticks_diff(t1, t2))
if frame_count % 5 == 0:
e_str = f"({pos_ellipse[0]:.1f},{pos_ellipse[1]:.1f})" if pos_ellipse else "None"
b_str = f"({pos_bright[0]:.1f},{pos_bright[1]:.1f})" if pos_bright else "None"
print(f"[LASER] ellipse={e_str} ({dt_e}ms) brightest={b_str} ({dt_b}ms) "
f"th={THRESHOLD} radius={SEARCH_RADIUS}")
# 叠加显示
pos = pos_ellipse if use_ellipse else pos_bright
h, w = img_cv.shape[:2]
cv2.circle(img_cv, (cx, cy), SEARCH_RADIUS, (0, 255, 0), 1)
cv2.circle(img_cv, (cx, cy), 2, (0, 255, 0), -1)
if pos:
x, y = int(pos[0]), int(pos[1])
cv2.circle(img_cv, (x, y), 6, (0, 0, 255), 2)
cv2.line(img_cv, (x - 14, y), (x + 14, y), (0, 0, 255), 1)
cv2.line(img_cv, (x, y - 14), (x, y + 14), (0, 0, 255), 1)
cv2.putText(img_cv, f"({x},{y})", (x + 10, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
info = [
f"pos={pos if pos else 'None'}",
f"method={'ellipse' if use_ellipse else 'brightest'} th={THRESHOLD}",
f"laser={'ON' if _laser_on else 'OFF'}",
]
for i, line in enumerate(info):
cv2.putText(img_cv, line, (8, 20 + i * 22),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)
display_frame = image.cv2image(img_cv, False, False)
else:
display_frame = frame
disp.show(display_frame)
# 按键处理(非阻塞)
key = read_key_ev()
if key > 0:
c = chr(key & 0xFF) if key < 256 else ""
if key == 113 or key == 81 or key == 0x1b: # q/Q/ESC
break
if c == "e" or key == 18: # e
use_ellipse = not use_ellipse
print(f"[KEY] Method: {'ellipse' if use_ellipse else 'brightest'}")
if c == "l" or key == 12: # l
_laser_on = not _laser_on
if _laser_uart:
try:
_laser_uart.write(LASER_ON if _laser_on else LASER_OFF)
time.sleep_ms(30)
_laser_uart.read(-1)
print(f"[KEY] Laser: {'ON' if _laser_on else 'OFF'}")
except Exception as e:
print(f"[KEY] Laser error: {e}")
else:
print("[KEY] Laser UART not available")
time.sleep_ms(30)
# 关闭激光
if _laser_on and _laser_uart:
try:
_laser_uart.write(LASER_OFF)
_laser_uart.read(-1)
print("[EXIT] 激光已关闭")
except Exception:
pass
print("[EXIT] 测试结束")
if _key_fd:
_key_fd.close()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
激光中心点检测单元测试(单文件,无项目依赖)
直接使用 maix 标准库,实现红色激光点坐标检测
运行方式:
python3 test/test_laser_center_point.py
Ctrl+C 退出,按 s 保存截图
"""
from maix import camera, display, image, time, app, uart, pinmap
import os
import struct
import select
_USE_CV = False
try:
import cv2
import numpy as np
_USE_CV = True
except ImportError:
pass
WIDTH = 640
HEIGHT = 480
THRESHOLD = 120
RED_RATIO = 1.3
SEARCH_RADIUS = 60
def read_key_ev():
"""非阻塞读取 /dev/input/event0 按键(返回 key_code 或 -1"""
try:
r, _, _ = select.select([_key_fd], [], [], 0)
if r:
event = _key_fd.read(16)
if len(event) == 16:
_, _, etype, code, value = struct.unpack("IIHHI", event)
if etype == 1 and value == 1:
return code
except Exception:
pass
return -1
def find_ellipse(img_cv, cx, cy, roi_r, th, ratio):
x1 = max(0, cx - roi_r)
x2 = min(WIDTH, cx + roi_r)
y1 = max(0, cy - roi_r)
y2 = min(HEIGHT, cy + roi_r)
roi = img_cv[y1:y2, x1:x2]
if roi.size == 0:
return None
r = roi[:, :, 0].astype(np.int32)
g = roi[:, :, 1].astype(np.int32)
b = roi[:, :, 2].astype(np.int32)
mask = (r > th) & (r > g * ratio) & (r > b * ratio)
oe = (r > 200) & (g > 200) & (b > 200) & (r >= g) & (r >= b) & ((r - g) > 10) & ((r - b) > 10)
combined = (mask | oe).astype(np.uint8) * 255
contours, _ = cv2.findContours(combined, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None
largest = max(contours, key=cv2.contourArea)
if cv2.contourArea(largest) < 5:
return None
cnt = largest.copy()
for pt in cnt:
pt[0][0] += x1
pt[0][1] += y1
if len(cnt) >= 5:
(ex, ey), (ew, eh), ang = cv2.fitEllipse(cnt)
mask_ellipse = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
cv2.ellipse(mask_ellipse, (int(ex), int(ey)), (int(ew / 2), int(eh / 2)), ang, 0, 360, 255, -1)
brightness = img_cv[:, :, 0].astype(np.int32) + img_cv[:, :, 1].astype(np.int32) + img_cv[:, :, 2].astype(np.int32)
masked = np.where(mask_ellipse > 0, brightness, 0)
vals = masked[masked > 0]
if len(vals) > 0:
bth = np.percentile(vals, 90)
bmask = (masked >= bth).astype(np.uint8) * 255
bcontours, _ = cv2.findContours(bmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if bcontours:
blargest = max(bcontours, key=cv2.contourArea)
if cv2.contourArea(blargest) >= 3 and len(blargest) >= 5:
(ix, iy), _, _ = cv2.fitEllipse(blargest)
return (float(ix), float(iy))
M = cv2.moments(blargest)
if M["m00"] > 0:
return (float(M["m10"] / M["m00"]), float(M["m01"] / M["m00"]))
return (float(ex), float(ey))
M = cv2.moments(cnt)
if M["m00"] > 0:
return (float(M["m10"] / M["m00"]), float(M["m01"] / M["m00"]))
return None
def find_brightest_bytes(frame, cx, cy, roi_r, th, ratio):
"""使用 frame.to_bytes() 两阶段搜索,避免 cv2 转换"""
x1 = max(0, cx - roi_r)
x2 = min(WIDTH, cx + roi_r)
y1 = max(0, cy - roi_r)
y2 = min(HEIGHT, cy + roi_r)
data = frame.to_bytes()
best_score = 0
best_pos = None
# 第一阶段:隔点粗搜
for y in range(y1, y2, 2):
for x in range(x1, x2, 2):
idx = (y * WIDTH + x) * 3
r = data[idx]; g = data[idx+1]; b = data[idx+2]
if (r > th and r > g * ratio and r > b * ratio) or \
(r > 200 and g > 200 and b > 200 and r >= g and r >= b and (r - g) > 10 and (r - b) > 10):
score = r + g + b
dx = x - cx; dy = y - cy
score *= max(0.5, 1.0 - ((dx*dx + dy*dy) ** 0.5 / roi_r) * 0.5)
if score > best_score:
best_score = score
best_pos = (x, y)
if best_pos is None:
return None
# 第二阶段:候选点 7x7 精细搜索
fx, fy = best_pos
x1f = max(0, fx - 3); x2f = min(WIDTH, fx + 4)
y1f = max(0, fy - 3); y2f = min(HEIGHT, fy + 4)
best_bright = 0
final_pos = best_pos
for y in range(y1f, y2f):
for x in range(x1f, x2f):
idx = (y * WIDTH + x) * 3
r = data[idx]; g = data[idx+1]; b = data[idx+2]
if (r > th and r > g * ratio and r > b * ratio) or \
(r > 200 and g > 200 and b > 200 and r >= g and r >= b and (r - g) > 10 and (r - b) > 10):
rgb_sum = r + g + b
if rgb_sum > best_bright:
best_bright = rgb_sum
final_pos = (float(x), float(y))
return final_pos
# 打开键盘输入设备
_key_fd = None
try:
_key_fd = open("/dev/input/event0", "rb")
except Exception:
try:
_key_fd = open("/dev/input/event1", "rb")
except Exception:
_key_fd = None
print("=" * 50)
print("激光中心点检测单元测试")
print("=" * 50)
print()
cam = camera.Camera(WIDTH, HEIGHT)
disp = display.Display()
print("[OK] 摄像头和显示初始化完成")
# 初始化激光串口
_laser_on = False
_laser_uart = None
try:
pinmap.set_pin_function("A18", "UART1_RX")
pinmap.set_pin_function("A19", "UART1_TX")
_laser_uart = uart.UART("/dev/ttyS1", 9600)
_laser_uart.read(-1)
print("[OK] 激光串口初始化完成")
except Exception as e:
print(f"[WARN] 激光串口初始化失败: {e}")
LASER_ON = bytes([0xAA, 0x00, 0x01, 0xBE, 0x00, 0x01, 0x00, 0x01, 0xC1])
LASER_OFF = bytes([0xAA, 0x00, 0x01, 0xBE, 0x00, 0x01, 0x00, 0x00, 0xC0])
# 默认开启激光
if _laser_uart:
try:
_laser_uart.write(LASER_ON)
time.sleep_ms(50)
_laser_uart.read(-1)
_laser_on = True
print("[OK] 激光已开启")
except Exception as e:
print(f"[WARN] 开启激光失败: {e}")
print()
pos_ellipse = None
pos_bright = None
frame_count = 0
use_ellipse = True
while not app.need_exit():
frame = cam.read()
if frame is None:
time.sleep_ms(10)
continue
frame_count += 1
cx, cy = WIDTH // 2, HEIGHT // 2
t0 = time.ticks_ms()
pos_bright = find_brightest_bytes(frame, cx, cy, SEARCH_RADIUS, THRESHOLD, RED_RATIO)
t1 = time.ticks_ms()
pos_ellipse = None
if _USE_CV:
img_cv = image.image2cv(frame, False, False)
t2 = time.ticks_ms()
pos_ellipse = find_ellipse(img_cv, cx, cy, SEARCH_RADIUS, THRESHOLD, RED_RATIO)
t3 = time.ticks_ms()
else:
img_cv = None
t3 = t2 = t1
dt_b = abs(time.ticks_diff(t0, t1))
dt_e = abs(time.ticks_diff(t2, t3))
if frame_count % 5 == 0:
e_str = f"({pos_ellipse[0]:.1f},{pos_ellipse[1]:.1f})" if pos_ellipse else "None"
b_str = f"({pos_bright[0]:.1f},{pos_bright[1]:.1f})" if pos_bright else "None"
print(f"[LASER] ellipse={e_str} ({dt_e}ms) brightest={b_str} ({dt_b}ms) "
f"th={THRESHOLD} ratio={RED_RATIO} radius={SEARCH_RADIUS}")
pos = pos_ellipse if use_ellipse else pos_bright
if img_cv is not None:
cv2.circle(img_cv, (cx, cy), SEARCH_RADIUS, (0, 255, 0), 1)
cv2.circle(img_cv, (cx, cy), 2, (0, 255, 0), -1)
if pos:
x, y = int(pos[0]), int(pos[1])
cv2.circle(img_cv, (x, y), 6, (0, 0, 255), 2)
cv2.line(img_cv, (x - 14, y), (x + 14, y), (0, 0, 255), 1)
cv2.line(img_cv, (x, y - 14), (x, y + 14), (0, 0, 255), 1)
cv2.putText(img_cv, f"({x},{y})", (x + 10, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
info = [
f"pos={pos if pos else 'None'}",
f"method={'ellipse' if use_ellipse else 'brightest'} th={THRESHOLD} ratio={RED_RATIO}",
f"laser={'ON' if _laser_on else 'OFF'}",
]
for i, line in enumerate(info):
cv2.putText(img_cv, line, (8, 20 + i * 22),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)
display_frame = image.cv2image(img_cv, False, False)
else:
display_frame = frame
disp.show(display_frame)
# 按键处理(非阻塞)
key = read_key_ev()
if key > 0:
c = chr(key & 0xFF) if key < 256 else ""
if key == 113 or key == 81 or key == 0x1b: # q/Q/ESC
break
if c == "e" or key == 18: # e
use_ellipse = not use_ellipse
print(f"[KEY] Method: {'ellipse' if use_ellipse else 'brightest'}")
if c == "l" or key == 12: # l
_laser_on = not _laser_on
if _laser_uart:
try:
_laser_uart.write(LASER_ON if _laser_on else LASER_OFF)
time.sleep_ms(30)
_laser_uart.read(-1)
print(f"[KEY] Laser: {'ON' if _laser_on else 'OFF'}")
except Exception as e:
print(f"[KEY] Laser error: {e}")
else:
print("[KEY] Laser UART not available")
time.sleep_ms(30)
# 关闭激光
if _laser_on and _laser_uart:
try:
_laser_uart.write(LASER_OFF)
_laser_uart.read(-1)
print("[EXIT] 激光已关闭")
except Exception:
pass
print("[EXIT] 测试结束")
if _key_fd:
_key_fd.close()