2025年全国大学生电子设计大赛E题视觉部分开源
·
2025年全国大学生电子设计大赛E题视觉部分开源
一、系统依赖与全局配置
# 依赖部分
import time
import os
import struct
from media.sensor import *
from media.display import *
from media.media import *
from time import ticks_ms
from machine import FPIOA, Pin, PWM, Timer
from machine import UART
# 全局变量配置
# 平滑处理变量
last_output_x = None # X轴上次输出值
last_output_y = None # Y轴上次输出值
# 输出变化率限制
max_change_x = 15 # X轴最大单次变化量(度)
max_change_y = 5 # Y轴最大单次变化量(度)
# 初始中心点坐标(屏幕中心)
last_center_x = 160
last_center_y = 120
# 目标点坐标(A4纸中心)
target_x = 171
target_y = 118
# 显示和摄像头参数
DISPLAY_WIDTH = 310 # 屏幕宽度
DISPLAY_HEIGHT = 277 # 屏幕高度
# A4纸检测阈值
black_threshold = (19, 100, -73, 77, -61, 63) # 黑色边框阈值
white_threshold = 330 # 白色纸张阈值
# 传感器声明
sensor = None
# PID参数(比例、积分、微分)
Kp = 0.5
Ki = 0.04
Kd = 0.2
# PID控制变量
last_error_x = 0
last_error_y = 0
integral_x = 0
integral_y = 0
# 舵机控制量
servo_dx = 0
servo_dy = 0
# 引脚分配
fpioa = FPIOA()
fpioa.set_function(34, FPIOA.GPIO34)
fpioa.set_function(35, FPIOA.GPIO35)
fpioa.set_function(40, FPIOA.UART1_TXD)
fpioa.set_function(41, FPIOA.UART1_RXD)
fpioa.set_function(60, FPIOA.GPIO60)
# 硬件接口初始化
key0 = Pin(34, Pin.IN, pull=Pin.PULL_UP, drive=7)
key1 = Pin(35, Pin.IN, pull=Pin.PULL_UP, drive=7)
lazer = Pin(60, Pin.OUT, pull=Pin.PULL_UP, drive=7)
uart1 = UART(UART.UART1, baudrate=115200, bits=UART.EIGHTBITS,
parity=UART.PARITY_NONE, stop=UART.STOPBITS_ONE)
二、串口通信模块(舵机控制)
def servo_control(servo_id, angle, interval=50):
"""
多圈角度控制
功能:控制舵机旋转到指定角度
输入:servo_id(0上/1下), angle(度), interval(ms)
"""
angle_raw = int(angle * 10) # 角度值放大10倍
# 构建数据包
packet = bytearray([0x12, 0x4C, 0x0D, 0x0B, servo_id])
packet.extend(struct.pack('<i', angle_raw))
packet.extend(struct.pack('<I', interval))
packet.extend(struct.pack('<H', 0)) # power=0
packet.append(sum(packet) % 256) # 校验和
uart1.write(bytes(packet)) # 发送指令
def servo_read(servo_id):
"""
读取多圈角度
功能:读取舵机当前角度和圈数
输入:servo_id(0上/1下)
"""
packet = bytearray([0x12, 0x4C, 0x10, 0x01, servo_id])
packet.append(sum(packet) % 256) # 校验和
uart1.write(bytes(packet)) # 发送指令
def parse_response(data):
"""
解析角度响应
功能:解析舵机返回的角度数据
输入:data字节数据
输出:(servo_id, angle, turns)或None
"""
if len(data) >= 12 and data[0] == 0x05 and data[2] == 0x10:
servo_id = data[4]
angle = struct.unpack('<i', data[5:9])[0] / 10.0
turns = struct.unpack('<H', data[9:11])[0]
return (servo_id, angle, turns)
return None
def receive():
"""
接收舵机响应数据
功能:处理舵机返回的数据
"""
data = uart1.read(128) # 读取串口数据
if data:
result = parse_response(data)
if result:
servo_id, angle, turns = result
print(f"舵机{servo_id}: {angle:.1f}度, {turns}圈")
return result
time.sleep_ms(10)
三、追踪控制模块(PID算法)
def pid_control(error, last_error, integral, axis='x'):
"""
PID控制函数
功能:计算PID输出值
输入:error(当前误差), last_error(上次误差), integral(积分项), axis(控制轴)
输出:(output, last_error, integral)
"""
# 根据控制轴选择参数
if axis == 'x':
# X轴参数配置
if abs(error) >= 0: # 大误差:快速响应
Kp, Ki, Kd, sep_thresh = 0.6, 0.2, 1, 10000000
else: # 小误差:精确控制
Kp, Ki, Kd, sep_thresh = 0.5, 0.3, 0.2, 5000
else: # Y轴
if abs(error) >= 0: # 大误差
Kp, Ki, Kd, sep_thresh = 0.6, 0.2, 2, 8000
else: # 小误差
Kp, Ki, Kd, sep_thresh = 0.6, 0.15, 0.2, 60
# PID计算
Proportional = Kp * error # 比例项
# 积分分离:大误差时不累积积分
if abs(error) < sep_thresh:
integral += error
integral_term = Ki * integral # 积分项
derivative = Kd * (error - last_error) # 微分项
output = Proportional + integral_term + derivative
last_error = error
return output, last_error, integral
def smooth_output(new_output, last_output, max_change):
"""
输出平滑处理函数
功能:限制输出变化率,防止突变
输入:new_output(新输出值), last_output(上次输出值), max_change(最大允许变化量)
输出:平滑后的输出值
"""
# 首次输出直接返回
if last_output is None:
return new_output
change = new_output - last_output
# 限制变化量
if abs(change) > max_change:
if change > 0:
smoothed_output = last_output + max_change
else:
smoothed_output = last_output - max_change
else:
smoothed_output = new_output
return smoothed_output
四、图像处理模块
def init_sensor():
"""初始化摄像头"""
global sensor
print("Starting camera tracking system...")
sensor = Sensor(width=1920, height=1080)
sensor.reset()
sensor.set_framesize(Sensor.QVGA) # 320*240
sensor.set_pixformat(Sensor.RGB565)
def init_display():
"""初始化显示屏"""
Display.init(Display.ST7701, to_ide=True, fps=60)
MediaManager.init() # 初始化媒体管理器
def find_a4_paper(img):
"""
寻找A4纸
功能:检测A4纸的黑色边框和白色区域
输入:img(图像对象)
输出:(outer, inner) - 外框和内框坐标
"""
# 1. 检测黑色边框
black_blobs = img.find_blobs([black_threshold],
area_threshold=1,
merge=True,
margin=1, invert=True)
for blob in black_blobs:
x, y, w, h = blob.rect()
# 跳过太小的区域
if w < 1 or h < 1:
continue
# 2. 在黑色边框内检测白色区域
inner_w = int(w * 0.9) # 假设白色区域比边框小10%
inner_h = int(h * 0.9)
inner_x = x + (w - inner_w) // 2
inner_y = y + (h - inner_h) // 2
# 检查白色区域
white_pixels = 0
total_pixels = 0
# 采样内部区域中心部分
for i in range(inner_x + 5, inner_x + inner_w - 5, 5):
for j in range(inner_y + 5, inner_y + inner_h - 5, 5):
pixel = img.get_pixel(i, j)
# 亮度检测
if pixel[0] + pixel[1] + pixel[2] > white_threshold:
white_pixels += 1
total_pixels += 1
# 白色像素占比超过45%视为有效
if total_pixels > 0 and white_pixels / total_pixels > 0.45:
return (x, y, w, h), (inner_x, inner_y, inner_w, inner_h)
return None, None
def img_process(img):
"""
图像处理流程
功能:检测A4纸,绘制边框和中心点
输入:img(图像对象)
输出:(center_x, center_y)或(None, None)
"""
if img:
outer, inner = find_a4_paper(img)
if outer:
# 绘制黑色外边框(绿色)
img.draw_rectangle(outer[0], outer[1], outer[2], outer[3],
color=(0, 255, 0), thickness=2)
if inner:
# 绘制白色内区域(蓝色边框)
img.draw_rectangle(inner[0], inner[1], inner[2], inner[3],
color=(0, 0, 255), thickness=2)
# 计算中心点
center_x = outer[0] + outer[2] // 2
center_y = outer[1] + outer[3] // 2
img.draw_cross(center_x, center_y, color=(255, 0, 0), size=10)
# 显示图像
Display.show_image(img, x=round((640 - sensor.width()) / 2),
y=round((480 - sensor.height()) / 2))
return center_x, center_y
else:
print("检测到黑色边框但未确认白色内区域")
Display.show_image(img, x=round((640 - sensor.width()) / 2),
y=round((480 - sensor.height()) / 2))
return None, None
else:
print("未检测到A4纸")
Display.show_image(img, x=round((640 - sensor.width()) / 2),
y=round((480 - sensor.height()) / 2))
return None, None
else:
print("图像捕获失败")
return None, None
五、主任务模块
任务2:固定位置追踪
def main_task2():
"""任务2:固定位置追踪"""
try:
# 初始化硬件
init_sensor()
time.sleep_ms(200)
init_display()
sensor.run()
clock = time.clock()
# 全局变量声明
global last_error_x, last_error_y, integral_x, integral_y
global last_output_x, last_output_y
# 状态变量
valid_count_fps = 0
valid_count_lazer = 0
# 摆正逻辑
servo_control(0, 73.5) # Y轴位置
servo_control(1, 0) # X轴位置
while True:
clock.tick()
img = sensor.snapshot()
center_x, center_y = img_process(img)
if center_x is not None:
valid_count_fps += 1
if valid_count_fps >= 3: # 连续3帧检测到目标
# 计算偏差
error_x = target_x - center_x
error_y = target_y - center_y
# 死区处理
if abs(error_x) < 5: error_x = 0
if abs(error_y) < 5: error_y = 0
# 激光锁定条件
if abs(error_y) < 5 and abs(error_x) < 5:
valid_count_lazer += 1
if valid_count_lazer >= 15: # 连续15帧稳定
lazer.value(1) # 开启激光
# PID控制
output_x, last_error_x, integral_x = pid_control(
error_x, last_error_x, integral_x, 'x')
output_y, last_error_y, integral_y = pid_control(
error_y, last_error_y, integral_y, 'y')
# 输出转换
output_x = output_x * 0.1
output_y = output_y * 0.1
# Y轴处理
base_angle_y = 73 # Y轴中心角度
raw_output_y = base_angle_y + output_y
raw_output_y = max(20, min(120, raw_output_y)) # 限幅
raw_output_y = 140 - raw_output_y # 方向转换
# X轴处理
raw_output_x = max(-360, min(360, output_x))
raw_output_x = -raw_output_x
# 平滑处理
smooth_output_x = smooth_output(raw_output_x, last_output_x, max_change_x)
smooth_output_y = smooth_output(raw_output_y, last_output_y, max_change_y)
# 更新输出值
last_output_x = smooth_output_x
last_output_y = smooth_output_y
# 发送控制指令
servo_control(1, smooth_output_x)
servo_control(0, smooth_output_y)
# 调试信息
print("原始输出: ({:.2f}, {:.2f})".format(raw_output_x, raw_output_y))
print("平滑输出: ({:.2f}, {:.2f})".format(smooth_output_x, smooth_output_y))
print("积分:{} {}".format(integral_x, integral_y))
print("比例项:{} {}".format(Kp * error_x, Kp * error_y))
print("微分项:{} {}".format(Kd * error_x, Kd * error_y))
print(clock.fps())
else:
valid_count_fps = 0 # 重置计数器
# 异常处理
except KeyboardInterrupt:
print("Program stopped by user")
except Exception as e:
print(f"Error occurred: {str(e)}")
finally:
# 清理资源
if 'sensor' in locals() and isinstance(sensor, Sensor):
sensor.stop()
Display.deinit()
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
time.sleep_ms(100)
lazer.value(0) # 关闭激光
MediaManager.deinit()
print("System shutdown complete")
任务3:动态搜索追踪(右转版)
def main_task3_right():
"""任务3:动态搜索追踪(右转版)"""
try:
# 初始化硬件
init_sensor()
time.sleep_ms(200)
init_display()
sensor.run()
clock = time.clock()
# 全局变量声明
global last_error_x, last_error_y, integral_x, integral_y
global last_output_x, last_output_y
# 状态变量
valid_count_fps = 0
valid_count_lazer = 0
search_error_x = -100 # 搜索方向参数(右转)
base_angle_x = 0 # X轴基准角度
while True:
clock.tick()
img = sensor.snapshot()
center_x, center_y = img_process(img)
if center_x is not None: # 检测到目标
valid_count_fps += 1
if valid_count_fps >= 3:
# 计算偏差
error_x = target_x - center_x
error_y = target_y - center_y
# 死区处理
if abs(error_x) < 5: error_x = 0
if abs(error_y) < 5: error_y = 0
# 激光锁定条件
if abs(error_y) < 5 and abs(error_x) < 5:
valid_count_lazer += 1
if valid_count_lazer >= 15:
lazer.value(1)
# PID控制
output_x, last_error_x, integral_x = pid_control(
error_x, last_error_x, integral_x, 'x')
output_y, last_error_y, integral_y = pid_control(
error_y, last_error_y, integral_y, 'y')
# 输出转换
output_x = output_x * 0.1
output_y = output_y * 0.1
# Y轴处理
base_angle_y = 70
raw_output_y = base_angle_y + output_y
raw_output_y = max(20, min(120, raw_output_y))
raw_output_y = 140 - raw_output_y
# X轴处理
raw_output_x = base_angle_x + output_x
raw_output_x = max(-360, min(360, raw_output_x))
raw_output_x = -raw_output_x
# 平滑处理
smooth_output_x = smooth_output(raw_output_x, last_output_x, max_change_x)
smooth_output_y = smooth_output(raw_output_y, last_output_y, max_change_y)
# 更新输出值
last_output_x = smooth_output_x
last_output_y = smooth_output_y
# 发送控制指令
servo_control(1, smooth_output_x)
servo_control(0, smooth_output_y)
# 调试信息
print("原始输出: ({:.2f}, {:.2f})".format(raw_output_x, raw_output_y))
print("平滑输出: ({:.2f}, {:.2f})".format(smooth_output_x, smooth_output_y))
print("积分:{} {}".format(integral_x, integral_y))
print("比例项:{} {}".format(Kp * error_x, Kp * error_y))
print("微分项:{} {}".format(Kd * error_x, Kd * error_y))
print(clock.fps())
else: # 未检测到目标,进入搜索模式
valid_count_fps = 0
valid_count_lazer = 0
# 固定偏差搜索
output_x, last_error_x, integral_x = pid_control(
search_error_x, last_error_x, integral_x, 'x')
# 输出转换
output_x = output_x * 0.1
# X轴处理
raw_output_x = base_angle_x + output_x
raw_output_x = max(-360, min(360, raw_output_x))
raw_output_x = -raw_output_x
# Y轴保持固定
raw_output_y = 70
raw_output_y = max(20, min(120, raw_output_y))
raw_output_y = 140 - raw_output_y
# 平滑处理
smooth_output_x = smooth_output(raw_output_x, last_output_x, max_change_x)
smooth_output_y = smooth_output(raw_output_y, last_output_y, max_change_y)
# 更新输出值
last_output_x = smooth_output_x
last_output_y = smooth_output_y
# 发送控制指令
servo_control(1, smooth_output_x)
servo_control(0, smooth_output_y)
print(f"搜索模式 - X轴积分: {integral_x:.2f}, 输出: {smooth_output_x:.2f}")
# 异常处理
except KeyboardInterrupt:
print("Program stopped by user")
except Exception as e:
print(f"Error occurred: {str(e)}")
finally:
# 清理资源
if 'sensor' in locals() and isinstance(sensor, Sensor):
sensor.stop()
Display.deinit()
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
time.sleep_ms(100)
lazer.value(0)
MediaManager.deinit()
print("System shutdown complete")
六、程序入口
# 程序入口
if __name__ == "__main__":
while True:
print("程序开始,等待按钮")
if key0.value() == 0: # 检测按键按下
time.sleep_ms(20)
if key0.value() == 0: # 确认按键按下
print("已检测到按下按钮")
lazer.value(1) # 开启激光
main_task2() # 启动任务2
else:
time.sleep_ms(100) # 等待按键
七、系统特点总结
-
自适应PID控制:
- 根据误差大小动态调整参数
- 大误差区快速响应,小误差区精细控制
- 积分分离防止过冲
-
智能目标搜索:
- 目标丢失自动进入搜索模式
- 可配置左右搜索方向
- 搜索速度可调
-
运动平滑处理:
- 输出变化率限制
- 防止舵机抖动
- 提升系统稳定性
-
双重目标验证:
- 黑色边框检测
- 内部白色区域验证
- 提高识别准确率
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)