资讯动态

基于OpenCV与贪心算法的网球自拾取机器人:视觉识别与路径规划实战

发布时间:2026/10/22 5:58:10 来源:尧图企业网站定制
简介这是一套面向计算机、人工智能、自动化等专业学生与开发者的网球自拾取机器人完整项目源码核心融合OpenCV视觉捕捉与贪心算法路径规划可用于毕业设计、课程设计或机器人视觉入门进阶。压缩包共80个文件约77.73MB包含18个cpp源文件、26张jpg测试图、4个xml与5个json配置、2个h头文件及pdf论文资料覆盖颜色检测、圆检测、投影变换、质心提取、轨迹规划与贪心/穷举算法对比等模块并附带SVM训练与实时预测脚本。项目代码均经测试运行成功答辩评审平均分达96分已有123人学习。读者可获取完整视觉识别与路径规划实现、可复用工具脚本及排错思路适合在此基础上修改扩展功能。1. 从一场捡球噩梦说起这套 OpenCV 网球自拾取机器人到底能干什么去年帮一个校队的朋友收拾场地两个小时训练下来散落在角落的网球超过一百个。弯腰捡球这件事对业余选手是体力活对做视觉的工程师来说其实是一道可以拆解的工程题球在哪、怎么走过去、按什么顺序捡最省时间。这套「基于 OpenCV 视觉捕捉与贪心算法路径规划的网球自拾取机器人系统」就是围绕这三个问题展开的完整源码包包含视觉识别、坐标标定、路径规划、运动控制几个模块还附带一份 SVM 分类的训练与预测代码以及两篇轨迹识别与目标跟踪方向的参考论文。它适合谁做计算机视觉课程设计、毕设、自动化相关课题的学生或者想找一个「视觉 规划 控制」闭环练手项目的开发者。整套代码用 C 写构建系统是 CMake视觉部分依赖 OpenCV规划部分实现了穷举法和贪心算法两套对照方案。下面我按「先看懂结构、再跑通识别、再理解规划、最后避坑」的顺序拆一遍尽量让新手能照着复现熟手能看到参数边界。2. 工程结构与构建链路先搞清楚每个 cpp 在干什么拿到一个源码包最忌讳上来就cmake ..然后对着报错发呆。这套工程的文件命名其实挺直白但模块之间的调用关系需要先理一遍否则改一个参数可能牵动整条链路。2.1 目录分层与模块职责工程根目录下有几个关键位置include放公共头文件src放视觉与规划的核心实现ml放 SVM 相关的数据准备、训练和预测代码data放测试图片和一段tennis.avi视频paper放两篇参考论文bin是编译产物目录config下有default.yaml配置文件。src里的文件可以按功能分成四组模块分组涉及文件职责颜色与圆形检测checkColour.cpp、checkCircle.cpp从图像中筛出网球候选区域形态学与滤波filter.cpp、edge2list.cpp去噪、边缘转列表、连通域处理坐标与投影ProjectiveTransform.cpp、get_centroid.cpp像素坐标到场地坐标的映射、质心计算路径规划与轨迹1.Method_of_exhaustion.cpp、2.Greedy_Algorithm.cpp、Trajectory.cpp、movemet.cpp穷举/贪心规划、轨迹生成、运动指令入口与辅助main.cpp、get_Random_points.cpp主流程调度、随机点生成测试include_all.h和function_declare.h是总头文件前者把常用 OpenCV 头一次性引入后者声明跨文件函数。这种写法在毕设项目里很常见好处是改函数签名时只动一处坏处是编译依赖变重改一个头文件会触发大面积重编。2.2 CMake 构建与依赖确认工程有两处CMakeLists.txt根目录一处src一处ml下还有独立的一份。根目录负责整体目标src负责把视觉与规划源码编成库或可执行文件。构建前先确认 OpenCV 版本这套代码用的是 OpenCV 3.x/4.x 的经典 APIcv::Mat、cv::inRange、cv::findContours等4.x 下基本能编但个别常量名有变化。# 在工程根目录下执行 mkdir -p build cd build cmake .. make -j4cmake ..会读取根目录的CMakeLists.txtmake -j4用四线程并行编译。如果报Could NOT find OpenCV说明 CMake 没找到 OpenCV 的配置文件常见做法是在CMakeLists.txt里显式指定# 在 find_package(OpenCV REQUIRED) 之前加上 set(OpenCV_DIR /usr/local/lib/cmake/opencv4) # 按实际安装路径改 find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(你的目标名 ${OpenCV_LIBS})OpenCV_DIR指向的是 OpenCV 安装后cmake子目录不是include目录这是新手最容易填错的地方。填错会报找不到OpenCVConfig.cmake。编译产物默认落在bin下libtennis.so是编出来的动态库主程序链接它。提示如果只想先跑通视觉部分可以单独进src目录编译减少 SVM 模块带来的依赖干扰。2.3 配置文件 default.yaml 的作用config/default.yaml是运行期参数入口常见做法是把颜色阈值、圆形检测的半径范围、贪心算法的距离权重放进去避免每次调参都重新编译。虽然源码里读取 yaml 的部分需要 OpenCV 的FileStorage支持但参数集中管理这个思路值得保留。改阈值时优先改 yaml不要直接改 cpp 里的魔法数字否则下次换光照条件你会找不到自己改过哪里。3. OpenCV 视觉捕捉从像素到球坐标的完整链路视觉部分是整套系统的入口识别不准后面规划再漂亮也是白搭。这一章把「颜色筛选 → 形态学去噪 → 圆形确认 → 质心与投影」这条链路拆开讲每一步都给可调参数和失败时的排查方向。3.1 颜色空间选择与阈值分割网球是黄绿色在 RGB 空间里受光照影响大同一颗球在阴影下和阳光下 RGB 值能差出一大截。常见做法是转到 HSV 空间做阈值分割因为 H色调对光照变化相对不敏感。// checkColour.cpp 核心逻辑示意 cv::Mat hsv, mask; cv::cvtColor(frame, hsv, cv::COLOR_BGR2HSV); // 黄绿色在 HSV 中的大致范围需按实际光照微调 cv::Scalar lower(25, 80, 80); cv::Scalar upper(75, 255, 255); cv::inRange(hsv, lower, upper, mask);lower和upper是 HSV 三通道的下上限。H 通道范围 0-179OpenCV 里 H 是 0-180S 和 V 是 0-255。H 取 25-75 覆盖黄到绿S 下限 80 是为了滤掉灰白色干扰V 下限 80 滤掉过暗区域。如果识别结果里混进了地板反光先把 S 下限往上提如果球在阴影里漏检把 V 下限降到 50 左右试试。这一步的输出是一张二值 mask白色是候选区域。3.2 形态学滤波与边缘转列表阈值分割出来的 mask 往往有噪点和小孔洞filter.cpp里一般用开运算去小噪点、闭运算填孔洞。开运算是先腐蚀后膨胀闭运算是先膨胀后腐蚀。cv::Mat kernel cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5)); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel); // 去噪点 cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel); // 填孔洞kernel大小 5x5 是经验值球在画面里越大kernel 可以适当加大但超过 9x9 容易把相邻的球粘连成一个区域。edge2list.cpp负责把 mask 里的连通域边缘转成点列表供后续圆形拟合和质心计算使用。这一步如果发现边缘断裂多半是 mask 本身不连续回到上一步调阈值而不是在这里加复杂的边缘连接逻辑。3.3 圆形确认与质心计算颜色筛选只能说明「这块区域是黄绿色」不能说明「这是个球」。checkCircle.cpp用霍夫圆变换或轮廓圆度判断来确认。霍夫圆对参数敏感param2越小越容易检出但也越多误检。std::vectorcv::Vec3f circles; cv::HoughCircles(mask, circles, cv::HOUGH_GRADIENT, 1, // dp累加器分辨率1 表示与原图同分辨率 mask.rows / 8, // minDist圆心最小间距 100, // param1Canny 高阈值 30, // param2累加器阈值越小越敏感 10, 100); // minRadius, maxRadiusminDist设成图像高度的八分之一是为了避免同一个球被检出多个圆。param2从 30 开始调误检多就加大漏检多就减小。minRadius和maxRadius按球在画面里的实际像素半径设设窄一点能大幅减少误检。确认圆之后get_centroid.cpp算质心ProjectiveTransform.cpp把像素坐标映射到场地平面坐标——这一步通常需要一个单应性矩阵用场地四个角点做标定。标定不准后面规划出来的路径会整体偏移这是血泪经验里最常见的一类问题。4. 贪心算法路径规划为什么不用穷举贪心边界在哪视觉给出球的位置列表后机器人要决定按什么顺序去捡。这是一个典型的旅行商问题TSP变体从当前位置出发经过所有球的位置使总移动距离最短。球的数量少时穷举可行数量一多就爆炸。这套代码同时给了穷举法和贪心算法两套实现正好用来理解两者的取舍。4.1 穷举法的复杂度与适用规模1.Method_of_exhaustion.cpp实现的是全排列枚举。n 个球有 n! 种访问顺序n8 时是 40320 种n10 时是 362 万种n12 就上亿了。所以穷举只适合球数很少的场景比如 6 个以内用来做小规模验证或给贪心结果做对照。// 穷举法核心递归生成全排列并计算总距离 double bestDist std::numeric_limitsdouble::max(); std::vectorint bestOrder; std::sort(order.begin(), order.end()); do { double d pathDistance(start, order, balls); if (d bestDist) { bestDist d; bestOrder order; } } while (std::next_permutation(order.begin(), order.end()));std::next_permutation要求序列先升序排列否则会漏掉排列。pathDistance累加起点到第一个球、球与球之间、最后一个球的距离。这段代码的价值在于当你怀疑贪心结果不好时用小规模穷举结果做基准能直观看出贪心差了多少。4.2 贪心策略的实现与距离度量2.Greedy_Algorithm.cpp的思路是从当前位置出发每次选最近的未访问球走过去再选下一个最近的。实现简单复杂度 O(n²)球数上百也能秒出结果。std::vectorint greedyOrder(const cv::Point2f start, std::vectorcv::Point2f balls) { std::vectorint order; std::vectorbool visited(balls.size(), false); cv::Point2f cur start; for (size_t i 0; i balls.size(); i) { int nearest -1; double minD std::numeric_limitsdouble::max(); for (size_t j 0; j balls.size(); j) { if (visited[j]) continue; double d cv::norm(cur - balls[j]); // 欧氏距离 if (d minD) { minD d; nearest j; } } visited[nearest] true; order.push_back(nearest); cur balls[nearest]; } return order; }cv::norm算的是欧氏距离如果场地有障碍物距离度量要换成实际可行走路径长度否则规划出的顺序看着近、走起来绕。贪心的边界在于它每一步都选局部最优不保证全局最优。典型反例是三个球呈「近-远-近」分布时贪心可能先捡近的导致后面多绕一段。球分布越均匀贪心越接近最优球分布越极端贪心越容易翻车。4.3 轨迹生成与运动指令衔接规划出访问顺序后Trajectory.cpp负责生成从当前点到目标点的轨迹movemet.cpp把轨迹转成运动指令。常见做法是直线插值加朝向调整先转向目标点再直行到达后微调对准球。// 轨迹生成示意直线插值 std::vectorcv::Point2f genTrajectory(const cv::Point2f from, const cv::Point2f to, double step) { std::vectorcv::Point2f traj; double dist cv::norm(to - from); int n static_castint(dist / step); for (int i 0; i n; i) { float t static_castfloat(i) / n; traj.push_back(from t * (to - from)); } return traj; }step是插值步长太小会生成大量点拖慢控制循环太大则轨迹不平滑。一般取 0.05 到 0.1 米。如果机器人运动学约束强比如不能原地转向这里要换成圆弧或样条插值直线插值会导致转向时走「折线」。这一步和硬件强相关源码里给的是通用框架实际落地要按底盘参数改。5. 避坑与排查跑这套代码最容易翻车的五个地方这套代码在作者的环境里跑通了但换一台机器、换一个光照条件问题就会冒出来。下面五条是我在复现这类视觉规划项目时反复遇到的按「现象 → 原因 → 解决」写清楚。5.1 编译报 OpenCV 常量未定义现象make时报CV_BGR2HSV was not declared或类似常量找不到。原因OpenCV 4.x 把部分常量从CV_前缀改成了cv::COLOR_前缀老代码用的是 3.x 写法。解决全局搜索CV_开头的颜色转换常量替换成cv::COLOR_版本比如CV_BGR2HSV改成cv::COLOR_BGR2HSV。如果不想改代码装 OpenCV 3.x 也能绕过但新项目不建议。5.2 识别结果里混入地板和鞋子现象mask 里除了球还有大片地板反光或深色鞋子区域。原因HSV 阈值范围开太宽或者 S/V 下限设太低。解决先把 S 下限从 80 提到 120观察反光是否消失再把 V 下限提到 100 滤掉暗区。如果鞋子是深色V 下限提高就能滤掉。调完阈值记得用data里的test1.jpg到test4.jpg逐张验证不要只看一张图就定参数。5.3 霍夫圆检测同一个球出多个圆现象一颗球被检出两三个重叠的圆质心算出来偏移。原因minDist设太小或者param2太低导致累加器过于敏感。解决把minDist提到图像高度的六分之一到八分之一param2从 30 提到 40 到 50。如果球在画面里大小差异大minRadius和maxRadius的范围要覆盖实际尺寸范围设太宽也会增加误检。5.4 贪心规划结果绕远路现象球数不多时贪心顺序明显比穷举差总距离多出一截。原因贪心只看当前最近不考虑后续分布。解决球数少于 8 个时直接用穷举法球数多时可以在贪心基础上加一步「2-opt」局部优化交换访问顺序中两段看总距离是否下降迭代几次通常能明显改善。源码里没带 2-opt但改起来不难在greedyOrder结果上再跑一轮交换即可。5.5 投影变换后坐标整体偏移现象规划出的路径方向对但整体平移了一段机器人总是停在球旁边。原因单应性矩阵标定的四个角点坐标和实际场地不匹配或者标定时相机位置动了。解决重新标定标定用的角点要在图像里清晰可见且四个点尽量分散、覆盖整个工作区域。标定完用get_Random_points.cpp生成几个已知点验证映射误差误差超过球半径就要重标。标定这件事没有后悔药相机一动就得重来。6. 进阶玩法用 SVM 模块做球与非球分类以及验证习惯视觉阈值法在光照稳定时够用但换场地、换时段就容易失效。这套代码里带了一个ml目录包含数据准备、SVM 训练和实时预测三部分思路是把「是不是球」从规则判断升级成学习判断。1.get_data.cpp负责从图像里截取候选区域并提取特征2.training_svm.cpp训练分类器3.predict.cpp和4.predictrealtime.cpp分别做离线预测和实时预测。non_linear_svms_test.cpp是非线性核的测试0.allinone.cpp把流程串在一起。用 SVM 替代阈值法的关键在特征设计。常见做法是提取候选区域的 H/S/V 均值、方差、圆形度、面积占比这几个特征正样本是球负样本是地板、鞋子、其他圆形物体。训练时注意正负样本数量要均衡否则分类器会偏向多数类。// 特征提取示意HSV 均值 圆形度 cv::Scalar meanHSV cv::mean(hsv(roi), mask(roi)); double circularity 4 * CV_PI * area / (perimeter * perimeter); cv::Mat feature (cv::Mat_float(1, 4) meanHSV[0], meanHSV[1], circularity, areaRatio);meanHSV取候选区域在 mask 内的 HSV 均值circularity越接近 1 越像圆areaRatio是候选区域面积占 ROI 的比例。这四个特征维度低、计算快适合实时场景。训练完的模型用3.predict.cpp在data里的测试图上验证准确率能到多少取决于样本质量不要指望一上来就完美。验证习惯这块我自己的做法是每次改完视觉参数先用data里的静态图跑一遍确认识别框位置对再用tennis.avi跑视频看连续帧里识别是否稳定最后才接规划模块看路径。跳过静态图直接跑全流程出了问题你分不清是识别错还是规划错。从那以后我每次调视觉参数都强制走一遍「静态图 → 视频 → 全流程」三步省下的排查时间比调参本身多得多。这套代码的README.md和readme.txt里有一些运行说明下载后先看这两个文件再按上面的顺序复现能少走不少弯路。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑