跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

2025 电赛 E 题视觉追踪系统开源实现

2025 年全国大学生电子设计大赛 E 题的视觉追踪系统开源实现。系统采用 MicroPython 编写,基于摄像头进行 A4 纸目标识别,结合 PID 算法控制双轴舵机完成固定位置追踪和动态搜索。代码涵盖硬件初始化、串口通信、图像处理和主任务逻辑,具备激光锁定及输出平滑功能,为嵌入式视觉竞赛提供完整参考方案。

DevStack发布于 2026/3/29更新于 2026/9/1170 浏览
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
last_output_y = None
# X 轴上次输出值
# 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


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)
# PID 参数(比例、积分、微分)
0.5
0.04
0.2
# PID 控制变量
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)  # 角度值放大 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
                            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)  # 等待按键

七、系统特点总结

  1. 自适应 PID 控制:
    • 根据误差大小动态调整参数
    • 大误差区快速响应,小误差区精细控制
    • 积分分离防止过冲
  2. 智能目标搜索:
    • 目标丢失自动进入搜索模式
    • 可配置左右搜索方向
    • 搜索速度可调
  3. 运动平滑处理:
    • 输出变化率限制
    • 防止舵机抖动
    • 提升系统稳定性
  4. 双重目标验证:
    • 黑色边框检测
    • 内部白色区域验证
    • 提高识别准确率

目录

  1. 2025 年全国大学生电子设计大赛 E 题视觉部分开源
  2. 一、系统依赖与全局配置
  3. 依赖部分
  4. 全局变量配置
  5. 平滑处理变量
  6. X 轴上次输出值
  7. Y 轴上次输出值
  8. 输出变化率限制
  9. 初始中心点坐标(屏幕中心)
  10. 目标点坐标(A4 纸中心)
  11. 显示和摄像头参数
  12. A4 纸检测阈值
  13. 传感器声明
  14. PID 参数(比例、积分、微分)
  15. PID 控制变量
  16. 舵机控制量
  17. 引脚分配
  18. 硬件接口初始化
  19. 二、串口通信模块(舵机控制)
  20. 三、追踪控制模块(PID 算法)
  21. 四、图像处理模块
  22. 五、主任务模块
  23. 任务 2:固定位置追踪
  24. 任务 3:动态搜索追踪(右转版)
  25. 六、程序入口
  26. 程序入口
  27. 七、系统特点总结

更多推荐文章

查看全部
  • 论文AI率多少算正常?各高校AIGC检测标准汇总
  • HTML5 存储技术详解:LocalStorage 与 SessionStorage
  • 将 Java KeyStore (.jks) 转换为 Nginx 可用的 PEM 格式
  • C++ list 容器的用法与简化实现
  • JetBrains 中 GitHub Copilot Agent Mode 与 MCP 配置实战
  • HTML Popover API:原生浮层交互的零 JS 解决方案
  • PPT 中嵌入 VR 图片及全景图播放方法
  • Python 开源库 Streamlit 详解与实战
  • 老手机 本地部署小龙虾OpenClaw(使用本地千问大模型)实机演示 Termux+Ubuntu+Llama 新手完整安装教程(含代码)
  • Gemini 2.5 Pro 技术突破与实战应用解析
  • Qwen3-VL 的双模式是怎么工作的?Instruct 与 Thinking 实践对比
  • C++ 基于正倒排索引的 Boost 搜索引擎:日志宏与 Server 主程序详解
  • Python 网络爬虫技术基础与实战
  • 整洁架构:构建可维护的 Python 项目分层指南
  • Java 核心面试知识点梳理:基础、JVM、并发与框架实战
  • MySQL 安装配置完整指南
  • 从零构建大模型:深入理解大型语言模型原理与实现
  • 数据库事务隔离级别与 Spring 传播行为深度解析
  • Flutter-OH开发利器:12个精选开源仓库全解析
  • 使用 LangChain 搭建本地知识库系统

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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