多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

智能车竞赛视觉组技术方案:双摄像头循迹与AprilTag识别实战

智能车竞赛视觉组技术方案:双摄像头循迹与AprilTag识别实战 在准备第二十一届全国大学生智能汽车竞赛华南赛区的过程中我们团队华工智能视觉组经历了从方案选型、代码调试到最终稳定进国赛的完整闭环。智能视觉组别因其融合了传统循迹控制与前沿的机器视觉识别挑战性和趣味性并存。本文将系统复盘我们最终进国赛版本的完整技术方案涵盖硬件选型、核心算法、代码架构以及关键的调参避坑经验目标是提供一份可供后来者直接复现或深度借鉴的实战指南。1. 智能视觉组核心任务与备赛思路解析全国大学生智能汽车竞赛的智能视觉组其核心任务是要求小车在完成基础循迹如电磁、摄像头巡线的同时利用车载摄像头对赛道旁的视觉标签进行实时识别并根据识别结果执行相应的动作例如搬运物料、定点停车、声光提示等。这要求参赛队伍不仅要有扎实的嵌入式控制和机械调校功底更需要掌握图像处理与机器学习的基本应用。1.1 竞赛规则与得分要点分析以21届为例根据第二十一届竞赛规则智能视觉组的任务通常包含以下几个关键得分点基础循迹小车需稳定、快速地沿赛道中心线行驶这是完成所有高级任务的前提。视觉标签识别在特定区域如物料区、任务点识别AprilTag、数字、颜色或特定图案等视觉标签。识别准确率和速度直接影响任务得分。任务执行根据识别结果控制机械臂、舵机等执行机构完成抓取、放置、按压等动作。执行的成功率和耗时是拉开差距的关键。整体完成时间在成功完成所有任务的前提下用时越短成绩越好。因此我们的备赛策略非常明确“稳”字当头先保证基础循迹和视觉识别的100%成功率再逐步优化速度。1.2 华工智能视觉组技术栈选型经过多轮迭代测试我们最终确定了以下稳定可靠的技术方案主控平台逐飞科技的i.MX RT1064核心板。其主频高600MHz内存大外设丰富能够同时流畅运行图像处理算法和电机控制程序。图像传感器总钻风MT9V034全局快门摄像头。全局快门在高速运动下无果冻效应是智能车竞赛的经典选择。循迹方案双摄像头方案。一个摄像头MT9V034用于视觉识别另一个摄像头可能同型号或OV系列专用于循迹。将循迹和识别解耦避免了单摄像头既要巡线又要找标签导致的资源竞争和算法干扰稳定性极大提升。图像处理与识别传统图像处理OpenCV算法思想 轻量级机器学习。对于AprilTag使用专门的轻量级检测库对于颜色、形状识别采用阈值分割、轮廓查找等传统算法在RT1064上足以达到实时性要求。开发环境Keil MDK。配合逐飞提供的开源库可以快速搭建工程专注于上层应用逻辑。这套方案的核心优势在于解耦与专精循迹摄像头只关心赛道边线视觉识别摄像头只关心前方的标签两者通过主控芯片进行数据融合和决策系统鲁棒性显著增强。2. 硬件系统搭建与机械调校稳定的硬件是算法得以发挥的基础。很多队伍前期出现的问题根源都在硬件。2.1 车模机械调整关键点重心配置电池、主控等重物应尽量放置在车体中心并降低高度。低重心能有效减少过弯时的侧倾提高高速稳定性。前轮前束与主销后倾根据规则允许的范围进行微调。适当的前束可以提高直道稳定性适当的主销后倾角能提供一定的回正力矩。这部分需要结合赛道实际情况反复路试。摄像头支架刚性务必保证摄像头支架牢固、无抖动。任何微小的震动都会导致图像模糊严重影响循迹和识别精度。我们使用了碳纤维杆配合3D打印的加固件效果很好。接线与供电所有线缆应捆扎整齐避免干扰传感器特别是电磁组或卷入车轮。电机、舵机与核心板、传感器应分开供电并使用大电容进行电源滤波防止大电流负载导致电压骤降引起单片机复位。2.2 电路设计与传感器布局电机驱动电路确保MOS管散热良好驱动能力充足。可以适当增加并联MOS管数量以降低内阻和发热。摄像头接口使用排线连接时注意固定接口防止在颠簸中松动。条件允许的话可以对排线接口进行打胶固定。视觉识别摄像头安装安装高度和俯角需要仔细计算。要确保在识别区域内标签图像既不会太小难以识别也不会因透视畸变过大而影响识别算法。通常需要制作一个可多维度调节的摄像头云台。3. 核心算法实现循迹与视觉识别这是整个系统的软件核心。我们采用模块化设计将循迹、识别、控制、决策等任务分开。3.1 基于摄像头的自适应循迹算法我们的循迹摄像头采集图像后处理流程如下// 伪代码流程基于逐飞库的框架 void track_line_process(void) { // 1. 图像采集 Get_Image(track_cam, track_image_buffer); // 2. 图像预处理 // 二值化采用动态阈值或大津法适应不同光照 uint8_t threshold OtsuThreshold(track_image_buffer, IMAGE_WIDTH, IMAGE_HEIGHT); Binaryzation(track_image_buffer, binary_image, threshold); // 3. 边线提取 // 使用“边线搜索”算法从图像底部向上左右两侧分别搜索跳变点 for (int row IMAGE_HEIGHT - 1; row 0; row - SEARCH_STEP) { Find_Left_Edge_Point(binary_image, row, left_edge[row]); Find_Right_Edge_Point(binary_image, row, right_edge[row]); } // 4. 中线计算与偏差生成 // 选取图像下方几行可靠的中线点计算平均偏差 float error_sum 0.0f; int valid_count 0; for (int i 0; i USEFUL_ROW; i) { if (left_edge[i].valid right_edge[i].valid) { center_point[i] (left_edge[i].col right_edge[i].col) / 2; error_sum (center_point[i] - IMAGE_CENTER_COL); valid_count; } } if (valid_count 0) { g_track_error error_sum / valid_count; // 最终的横向偏差 } else { // 丢线处理使用上一次偏差或执行恢复策略 g_track_error g_last_track_error; } // 5. 控制量计算 (PD控制) g_track_pid.d_error g_track_error - g_last_track_error; g_track_pid.output g_track_pid.kp * g_track_error g_track_pid.kd * g_track_pid.d_error; g_last_track_error g_track_error; // 6. 输出控制舵机打角 Set_Steer_Angle(DEFAULT_ANGLE g_track_pid.output); }关键优化点动态二值化固定阈值在光照变化时极易失效。我们实现了大津法Otsu或根据图像灰度统计进行动态计算鲁棒性大幅提升。丢线处理当搜索不到边线时不能简单地将偏差置零。我们采用记忆上一次有效偏差或根据赛道元素如十字、环岛进行预测的策略帮助小车快速找回赛道。控制分段直道、弯道、入弯、出弯使用不同的PID参数。可以通过偏差大小或偏差变化率来动态切换。3.2 视觉标签识别算法以AprilTag为例AprilTag是一种高效的视觉基准库其识别速度快、抗干扰能力强。我们在RT1064上移植了轻量级的AprilTag 3库。// AprilTag识别任务流程 void vision_tag_detect_task(void) { // 1. 采集识别专用摄像头的图像 Get_Image(vision_cam, vision_image_buffer); // 2. 图像预处理可适当降分辨率以提速 image_u8_t im { .width VISION_WIDTH, .height VISION_HEIGHT, .stride VISION_WIDTH, .buf vision_image_buffer }; // 3. 调用AprilTag检测器 zarray_t *detections apriltag_detector_detect(tag_detector, im); // 4. 解析结果 if (zarray_size(detections) 0) { apriltag_detection_t *det; zarray_get(detections, 0, det); // 通常只处理第一个检测到的标签 g_detected_tag_id det-id; // 获取标签ID g_tag_center_x det-c[0]; // 标签中心图像坐标X g_tag_center_y det-c[1]; // 标签中心图像坐标Y // 5. 计算距离或姿态可选用于精细控制 // 需要已知标签的物理尺寸和相机内参 // estimate_tag_pose(det, ...); } else { g_detected_tag_id -1; // 未识别到标签 } zarray_destroy(detections); // 6. 根据识别结果设置全局任务标志位 if (g_detected_tag_id ! -1) { switch (g_detected_tag_id) { case 0: g_task_command PICK_UP_RED; break; case 1: g_task_command PICK_UP_BLUE; break; case 2: g_task_command DROP_AT_ZONE_A; break; // ... 其他任务映射 default: break; } } }关键优化点识别区域限定ROI只在赛道规定的任务区域内进行全图识别其他区域直接跳过极大节省计算时间。多帧确认连续识别到相同ID的标签N帧后才判定为有效识别避免单帧误触发。标签ID过滤只关注当前赛段可能出现的标签ID忽略无关ID。资源管理AprilTag检测器在初始化后常驻内存避免重复创建销毁。图像可降采样至QVGA320x240或更低分辨率在RT1064上能达到60fps以上的识别速度。4. 系统软件架构与任务调度一个清晰的软件架构能让调试事半功倍。我们采用“前台-后台”与有限状态机FSM结合的模式。4.1 主程序框架// main.c 主循环框架 int main(void) { // 硬件初始化 board_init(); camera_init(track_cam, TRACK_CAM_ADDR); camera_init(vision_cam, VISION_CAM_ADDR); motor_init(); steer_init(); uart_init(); // 用于调试输出 // AprilTag等算法初始化 apriltag_init(); // 全局变量初始化 g_system_state STATE_BOOT; g_task_command CMD_NONE; while (1) { // 1. 状态机调度 switch (g_system_state) { case STATE_BOOT: if (check_start_condition()) g_system_state STATE_RUNNING; break; case STATE_RUNNING: running_state_handler(); break; case STATE_TASK_EXECUTING: task_execute_handler(); break; case STATE_ERROR: error_handler(); break; } // 2. 后台任务固定频率执行 static uint32_t last_tick 0; if (get_systick() - last_tick 10) { // 每10ms执行一次 last_tick get_systick(); // 图像采集与处理放在不同周期避免冲突 if (g_image_capture_flag) { track_line_process(); // 循迹处理 g_image_capture_flag 0; } // 电机PID控制 motor_pid_control(); // 调试信息发送 send_debug_info(); } // 视觉识别任务频率可略低于循迹如20ms static uint32_t last_vision_tick 0; if (get_systick() - last_vision_tick 20) { last_vision_tick get_systick(); vision_tag_detect_task(); } } }4.2 状态机设计示例任务执行状态// 任务执行状态处理函数 void task_execute_handler(void) { switch (g_task_command) { case PICK_UP_RED: // 控制机械臂运动到预定义位置 set_servo_angle(ARM, PICK_ANGLE); delay_ms(300); // 闭合夹爪 set_servo_angle(CLAW, CLOSE_ANGLE); delay_ms(200); // 抬起机械臂 set_servo_angle(ARM, CARRY_ANGLE); g_task_command CMD_NONE; // 清除命令 g_system_state STATE_RUNNING; // 返回循迹状态 break; case DROP_AT_ZONE_A: // 行驶到投放区可通过编码器或视觉辅助定位 move_forward_encoder(1000); // 前进一定距离 // 执行投放动作 set_servo_angle(CLAW, OPEN_ANGLE); delay_ms(200); g_task_command CMD_NONE; g_system_state STATE_RUNNING; break; // ... 其他任务 } }5. 调试技巧与参数整定方法论调车是智能车竞赛中最耗时但也最关键的环节。5.1 循迹PID参数调试步骤归零先将Kp,Ki,Kd全部设为0。调Kp比例逐渐增大Kp让小车在直道上能对偏差有反应开始“摇摆”地回到中线。此时摇摆收敛即可不要过大。调Kd微分增加Kd来抑制Kp引起的振荡。观察小车过弯Kd能提供“预见性”防止冲出赛道。调试目标是过弯平滑出弯迅速回正。慎用Ki积分在智能车这种快速动态系统中积分项容易导致超调和振荡。除非存在静态误差如小车始终偏一侧否则一般设为0或极小值。分段参数在代码中根据偏差绝对值或偏差变化率设置多组PID参数。例如小偏差时用一组柔和参数保证直道稳定大偏差时用一组激进参数快速过弯。5.2 视觉识别调试工具利用串口无线模块如蓝牙、WiFi将调试信息发送到上位机如匿名上位机、逐飞的上位机、自己编写的Python工具是必不可少的。发送图像二值化结果将摄像头二值化后的图像通过串口发送在上位机实时显示可以直观判断阈值是否合适。发送边线坐标将提取到的左右边线点坐标发送出去绘制成曲线可以分析巡线算法的可靠性。发送识别结果与坐标将识别到的标签ID、中心坐标、置信度实时发送便于判断识别区域和稳定性。发送控制量发送舵机打角值、电机目标速度等可以绘制控制曲线分析系统响应。5.3 系统性测试清单在每次修改代码或参数后按顺序进行测试静态测试小车架空观察电机、舵机响应是否符合预期串口数据是否正常。低速闭环测试在赛道上以很低的速度运行测试循迹基础逻辑是否正确能否走完全程。视觉触发测试在低速下手持标签在摄像头前移动测试识别是否准确任务状态机切换是否正确。逐速测试从低速开始逐步提高速度上限每提高一次进行多次全赛道测试确保稳定性。压力测试连续运行10-20圈观察是否有内存泄漏、程序跑飞、电机过热等问题。6. 常见问题与故障排查以下是我们在备赛过程中遇到的高频问题及解决方案问题现象可能原因排查思路与解决方案小车启动后原地抖动或跑偏1. 电机初始化相位错误。2. 陀螺仪未校准或安装不水平。3. 机械结构卡滞。1. 交换电机线序或检查驱动芯片初始化代码。2. 重新校准陀螺仪确保车体静止水平。3. 检查轮胎、齿轮是否顺畅底盘是否平整。循迹时频繁丢线1. 摄像头阈值不适用当前光照。2. 边线搜索算法容错太低。3. 摄像头抖动或焦距不对。1. 改用动态阈值算法大津法。2. 增加无效点的容忍度优化丢线后的预测策略。3. 加固摄像头支架重新调整焦距使赛道清晰。识别AprilTag时有时无1. 图像模糊运动模糊或失焦。2. 光照不均标签过曝或过暗。3. 识别区域ROI设置不当。1. 确保使用全局快门摄像头并调好焦距。2. 调整摄像头曝光时间或增益或增加补光灯。3. 精确计算并缩小ROI减少处理面积提高帧率。执行任务时车身晃动导致失败1. 执行机构动作时未考虑车身惯性。2. 机械臂动作太快冲击大。1. 在执行任务前先让小车减速或短暂停车。2. 为舵机动作增加缓启动/缓停止控制分散冲击力。长时间运行后程序死机1. 堆栈溢出。2. 中断冲突。3. 内存泄漏如频繁malloc/free。1. 增大任务堆栈大小检查递归调用深度。2. 梳理中断优先级避免在中断中处理复杂任务。3. 使用静态内存分配避免动态分配。7. 进阶优化与国赛准备建议当小车能够稳定完赛后以下优化可以进一步提升成绩和鲁棒性运动控制优化速度规划不是全程全速。在直道加速入弯前减速弯心保持出弯加速。可以预先对赛道进行分段设置每段的目标速度。前馈控制在已知的弯道如通过元素识别提前给舵机一个偏角减少纯反馈控制的滞后。差速控制在弯道中让内侧轮速略低于外侧轮速实现更流畅的过弯。视觉识别增强多标签处理当视野中出现多个标签时根据位置、大小进行优先级排序选择最可能是目标的那一个。融合定位结合编码器里程计信息粗略判断小车是否到达了任务区域从而决定是否开启识别避免误识别赛道旁的干扰物。系统容错设计看门狗务必启用硬件看门狗IWDG在程序跑飞时能自动复位。传感器冗余校验例如用编码器速度反馈辅助判断电机是否堵转。安全状态设计一个独立的“安全守护”任务监控主要任务的心跳。一旦某个任务卡死能强制系统进入安全停车状态。国赛适应性训练多变光照在强光、弱光、侧光等不同环境下测试确保算法适应性。赛道干扰自己制作一些类似标签的干扰物放在赛道旁测试识别的抗干扰能力。压力测试进行上百次的连续发车测试统计成功率寻找并消除随机性故障。备战国赛除了技术打磨心态和团队协作同样重要。合理安排进度定期组织模拟比赛做好代码版本管理如使用Git确保每个成员都清晰理解系统全貌。最后祝各位在智能车竞赛的舞台上稳定发挥赛出佳绩。
返回列表