
1. 项目概述与核心思路拆解看到“2021电赛F题送药小车视觉部分的一种思路双OpenMV法”这个标题相信很多参加过电赛或者正在备赛的同学都会眼前一亮。2021年的这道题可以说是把当年电赛的难度和综合性推上了一个新高度。题目要求小车在模拟医院病房的复杂场地内完成识别病房号、识别药盒颜色与数字、抓取并配送至指定病房等一系列任务。这其中视觉识别是整套系统的“眼睛”和“大脑”直接决定了小车能否“看得清”、“认得准”进而完成后续的路径规划和机械动作。当时赛场上视觉方案的选型五花八门从树莓派OpenCV到K210再到我们今天要重点聊的OpenMV。我之所以选择并最终实现了“双OpenMV法”核心驱动力在于任务解耦与实时性保障。一个OpenMV要同时处理病房门牌识别远距离、大视野、需快速定位和药盒信息识别近距离、高精度、需颜色与数字双重判断在有限的算力和帧率下极易顾此失彼导致识别延迟或误判这在分秒必争的电赛中是致命的。“双OpenMV法”的思路本质上是一种分布式视觉处理架构。我将视觉任务拆分为两个独立的子模块分别由两个OpenMV摄像头负责OpenMV A全局导航摄像头固定在小车前端较高位置俯视整个场地。它的核心任务是进行大范围的病房门牌号搜索与识别为小车提供全局定位和导航目标。它不需要关心药盒的细节只需要快速、准确地告诉主控“目标病房在视野的哪个方位”。OpenMV B机械臂末端摄像头安装在机械臂的末端执行器如夹爪上随机械臂运动。它的核心任务是当小车到达药架或病房门口时进行近距离的药盒颜色识别、数字识别以及辅助精确定位抓取点。它的视野小但精度要求极高。这两个摄像头各司其职通过串口与主控通常是STM32等单片机通信。主控根据当前任务阶段决定读取哪个摄像头的数据并做出决策。这套方案的优势非常明显降低了单个处理器的负载提高了系统整体的响应速度和可靠性模块化的设计也便于调试和故障排查。对于电赛这种软硬件结合紧密、对稳定性要求极高的场景这种“专事专办”的思路往往能带来意想不到的稳健表现。2. 硬件选型、搭建与核心参数配置确定了双摄像头的架构接下来就是落地。硬件是骨架配置是神经这一步走扎实了后面的算法才能跑得顺畅。2.1 OpenMV选型与硬件连接当时主流的选择是OpenMV H7 Plus和OpenMV H7。两者核心都是STM32H7系列芯片性能强劲。我的选择是OpenMV A全局摄像头使用OpenMV H7因为病房门牌识别算法相对固定对帧率要求高但对极端复杂的图像处理需求稍低H7的性能完全足够性价比更高。OpenMV B末端摄像头使用OpenMV H7 Plus因为药盒识别涉及颜色分割、数字模板匹配或特征点检测计算量更大且可能需要在不同光照下保持稳定。H7 Plus更大的RAM和更快的CPU在处理这些任务时更有优势确保抓取前的最后一次识别万无一失。连接方式上强烈推荐使用串口UART通信。相比I2C串口全双工、速率高、可靠性好相比SPI接线简单仅需TX、RX、GND三线编程方便。每个OpenMV的串口如UART3连接到主控STM32的对应串口上。务必注意TX接RXRX接TX。供电建议独立或从主控的稳压模块引出避免因电机等大电流设备动作导致摄像头重启。2.2 摄像头安装与视角调试这是非常关键且容易忽略的一步直接决定了识别算法的上限。OpenMV A全局安装安装在小车前端高度以能完整覆盖前方数个病房门牌为宜。镜头略微向下俯视确保地平线在画面中上部这样既能看远又能兼顾近处。一定要用硬质支架固定杜绝抖动画面模糊是识别的大敌。我常用热熔胶和尼龙柱进行加固。OpenMV B末端安装安装在机械臂末端确保其光轴尽可能垂直于药盒表面。距离药盒约10-15cm为佳这个距离下OpenMV的镜头视野能覆盖一个药盒并留有裕量。需要考虑机械臂运动时线缆的束缚我用螺旋缠绕管保护排线防止拉扯脱落。视角调试方法上电后通过OpenMV IDE的“帧缓冲区”查看实时画面。手动移动小车或机械臂到典型位置如正对病房门、正对药架观察目标物体在画面中的位置、大小、有无畸变。通过调整摄像头物理角度确保目标在大多数预期位置都能出现在画面中央区域且大小合适既不过小导致特征不清也不过大超出视野。2.3 核心软件参数初始化在OpenMV IDE中初始化脚本的配置是性能基石。以下是我的常用配置你可以根据实际光照调整# OpenMV A (全局导航摄像头) 初始化概览 import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) # 颜色识别需要RGB sensor.set_framesize(sensor.QVGA) # 320x240兼顾处理速度和视野范围 sensor.skip_frames(time 2000) # 让相机稳定 sensor.set_auto_gain(False) # 必须关闭自动增益防止图像亮度突变 sensor.set_auto_whitebal(False) # 必须关闭自动白平衡防止颜色识别漂移 clock time.clock() # OpenMV B (末端摄像头) 初始化概览 (对颜色更敏感) import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) # 160x120因为视野小不需要高分辨率提升处理速度 sensor.skip_frames(time 2000) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) # 可以额外设置色彩饱和度增强颜色对比度 # sensor.set_saturation(2) clock time.clock()关键提示set_auto_gain(False)和set_auto_whitebal(False)是保证识别稳定的生命线。电赛现场光照复杂自动调节会导致帧与帧之间亮度、色温变化让你的颜色阈值和特征匹配完全失效。固定这些参数虽然在不同场地需要微调但一旦调好稳定性极高。3. 双摄像头视觉算法实现详解硬件和基础配置就绪后就进入了最核心的算法部分。双摄像头分工不同算法策略也截然不同。3.1 OpenMV A病房门牌号的快速定位与识别病房门牌通常是打印的数字贴在门框上。我们的目标是在移动中快速从画面中找到它并识别数字。1. 图像预处理与ROI感兴趣区域设定直接在全图搜索效率低。由于门牌位置相对固定一般在画面中上部可以设定一个ROI来缩小搜索范围大幅提升帧率。# 假设门牌出现在画面顶部1/3区域 ROI_DOOR (0, 0, sensor.width(), sensor.height()//3) img sensor.snapshot() door_img img.copy(roiROI_DOOR)2. 数字识别方案选择方案一模板匹配赛前拍摄所有数字1-8的清晰模板。使用image.find_template()函数。优点是简单直接缺点是受光照、角度影响大且如果数字字体与模板不同会失效。方案二特征点匹配推荐使用image.find_keypoints()提取门牌数字的ORB特征点与预存的特征点模板匹配。OpenMV内置了image.MATCHING功能。这种方法对光照、轻微形变鲁棒性更好。方案三神经网络如果OpenMV固件支持使用内置的TensorFlow Lite Micro进行数字分类。这是最鲁棒的方法但需要训练模型并可能消耗较多资源。我当时采用方案二在稳定性与资源消耗间取得了平衡。核心步骤如下# 预先加载数字1-8的特征点模板kpt1, kpt2... kpt_templates [None, kpt1, kpt2, kpt3, kpt4, kpt5, kpt6, kpt7, kpt8] # 索引即数字 def recognize_door_number(img): kpt img.find_keypoints(max_keypoints150, threshold10, scale_factor1.2) if not kpt: return None max_match 0 matched_num 0 for num in range(1, 9): c image.match_descriptor(kpt_templates[num], kpt, threshold85) if c and c.count() max_match: max_match c.count() matched_num num if max_match 50: # 匹配阈值需实验确定 return matched_num return None3. 方位计算与数据发送识别到数字后还需要告诉主控“门牌在画面的哪个位置”以便小车转向。计算门牌区域特征点聚集区的中心坐标(cx, cy)与画面中心(img.width()//2, img.height()//2)比较可以判断偏左还是偏右进而生成简单的方位指令如‘L’ ‘R’ ‘C’代表中心。 最后通过串口将[房间号 方位]打包发送给主控例如发送字符串“5,C\n”表示“识别到5号房目标在画面中心”。3.2 OpenMV B药盒颜色与数字的精确认别当小车到达药架机械臂运动到位OpenMV B开始工作。它的任务更精细。1. 颜色识别药盒颜色红、绿、蓝等是首要过滤条件。OpenMV的颜色追踪功能image.find_blobs()非常强大。关键步骤LAB色彩空间与阈值调试不要在RGB空间调阈值OpenMV的Tools - Machine Vision - Threshold Editor工具选择LAB色彩空间。L代表亮度A代表红绿B代表蓝黄。通过拖动滑块框选药盒颜色区域可以获取稳定的阈值元组例如红色可能为(20, 60, 10, 60, 5, 40)。这个阈值对光照变化比RGB更鲁棒。# 红色药盒的LAB阈值示例具体值必须现场调试 red_threshold (20, 60, 10, 60, 5, 40) img sensor.snapshot() red_blobs img.find_blobs([red_threshold], area_threshold100, mergeTrue) if red_blobs: largest_blob max(red_blobs, keylambda b: b.area()) # 在largest_blob的区域内进行数字识别避免背景干扰2. 数字识别在颜色区域内找到颜色色块后在该色块的边界框largest_blob.rect()内裁剪出子图像进行数字识别。这里面临挑战药盒上的数字可能更小、字体更随意。方案轮廓分析与模板匹配结合先对子图像进行二值化、腐蚀膨胀等形态学操作突出数字轮廓。然后使用image.find_template()进行匹配。由于已经通过颜色框定了范围且距离固定数字的大小和角度变化较小模板匹配的成功率会很高。提升技巧可以准备多套不同光照条件下的数字模板。识别时依次尝试匹配取置信度最高的结果。3. 坐标输出与抓取引导识别出颜色和数字后OpenMV B还需要输出药盒中心相对于摄像头视野中心的坐标偏移(dx, dy)。这个坐标会发送给主控主控将其转换为机械臂坐标系的运动指令引导夹爪移动到药盒正上方进行抓取。# 计算偏移量 img_center_x sensor.width() // 2 img_center_y sensor.height() // 2 blob_center_x largest_blob.cx() blob_center_y largest_blob.cy() dx blob_center_x - img_center_x dy blob_center_y - img_center_y # 通过串口发送例如R,3,-5,10\n 表示红色3号药盒中心偏左5像素偏下10像素4. 双机通信协议与主控协同逻辑两个摄像头是“感官”主控STM32才是“决策中枢”。设计一套简洁高效的通信协议至关重要。4.1 通信协议设计我采用字符串指令协议简单可靠易于调试。每条指令以换行符\n结尾。OpenMV A - STM32“FIND,n,pos\n” 发现目标。n为识别到的病房号1-8pos为方位L/C/R。例如“FIND,5,C\n”。“LOST\n” 丢失目标。OpenMV B - STM32“DRUG,color,num,dx,dy\n” 识别到药盒。color为颜色首字母R/G/Bnum为数字dx, dy为坐标偏移。例如“DRUG,R,3,-5,10\n”。“READY\n” 就绪上电或完成一次识别后发送。STM32 - OpenMV A/B“MODE,A\n”或“MODE,B\n” 切换模式。主控发送“MODE,A\n”表示进入导航阶段OpenMV A开始工作OpenMV B进入低功耗或休眠。发送“MODE,B\n”则相反。“TRIG\n” 触发识别。在特定时刻如机械臂到位后主控发送此命令触发对应摄像头进行一次识别并返回结果避免摄像头持续发送数据造成串口拥堵。4.2 主控STM32协同逻辑主控的程序是一个状态机根据任务阶段切换摄像头模式并解析数据。上电初始化打开两个串口接收摄像头“READY”信号。导航阶段发送“MODE,A”。循环读取OpenMV A的数据。当收到“FIND,n,C”目标在中心时控制小车直行逼近收到“FIND,n,L/R”时控制小车原地旋转直至收到“C”。收到“LOST”则进入搜索模式。当接近病房时切换模式。取药阶段小车到达药架机械臂运动到预置位。主控发送“MODE,B”然后发送“TRIG”。等待并解析OpenMV B发回的“DRUG,…”信息计算出机械臂需要移动的精确距离控制机械臂完成抓取。送货与返回阶段抓取后再次切换为“MODE,A”重复导航逻辑将药盒送至目标病房。完成后可能还需要OpenMV B进行一次放置确认。这种主控问摄像头答的触发式通信避免了串口数据冲突也让程序流程非常清晰。5. 现场调试心得与避坑指南电赛的考验不仅在方案更在临场调试。以下是我用双OpenMV法踩过的坑和总结的经验比代码本身更有价值。5.1 光照适应性调试现场光照是最大变量。绝不能指望一套参数通吃。提前准备赛前用手机手电筒、台灯等模拟不同角度的强光、弱光和侧光测试你的识别算法。记录下几种典型光照下的最佳阈值和参数。现场快速调参编写一个简单的调试模式通过串口发送指令动态调整OpenMV的阈值、曝光等参数。例如发送“THRESHOLD,20,60,10,60,5,40”来更新颜色阈值。这能让你在半小时内适应赛场光线。利用白纸做白平衡如果现场允许在每次启动前用一张白纸充满摄像头视野运行一个白平衡校准脚本虽然我们关了自动白平衡但可以手动设置一个基准值能极大改善颜色识别的准确性。5.2 稳定性与抗干扰优化数据滤波不要相信单次识别结果。对连续多次的识别结果进行滤波。例如连续5帧中有4帧识别到同一个数字才认为识别有效。这能滤除偶尔的误识别。超时与恢复机制主控程序里对每个识别步骤都要设置超时。比如发送“TRIG”后如果2秒内没收到回复则认为本次识别失败可以重试或进入异常处理流程防止程序“卡死”。电源隔离电机驱动模块的电源一定要和摄像头、主控的电源隔离使用不同的稳压模块或加二极管隔离。电机启停的瞬间会产生巨大的电压毛刺足以导致摄像头重启或单片机复位。我吃过亏调试半天发现是电源问题。5.3 效率提升技巧降低分辨率与帧率在满足识别精度的前提下尽量使用低分辨率如QQVGA和适当的帧率。这能显著降低CPU负载提高处理速度让识别更跟手。优化搜索算法对于OpenMV A不要每一帧都进行全图特征点匹配。可以先使用img.find_edges()或img.find_blobs()寻找门牌可能的矩形区域进行粗定位只在候选区域内进行精细的特征点匹配这叫“由粗到精”的搜索策略能大幅提升效率。固定识别时机对于OpenMV B不要让它一直识别。只在机械臂稳定到位后由主控发送“TRIG”命令触发一次识别。识别完成后摄像头可以进入空闲状态节省功耗和算力。6. 备选方案对比与扩展思考双OpenMV法虽然稳健但并非唯一解。了解其他方案能更深刻理解自己选择的优劣。单OpenMV方案所有视觉任务压给一个摄像头。优点是成本低、结构简单。缺点是在复杂任务切换时需要频繁更换算法和参数帧率下降实时性差且调试困难一个任务出问题会影响全局。树莓派OpenCV方案算力碾压可以使用更复杂的神经网络模型如YOLO做目标检测识别精度和鲁棒性理论上限更高。缺点是系统复杂需要运行Linux功耗大实时性受系统调度影响对电源要求高且与单片机通信通常用串口或网络也需要额外调试。在电赛紧张的环境中稳定性挑战更大。K210等专用AI芯片在边缘计算和神经网络加速方面有优势。但当时其生态和资料相对OpenMV较少开发门槛稍高。“双OpenMV法”的核心价值在于它用适中的成本两个OpenMV和复杂度通过硬件层面的任务分工换取了媲美高端方案的实时性与可靠性同时保留了OpenMV生态易开发、易调试的优点非常符合电赛对“稳定完赛”的追求。这套思路其实可以扩展到更多场景。比如一个机器人需要同时进行大范围目标搜索和小范围精细操作都可以考虑使用多个专用视觉传感器进行分布式处理。关键在于做好任务拆解、接口定义和主控协同。