跳到主要内容2025 电赛 E 题视觉追踪系统开源实现 | 极客日志PythonAI算法
2025 电赛 E 题视觉追踪系统开源实现
2025 年全国大学生电子设计大赛 E 题的视觉追踪系统开源实现。系统采用 MicroPython 编写,基于摄像头进行 A4 纸目标识别,结合 PID 算法控制双轴舵机完成固定位置追踪和动态搜索。代码涵盖硬件初始化、串口通信、图像处理和主任务逻辑,具备激光锁定及输出平滑功能,为嵌入式视觉竞赛提供完整参考方案。
DevStack24 浏览 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
last_output_y = None
max_change_x = 15
max_change_y = 5
last_center_x = 160
last_center_y = 120
target_x = 171
target_y = 118
DISPLAY_WIDTH = 310
DISPLAY_HEIGHT = 277
black_threshold = (19, 100, -73, 77, -61, 63)
white_threshold = 330
sensor = None
Kp =
Ki =
Kd =
last_error_x =
last_error_y =
integral_x =
integral_y =
servo_dx =
servo_dy =
fpioa = FPIOA()
fpioa.set_function(, FPIOA.GPIO34)
fpioa.set_function(, FPIOA.GPIO35)
fpioa.set_function(, FPIOA.UART1_TXD)
fpioa.set_function(, FPIOA.UART1_RXD)
fpioa.set_function(, FPIOA.GPIO60)
key0 = Pin(, Pin.IN, pull=Pin.PULL_UP, drive=)
key1 = Pin(, Pin.IN, pull=Pin.PULL_UP, drive=)
lazer = Pin(, Pin.OUT, pull=Pin.PULL_UP, drive=)
uart1 = UART(UART.UART1, baudrate=, bits=UART.EIGHTBITS, parity=UART.PARITY_NONE, stop=UART.STOPBITS_ONE)
0.5
0.04
0.2
0
0
0
0
0
0
34
35
40
41
60
34
7
35
7
60
7
115200
二、串口通信模块(舵机控制)
def servo_control(servo_id, angle, interval=50):
""" 多圈角度控制
功能:控制舵机旋转到指定角度
输入:servo_id(0 上/1 下), angle(度), interval(ms)
"""
angle_raw = int(angle * 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))
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':
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:
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
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)
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) - 外框和内框坐标
"""
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
inner_w = int(w * 0.9)
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
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)
servo_control(1, 0)
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)
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
base_angle_y = 73
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
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
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)
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
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
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
raw_output_x = base_angle_x + output_x
raw_output_x = max(-360, min(360, raw_output_x))
raw_output_x = -raw_output_x
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()
else:
time.sleep_ms(100)
七、系统特点总结
- 自适应 PID 控制:
- 根据误差大小动态调整参数
- 大误差区快速响应,小误差区精细控制
- 积分分离防止过冲
- 智能目标搜索:
- 目标丢失自动进入搜索模式
- 可配置左右搜索方向
- 搜索速度可调
- 运动平滑处理:
- 双重目标验证:
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online