ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

2026年湖北电赛H题省奖方案:钢珠平衡小车的设计与视觉识别

2026年湖北电赛H题省奖方案:钢珠平衡小车的设计与视觉识别 前言本文主要介绍我们参加2026年全国大学生电子设计竞赛H题时的设计思路包括赛题分析、系统总体方案、小车底盘、机械结构以及钢珠视觉识别程序的具体实现。这道题表面上看是一个钢珠平衡问题但真正开始调试以后就会发现最终效果并不只取决于平衡控制算法。小车启动和制动是否平稳、摆杆有没有机械间隙、舵机连杆是否存在空程、摄像头曝光是否稳定都会直接影响钢珠的位置控制精度。因此我们的整体思路并不是一上来就堆复杂算法而是先让底盘、机械结构和视觉测量尽量稳定再利用视觉反馈和闭环控制进一步提高系统精度。图1 钢珠平衡小车整体实物图一、赛题任务与核心难点本题是在智能小车运动控制的基础上增加钢珠位置识别与动态平衡任务。系统不仅需要控制小车完成规定路线还需要实时检测凹槽内钢珠的位置并通过摆杆机构让钢珠移动到指定区域。实际调试时我们把整个问题分成了四个部分。1.1 小车运行平稳性小车启动、制动和转向时如果左右轮速度变化过快车身就会产生明显的加速度和振动钢珠也会随之发生较大位移。因此小车控制不能只追求响应速度还需要限制加速度和转向修正量使整个运行过程尽量平滑。1.2 机械结构一致性摆杆、合页和舵机连杆需要尽量减小间隙并保证左右两个方向的运动阻力基本一致。如果一侧阻力大、另一侧阻力小控制器就很难使用同一组参数获得对称的响应效果。1.3 动态视觉识别视觉系统不仅要在静止状态下识别钢珠还要适应以下情况车体振动摆杆倾斜钢珠快速运动钢珠表面反光现场光照变化画面中的支架、端件和阴影干扰。视觉部分很容易出现一个误区静止时能够框到钢珠就认为识别程序已经调好了。真正运行起来以后钢珠快速运动会产生拖影和反光摆杆倾斜后固定识别区域可能偏离凹槽小车振动还会让摄像头画面产生轻微位移。1.4 视觉数据稳定性视觉程序不能把某一帧的误识别结果直接交给后续控制程序。除了正确识别钢珠还需要通过物理范围限制、上一帧位置约束和目标丢失判断减少坐标跳变。因此我们最终采用的不是简单的“在整幅图中找圆”而是一套结合摆杆结构、钢珠尺寸和运动范围设计的动态识别方案。二、系统总体设计2.1 系统总体框架整个系统主要由以下四个部分组成小车运动控制模块摆杆机械执行模块钢珠视觉识别模块任务控制与数据通信模块。Linux开发板通过摄像头获取摆杆和钢珠图像运行C和OpenCV视觉程序完成图像解码、ROI裁剪、摆杆中心线跟踪、钢珠位置提取和像素坐标标定。MSPM0G3519负责读取循迹传感器和编码器数据同时完成小车运动控制及舵机控制。整个系统的数据关系可以概括为摄像头采集图像 ↓ Linux开发板运行视觉程序 ↓ 计算钢珠实际位置 ↓ 任务模块或主控读取位置结果 ↓ 控制舵机调整摆杆角度图2 系统总体结构框图2.2 小车底盘方案常见的小车底盘主要包括三轮差速结构和四轮结构。四轮底盘可以采用差速转向或舵机转向而三轮底盘通常使用左右驱动轮完成差速转向再通过万向轮提供支撑。综合考虑转向灵活性、机械复杂度和低速运行稳定性我们最终选择了三轮差速底盘。三轮结构的转向阻力相对较小对电机扭矩的要求不高也比较适合本题低速、平稳运行的需求。小车运动控制采用速度闭环和转向闭环相结合的方式。编码器负责反馈左右轮的实际速度循迹传感器负责计算小车相对于赛道中心线的位置偏差。左右电机的控制量可以表示为$$PWM_LPWM_{speed}PWM_{turn}$$$$PWM_RPWM_{speed}-PWM_{turn}$$其中$PWM_{speed}$ 为速度环输出$PWM_{turn}$ 为转向修正量$PWM_L$ 和 $PWM_R$ 分别为左右电机的最终控制量。通过两部分叠加可以在维持运行速度基本稳定的同时完成方向调整。这里需要注意本题中的小车控制不能只追求“转得快”或者“响应灵敏”。小车动作越平滑钢珠平衡部分的控制压力就越小。2.3 机械结构方案机械结构对钢珠平衡效果的影响非常明显。如果摆杆存在较大间隙、转轴摩擦不均匀或者整体刚度不足即使后续使用比较复杂的PID控制也很难得到稳定效果。根据题目要求摆杆一端需要使用合页、链条或类似结构连接另一端可以使用舵机或步进电机驱动。考虑到步进电机的体积和重量相对较大而舵机的速度、扭矩和控制精度已经能够满足需求因此我们最终采用了“自制合页舵机连杆”的机械结构。机械部分调试时建议优先检查下面几个问题合页或转轴能否顺畅转动摆杆向左右两侧运动时阻力是否基本一致舵机摇臂和连杆之间是否存在明显空程连杆与摆杆的连接点是否会松动舵机动作时摄像头支架是否会随之振动摆杆运动到极限位置时是否仍处于摄像头画面内。这些问题如果没有处理好摄像头画面和摆杆中心位置都会发生变化。此时无论怎样修改视觉阈值都很难获得真正稳定的识别结果。图3 摆杆与舵机连杆机械结构2.4 主要硬件组成本系统使用的主要硬件如下硬件主要作用MSPM0G3519主控板小车运动控制、编码器采集和舵机控制Linux视觉开发板运行C与OpenCV视觉程序USB或CSI摄像头采集摆杆及钢珠画面直流减速电机驱动小车运动编码器测量左右轮实际速度电机驱动模块驱动左右电机循迹或灰度传感器检测赛道位置偏差舵机调整摆杆角度自制合页与连杆构成摆杆执行机构电池与电源模块为主控、开发板和执行机构供电主控最终选择MSPM0G3519主要是因为它的片上资源可以满足双电机编码器采集、电机PWM控制、循迹传感器读取、舵机控制和通信等需求。结合实际接口分配情况MSPM0G3519比MSPM0G3507更加适合完成双编码器闭环控制。三、视觉识别方案3.1 方案选择钢珠识别常见的方案主要有YOLO目标检测和OpenCV传统图像处理两种。YOLO具有较强的环境适应能力但需要提前采集、标注和训练数据对视觉处理平台的算力也有一定要求。本题中的钢珠始终位于一条已知摆杆附近钢珠尺寸和活动范围相对固定。因此我们最终选择了更加轻量的OpenCV灰度图像处理方案。这套方案的核心思路可以概括为一句话先找到摆杆再沿着摆杆寻找钢珠。这样做主要有两个优势可以大幅缩小搜索范围减少底盘、舵机、支架和背景对识别结果的影响当摆杆发生倾斜时钢珠检测区域可以跟随摆杆移动不需要依赖完全固定的矩形ROI。3.2 视觉程序整体流程这套视觉工程使用C和OpenCV实现与视觉识别直接相关的文件主要包括文件主要作用vision.cpp / vision.h摄像头初始化、MJPEG采集、灰度解码、ROI裁剪和曝光调整ball_detect.cpp / ball_detect.h摆杆中心线跟踪、钢珠特征提取、目标筛选和位置标定image_server.cpp / image_server.h发送调试图像和识别元数据intrrupt.cpp启动视觉线程、注册回调和刷新调试画面视觉程序的主要流程如下摄像头采集MJPEG图像 ↓ 解码为灰度图 ↓ 裁剪摆杆活动区域 ↓ 跟踪摆杆中心线 ↓ 沿中心线建立动态ROI ↓ 计算局部背景亮度 ↓ 提取钢珠暗度特征 ↓ 使用球径匹配滤波 ↓ 筛选候选目标 ↓ 计算钢珠亚像素中心 ↓ 转换为实际厘米位置为了避免文章被大量工程代码占满下面只保留几段最能体现视觉思路的核心代码。边界检查、日志输出和调试统计等辅助部分不再全部展示。四、视觉核心代码讲解4.1 图像采集与ROI裁剪摄像头以MJPEG格式输出图像。程序获得JPEG数据以后直接使用OpenCV将其解码为单通道灰度图。后续算法主要利用摆杆和钢珠之间的亮度差异因此没有必要一直保留彩色图像。核心代码如下const uint8_t *jpg nullptr; size_t jlen 0; uint64_t ts 0; if (!cam.grab(jpg, jlen, ts, 500)) { return; } cv::Mat raw( 1, (int)jlen, CV_8UC1, const_castuint8_t *(jpg) ); detect_gray cv::imdecode( raw, DETECT_IMREAD_FLAG ); int cy0 DETECT_CROP_TOP / DETECT_REDUCE; int cy1 detect_gray.rows - DETECT_CROP_BOTTOM / DETECT_REDUCE; int cx0 DETECT_CROP_LEFT / DETECT_REDUCE; int cx1 detect_gray.cols - DETECT_CROP_RIGHT / DETECT_REDUCE; cv::Mat strip detect_gray( cv::Rect( cx0, cy0, cx1 - cx0, cy1 - cy0 ) );这里真正需要注意的不是cv::imdecode()而是后面的ROI裁剪。摄像头原始画面中通常会包含底盘、桌面、支架、铰链座和舵机连杆。这些物体有时比钢珠更黑如果直接处理整幅图像很容易形成错误目标。因此我们只保留摆杆可能出现的区域。裁剪以后一方面可以减少背景干扰另一方面也能降低后续逐像素扫描的运算量。图4 摄像头采集到的摆杆与钢珠原始画面除了裁剪范围曝光时间也非常重要。如果曝光时间太长画面确实会变亮但快速运动的钢珠会出现明显拖影如果曝光时间太短图像噪声又会增加。所以调试曝光时不要只看画面够不够亮还需要观察运动中的钢珠有没有被拉长以及钢珠和管体之间是否仍然具有稳定的灰度差异。4.2 摆杆中心线跟踪如果使用完全固定的ROI摆杆一旦发生倾斜检测区域就可能偏离凹槽。因此程序会先对摆杆位置进行跟踪。具体做法是逐列扫描图像在每一列中寻找最长的一段连续亮像素再把这段亮区域的中心作为当前列的摆杆中心点。核心代码如下const int thr (cmax * BALL_RUN_KEEP_Q8) 8; int bestLen 0; int bestStart 0; int curLen 0; int curStart 0; for (int y y0; y y1; y, p stride) { if (*p thr) { if (curLen 0) { curStart y; } if (curLen bestLen) { bestLen curLen; bestStart curStart; } } else { curLen 0; } } s_trkY[n] (float)bestStart 0.5f * (float)(bestLen - 1);这里没有直接寻找“最亮的一个像素”而是寻找“最长的一段亮像素”。这是因为钢珠表面可能存在很亮的反光点。如果只寻找最大灰度值钢珠上的高光就可能把摆杆中心位置带偏。白色摆杆在每一列中通常会形成一段连续亮区域因此使用最长亮段的中心会更加可靠。获得多列中心点以后程序使用二次曲线拟合摆杆中心线$$yAt^2BtC$$其中$t$ 为归一化后的横坐标。使用二次曲线而不是简单直线主要是为了适应广角摄像头产生的桶形畸变。原本笔直的摆杆在画面中可能呈现出轻微弯曲二次曲线能够更好地贴合实际图像。拟合过程中还可以使用绝对偏差中位数也就是MAD剔除少量离群点避免钢珠、支架和局部反光把整条中心线拉偏。4.3 沿中心线建立动态ROI得到摆杆中心线以后程序不会继续处理整个裁剪区域而是沿拟合曲线建立一条窄带作为钢珠检测区域。int roiH (int)lroundf( s_band * BALL_ROI_FILL ) | 1; roiH clampi( roiH, 3, BD_MAX_H );检测区域的高度根据摆杆亮带的实际厚度动态计算。ROI并不是越大越好。如果ROI太高管道外沿、黑色底盘和刻度线都会被包含进来如果ROI太低又可能只能看到钢珠的一部分。比较合适的范围是覆盖凹槽内部同时尽量避开水管外沿和其他机械结构。由于ROI沿着拟合后的中心线建立所以无论摆杆处于水平状态还是倾斜状态检测区域都可以跟着摆杆移动。4.4 使用局部中位数提取钢珠特征钢珠通常比白色凹槽更暗但不能简单使用一个固定灰度阈值。例如在较亮环境中白色管体灰度可能接近180钢珠灰度可能为60换到较暗环境后管体可能下降到100钢珠也可能下降到40。如果一直使用固定阈值阴影和管壁都有可能被错误识别成钢珠。因此程序会计算当前位置附近的局部中位数并将它作为背景亮度参考。钢珠的暗度特征可以表示为$$f(x,k)\max\left(0,\alpha m(x,k)-I(x,k)\right)$$其中$m(x,k)$ 表示当前位置附近的局部中位数$I(x,k)$ 表示当前像素灰度$\alpha$ 表示背景亮度比例参数。对应的核心代码如下const int32_t d ((med * BALL_DARK_Q8) 8) - (int32_t)s_row[i]; if (d 0) { s_cnt[i]; s_feat[i] (float)d; }这里使用中位数而不是均值是一个比较关键的细节。当窗口经过阴影边缘时均值很容易落在明暗区域之间导致阴影附近的一大片像素都被判断为暗目标。中位数通常更接近明区或暗区中的一侧不容易在阴影边缘形成假的钢珠特征。另外钢珠只占局部窗口中的一小部分对中位数的影响相对较小。如果使用均值钢珠自身反而可能把背景参考亮度拉低。为了提高实时性程序使用直方图维护滑动中位数。窗口每向右移动一列只需要加入一个像素、移出一个像素不需要重新对整个窗口排序。4.5 球径匹配与候选目标筛选钢珠表面经常会出现高光。因此钢珠在灰度图中的暗区不一定是一个完整的实心圆有时会被中间的高光分成左右两部分。如果程序只寻找最暗的一列检测坐标就容易在钢珠左右两侧来回跳动。我们的处理方法是使用一个接近钢珠直径的窗口将窗口内的暗度特征累加起来。匹配得分可以表示为$$S(i)\sum_{ji-D/2}^{iD/2}\sum_k f(j,k)$$其中$D$ 表示钢珠直径对应的像素宽度。这个操作可以理解成一个一维匹配滤波。孤立噪点在球径窗口内无法积累出足够高的得分而被高光分开的两部分暗区仍然会落在同一个窗口中因此钢珠整体仍然能够形成比较明显的峰值。程序还会结合以下条件筛选候选目标候选位置不能超出摆杆的实际量程暗像素填充率不能太低填充率不能接近整片全黑目标宽度需要与钢珠尺寸基本一致目标与周围背景之间需要具有足够的亮度差异连续跟踪时优先检查上一帧位置附近的候选目标偏离上一帧过远的目标不能立即替代原目标。这里还有一个比较实用的处理方式。如果当前得分最高的目标不满足条件程序只淘汰当前候选然后继续检查其他目标而不是直接把整帧判断为识别失败。这样可以避免铰链、舵机端件或者阴影获得最高得分以后让真正的钢珠失去被检查的机会。图5 钢珠视觉识别与位置计算结果4.6 亚像素定位与厘米标定找到最佳候选区域以后程序不会直接使用整数峰值位置而是在峰值附近计算暗度加权质心。核心代码如下const int lo std::max(0, iPeak - D); const int hi std::min(nAll - 1, iPeak D); double sw 0.0; double swx 0.0; for (int i lo; i hi; i) { sw s_feat[i]; swx (double)s_feat[i] * i; } const float xBall (float)x0 (float)(swx / sw);这里将质心计算限制在匹配峰值附近而不是对整个ROI计算全局质心。如果使用全局质心摆杆另一端只要出现一个较暗的杂点就可能把最终坐标拉偏。限制计算范围以后既可以获得浮点数形式的亚像素坐标也能减少远处干扰。得到钢珠像素坐标后还需要将其转换成摆杆上的实际厘米位置。程序首先将像素坐标归一化$$u\frac{x}{W}-u_0$$然后使用二次多项式完成标定$$pC_2u^2C_1uC_0$$对应代码如下static inline float uToCm(float u) { return BALL_PX2CM_C2 * u * u BALL_PX2CM_C1 * u BALL_PX2CM_C0; }这里保留二次项是因为摄像头可能存在广角畸变和斜视问题。画面左侧的1 cm和画面右侧的1 cm不一定对应完全相同的像素数量。使用多点标定和二次拟合可以减小这种非线性误差。标定时可以按照下面的方法进行将钢珠依次放在摆杆上的多个已知刻度位置记录每个位置对应的像素坐标对像素坐标进行归一化使用最小二乘法拟合二次多项式使用没有参与拟合的位置检查实际误差。需要特别注意ROI裁剪范围和标定参数是相互关联的。只要修改了左右裁剪范围、图像缩放比例或者摄像头安装位置就应该重新检查像素—厘米标定参数。否则可能出现一种比较隐蔽的问题检测框看起来始终套在钢珠上但输出的厘米位置存在整体偏移或比例误差。4.7 时序约束与识别失败处理动态画面中偶尔漏掉一帧并不可怕真正危险的是把远处的伪影突然当成钢珠。因此程序会保存上一帧的钢珠位置。正常跟踪时程序优先在上一帧位置附近寻找候选目标如果连续几帧没有找到可信目标再逐渐扩大搜索范围。连续丢失达到设定次数以后程序会清除旧轨迹并重新进行全局搜索。这种方式既可以减少坐标突然跳变也能避免钢珠真正移动到其他位置以后程序一直被旧坐标锁住。这里还需要明确区分两个状态误差为0表示钢珠已经到达目标位置识别失败表示当前没有可靠的视觉测量结果。两者在后续程序中不能采用相同的处理方式。识别失败时更合理的做法是设置独立的无效标志并采用短时保持、安全回中或者暂时停止更新控制量等策略。五、系统调试与日志分析5.1 推荐调试顺序视觉系统建议按照下面的顺序逐步调试固定摄像头和机械结构确认摆杆在极限角度时仍然位于裁剪区域内检查摆杆中心线能否稳定跟踪观察动态ROI是否能够跟随摆杆移动固定钢珠调整局部暗度比例和候选门限排除端件、阴影和刻度线产生的假目标缓慢移动钢珠检查输出坐标是否连续完成像素—厘米多点标定使用直尺复查没有参与拟合的位置让钢珠快速运动调整曝光时间和识别门限最后完成视觉、舵机和小车运动的联合调试。调试时不要只看检测框有没有套住钢珠还应该同时记录以下数据ROI平均亮度管体参考亮度钢珠匹配峰值暗像素填充率连续丢失帧数单帧解码时间单帧扫描时间实际识别帧率。只有知道某一帧为什么识别成功、为什么识别失败参数调整才有依据。5.2 日志曲线分析为了观察钢珠在实际运行过程中的位置变化我们记录了目标位置、钢珠实测位置、运动速度、PWM输出以及不同运行阶段的状态。图6 调参阶段目标位置与钢珠实测位置对比通过这张曲线可以重点观察以下几个问题实测位置能否跟随目标位置变化钢珠到达目标附近时是否存在明显超调系统是否发生持续振荡最终位置能否进入允许误差范围不同运动阶段的响应速度是否一致。需要说明的是这组日志属于调参阶段记录不能直接作为最终达标结果。在该次测试结束时目标位置约为 $-5.0\text{ cm}$钢珠实测位置约为 $-3.45\text{ cm}$仍然存在约 $1.55\text{ cm}$ 的误差日志最终事件也标记为verification_fail。不过这张曲线依然具有较大的分析价值。它说明视觉测量能够连续反映钢珠的位置变化但最终控制效果还会受到舵机响应、机械摩擦、连杆间隙以及控制参数的影响。换句话说视觉曲线连续只能说明测量链路基本正常并不代表整个系统已经完成调试。5.3 常见问题总结1. 修改裁剪范围后没有重新标定这种情况下检测框可能仍然能够准确跟随钢珠但厘米坐标会出现整体偏移或比例误差。2. 曝光时间只看画面亮度不看运动拖影画面虽然更亮但快速运动的钢珠会被拉长匹配滤波得分反而可能下降。3. 使用完全固定的灰度阈值现场光照发生变化后管体和钢珠的灰度会一起变化固定阈值很容易失效。4. 只保留全局最强峰铰链、舵机端件或阴影可能比钢珠更黑。正确做法是先判断候选目标是否符合钢珠特征再比较候选得分。5. 把识别失败当成误差为0这会让系统误以为钢珠已经到达目标位置。识别失败必须保留独立的无效状态。6. 只调整控制参数不检查机械结构如果摆杆摩擦不均匀或者连杆存在明显空程控制器就需要不断补偿这些机械误差最终效果很难稳定。六、总结本系统采用三轮差速底盘以MSPM0G3519作为运动控制核心机械部分采用合页、摆杆和舵机构成钢珠位置调整机构。视觉程序运行在Linux开发板上通过OpenCV完成图像采集、摆杆跟踪、钢珠检测和位置标定。这套视觉算法最核心的地方并不是使用了某一个特别复杂的图像处理算子而是充分利用了题目本身的结构特点钢珠始终位于摆杆凹槽附近摆杆主体通常比周围背景更亮钢珠又比局部管体更暗钢珠直径和运动范围是已知的连续两帧之间的位置变化存在物理限制。程序首先通过最长亮段和二次曲线拟合跟踪摆杆然后沿摆杆中心线建立动态ROI。接着利用局部中位数抵消光照变化通过球径匹配滤波累积钢珠暗度特征并结合物理量程、目标形态和时序约束排除干扰。最后通过亚像素质心计算钢珠中心坐标再利用二次多项式将像素位置转换为实际厘米位置。相比固定阈值或者直接在整幅图中找圆这种方法更加适合装车后的动态环境。整个项目调试下来我们最大的体会是视觉、机械和控制并不是三个互相独立的部分。视觉测量稳定以后控制器才有可靠输入机械结构足够顺畅以后控制器才不需要不断补偿摩擦和间隙小车运行足够平稳以后钢珠平衡的难度也会明显降低。只有将底盘、机械结构、视觉识别和控制系统放在一起联合调试才能真正提高钢珠的位置控制精度和整个系统的环境适应能力。
返回列表