import time, os, math, gc
from media.sensor import *
from media.display import *
from media.media import *
from machine import FPIOA, UART, TOUCH, Pin
# 图像参数 - 使用320x240标准分辨率
IMAGE_WIDTH = 320
IMAGE_HEIGHT = 240
SENSOR_ID = 2
sensor = None
THRESHOLD = (69, 252) # 初始灰度阈值
# 显示模式设置
DISPLAY_MODE = "LCD" # 可选 "VIRT", "LCD", "HDMI"
# 根据显示模式设置分辨率
if DISPLAY_MODE == "VIRT":
DISPLAY_WIDTH, DISPLAY_HEIGHT = 320, 240
elif DISPLAY_MODE == "LCD":
DISPLAY_WIDTH, DISPLAY_HEIGHT = 320, 240
elif DISPLAY_MODE == "HDMI":
DISPLAY_WIDTH, DISPLAY_HEIGHT = 320, 240
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 # 角度余弦阈值(绝对值)
# 初始化UART2
fpioa = FPIOA()
fpioa.set_function(11, FPIOA.UART2_TXD) # UART2发送
fpioa.set_function(12, FPIOA.UART2_RXD) # UART2接收
uart2 = UART(UART.UART2, 115200, 8, 0, 1, timeout=1000) # 用于坐标通信
# 初始化按键
fpioa.set_function(53, FPIOA.GPIO53)
button = Pin(53, Pin.IN, Pin.PULL_DOWN) # 使用下拉电阻
debounce_delay = 20 # 毫秒
last_press_time = 0 # 上次按键按下的时间,单位为毫秒
button_last_state = 0 # 上次按键状态
timeTouch = 0
tp = TOUCH(0)
# 按钮文本 (简化版本)
textL = ["GL-", "GH-", "Step", "Back"]
textR = ["GL+", "GH+", "Invert", " "]
isInvertFlag = False # 是否反转
# 虚拟按钮范围 (320x240分辨率下的布局)
buttonsL = [
(10, 10, 70, 30), # GL-
(10, 50, 70, 30), # GH-
(10, 90, 70, 30), # Step
(10, 130, 70, 30) # Back
]
buttonsR = [
(240, 10, 70, 30), # GL+
(240, 50, 70, 30), # GH+
(240, 90, 70, 30), # Invert
(240, 130, 70, 30) # 空按钮
]
# 灰度范围初始化
GL, GH = 0, 255 # 灰度范围0~255
step = 1 # 脱机阈值步进
Gray_thresholds = [(int(GL), int(GH))] # 灰度阈值列表
# 状态标志
flag = False # 页面标志,True为阈值设置
# 脱机调整阈值界面设计 (优化版本)
def changeScreen(img):
# 直接处理灰度图像,避免闪白
GrayImg = img.to_grayscale().binary(Gray_thresholds)
if isInvertFlag:
GrayImg = GrayImg.invert()
# 创建屏幕图像
screen = GrayImg.copy()
# 绘制按钮
for i in range(4):
# 左侧按钮
screen.draw_rectangle(buttonsL[i][0], buttonsL[i][1],
buttonsL[i][2], buttonsL[i][3],
(200, 201, 202), thickness=1, fill=True)
screen.draw_string(buttonsL[i][0] + 5, buttonsL[i][1] + 10,
textL[i], color=(0, 0, 0), scale=1)
# 右侧按钮
screen.draw_rectangle(buttonsR[i][0], buttonsR[i][1],
buttonsR[i][2], buttonsR[i][3],
(200, 201, 202), thickness=1, fill=True)
screen.draw_string(buttonsR[i][0] + 5, buttonsR[i][1] + 10,
textR[i], color=(0, 0, 0), scale=1)
# 显示阈值和步进信息
screen.draw_string(100, 180, f"GL: {GL} GH: {GH}",
color=(255, 255, 255) if isInvertFlag else (0, 0, 0),
scale=1)
screen.draw_string(100, 200, f"Step: {step}",
color=(255, 255, 255) if isInvertFlag else (0, 0, 0),
scale=1)
# 显示处理后的图像
Display.show_image(screen, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
# 触摸屏按钮事件动作
def buttonAction(direction, index):
global GL, GH, flag, step, isInvertFlag, textR, Gray_thresholds
# 阈值列表重置
Gray_thresholds = [(int(GL), int(GH))]
# 按钮处理
if direction == 'left':
if index == 0: # GL-
GL = max(GL - step, 0)
elif index == 1: # GH-
GH = max(GH - step, 0)
elif index == 2: # Step
step = step + 1 if step < 5 else 1
elif index == 3: # Back
flag = False
elif direction == 'right':
if index == 0: # GL+
GL = min(GL + step, 255)
elif index == 1: # GH+
GH = min(GH + step, 255)
elif index == 2: # Invert
isInvertFlag = not isInvertFlag
textR[2] = 'Normal' if isInvertFlag else 'Invert'
# 触摸屏事件
def touchAction():
global timeTouch
touchP = tp.read(2)
if touchP:
timeTouch += 1
if timeTouch >= 3:
p = touchP
timeTouch = 0
if p:
x, y = p[0].x, p[0].y
# 检查左侧按钮
for i, rect in enumerate(buttonsL):
if (rect[0] <= x <= rect[0] + rect[2] and
rect[1] <= y <= rect[1] + rect[3]):
buttonAction('left', i)
return
# 检查右侧按钮
for i, rect in enumerate(buttonsR):
if (rect[0] <= x <= rect[0] + rect[2] and
rect[1] <= y <= rect[1] + rect[3]):
buttonAction('right', i)
return
# 发送坐标函数
def send_coordinates(center):
"""通过串口发送坐标数据"""
if center:
cx, cy = center
# 协议格式: $X[坐标x]Y[坐标y]\n
data_str = f"$X{cx:04d}Y{cy:04d}\n"
uart2.write(data_str.encode())
else:
uart2.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
# 分类角点
def distance_to_center(corner):
return (corner[0] - center_x)**2 + (corner[1] - center_y)**2
top_left = min(corners, key=lambda c: distance_to_center(c) if c[0] < center_x and c[1] < center_y else float('inf'))
top_right = min(corners, key=lambda c: distance_to_center(c) if c[0] > center_x and c[1] < center_y else float('inf'))
bottom_right = min(corners, key=lambda c: distance_to_center(c) if c[0] > center_x and c[1] > center_y else float('inf'))
bottom_left = min(corners, key=lambda c: distance_to_center(c) if c[0] < center_x and c[1] > center_y else float('inf'))
return [top_left, top_right, bottom_right, bottom_left]
# 矩形有效性检查
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), 5, color=color, thickness=2)
img.draw_string(int(x) + 5, int(y) - 5, 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=1)
# 绘制对角线
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=10, color=(255, 0, 255), thickness=2)
img.draw_circle(int(cx), int(cy), 3, color=(255, 0, 255), thickness=-1)
img.draw_string(int(cx) + 5, int(cy) - 5, "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([(GL, GH)])
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_x = sum(c[0] for c in corners) / len(corners)
center_y = sum(c[1] for c in corners) / len(corners)
center = (int(center_x), int(center_y))
# 绘制结果
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=DISPLAY_WIDTH, height=DISPLAY_HEIGHT)
sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0)
sensor.set_hmirror(1)
sensor.set_vflip(1)
print("初始化显示器...")
# 初始化显示器 - 修复Display.init调用
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=800, height=480)
elif DISPLAY_MODE == "HDMI":
Display.init(Display.LT9611, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT)
print("初始化媒体管理器...")
# 初始化媒体管理器
MediaManager.init()
sensor.run()
clock = time.clock()
print("开始主循环...")
while True:
os.exitpoint()
clock.tick()
# 捕获图像
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 处理按键事件
button_state = button.value() # 获取当前按键状态
current_time = time.ticks_ms() # 获取当前时间(单位:毫秒)
# 检测按键从未按下(0)到按下(1)的变化(上升沿)
if button_state == 1 and button_last_state == 0:
# 检查按键是否在消抖时间外
if current_time - last_press_time > debounce_delay:
if button.value() == 1:
flag = not flag
print("进入阈值设置模式" if flag else "退出阈值设置模式")
last_press_time = current_time
# 更新上次按键状态
button_last_state = button_state
# 仅在阈值设置界面显示阈值调整UI
if flag:
changeScreen(img)
touchAction()
else:
# 正常模式下显示原始图像和处理结果
# 处理图像并检测角点
corners, center = process_frame(img)
# 发送坐标
send_coordinates(center)
# 显示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, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
# 控制处理频率
time.sleep_ms(20)
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("程序退出")
将上述程序的图像参数修改成合理的最小
最新发布