资讯动态

OpenCV视觉捕捉与贪心算法:网球自拾取机器人的完整实现解析

发布时间:2026/10/6 12:59:48 来源:尧图企业网站定制
简介基于OpenCV视觉捕捉与贪心算法路径规划的网球自拾取机器人系统项目包面向计算机视觉、机器人控制及自动化相关专业的学生与开发者是一套可直接用于毕业设计或课程设计的完整方案。资源共80个文件以C源代码cpp/h为核心另含JSON/XML/YAML配置、JPG/PNG测试图像、AVI演示视频、PDF论文资料以及可执行程序、动态库和CMake构建文件压缩包整体约77.73MB目录划分清晰。项目已有123人学习下载代码均通过运行验证可复现网球识别与自动拾取流程。内容涵盖颜色检测、圆形识别、SVM分类、贪心算法路径规划、轨迹预测等关键模块并配套README与论文说明便于逐模块调试和整体理解。读者既可将其作为OpenCV机器人应用的实战样例也可在此基础上扩展硬件适配或算法优化用于课设、毕设或机器人竞赛准备。1. 把“满地找球”变成一条不会漏球的轨迹OpenCV 视觉捕捉 贪心算法到底干了什么做网球捡球机器人最难的不是底盘和机械臂而是让机器人在一个半场里“知道自己该往哪走”。我拆过不少视觉项目多数卡在同一个地方摄像头看到球了但球在图像里的像素坐标无法换算成地面坐标坐标换算了机器人又不知道该按什么顺序去捡。这份基于OpenCV视觉捕捉与贪心算法路径规划的网球自拾取机器人系统把这两件事串成了一条完整链路先用HSV颜色过滤和霍夫圆检测从图像里把网球找出来再做透视变换把像素坐标映射到真实地面最后用贪心算法把捡球顺序算成一条遍历序列交给底盘执行。整个项目不是单个算法演示而是一个能跑的闭环源码、测试图、视频和SVM训练代码都齐。适合正在做毕设、课设或者想从OpenCV图像处理往机器人路径规划跨一步的同学。2. 拆开这份源码视觉捕捉模块与透视变换拿到压缩包第一件事不是找main.cpp而是先把目录结构摸清楚。这份资源的视觉部分散落在src和lib两个地方编译好的动态库libtennis.so放在lib目录源码集中在src下。下面按我实际拆解的顺序讲。2.1 源码包的文件结构哪些文件是视觉核心先看整体布局避免一上来就迷路├── include │ ├── include_all.h │ └── function_declare.h ├── lib │ └── libtennis.so ├── src │ ├── checkColour.cpp │ ├── checkCircle.cpp │ ├── ProjectiveTransform.cpp │ ├── get_centroid.cpp │ ├── get_Random_points.cpp │ ├── filter.cpp │ ├── edge2list.cpp │ ├── Greedy_Algorithm.cpp │ ├── Trajectory.cpp │ ├── Method_of_exhaustion.cpp │ └── main.cpp ├── ml │ ├── 0.allinone.cpp │ ├── 1.get_data.cpp │ ├── 2.training_svm.cpp │ ├── 3.predict.cpp │ └── 4.predictrealtime.cpp ├── data │ ├── test1.jpg、test2.jpg、tennis.avi、ball_1.jpg... ├── paper │ └── 基于机器视觉的轨迹识别和位置标定.pdf └── config └── default.yaml从命名就能看出设计思路checkColour做颜色筛选checkCircle做圆形验证ProjectiveTransform做坐标映射Greedy_Algorithm做路径规划Trajectory生成轨迹。这是典型的模块化流水线每个cpp文件负责一个环节串起来就是“检测 → 过滤 → 定位 → 规划 → 执行”。main.cpp是调度入口include里的function_declare.h声明了所有模块的接口改参数时不需要翻每个cpp找函数定义。2.2 HSV颜色过滤 霍夫圆检测checkColour.cpp 与 checkCircle.cpp网球检测第一步不是深度学习而是颜色。网球的标准颜色是荧光黄绿在HSV色彩空间里有非常集中的色调区间。checkColour.cpp做的就是这件事把BGR图像转到HSV然后用inRange筛出候选区域。代码逻辑大致如下cv::Mat hsv; cv::cvtColor(frame, hsv, cv::COLOR_BGR2HSV); cv::Scalar lower(20, 100, 100); cv::Scalar upper(40, 255, 255); cv::Mat mask; cv::inRange(hsv, lower, upper, mask); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5)));这里lower和upper是HSV阈值色调H在20到40之间基本覆盖了黄色网球在常见光照下的表现。morphologyEx里的MORPH_OPEN操作用椭圆核做开运算作用是先腐蚀后膨胀把图像里的细小噪点消掉同时保留球的整体形状。如果你的场地是红色塑胶或灰蓝色这两个阈值必须重新标定不要直接套用。checkCircle.cpp在mask基础上用霍夫圆检测做第二轮验证。只看颜色会把场地上的黄色线条、矿泉水瓶误判成球所以要用圆的几何特征过滤std::vectorcv::Vec3f circles; cv::HoughCircles(mask, circles, cv::HOUGH_GRADIENT, 1.2, mask.rows / 8, 100, 30, 10, 30); for (size_t i 0; i circles.size(); i) { cv::circle(frame, cv::Point(circles[i][0], circles[i][1]), circles[i][2], cv::Scalar(0, 255, 0), 2); }HoughCircles的参数需要仔细说dp1.2表示累加器分辨率略低于原图检测速度更快minDistmask.rows/8是最小圆心间距防止同一个球被重复检出param1100是Canny边缘检测的高阈值param230是圆心累加器阈值值越小检出的圆越多但误检也越多最后两个参数10和30是半径范围单位是像素。这套参数针对的是摄像头距离地面1.5米到2米的场景球在画面里的直径大概在20到60像素之间。如果你的摄像头安装高度不同半径范围是第一个要调的参数。2.3 把像素坐标变回地面坐标ProjectiveTransform.cpp 的四点标定颜色过滤和圆检测解决了“球在哪里”但这里的“哪里”是图像坐标系机器人底盘需要的是地面坐标。ProjectiveTransform.cpp做的就是坐标映射。摄像头不是垂直向下看的它有一个俯仰角所以地面上的矩形区域在图像里是梯形。要从梯形还原矩形需要做透视变换。代码里用的是四点标定法先在图像中选取场地四个角点再指定它们对应的真实地面坐标算出单应性矩阵std::vectorcv::Point2f src_pts { cv::Point2f(160, 140), cv::Point2f(560, 140), cv::Point2f(40, 460), cv::Point2f(600, 460) }; std::vectorcv::Point2f dst_pts { cv::Point2f(0, 0), cv::Point2f(1200, 0), cv::Point2f(0, 800), cv::Point2f(1200, 800) }; cv::Mat homography cv::getPerspectiveTransform(src_pts, dst_pts); cv::Mat ground_view; cv::warpPerspective(frame, ground_view, homography, cv::Size(1200, 800));getPerspectiveTransform要求输入四个点计算出的3x3矩阵把图像坐标映射到地面坐标。注意src_pts的顺序必须和dst_pts一一对应大部分坐标漂移问题都出在点序搞反。如果你需要更精确的外参标定可以用cv::solvePnP配合棋盘格先求相机旋转和平移向量再投影到地面。四种点标定适合快速验证solvePnP适合精度要求高的场景代价是标定流程更繁琐。2.4 实测视角在 test1.jpg 上跑通单帧检测资源data目录里有test1.jpg、test3.jpg、ball_1.jpg到ball_8.jpg这些测试图我建议第一次跑的时候先不要直接上摄像头而是用这些静态图验证管线。用main.cpp编译出的可执行文件输入test1.jpg正常的输出应该是画面上所有网球被绿色圆框标注同时终端打印出每个球在真实地面坐标系的坐标。test1.jpg是半场俯视图里面有4到5颗球如果检测结果只有2颗大概率是HSV阈值把暗处的球当成了背景。这时候打开HSV阈值调试窗口观察球在不同光照下的像素值分布。// 调试用滚动条调阈值观察哪些球能被筛出来 cv::namedWindow(hsv_debug, cv::WINDOW_NORMAL); cv::createTrackbar(H_min, hsv_debug, h_min, 179, nullptr); cv::createTrackbar(H_max, hsv_debug, h_max, 179, nullptr); cv::createTrackbar(S_min, hsv_debug, s_min, 255, nullptr); cv::createTrackbar(S_max, hsv_debug, s_max, 255, nullptr);这条调试代码是我自己加进去的原项目没给可视化调参窗口。用滚动条把阈值范围拉开看哪些球能进mask哪些球漏掉记下临界值。网球在不同光照下色温差异很明显阳光直射时H值偏30上下阴影里会掉到15附近。阈值范围设窄了会漏球设宽了会把场地标线卷进来这个平衡点只能在你的实际场景里试出来。3. 贪心算法路径规划把“捡球顺序”算成一个遍历序列视觉模块找到球的位置接下来是路径规划。这份资源用的是贪心算法不是A*或RRT原因是场景特殊机器人不用避障场地是开阔的球的位置在捡球前已经全部知道问题本质是“以最短总路程访问所有球”。贪心在这个场景下足够好实现也简单。3.1 为什么选贪心而不是全局优化edge2list.cpp 与图建模路径规划问题可以建模成完全图每个球是一个节点任意两个球之间的距离是边的权重目标是从机器人当前位置出发访问所有节点并回到起点这是旅行商问题TSP。TSP是NP难的用穷举法在球数量超过10个时计算量就爆炸了。// edge2list.cpp 核心逻辑把球坐标转成邻接表 struct Graph { int n; std::vectorstd::vectordouble adj; Graph(int nodes) : n(nodes), adj(nodes, std::vectordouble(nodes, 0)) {} }; void edge2list(std::vectorcv::Point2f balls, Graph g) { for (int i 0; i g.n; i) { for (int j i 1; j g.n; j) { double dx balls[i].x - balls[j].x; double dy balls[i].y - balls[j].y; g.adj[i][j] g.adj[j][i] std::sqrt(dx * dx dy * dy); } } }edge2list把地面坐标转成带权完全图。资源里还保留了Method_of_exhaustion.cpp这是穷举法它和Greedy_Algorithm.cpp形成了对照。穷举在小规模下能算出全局最优但复杂度是O(n!)我在10个球时跑过一次等了很久还没出结果。贪心算法选择的是局部最优策略每次从当前位置选最近的未访问球复杂度只有O(n²)在20个球以内响应是毫秒级的。3.2 Greedy_Algorithm.cpp最近邻贪心遍历的展开最近邻贪心是解决TSP最直观的近似方法。流程是从起点开始找距离当前点最近的未访问节点走过去标记为已访问重复直到所有节点都被访问。代码实现很清晰std::vectorint GreedyAlgorithm::solve(int start, const Graph graph) { int n graph.n; std::vectorbool visited(n, false); std::vectorint path; path.reserve(n); int current start; visited[current] true; path.push_back(current); for (int step 1; step n; step) { int nearest -1; double min_dist std::numeric_limitsdouble::max(); for (int i 0; i n; i) { if (!visited[i] graph.adj[current][i] min_dist) { min_dist graph.adj[current][i]; nearest i; } } visited[nearest] true; path.push_back(nearest); current nearest; } return path; }这段代码的关键是“当前点”会不断更新。每捡完一颗球机器人就从新位置继续找最近的下一颗。贪心的代价是可能错过全局最优如果第一颗球选错了方向后面可能要绕一大圈。但在网球场的场景里球分布通常比较随机最近邻的路径长度一般只比最优解多10%到20%换取的是实时计算能力。start参数建议设成机器人的初始位置也就是场地角落而不是第一颗球的位置。3.3 Trajectory.cpp把访问序列转成底盘可执行的轨迹贪心算法输出的是节点访问顺序比如[3, 1, 5, 2, 4]但这只是路径的点序列底盘执行需要知道两点之间怎么走。Trajectory.cpp负责把折线路径平滑成机器人能跟随的轨迹// Trajectory.cpp 思路线性插值 路径点写出 std::vectorcv::Point2f buildTrajectory( const std::vectorcv::Point2f balls, const std::vectorint order, double step_size 0.05) { std::vectorcv::Point2f trajectory; for (size_t i 0; i order.size() - 1; i) { cv::Point2f a balls[order[i]]; cv::Point2f b balls[order[i 1]]; double dist cv::norm(b - a); int steps std::max(1, (int)(dist / step_size)); for (int j 0; j steps; j) { double t (double)j / steps; trajectory.push_back(cv::Point2f( a.x (b.x - a.x) * t, a.y (b.y - a.y) * t )); } } trajectory.push_back(balls[order.back()]); return trajectory; }step_size是轨迹点的间距单位是米我习惯设0.05米也就是每5厘米一个点。这个值要配合底盘的运动控制频率如果控制周期是20Hz机器人移动速度是1m/s那5厘米间距刚好让每个控制周期都有一个新目标点。如果设太密比如0.01米控制频率跟不上就会造成轨迹抖动设太疏机器人转弯会走割线脱离期望路径。原项目没有做贝塞尔平滑输出就是折线插值对捡球这种低速场景够用不需要额外加路径平滑模块。3.4 参数调节栅格尺寸、起点选择与路径平滑贪心算法本身没有栅格尺寸这个参数但这份资源在视觉检测后会经过get_Random_points.cpp生成候选点。这个文件做的事情是把透视变换后的地面图划分成网格在网格内搜索颜色过滤留下的连通域取每个连通域的中心作为“球的位置”。这里的栅格尺寸直接影响检测质量。我实际跑下来栅格边长在30到50像素比较合适。小于30像素会把一颗球拆成好几个中心点轨迹里会出现一堆距离极近的冗余点大于50像素两颗离得近的球会被合并成一颗。这个参数不是写在config里而是在get_Random_points.cpp里直接定义的要改就得重新编译。config/default.yaml里的内容主要是相机内参、HSV初始值和机器人速度上限路径规划相关的参数不多这点要注意。路径平滑方面如果你的底盘支持角速度控制直接喂折线点也能走但转弯会一顿一顿。更顺的做法是把折线点用三次样条插值重采样再送进运动控制。我试过用OpenCV的cv::approxPolyDP先做轮廓简化效果不理想因为那是为图像轮廓设计的与轨迹平滑目标不一致。4. 视觉与模型的闭环SVM 训练与实时预测颜色过滤加霍夫圆检测组合已经能解决大部分情况但在复杂背景下误检率还是偏高。这份资源在ml目录里给出了一个升级方案用SVM做二次分类把误检的候选框淘汰掉。这也是整份代码里最容易被忽略但又最有价值的部分。4.1 为什么需要第二道识别防线filter.cpp 之后的必要性纯颜色加圆检测的局限我在前面提过黄色区域不只是网球。比如场地上的黄色广告字、阳光反射的白色线条边缘都可能同时满足颜色和圆形两个条件。filter.cpp做的是形态学过滤但它过滤的是像素级别的噪声无法过滤“形状像球但不是球”的物体。SVM的作用就是在这个位置加一道语义判断。训练SVM需要正负样本。正样本是真正的网球图像块从ball_1.jpg到ball_8.jpg这些图中裁剪负样本是从场地背景里随机裁剪的不含球的图像块。ml/1.get_data.cpp做的事情就是自动完成这个裁剪和标注过程// 1.get_data.cpp 核心逻辑生成训练数据 std::vectorcv::Mat samples; std::vectorint labels; // 从标注好的球位置裁剪正样本 for (auto pos : ball_positions) { cv::Mat roi frame(cv::Rect(pos.x - r, pos.y - r, 2*r, 2*r)); cv::resize(roi, roi, cv::Size(32, 32)); samples.push_back(roi); labels.push_back(1); } // 从非球区域随机裁剪负样本 for (int i 0; i negative_count; i) { int x rand() % (frame.cols - 2*r); int y rand() % (frame.rows - 2*r); cv::Mat roi frame(cv::Rect(x, y, 2*r, 2*r)); cv::resize(roi, roi, cv::Size(32, 32)); samples.push_back(roi); labels.push_back(0); }这里的关键参数是负样本数量。我一般会保持正负样本比例1:2到1:3负样本太少SVM会把所有候选框都判成正类负样本太多正类会被淹没。32x32的尺寸是速度和精度的平衡点如果你在实时预测时发现帧率太低可以降到28x28但低于24x24后分类准确率会明显下降。4.2 ml 目录的三个阶段get_data → training_svm → predictrealtimeml目录里的文件是分阶段设计的1.get_data.cpp负责生成训练数据2.training_svm.cpp负责训练模型并保存3.predict.cpp和4.predictrealtime.cpp分别是单张图片预测和视频实时预测。0.allinone.cpp是把整个流程串在一起的脚本适合不想手动分步执行的情况。训练SVM的代码在2.training_svm.cpp里核心参数设置如下cv::Ptrcv::ml::SVM svm cv::ml::SVM::create(); svm-setType(cv::ml::SVM::C_SVC); svm-setKernel(cv::ml::SVM::RBF); svm-setC(10.0); svm-setGamma(0.01); svm-setTermCriteria(cv::TermCriteria( cv::TermCriteria::MAX_ITER, 1000, 1e-6));svmType选择C_SVC是多分类的支持向量分类器适合二分类判断“是球/不是球”。kernel选择RBF径向基核函数这是默认也最稳的选择线性核分类能力不够多项式核对参数太敏感。C是惩罚系数C越大越不允许分类错误但过大容易过拟合gamma是RBF核的宽度参数gamma越小决策边界越平滑越大越容易围着训练样本转。训练完成后模型保存为XML或YAML文件实时预测时加载这个模型对候选区域用和训练时相同的尺寸32x32做resize然后调用predict// 4.predictrealtime.cpp 核心逻辑 cv::Ptrcv::ml::SVM svm cv::ml::SVM::load(tennis_svm.yml); cv::Mat candidate getROI(frame, circle_center, radius); float response svm-predict(candidate.reshape(1, 1)); if (response 0.5) { // 确认是网球加入路径规划队列 balls.push_back(ground_point); }predict的输入必须是1行N列的特征向量所以要把图像Mat先reshape成单行再送入模型。原始图像32x32x3就是3072维特征不额外提取HOG特征直接像素灰度值作为输入这个方案在光照稳定的室内场地跑得通室外阳光变化剧烈时准确率会下降。想要更稳可以换成HOG特征加线性SVM但那样需要改特征提取部分。4.3 non_linear_svms_test.cppgamma / C 参数的影响细节non_linear_svms_test.cpp是个独立测试文件它验证的是非线性SVM在不同gamma和C组合下的表现。我自己跑了一遍记录了几组典型参数的效果gammaC训练准确率实时误检率说明0.0011.089%偏高决策边界过于平滑正负样本区分度差0.0110.097%低推荐组合边界与泛化能力平衡0.1100.099.5%中高过拟合训练集表现极好但新场景泛化差0.01100.094%中C过大少量异常样本被过度关注最稳妥的做法是在一个场地采集数据训练然后换到另一个光照条件不同的场地做交叉验证。如果实时误检率高优先降gamma其次降C如果漏检率高优先升C其次升gamma。这套调参逻辑适用于大部分SVM场景。5. 避坑指南OpenCV 版本、HSV 阈值与坐标漂移的五个翻车点这份资源我前前后后编译运行了不止一次每次都踩出一些新问题。下面这五条是最典型的我按“现象 → 原因 → 解决”写清楚。5.1 链接时报 undefined reference 到 libtennis.so现象编译src下的代码时链接阶段报出一大堆cv::xxx undefined reference明明OpenCV已经装好了。原因lib/libtennis.so是在特定OpenCV版本下编译的。如果编译库时用的是OpenCV 3.4而你系统里现在装的是OpenCV 4.6符号表不兼容链接器找不到对应函数实现。解决查看libtennis.so依赖的OpenCV版本ldd lib/libtennis.so | grep opencv输出里会显示libopencv_core.so.3.4之类的具体版本。如果和你系统里的不一致有两条路一是装回对应的OpenCV版本二是不用这个预编译库直接用src下的源码重新编译整个项目把libtennis.so里的函数实现从源码里重新构建。我一般选第二条源码都在没必要跟动态库版本较劲。5.2 HSV阈值照搬导致整片场地误检现象把代码里的HSV阈值原封不动跑在自己的场地上结果mask里一大片黄色区域霍夫圆检测检出一堆假的圆。原因网球场的围栏、地面标线、阳光反射到塑胶地面的黄色光斑色调值都在20到40这个区间。原项目的阈值是在室内灯光下标的室外晴天和阴天的色温差异能让同一个球的H值偏移10以上。解决不要在代码里硬编码HSV阈值把阈值参数挪到config/default.yaml里每次换场地先用调试工具重新标定。具体做法是用createTrackbar拉出上下限然后观察mask里球和背景的分界线把S和V的下限往上提减少阴影和反光的影响。血的教训永远不要相信一套阈值能通吃所有场地。5.3 透视变换后球的位置偏移严重现象球在画面中心时地面坐标基本准但靠近画面边缘时坐标偏出20到30厘米。原因四点标定的四个角点没有覆盖整个运动区域或者src_pts和dst_pts的对应顺序错了。getPerspectiveTransform只保证四个角点精确映射角点围成的区域内部是靠线性插值逼近的标定区域越大边缘误差越大。解决如果场地是矩形四个角点一定要选在实际可运动的边界上不要选在画面边缘。画面边缘通常有镜头畸变透视变换校正不了畸变。畸变明显时先做相机畸变校正用cv::undistort配合标定板算畸变系数。或者把运动区域缩小到画面中间区域别让机器人跑到透视变换误差太大的角落去捡球。5.4 贪心路径边缘漏检现象贪心算法生成的路径里场地边缘的球没有被访问到轨迹漏掉了靠近边界的球。原因get_Random_points.cpp生成候选点时有栅格边界裁剪逻辑靠近图像边缘的连通域中心点如果在栅格外会被丢弃。这些球在视觉检测阶段就丢掉了贪心算法根本看不到它们。解决给get_Random_points.cpp的边缘处理加一个膨脹逻辑把有效区域向外扩展半个栅格。或者干脆不按栅格裁剪直接对mask做连通域分析取每个连通域的质心作为球的位置这样就不会有边缘被裁剪的问题。改完后边缘漏检问题立刻消失。5.5 SVM训练后实时识别准确率反而下降现象离线测试时SVM准确率接近98%放到tennis.avi视频里跑漏检率却很高很多球没被框出来。原因tennis.avi是摄像头俯拍视角球在画面里大多只有20像素左右但训练数据里正样本是从特写图片ball_1.jpg到ball_8.jpg里裁剪的球占图像比例很大resize到32x32后纹理保留完整。实时检测时小球resize到32x32后会模糊特征分布和训练样本差异太大。解决收集实时视频里的真实球图像作为训练正样本不要只拿特写图训练。实践做法是从tennis.avi里抽帧手动标注球的位置再跑1.get_data.cpp生成训练集。这个方法能有效对齐训练分布和实际分布。那次之后我每次换摄像头视角都会重新收一轮数据。6. 进阶用法把离线检测变成可复现的验证脚本到这一步你已经能在自己的机器上跑通视觉检测和路径规划了。最后一个实用的进阶技巧用data目录里的tennis.avi做全流程自动化验证把每次调参的结果量化对比。6.1 用 tennis.avi 做全流程自检tennis.avi是原项目附带的一段实拍视频大约几十秒画面里有连续出现的网球和空场地切换。我建议把它当成回归测试用例每次修改HSV阈值、SVM参数或贪心算法逻辑都让程序跑一遍这个视频统计检测率。# 跑完整视频记录每一帧的检测结果 ./bin/Trajectory --video data/tennis.avi \ --config config/default.yaml \ --output result_path.csv --verbose输出result_path.csv里会包含帧号、球数量、圆心坐标和地面坐标四列。以此作为基线改参数后再跑一次对比同一帧里球的数量和位置变化。用这个数据说话比肉眼盯屏幕靠谱得多。比如你调了HSV阈值判断标准不是“看起来球被圈住了”而是result_path.csv里有效检测帧数占视频总帧数的比例有没有提升。6.2 在贪心路径上叠加避障检测如果你做的是课设进阶或者想冲一下优秀毕设可以在这份资源的基础上加一个动态障碍物规避在机器人执行贪心路径时实时检测前方是否有障碍物比如另一台机器人、围栏边的人如果有就跳过这个目标点先访问下一个等障碍物移走后再回来补捡。这就是AGV路径规划里常见的“局部重规划”思路不需要改贪心算法主体只需要在Trajectory执行时增加一个中断条件。实现方式也很简单底盘每走一步重新跑一次目标点合法性检查如果当前目标被障碍物占据把路径里当前位置到目标点的边权设为无穷大贪心算法重新计算剩余路径即可。这种“全局贪心 局部重规划”的组合在真实场地里的表现已经接近动态避障小车的效果。6.3 收尾排查先跑数据再上真机调试这类项目我养成的一个习惯是先离线跑通全部测试数据再考虑接摄像头。每次拿到一份视觉加路径规划的代码我都会把静态测试图、视频、SVM训练三件事按顺序各跑一遍确认每一环都正常最后才上真机。因为真机调试的时间成本和不可控因素太多把问题留在离线阶段解决性价比高得多。这份资源里data目录的测试图正好覆盖了形态差异很大的场景从单球特写到多球俯视从室内到室外全部跑通后再接摄像头踩坑概率会降低很多。希望这份拆解能帮你把项目跑起来。建议先复现一遍原项目的默认参数再按照第5章的避坑记录调整你自己场地的参数最后再加上本章的验证流程。资源包里的README.md和config/default.yaml是入口下载后先打开这两个文件会比直接翻源码省力得多。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑