ARTICLE DETAIL

资讯详情

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

OpenCV视觉与贪心算法:网球自拾取机器人三大模块闭环实现

OpenCV视觉与贪心算法:网球自拾取机器人三大模块闭环实现 简介面向计算机视觉与机器人方向的学生及毕业设计开发者源码包完整实现了网球自拾取机器人的视觉捕捉与路径规划两大核心模块。系统基于OpenCV完成颜色识别、圆心检测与投影变换结合贪心算法规划拾取路径并融入SVM训练与轨迹预测适合作为毕设项目或课程设计的进阶参考。资源共80个文件以18个C源码文件为主体配套26张jpg测试图像、SVM训练数据、YAML配置及PDF论文文档整体约77.73MB目录按源码、算法、数据、论文等模块划分便于按需检索。已有123人学习下载代码均测试运行成功。从内容预览看包内包含颜色检测、圆心识别、质心提取等视觉处理源码以及贪心算法、轨迹预测、实时识别等可执行程序并附《基于机器视觉的轨迹识别和位置标定》论文供对照理解。读者可借此掌握视觉标定、路径规划与SVM结合的完整工程思路也可在现有代码基础上扩展功能用于其他机器人场景。1. 基于OpenCV视觉捕捉与贪心算法路径规划的网球自拾取机器人三个模块如何闭环第一次把网球自拾取机器人放进训练场我的预期很朴素OpenCV视觉捕捉框住黄色小球贪心算法路径规划排出一个抓球顺序底盘沿坐标走过去吸球机构把球收进筐。结果机器人在场地中央原地打转——开窗的阳光把裁判椅影子染成了“网球色”。标题里三个关键词拆开就是这类拾取机器人最典型的闭环OpenCV视觉捕捉回答“球在哪”贪心算法路径规划回答“先抓哪个、后抓哪个”底盘和抓取机构把规划变成位移和动作。整套方案一台树莓派、一个USB摄像头、一个麦克纳姆轮底盘就能跑通链路配源代码和文档说明照着接线和跑脚本两天能打通核心流程。做机器人课程设计、竞赛底盘或毕设选型的人这套路线是上手最快、坑相对可控的组合。2. OpenCV视觉捕捉链路HSV阈值、轮廓拟合与坐标映射把网球变成抓取点2.1 网球识别的HSV阈值标定为什么RGB切不出干净的目标第一版我直接用RGB范围切黄色翻车翻得很快。网球是荧光黄Opti Yellow但RGB三通道会随光照联动变化太阳一偏G和B一起涨固定的RGB阈值立即失效更麻烦的是网球表面的白色商标圈在RGB空间和白色背景很难分开。换成HSV之后H色相只表达“颜色种类”S和V分别管饱和度和明度。OpenCV的H范围是0到179不是常规的0到360荧光黄在大多数USB摄像头下H落在20到35之间S要高于80才能滤掉灰白地面V的容忍度最大只要不逆光40到255都能用。标定这一步不要靠猜直接用调节条实时看mask。下面是常用的HSV trackbar标定脚本import cv2 import numpy as np def nothing(x): pass cv2.namedWindow(hsv_tune) # H范围0~179S和V范围0~255 cv2.createTrackbar(H_min, hsv_tune, 20, 179, nothing) cv2.createTrackbar(H_max, hsv_tune, 35, 179, nothing) cv2.createTrackbar(S_min, hsv_tune, 80, 255, nothing) cv2.createTrackbar(S_max, hsv_tune, 255, 255, nothing) cv2.createTrackbar(V_min, hsv_tune, 80, 255, nothing) cv2.createTrackbar(V_max, hsv_tune, 255, 255, nothing) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) h_min cv2.getTrackbarPos(H_min, hsv_tune) h_max cv2.getTrackbarPos(H_max, hsv_tune) s_min cv2.getTrackbarPos(S_min, hsv_tune) s_max cv2.getTrackbarPos(S_max, hsv_tune) v_min cv2.getTrackbarPos(V_min, hsv_tune) v_max cv2.getTrackbarPos(V_max, hsv_tune) lower np.array([h_min, s_min, v_min]) upper np.array([h_max, s_max, v_max]) mask cv2.inRange(hsv, lower, upper) cv2.imshow(frame, frame) cv2.imshow(mask, mask) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明cvtColor把每一帧BGR转成HSVinRange输出一张二值图像素在阈值区间内为255否则为0。调节条上H_min和H_max这组参数是整个视觉模块的地基H区间收得太窄换个色温的灯就丢球放得太宽广告牌、黄色训练桩也会进来。我一般先把S拉到80以上V保持默认下限只在H上做微调最后再看现场有没有误检。参数说明S_min80是一个经验起点室内LED灯下荧光黄的S大约在120到200之间如果是室外阳光直射S可能掉到70以下这时要降到60并用形态学过滤来补。V_min设80是为了挡掉暗部阴影里的灰色噪点但如果你在傍晚或室内暗光环境跑这个值要往下调否则球的暗面会被切掉一半。2.2 从二值图到球心坐标findContours与minEnclosingCircle的参数选择拿到干净的二值图之后下一步是找每个球的圆心。惯用的链路是形态学开闭运算 → findContours → contourArea过滤 → minEnclosingCircle。开运算去掉白色小噪点闭运算把球面上高光造成的黑孔补上。下面这段代码是检测主循环的核心import cv2 import numpy as np lower_yellow np.array([20, 80, 40]) upper_yellow np.array([35, 255, 255]) def detect_balls(frame): hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, lower_yellow, upper_yellow) # 椭圆核对圆形目标更友好 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) balls [] for cnt in contours: area cv2.contourArea(cnt) if area 200: # 640x480下小于200px的是噪声 continue (x, y), radius cv2.minEnclosingCircle(cnt) if radius 5 or radius 60: continue balls.append((int(x), int(y), float(radius))) return balls, mask逻辑说明findContours用RETR_EXTERNAL只取最外层轮廓避免网球内高光区被单独拆成子轮廓CHAIN_APPROX_SIMPLE只保留端点减少后续几何计算量。minEnclosingCircle对每个轮廓求最小外接圆输出的x、y就是球心像素坐标radius能用来估计球离相机远近。参数说明面积下界200是640x480分辨率、相机高度1.5米左右的经验值。一颗网球直径约6.7厘米在画面里通常占直径30到50像素、面积700到2000像素所以200以下的基本是地砖反光或噪点。半径上界60用来挡掉大面积的黄色块比如球场广告牌。如果你把相机架到3米高这些阈值要按比例重新标最简单的办法是在现场打印一个直径6.7cm的圆纸片放到最近和最远处各测一组像素半径。提示如果换用HoughCircles建议在同一段视频上对比检测稳定性。通常你会发现它比minEnclosingCircle多出几个幻觉圆而且每次阈值微调都要重新标定调试成本高一个量级。为什么不选HoughCircles它要同时调dp、minDist、param1、param2四个参数对阴影边缘和虚焦点极其敏感稍微换个场地就要重调一轮。而这里先由HSV通道完成语义过滤再用几何拟合误检率低一个数量级调试成本也低。2.3 像素坐标到机器人坐标单应矩阵与solvePnP怎么选检测输出的球心是像素坐标(u, v)底盘要的是机器人坐标系下的毫米坐标(x, y)。因为球场地面是平面所有球都落在地面上z0的假设成立这时候最简单的做法是标定一个3x3单应矩阵H在场地里放四个已知毫米坐标的点在画面里点出对应像素点用cv2.getPerspectiveTransform就能算出H。之后任意像素坐标都能通过透视变换换算到场地坐标。import cv2 import numpy as np # 四个地面标记点的毫米坐标与机器人坐标系原点一致 obj_pts np.float32([[0, 0], [1500, 0], [1500, 1500], [0, 1500]]) # 这四个标记点在画面中的像素坐标人工点击获得 img_pts np.float32([[120, 240], [520, 230], [540, 440], [110, 450]]) H cv2.getPerspectiveTransform(img_pts, obj_pts) def pixel_to_mm(u, v): src np.float32([[[u, v]]]) dst cv2.perspectiveTransform(src, H) return dst[0][0] # (x_mm, y_mm)逻辑说明getPerspectiveTransform要求正好4组对应点输出3x3单应矩阵。perspectiveTransform把单个像素点映射到毫米坐标机器人拿到这个坐标就能算平移量。这个方案不需要先标定相机内参是地面平面场景下的最快路径。参数说明四个标记点要覆盖机器人的工作区域不要集中在一个角落否则外推区域误差会显著放大。毫米坐标的原点我习惯放在机器人起步充电位这样路径规划算出来的距离可以直接对应底盘位移。如果换场地只需要重新点一次四个标记点H矩阵实时更新视觉代码完全不用动。如果相机装在底盘上随车运动单应矩阵失效因为外参一直在变。这种情况常见做法是改用solvePnP先用棋盘格标定内参和畸变系数再用solvePnP求相机相对地面的外参球心坐标通过反投影射线与z0平面求交得到。代价是多一步内参标定好处是相机姿态变化后仍然能算出球相对机器人的位置。如果只是固定机位的课程设计用单应矩阵就够了。3. 贪心算法路径规划最近邻策略为什么在网球自拾取场景里够用3.1 抓球顺序本质是TSP近似贪心的时间复杂度优势路径规划先要定义目标函数这里不是让机器人“走最短路径去抓最近一个球”——那是逐点决策而是给定起点、一堆球坐标、终点回筐位置求一条访问所有球且不重复的次序让总移动距离最短。这是货郎担问题TSPN个球有N!种排列。N30时暴力枚举不可能动态规划Held-Karp算法的复杂度是O(n²·2ⁿ)n30也扛不住。贪心算法每步只挑“离当前位置最近的未访问球”一步一个决策复杂度是O(n²)50个球在树莓派上算一次也就几十毫秒这就是它成为这类拾取机器人默认选择的原因。那它是不是最优解不是。贪心属于近似算法在随机均匀分布下最近邻贪心解的总路程通常是全局最优的1.2到1.5倍。对这个场景来说多跑几十厘米的距离换算成时间也就一两秒远小于视觉识别抖动和底盘加减速带来的不确定性。与其纠结最优性不如先让链路稳定。这种“预先生成访问序列 点对点避障”的分层思路和工业AGV路径规划里的全局规划/局部规划分工是一回事。AGV在仓库里先由全局规划器算出一条路径再由局部规划器处理临时障碍物网球机器人的贪心排序就相当于全局规划A*避障相当于局部规划只不过场景简单可以让贪心直接兼任全局规划。3.2 最近邻贪心的Python实现从球坐标列表到抓取序列拿到视觉模块输出的ball列表后路径规划模块只需要做一件事按贪心规则返回一个抓取顺序。下面是核心实现import numpy as np def build_greedy_path(balls, start(0.0, 0.0)): balls: [(x_mm, y_mm), ...]来自视觉模块的毫米坐标 start: 机器人当前位置默认是充电位原点 返回: (抓取顺序索引列表, 总路径长度毫米) n len(balls) if n 0: return [], 0.0 visited [False] * n order [] cur np.array(start, dtypefloat) total 0.0 for _ in range(n): best_i -1 best_d float(inf) for i, (x, y) in enumerate(balls): if visited[i]: continue dx x - cur[0] dy y - cur[1] d np.hypot(dx, dy) if d best_d: best_d d best_i i visited[best_i] True order.append(best_i) cur np.array(balls[best_i], dtypefloat) total best_d return order, total逻辑说明外层循环执行n次每次从剩余未访问球里找欧氏距离最近的。cur每次更新为刚选中的球坐标这样就保证“下一步从当前位置出发”这个贪心前提。返回的order列表直接交给运动控制模块按顺序执行。参数说明start默认传入充电位原点但实际运行时机器人每次执行完一个球位置已经变了我一般会把当前底盘坐标作为新一轮贪心的起点传入。这样即使视觉模块中途漏检了一个球补检后重算也不会从头开始绕路。欧氏距离用np.hypot比手动开平方可读性好也不会在毫米级坐标下溢出。如果要把路径画出来确认可以用matplotlib把order里的点按顺序连线一眼就能看出贪心有没有出现“穿场回头”的绕行。源码里通常还会加一个可视化开关只在调试时打开真机运行时关掉省CPU。3.3 贪心失效的三种场景聚类分层、2-opt与障碍规避贪心有个知名毛病它不会回头。比如球分成左右两簇机器人从左簇中间出发贪心会把左簇零星抓完再跨到右簇过程中可能反复穿越两簇边界。对这种场景常见做法是分层贪心——先用KMeans把球聚成3到5簇按簇中心之间的距离排一个访问顺序进到每个簇内部再用贪心。相当于从“逐球贪心”升级成“区域贪心”。如果不想引入聚类依赖可以在贪心得到的序列上跑2-opt局部优化。2-opt的核心思想是把路径上任意两段反向如果总长度变短就接受反复迭代直到收敛。它不改变“每个球只抓一次”的约束只是把贪心的局部最优进一步打磨。def two_opt(order, balls, max_iter50): n len(order) improved True while improved and max_iter 0: improved False max_iter - 1 for i in range(n - 1): for k in range(i 2, n): new_order order[:i] order[i:k1][::-1] order[k1:] if path_len(new_order, balls) path_len(order, balls): order new_order improved True return order逻辑说明2-opt的每轮复杂度是O(n²)50个球也就是2500次长度比较迭代50轮在PC上瞬时完成树莓派上也就百毫秒以内完全可以作为贪心的后处理。path_len函数就是把相邻点距离累加代码很短按下标顺序求和即可。参数说明max_iter设50轮通常足够收敛设太大在极端分布下可能空转。2-opt只改顺序不改变起点所以对“从当前位置出发”的约束天然兼容。配合文档说明里的“贪心排序 2-opt打磨 A*避障”三层结构哪个模块替换都不互相影响。还要注意障碍物。贪心只认欧氏距离不认“能不能直线过去”。球场里的网柱、球网、还有另一台机器人都是障碍。常见的做法是把贪心输出的每一段“当前点→下一个球”交给A*或Dijkstra做一次点对点避障路径规划把得到的路径长度作为代价回填给贪心重新排序。这样路径规划拆成两层贪心决定访问顺序避障算法决定两个点之间怎么走。如果场地是空阔的平地连这层都可以省。4. 底盘控制与抓取执行串口协议、运动指令和吸球时序4.1 树莓派加Arduino的经典分工视觉慢、控制快的矛盾怎么解OpenCV处理一帧640x480的图像在树莓派4上大约要40到80毫秒路径规划也在这个量级而电机PID和编码器读取需要毫秒级响应。两者放在同一块板子上视觉一卡PWM就跟着抖底盘会一顿一顿。常见的做法是分两块板子树莓派负责视觉捕捉和路径规划Arduino负责电机驱动、编码器读取、限位开关和抓取机构的IO中间用USB串口按115200波特率通信。这样OpenCV再慢Arduino依然能稳定维持底盘速度环。通信协议要尽量简单不要用JSON。树莓派每帧检测完把当前要去的目标点或速度指令打包成固定长度字节串发给ArduinoArduino按帧头解析校验通过才执行。协议越短粘包和脏数据带来的问题越少。树莓派上还要先做一步权限设置否则打开串口设备会报Permission deniedsudo usermod -a -G dialout $USER执行后重新登录当前用户才能访问/dev/ttyACM0这类串口设备。这个坑我第一次搭环境时卡了半小时文档说明里一般都会写但自己搭的时候经常忘。4.2 运动指令协议帧结构、校验与丢帧应对底盘用的是麦克纳姆轮可以实现全向平移。树莓派发给Arduino的指令包括vx、vy、omega三个分量单位为毫米每秒和弧度每秒。一个固定帧结构是0xAA帧头 三个int16数据 一个字节累加和校验。下面是发送端代码import serial import struct # 实际端口用串口枚举确认tree /dev/tty* 查看 ser serial.Serial(/dev/ttyACM0, 115200, timeout0.05) def send_move(vx_mmps, vy_mmps, omega_radps): 把速度指令编码成8字节帧发送 vx_mmps: 前后方向速度单位mm/s范围-1000~1000 vy_mmps: 左右方向速度单位mm/s范围-1000~1000 omega_radps: 自转角速度单位rad/s范围-3.14~3.14 vx int(vx_mmps) vy int(vy_mmps) w int(omega_radps * 1000) # 帧结构: 帧头0xAA 3个int16 校验字节 8字节 payload struct.pack(Bhhh, 0xAA, vx, vy, w) # 前7字节 checksum 0 for byte in payload: checksum (checksum byte) 0xFF frame payload bytes([checksum]) ser.write(frame)逻辑说明struct.pack把小端序的有符号short打包校验字节取前7字节的累加和低8位。Arduino收到后同样计算累加和不一致就丢弃整帧。速度指令是状态量不需要重传旧值下一帧会带上最新速度所以丢一帧不影响最终结果。参数说明波特率115200帧长8字节一帧传输时间不到1毫秒远高于控制周期。vx、vy范围正负1000对应麦克纳姆轮的大多数使用场景如果底盘跑得慢可以缩到正负500提高低速度下的分辨率。注意树莓派的USB串口在系统压力大时可能有延迟发送前加一把互斥锁避免视觉线程和控制线程同时写串口。Arduino端解析逻辑如下放在loop里每次检查串口缓冲if (Serial.available() 8) { uint8_t buf[8]; Serial.readBytes(buf, 8); if (buf[0] ! 0xAA) return; uint8_t sum 0; for (int i 0; i 7; i) sum buf[i]; if (sum ! buf[7]) return; // 校验失败直接丢帧 int16_t vx (int16_t)(buf[1] | (buf[2] 8)); int16_t vy (int16_t)(buf[3] | (buf[4] 8)); int16_t omega_q (int16_t)(buf[5] | (buf[6] 8)); float omega omega_q / 1000.0f; // 更新速度环目标值 }逻辑说明Arduino端每20ms一个中断周期读帧、更新PID、刷新PWM输出。这里没有用delay因为delay会阻塞串口读取反而更容易丢帧。校验失败直接return不更新目标速度底盘按上一帧速度继续跑避免突然停住。关于控制周期和速度环我用的是增量式PID三个轴各一个环采样周期20ms。PID参数一般从P0.8开始调I0.05D先设0如果底盘低频振荡加一点D到0.02。这个调参过程在文档说明里最好单独写一节因为不同底盘的减速比和轮径差异很大参数不能直接抄。4.3 吸球机构时序风机的启停、到位判定与失败重试拾取网球最常见的执行机构是风机负压吸球吸管直径比网球略大风机启动后在管口形成负压球被吸住断电释放。比起夹爪吸球对视觉定位精度的要求低一截球心偏差一两厘米也能吸住这正好匹配视觉坐标的误差范围。吸球动作的时序我会写成一个状态机步骤动作时间/条件1底盘运动到位停止到达目标点后2等待车身稳定200ms3启动风机打开负压立即4光电管检测吸口是否被球堵住50ms轮询5成功则断电释放计数球落入回收筐6失败则后退150mm重新定位再试最多重试2次步骤2的200ms不能省因为底盘停止时还有残余惯性立即落管会砸偏。步骤4的光电管是关键如果没有它机器人会一直以为吸住了循环空跑。常见的光电方案是一对红外对管横跨吸管口球堵住光路就输出翻转成本几块钱比用视觉二次确认可靠得多。一个血泪经验是风机和电机别共电源。风机启动瞬间电流可以达到正常工作电流的三倍如果和底盘电机共用一个稳压模块会造成单片机复位或串口乱码。我后来把风机独立接到一个12V继电器用MOS管驱动树莓派只给PWM信号问题才消失。源码里的电机驱动和风机驱动最好分成两个文件接线图也要标清楚两个独立电源回路不然照着重现有概率烧板子。5. 调试避坑OpenCV检测与贪心规划最常翻车的5个点5.1 光照变化导致HSV阈值失效现象上午在室内开灯调好的HSV参数下午有阳光从窗户进来同一个mask突然全黑或全是噪点视觉识别率从95%掉到30%。原因阳光的色温从LED的4000K变成自然光的6000K以上荧光黄的H值会偏移5到10S和V的波动更明显固定阈值是静态的环境一变就失效。解决两步走。一是关掉相机的自动曝光和白平衡在OpenCV里设置CAP_PROP_AUTO_EXPOSURE为0.25手动模式固定曝光时间和白平衡增益让输入图像稳定。二是把HSV阈值做成多组配置室内和室外各一组或者用“画面整体亮度统计”自动切换。我最终的方案是采集10帧求H通道直方图以直方图峰值中心动态扩展H区间相当于一个轻量自标定。5.2 网球相互遮挡与轮廓粘连现象两颗球相距很近时mask里它们连成一个连通域minEnclosingCircle给出的圆心落在两球之间抓取机构过去直接抓空。原因形态学闭运算把球之间的缝隙填平了findContours把粘连区域当成一个整体尤其在阳光斜射下球的阴影让缝隙更不明显。解决先看单球标准面积当连通域面积超过单球面积的1.6倍时判定为粘连。对这个粘连轮廓计算凸缺陷以缺陷点为分割点把轮廓切开或者直接用分水岭算法。工程上更快的做法是如果粘连不严重把形态学闭运算去掉只保留开运算让缝隙保留下来然后对每个候选圆的圆心做一次距离合并距离小于球半径的就合并成一个避免重复抓取。5.3 贪心路径的局部最优导致穿场绕路现象机器人按贪心序列执行走到场地一半时发现路线明显绕比如从左下角抓一个球又跑到右上角再回到左中区域总路程肉眼可见地浪费。原因贪心只看下一步最近看不到全局分布。球分成两簇时贪心会在簇间来回穿越这是它的固有缺陷。解决先用KMeans把球聚成几簇按簇中心排序簇内再用贪心或者在贪心序列上跑2-opt50轮内基本能收敛到一个接近全局最优的序列。我实测过2-opt对20到40个球的分布能把总路程优化8%到15%改动成本很低。5.4 坐标标定误差导致抓偏现象视觉显示球在坐标(500, 300)毫米底盘走到后吸管口离球差5到10厘米离相机视野边缘越远的球偏差越大。原因单应矩阵标定时四个点没有覆盖整个工作区外推区域误差放大另一个常见原因是画面畸变没有处理广角摄像头的桶形畸变在边缘能把位置拉偏好几个像素。解决标定样点从工作区域四角往中间铺尽量覆盖机器人全程活动范围如果用的是广角镜头先用棋盘格跑一遍cv2.calibrateCamera拿到畸变系数后对每一帧做undistort再做坐标映射。检验标定结果的方法是在场地随机撒5个球视觉坐标和手动测量坐标对比平均误差小于2厘米再上车。5.5 串口丢帧导致底盘卡顿现象底盘运动一卡一卡有时执行完一个指令后停在原地不动要等一两秒才继续。原因树莓派上OpenCV线程和串口发送线程抢CPU串口缓冲被覆盖或者Arduino主循环里的串口读和PWM更新互相阻塞115200波特率下偶发丢帧速度指令又是状态量丢一帧就少一次更新表现为卡顿。解决串口发送加互斥锁避免两个线程同时写数据帧加自增序号Arduino只接受序号递增的帧把视觉降到10到15FPS路径规划一次性输出完整序列底盘缓存最近的目标点而不是依赖每一帧的指令。这样丢一两帧不影响运动Arduino按缓存目标继续走。6. 验证与进阶先录视频离线回放再谈多机协同6.1 离线回放验证法把视觉和底盘的问题分开调试这个系统时最大的难点在于视觉、路径规划和底盘三个模块互相牵制。一个简单有效的验证方法是先在场地上固定机位录一段包含所有球和若干次人为摆放的视频然后用这段视频离线跑视觉检测把每帧检测到的球坐标和识别总数打印出来。如果离线跑出来的坐标序列稳定、漏检率低于5%说明视觉没问题剩下的运动偏差就是底盘和标定的事如果离线跑都不稳定就别浪费时间调电机问题在视觉。这个习惯我用了很多年能直接砍掉一半的排查时间。具体做法录视频时用固定曝光帧率30离线代码把视频文件路径替换摄像头编号其他逻辑原样保留。对比标准是“人工数球离线检出的球数是否一致每颗球的圆心是否稳定在±3像素内”。6.2 从单机自拾取到多机协同区域划分与贪心的组合单机自拾取的局限是场地大、球多时时间太长。改成多机时最直接的设计不是让多台机器人共享一个视野去抢球而是把场地按区域分成几块每台机器人只负责自己的区域。这个划分可以用KMeans做在线分配所有球的坐标汇总后聚类按簇中心分配给最近的机器人机器人再在自己的簇内跑贪心。这时贪心仍然承担底层的访问顺序只是决策范围变成了区域子集。边界球是这种方案最实际的坑球刚好落在两个区域边界两台机器人同时靠近会产生碰撞。我通常的约定是区域之间留一条50厘米的缓冲区落在缓冲区的球由当前电量低的那台机器人负责避免两者竞争同一个目标。多机之间不需要实时通信只需要启动前一次性同步任务分配然后各自按贪心路径执行。有一次我为了省时间跳过离线回放把代码直接部署上车结果在场地里跑了十分钟视觉、规划、底盘三个模块互相甩锅最后发现是地砖颜色变了导致HSV误检。后来我养成了习惯任何改动先录一段带时间戳的视频离线跑一遍视觉确认没问题再碰底盘。这条流程帮我省下了大量在真机上反复重启的时间。如果你也打算照着这套方案做建议先离线打通视觉和贪心再上车调运动控制顺序别反。希望帮到你。本文还有配套的精品资源点击获取
返回列表