import time, os, math
from media.sensor import *
from media.display import *
from media.media import *
from machine import FPIOA, PWM, Pin
from machine import UART
import gc
图像参数
IMAGE_WIDTH = 352
IMAGE_HEIGHT = 240
SENSOR_ID = 2
sensor = None
THRESHOLD = (77, 206) # 二值化阈值
显示模式设置
DISPLAY_MODE = “LCD” # 可选 “VIRT”, “LCD”, “HDMI”
根据显示模式设置分辨率
if DISPLAY_MODE == “VIRT”:
DISPLAY_WIDTH, DISPLAY_HEIGHT = 1920, 1080
elif DISPLAY_MODE == “LCD”:
DISPLAY_WIDTH, DISPLAY_HEIGHT = 800, 480
elif DISPLAY_MODE == “HDMI”:
DISPLAY_WIDTH, DISPLAY_HEIGHT = 1920, 1080
else:
raise ValueError(“无效的显示模式”)
角点检测阈值
CORNERS_THRESHOLD = 5000
透视变换目标尺寸
PERSPECTIVE_SIZE = 200 # 变换后的矩形大小
矩形过滤参数
MIN_AREA_RATIO = 0.01 # 最小面积占图像比例
MAX_AREA_RATIO = 0.8 # 最大面积占图像比例
ASPECT_RATIO_MIN = 0.5 # 最小宽高比
ASPECT_RATIO_MAX = 2.0 # 最大宽高比
COS_THRESHOLD = 0.3 # 角度余弦阈值(绝对值)
fpioa = FPIOA()
fpioa.set_function(11, FPIOA.UART2_TXD)
fpioa.set_function(12, FPIOA.UART2_RXD)
uart = UART(UART.UART2, 115200, 8, 0, 1, timeout=1000)
def send_coordinates(center):
“”“通过串口发送坐标数据”“”
if center:
cx, cy = center
# 协议格式: $X[坐标x]Y[坐标y]\n
data_str = f"$X{cx:04d}Y{cy:04d}\n"
uart.write(data_str.encode())
else:
uart.write(b"$X9999Y9999\n") # 无效坐标标志
def sort_corners(corners):
“”“对矩形角点进行排序(左上、右上、右下、左下)”“”
if len(corners) != 4:
return corners
# 计算中心点 center_x = sum(c[0] for c in corners) / 4 center_y = sum(c[1] for c in corners) / 4 # 分类角点 top_left = min(corners, key=lambda c: (c[0] - center_x)**2 + (c[1] - center_y)**2 if c[0] < center_x and c[1] < center_y else float('inf')) top_right = min(corners, key=lambda c: (c[0] - center_x)**2 + (c[1] - center_y)**2 if c[0] > center_x and c[1] < center_y else float('inf')) bottom_right = min(corners, key=lambda c: (c[0] - center_x)**2 + (c[1] - center_y)**2 if c[0] > center_x and c[1] > center_y else float('inf')) bottom_left = min(corners, key=lambda c: (c[0] - center_x)**2 + (c[1] - center_y)**2 if c[0] < center_x and c[1] > center_y else float('inf')) return [top_left, top_right, bottom_right, bottom_left]
def matrix_multiply(A, B):
“”“3x3矩阵乘法”“”
result = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
for i in range(3):
for j in range(3):
for k in range(3):
result[i][j] += A[i][k] * B[k][j]
return result
def matrix_vector_multiply(M, v):
“”“矩阵向量乘法”“”
result = [0, 0, 0]
for i in range(3):
for j in range(3):
result[i] += M[i][j] * v[j]
return result
def matrix_inverse(M):
“”“3x3矩阵求逆”“”
det = (M[0][0] * (M[1][1]*M[2][2] - M[1][2]*M[2][1]) -
M[0][1] * (M[1][0]*M[2][2] - M[1][2]*M[2][0]) +
M[0][2] * (M[1][0]*M[2][1] - M[1][1]*M[2][0]))
if abs(det) < 1e-10: # 行列式接近0,不可逆 return [[1, 0, 0], [0, 1, 0], [0, 0, 1]] inv_det = 1.0 / det inv = [ [(M[1][1]*M[2][2] - M[2][1]*M[1][2]) * inv_det, (M[0][2]*M[2][1] - M[0][1]*M[2][2]) * inv_det, (M[0][1]*M[1][2] - M[0][2]*M[1][1]) * inv_det], [(M[1][2]*M[2][0] - M[1][0]*M[2][2]) * inv_det, (M[0][0]*M[2][2] - M[0][2]*M[2][0]) * inv_det, (M[1][0]*M[0][2] - M[0][0]*M[1][2]) * inv_det], [(M[1][0]*M[2][1] - M[2][0]*M[1][1]) * inv_det, (M[2][0]*M[0][1] - M[0][0]*M[2][1]) * inv_det, (M[0][0]*M[1][1] - M[1][0]*M[0][1]) * inv_det] ] return inv
def calculate_perspective_transform(src_points):
“”"
计算透视变换矩阵
src_points: 源图像中的四个点 [左上, 右上, 右下, 左下]
返回: 变换矩阵和逆变换矩阵
“”"
# 目标点 (校正后的正方形)
dst_points = [
[0, 0],
[PERSPECTIVE_SIZE, 0],
[PERSPECTIVE_SIZE, PERSPECTIVE_SIZE],
[0, PERSPECTIVE_SIZE]
]
# 构建A矩阵 A = [] for i in range(4): x, y = src_points[i] u, v = dst_points[i] A.append([x, y, 1, 0, 0, 0, -u*x, -u*y]) A.append([0, 0, 0, x, y, 1, -v*x, -v*y]) # 构建b向量 b = [] for i in range(4): u, v = dst_points[i] b.append(u) b.append(v) # 解线性方程组 (使用最小二乘法简化) h = [0] * 8 for i in range(8): for j in range(8): # 计算A^T * A ata = 0 for k in range(8): ata += A[k][i] * A[k][j] # 计算A^T * b atb = 0 for k in range(8): atb += A[k][i] * b[k] # 简单求解 if abs(ata) > 1e-10: h[i] = atb / ata # 构造变换矩阵 H = [ [h[0], h[1], h[2]], [h[3], h[4], h[5]], [h[6], h[7], 1] ] # 计算逆矩阵 H_inv = matrix_inverse(H) return H, H_inv
def apply_perspective_transform(point, H):
“”“应用透视变换到单个点”“”
x, y = point
src = [x, y, 1]
dst = matrix_vector_multiply(H, src)
if abs(dst[2]) > 1e-10:
dst = [dst[0]/dst[2], dst[1]/dst[2]]
return dst
def calculate_center(corners):
“”“计算矩形中心点(使用透视校正)”“”
if len(corners) < 4:
return None
# 计算几何中心(原始中心) raw_center_x = sum(c[0] for c in corners) / len(corners) raw_center_y = sum(c[1] for c in corners) / len(corners) # 计算透视变换 try: H, H_inv = calculate_perspective_transform(corners) # 将中心点映射到透视校正后的空间 corrected_center = apply_perspective_transform((raw_center_x, raw_center_y), H) # 将校正后的中心点映射回原始图像空间 final_center = apply_perspective_transform(corrected_center, H_inv) return (int(final_center[0]), int(final_center[1])) except Exception as e: # 计算失败时使用原始中心 return (int(raw_center_x), int(raw_center_y))
def is_valid_rectangle(corners, cos_threshold=COS_THRESHOLD):
“”"
检查是否为有效矩形(内角接近90度)
corners: 排序后的四个角点 [左上, 右上, 右下, 左下]
cos_threshold: 余弦值阈值(绝对值应小于此值)
返回: True/False
“”"
if len(corners) != 4:
return False
# 计算四个内角的余弦值 for i in range(4): # 获取三个连续点: A-B-C A = corners[i] B = corners[(i + 1) % 4] C = corners[(i + 2) % 4] # 计算向量BA和BC BA = (A[0] - B[0], A[1] - B[1]) BC = (C[0] - B[0], C[1] - B[1]) # 计算点积和模长 dot_product = BA[0] * BC[0] + BA[1] * BC[1] mod_BA = math.sqrt(BA[0]**2 + BA[1]**2) mod_BC = math.sqrt(BC[0]**2 + BC[1]**2) # 避免除零错误 if mod_BA * mod_BC < 1e-5: return False # 计算余弦值 cos_value = dot_product / (mod_BA * mod_BC) # 检查是否接近90度 (|cos| < threshold) if abs(cos_value) > cos_threshold: return False return True
def calculate_rect_properties(corners):
“”“计算矩形的面积和宽高比”“”
if len(corners) != 4:
return 0, 0
# 计算边长 top = math.sqrt((corners[1][0] - corners[0][0])**2 + (corners[1][1] - corners[0][1])**2) right = math.sqrt((corners[2][0] - corners[1][0])**2 + (corners[2][1] - corners[1][1])**2) bottom = math.sqrt((corners[3][0] - corners[2][0])**2 + (corners[3][1] - corners[2][1])**2) left = math.sqrt((corners[0][0] - corners[3][0])**2 + (corners[0][1] - corners[3][1])**2) # 计算平均宽度和高度 width = (top + bottom) / 2 height = (left + right) / 2 # 计算面积和宽高比 area = width * height aspect_ratio = max(width, height) / min(width, height) if min(width, height) > 0 else 0 return area, aspect_ratio
def draw_corner_info(img, corners, center, area, aspect_ratio):
“”“在图像上绘制角点和中心信息”“”
if len(corners) != 4:
return
# 定义角点颜色和标签 corner_colors = [(0, 255, 0), (0, 255, 0), (0, 255, 0), (0, 255, 0)] labels = ["TL", "TR", "BR", "BL"] # 绘制角点 for i, (x, y) in enumerate(corners): color = corner_colors[i] img.draw_circle(int(x), int(y), 8, color=color, thickness=2) img.draw_string(int(x) + 10, int(y) - 10, labels[i], color=color, scale=1) # 绘制矩形边框 for i in range(4): x1, y1 = corners[i] x2, y2 = corners[(i + 1) % 4] img.draw_line(int(x1), int(y1), int(x2), int(y2), color=(0, 255, 255), thickness=2) # 绘制对角线 img.draw_line(int(corners[0][0]), int(corners[0][1]), int(corners[2][0]), int(corners[2][1]), color=(255, 0, 0), thickness=1) img.draw_line(int(corners[1][0]), int(corners[1][1]), int(corners[3][0]), int(corners[3][1]), color=(255, 0, 0), thickness=1) # 绘制中心点 if center: cx, cy = center img.draw_cross(int(cx), int(cy), size=15, color=(255, 0, 255), thickness=2) img.draw_circle(int(cx), int(cy), 5, color=(255, 0, 255), thickness=-1) img.draw_string(int(cx) + 10, int(cy) - 10, "Center", color=(255, 0, 255), scale=1) # 显示矩形属性 img.draw_string(10, 30, f"Area: {area:.1f} px", color=(0, 255, 0), scale=1) img.draw_string(10, 50, f"Aspect: {aspect_ratio:.2f}", color=(0, 255, 0), scale=1)
def process_frame(img):
“”“处理图像帧,检测矩形角点和中心(带形状验证)”“”
img_gray = img.to_grayscale(copy=False)
img_bin = img_gray.binary([THRESHOLD])
rects = img_bin.find_rects(threshold=CORNERS_THRESHOLD)
# 计算图像总面积 total_image_area = img.width() * img.height() min_area = MIN_AREA_RATIO * total_image_area max_area = MAX_AREA_RATIO * total_image_area # 找出所有可能的矩形 valid_rects = [] for rect in rects: corners = rect.corners() if len(corners) == 4: sorted_corners = sort_corners(corners) # 计算矩形属性 area, aspect_ratio = calculate_rect_properties(sorted_corners) # 检查角度、面积和宽高比 angle_valid = is_valid_rectangle(sorted_corners) area_valid = min_area <= area <= max_area aspect_valid = ASPECT_RATIO_MIN <= aspect_ratio <= ASPECT_RATIO_MAX if angle_valid and area_valid and aspect_valid: valid_rects.append((area, aspect_ratio, sorted_corners)) # 选择面积最大的有效矩形 corners = [] center = None area = 0 aspect_ratio = 0 if valid_rects: valid_rects.sort(key=lambda x: x[0], reverse=True) area, aspect_ratio, corners = valid_rects[0] center = calculate_center(corners) # 绘制结果 if center: draw_corner_info(img, corners, center, area, aspect_ratio) else: # 显示未检测到矩形的信息 img.draw_string(img.width()//2 - 50, img.height()//2, "No valid rectangle", color=(255, 0, 0), scale=1) # 资源清理 del img_gray, img_bin gc.collect() return corners, center
try:
print(“初始化摄像头…”)
# 初始化摄像头
sensor = Sensor(id=SENSOR_ID)
sensor.reset()
sensor.set_framesize(width=IMAGE_WIDTH ,height=IMAGE_HEIGHT) # 352x240
sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0)
sensor.set_hmirror(1)
sensor.set_vflip(1)
print("初始化显示器...") # 初始化显示器 if DISPLAY_MODE == "VIRT": Display.init(Display.VIRT, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, fps=60) elif DISPLAY_MODE == "LCD": Display.init(Display.ST7701, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True) elif DISPLAY_MODE == "HDMI": Display.init(Display.LT9611, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True) print("初始化媒体管理器...") # 初始化媒体管理器 MediaManager.init() sensor.run() clock = time.clock() print("开始主循环...") while True: os.exitpoint() clock.tick() # 捕获图像 img = sensor.snapshot(chn=CAM_CHN_ID_0) # 处理图像并检测角点 corners, center = process_frame(img) # 发送坐标 send_coordinates(center) time.sleep_ms(20) # 控制发送频率 # 显示FPS img.draw_string(10, 10, f"FPS: {clock.fps():.1f}", color=(255, 0, 0), scale=1) # 显示图像 img.compressed_for_ide() Display.show_image(img, x=int((DISPLAY_WIDTH - img.width()) / 2), y=int((DISPLAY_HEIGHT - img.height()) / 2))
except KeyboardInterrupt:
print(“程序被用户中断”)
except Exception as e:
print(f"发生错误: {e}")
import sys
sys.print_exception(e)
finally:
print(“清理资源…”)
# 清理资源
if sensor:
sensor.stop()
Display.deinit()
MediaManager.deinit()
print(“程序退出”)
在上述代码的基础上,添加以下功能:识别的矩形中有红色的圆环,识别圆环并大体确定矩形的感兴趣区域ROI,同时标定圆环,让中心识别更加精准
最新发布