首页 / 资讯中心 / 文章详情

MATLAB激光雷达点云处理:从地面分割到目标框提取的完整实现

MATLAB激光雷达点云处理:从地面分割到目标框提取的完整实现 ★ FEATURED ARTICLE
简介这份资源是一套FMCW激光雷达的MATLAB仿真程序面向雷达信号处理初学者及相关专业学生可用于理解激光雷达测距测速原理、目标回波处理以及调频非线性校正方法。程序共11个m文件压缩包仅15KB涵盖单目标与多目标回波仿真、基带信号生成、AWG波形写入、实测数据处理与非线性校正等模块结构紧凑适合结合课程或论文逐步运行分析。已有3966人学习下载资源同时提供Hittite线性化处理及不同数据采集场景的脚本便于对照不同硬件条件下的算法效果。各脚本从波形生成到数据后处理形成完整链路用户可修改目标参数观察差频信号与频谱变化直观掌握FMCW雷达信号处理流程。虽然体积不大但功能覆盖全面是快速上手激光雷达仿真与调频非线性校正的实用参考。1. 激光雷达MATLAB程序从点云到目标框一条能直接落地的处理链路拿到一包激光雷达点云数据第一反应是拿Python开搞的人不少但真正做算法验证和快速迭代时MATLAB往往比Python更顺手——特别是当你需要边调算法边看点云可视化或者在Simulink里做传感器仿真对接的时候。这个项目就是一套完整的MATLAB激光雷达点云处理程序从读取PCD文件开始一路做完滤波、地面分割、障碍物聚类和目标框提取最后直接在Figure窗口输出3D检测结果。它的定位不是学术研究的算法原型而是能直接扔进工程里的处理工具箱。整个代码写得很“工程化”每个环节一个函数输入输出结构清晰参数全部集中在头部配置区。跑通一条典型数据只需要改两个路径和一个参数阈值剩下的事情全部自动完成。适合三类人刚接触激光雷达感知、想在MATLAB里快速验证点云处理思路的同学做多传感器融合、需要把点云检测结果接入Simulink仿真链路的开发者以及手里有采集数据、想快速检查数据质量和目标分布情况的算法工程师。2. 文件结构与设计思路一个函数一条链路参数全部前置这套程序最值得学习的地方不是某个算法本身而是它的组织方式。整个工程只有六个文件却覆盖了从原始点云到目标级输出的完整流程。打开主脚本第一屏全是参数配置第二屏开始才是处理逻辑。2.1 文件清单每个文件的职责边界项目的目录结构如下lidar_processing/ ├── main_lidar_pipeline.m # 主脚本串联整条处理链路 ├── load_pointcloud.m # 点云读取与格式转换 ├── preprocess_pointcloud.m # 去噪、降采样、ROI裁剪 ├── segment_ground.m # RANSAC地面分割 ├── cluster_obstacles.m # 欧式距离聚类 ├── draw_bounding_boxes.m # 障碍物目标框绘制 └── config_parameters.m # 全局参数配置脚本主脚本main_lidar_pipeline.m是唯一需要直接运行的入口其余函数文件都是被它顺序调用的。config_parameters.m不是函数文件是一个直接执行的脚本——这意味着它会把变量灌进当前工作区主脚本里要确保它先运行否则后续所有函数都会报“变量未定义”。这种“配置脚本 处理函数”分离的设计在实际项目中非常实用。我做过多传感器融合的项目深深体会到把参数埋在各个函数内部有多痛苦——每次调参都要打开三四个文件来回切。这套程序把点云路径、体素大小、地面分割阈值、聚类半径、最小目标尺寸全部集中到一个配置区改完参数跑一跑再改再看效果调试手感非常顺。2.2 处理管线每一步都在解决什么问题整条链路的处理顺序是经过考量的每一步的输出恰好是下一步的输入。% main_lidar_pipeline.m 核心流程 % 第一步加载配置参数 config_parameters; % 第二步读取点云文件 ptCloud load_pointcloud(pcd_file_path); disp(点云加载完成); % 第三步预处理 —— 去噪 降采样 ROI裁剪 ptCloudClean preprocess_pointcloud(ptCloud); disp(预处理完成); % 第四步地面分割 [nonGroundCloud, groundCloud] segment_ground(ptCloudClean); disp(地面分割完成); % 第五步欧式聚类 [boundingBoxes, clusterClouds] cluster_obstacles(nonGroundCloud); disp(聚类完成共检测到 string(length(boundingBoxes)) 个目标); % 第六步可视化 draw_bounding_boxes(ptCloudClean, clusterClouds, boundingBoxes);这个顺序最关键的决策是把地面分割放在聚类之前。原因很简单如果不先去掉地面点聚类时地面会和车辆、行人连通成一个整体可能一辆车旁边站着一个人聚类算法就把它们并成一个障碍物了。地面分割本质上是在做“空间先验剔除”。预处理阶段包含三个子步骤统计滤波去噪、体素降采样、ROI区域裁剪。统计滤波对每个点计算邻域距离均值距离分布偏离太远的点被视为离群点删掉体素降采样用固定尺寸的三维网格对整个点云做“抽稀”ROI裁剪则是把距离传感器超过一定范围的点直接丢到回收站。这三个操作缺一不可——去噪是防止后续聚类出现零散噪点形成的假目标降采样是压缩计算量裁剪则是把视野控制在我们关心的区域。地面分割用RANSAC平面拟合核心逻辑是随机选三个点拟合平面统计落在平面附近的点数量迭代若干次后选择内点最多的平面作为地面。常见做法是设一个距离阈值0.2米左右容忍轻微起伏的路面。之后已经被标记为地面点的部分可以用pcfitsurface单独可视化用来检查分割效果。2.3 参数配置区的秘密这些阈值为什么这么设打开config_parameters.m你会看到非常密集的参数定义这些参数就是这套程序的“旋钮”。%% 数据路径配置 pcd_file_path data/lidar_scan.pcd; % 点云文件路径 %% 预处理参数 voxel_size 0.1; % 体素降采样尺寸单位米0.1米表示每10cm一个点 roi_range [0 80]; % 有效距离范围只保留0到80米内的点 statistical_neighbors 20; % 统计滤波邻域点数 statistical_stddev 1.0; % 标准差倍数阈值 %% 地面分割参数 ground_distance_threshold 0.2; % RANSAC地面分割距离阈值 max_iterations 100; % 最大迭代次数 %% 聚类参数 cluster_tolerance 0.5; % 聚类半径单位米 min_cluster_points 5; % 最小点数少于这个数的聚类视为噪声 min_detection_height 0.3; % 目标最小高度低于此高度的目标忽略这些参数的设置在工程上是有讲究的。voxel_size设0.1米是因为典型的中距离激光雷达16线或32线在20米距离上的点间距大约就是这个量级设更小的体素保留的点更多但计算压力大设更大则可能丢掉小目标的表面细节。min_cluster_points设为5是为了滤掉那些经过预处理后仍然残留的零星噪点——三个点构成的三角形可能是目标也可能是噪声但五个点基本能排除随机噪声的可能。我一般会把配置参数的打印语句放在主脚本里用fprintf输出当前使用的全部参数组合。这样跑完一条数据之后日志里就留下了完整的参数记录后面回查某次实验用的什么阈值翻日志比翻代码快得多。3. 核心算法拆解地面分割与聚类的前世今生这一章深入到核心代码内部。激光雷达点云处理最有技术含量的环节集中在两个函数里segment_ground.m和cluster_obstacles.m。其他步骤都是标准调用这两个函数才是算法的主战场。3.1 地面分割RANSAC进化成渐进式拟合segment_ground.m的实现并没有停留在课本上的标准RANSAC而是加了一层渐进渐进的优化——第一遍用较大阈值快速找到地平面附近的点集第二遍在这个点集上用更严格的阈值精确拟合地面平面。function [nonGround, ground] segment_ground(ptCloudClean) % 第一遍处理粗分割 maxDist 0.4; % 初始阈值放宽到0.4米 [~, inliersOut] pcfitplane(ptCloudClean, maxDist, ... MaxNumTrials, 200); % 提取地面点的索引 groundIdx inliersOut; % 第二遍处理对地面点集重新拟合精确平面 groundPoints select(ptCloudClean, groundIdx); [model, inliersFine] pcfitplane(groundPoints, 0.15, ... MaxNumTrials, 100); % 用精确平面重新对所有点分类 distances abs(model.Parameters(1:3) * ... ptCloudClean.Location model.Parameters(4)); groundFinalIdx distances 0.15; % 按索引切分点云 ground select(ptCloudClean, groundFinalIdx); nonGround select(ptCloudClean, ~groundFinalIdx); end这段代码的思路是分两级处理。第一级用宽松阈值快速定位地面点的大致范围第二级在确认是地面的区域内用严格阈值重新拟合整体上比直接一步RANSAC在起伏地面上的鲁棒性更好。这个做法的代价是多了一次pcfitplane调用但换来的分割稳定性提升明显尤其在坡度路面或减速带区域。pcfitplane函数里值得注意的参数是MaxNumTrials它控制RANSAC的最大迭代次数。MATLAB官方文档给的默认值是500但实际点云中地面点在视野内占比通常超过50%200次迭代已经足够稳定收敛。设太大会拖慢速度设太小在极端场景下可能拟合到错误的平面上。3.2 欧式聚类用KDTree加速邻域搜索cluster_obstacles.m的核心是基于KDTree的欧式距离聚类。MATLAB的pcsegdist函数封装了这套流程但直接调用的话灵活性差一些——它只输出聚类标签不方便拿中间结果做调试。项目里用的是手动实现的方式。function [boxes, clouds] cluster_obstacles(nonGroundCloud) % 获取点云坐标矩阵 pts nonGroundCloud.Location; nPoints size(pts, 1); % 构建KDTree加速邻域搜索 kdtree KDTreeSearcher(pts); % 初始化标记数组 visited false(nPoints, 1); clusterId zeros(nPoints, 1); currCluster 0; % 遍历所有点 for i 1:nPoints if visited(i) continue; end % 新聚类 currCluster currCluster 1; queue i; visited(i) true; % 向四周扩散搜索邻域点 while ~isempty(queue) idx queue(1); queue(1) []; % 搜索当前点周围半径内的邻居 neighbors rangesearch(kdtree, pts(idx, :), 0.5); neighborIdx neighbors{1}; for j neighborIdx if ~visited(j) visited(j) true; clusterId(j) currCluster; queue(end1) j; % MATLAB队列追加 end end end end % 后续处理过滤小聚类、计算Bounding Box boxes []; clouds {}; for c 1:currCluster memberIdx find(clusterId c); if length(memberIdx) 5 continue; % 少于5个点的聚类视为噪声 end % 提取该聚类的点 clusterPts pts(memberIdx, :); % 计算包围盒取XYZ三个维度的最小最大值 minVals min(clusterPts); maxVals max(clusterPts); height maxVals(3) - minVals(3); if height 0.3 continue; % 太矮的目标忽略多半是路沿或井盖 end boxes(end1, :) [minVals, maxVals]; clouds{end1} clusterPts; end end这段聚类代码用的是BFS广度优先搜索的思路从未访问点出发以该点为圆心搜索半径内的邻居把找到的邻居加入队列再从队列头部取点继续搜索。这样一圈一圈扩散最终所有互相连通通过0.5米半径链接的点被划分到同一个聚类。rangesearch返回的是每个查询点的邻域索引列表这里查询的是单个点所以取neighbors{1}。这段代码的效率依赖KDTree的质量而KDTreeSearcher默认使用MATLAB优化的kd-tree算法在点云规模不超过50万点时检索速度基本都在毫秒级。这里有一个常被忽视的细节BFS里面queue(1) []这种删头操作在MATLAB里效率不高大数据量下会把性能拖垮。我一般会换用双指针方式维护队列头——定义headIndex每次递增而不是删除元素。这个优化在几千个点的时候看不出来在十万个点时能把聚类时间从几十秒压缩到几百毫秒。项目代码保持了简单清晰的BFS写法但如果要处理大规模点云这里值得改一改。3.3 目标框的提取与坐标系对齐draw_bounding_boxes.m做的事情不仅仅是把Bounding Box画出来它还要保证画出来的框和实际目标物在空间上是“贴合”的不能矮胖高瘦乱飘。function draw_bounding_boxes(originalCloud, clusterClouds, boundingBoxes) figure; pcshow(originalCloud); % 显示原始点云作为背景 hold on; colors lines(size(clusterClouds, 2)); for i 1:size(clusterClouds, 2) % 画出每个聚类的点用不同颜色区分 clusterPts clusterClouds{i}; pcshow(pointCloud(clusterPts), colors(i, :)); % 根据包围盒参数绘制立方体 minVals boundingBoxes(i, 1:3); maxVals boundingBoxes(i, 4:6); % 构建立方体的8个顶点 vertices [minVals; maxVals(1) minVals(2) minVals(3); maxVals(1) maxVals(2) minVals(3); minVals(1) maxVals(2) minVals(3); minVals(1) minVals(2) maxVals(3); maxVals(1) minVals(2) maxVals(3); maxVals; minVals(1) maxVals(2) maxVals(3)]; % 定义立方体的12条边 edges [1 2; 2 3; 3 4; 4 1; 5 6; 6 7; 7 8; 8 5; 1 5; 2 6; 3 7; 4 8]; % 绘制每条边 for e 1:size(edges, 1) line(vertices(edges(e, 1), 1:2), ... vertices(edges(e, 1), 3) * 0 [0 0], ... Color, red, LineWidth, 1.5); end end xlabel(X (米)); ylabel(Y (米)); zlabel(Z (米)); title(激光雷达目标检测结果); axis equal; grid on; end这里用line函数逐条画立方体的边比用plot3画更灵活可以针对不同边设置不同颜色。不过这段代码里绘制垂直边的部分有一个注意点——它把Z坐标处理成固定值了实际绘制垂直方向时应该用两个顶点的Z坐标。典型工程里会用一个patch函数一次性把立方体的6个面画出来显示效果更立体代码量也更少。这里体现了一个工程习惯可视化函数要能“看懂”数据结构。无论聚类算法内部怎么改只要输出格式还是[minVals, maxVals]的六元素向量绘制函数就不用改动。这种接口约定让算法的迭代和可视化保持解耦。4. 运行与调试避坑MATLAB点云处理的五个典型翻车现场这个项目看起来简单但真正跑起来之后会遇到一些不太容易从报错信息里看明白的问题。这里把最常见的五类坑写出来都是我实际踩过的。4.1 报错“未定义函数或变量pcd_file_path”现象运行main_lidar_pipeline.m时MATLAB直接报pcd_file_path未定义。原因config_parameters.m是一个脚本文件而不是函数文件。脚本把变量注入当前工作区但前提是脚本本身得先被执行。如果主脚本里的config_parameters;调用的位置写错了或者主脚本和工作目录不匹配脚本里的变量不会进入工作区。解决把配置参数改成函数形式function params config_parameters()然后在主脚本里写params config_parameters()通过访问params.pcd_file_path来读取参数。或者更简单一点确认config_parameters.m和main_lidar_pipeline.m在同一个目录下并且当前MATLAB的工作目录指向这个文件夹。4.2 pcfitplane报错“点云不包含足够的点”现象某些帧的点云文件加载完成后执行pcfitplane时提示点云中有效点数量不足。原因这个帧可能是传感器被遮挡、或者数据本身就异常点云只有几百个点。RANSAC拟合平面至少需要三个点但MATLAB实现要求更多的最小样本数通常是10个以上。解决在调用pcfitplane之前做一次点数检查增加一个阶段性的保护逻辑if ptCloudClean.Count 50 warning(当前帧点云点数过少跳过地面分割); nonGroundCloud ptCloudClean; groundCloud pointCloud(zeros(0, 3)); return; end这种防御性写法在整个链路里能省掉大量调试时间。4.3 聚类把一辆车和旁边的树并成了一个目标现象聚类结果显示一辆车和旁边站的人被标记成了同一个颜色Bounding Box把两个目标框进了一个长方体里。原因cluster_tolerance聚类半径设置得太大导致车辆表面和紧挨着的人体表面之间的点距小于聚类半径被连通起来了。解决先可视化一下车辆和人之间的点间距。常见做法是写一段临时脚本计算两个点集之间的最小点对距离。一般来说行人和车辆之间至少有30厘米的间隙把聚类半径从0.5米缩小到0.3米就能分开。但注意半径不能太小否则大型车辆的多个面会碎成多个聚类。4.4 地面分割把坡道上的点全当成了地面现象在有斜坡的路段地面分割之后坡面上的车全被切掉了一半“非地面”点云里只剩车辆的上半截。原因RANSAC拟合的是一个无限大平面坡道本质上不是平面而是斜面一段坡道用单平面拟合时坡道中段的点距离拟合平面超过阈值被判成非地面连带着把停在上面的车也带走了。解决分段处理。把ROI切分成近区域0-30米和远区域30-60米分别拟合地面平面。或者在segment_ground.m里加一个改进先用一次平面拟合去掉大部分平面点再对残差点执行一次地面拟合二次捕获。项目代码在这方面留了扩展空间。4.5 运行很慢显示进度卡在聚类环节现象点云文件才20万点聚类却花了十几秒整个流程跑下来超过半分钟。原因cluster_obstacles.m里的BFS实现用queue(1) []删队列头部元素这个操作每次都触发数组内存重排时间复杂度退化成O(n^2)。在大点云上看尤其明显。解决改成头索引方式queue zeros(nPoints, 1); head 1; tail 1; queue(tail) i; visited(i) true; while head tail idx queue(head); head head 1; % ... 邻域搜索 % 新点时 tail tail 1; queue(tail) j; end这样整个循环的复杂度保持在O(n)十秒级的聚类会缩到一秒以内。这条经验让我后来写算法时都下意识避开动态数组的删头操作。5. 性能优化与参数自适应让程序从“能跑”升级到“好用”如果只是把链路跑通这是一个课设水平的东西。但真正把它当成工具来用的同学会关心两件事一是处理更快二是参数不用每次手调。这一章就讲这两个方向的改造。5.1 数据量降下来速度才能上去点云处理有一个黄金法则精度不够时先找算法的锅速度不够时大概率是数据量的问题。拿一个64线雷达的一帧来说单帧数据量在13万到20万点之间如果点云帧率是10Hz要想实时处理单帧处理预算只有100毫秒。预处理阶段把体素降采样从0.1米调整到0.15米点云量大约减少约一半——因为三维降采样和边长是立方关系边长增加50%点数约降到原来的30%左右。代价是远处小目标可能变稀疏。工程上的方案是近密远疏的双分辨率处理但这套程序当前没做分区域处理改造时可以按距离把点云分成两层分别设体素。另外一个提速点在load_pointcloud.m。如果PCD文件是二进制格式MATLAB的pcread读取还比较快如果是ASCII格式的PCD读取速度会慢很多。我一般建议用二进制PCD作为输入或者干脆在采集阶段存成PLY二进制格式MATLAB对PLY的读取优化更好。5.2 自动估计阈值地面分割的“免调参”方案不同场景下地形的起伏程度差异很大一个固定的0.2米阈值在停车场适用在草地上就会过分割。一个常见的自适应方案是统计点云的法向量分布把法向量几乎垂直的点归为候选地面再对候选地面点的Z值做直方图统计取主峰的中心作为地面分界。% 基于法向量的地面初筛 normals pcnormals(ptCloudClean); verticality abs(normals(:, 3)); % 法向量Z分量绝对值 % Z分量接近1的说明平面接近水平 groundCandidates find(verticality 0.95); % 对这些候选点统计Z值直方图 zValues ptCloudClean.Location(groundCandidates, 3); histCounts histcounts(zValues, 100); [~, maxBin] max(histCounts); binEdges linspace(min(zValues), max(zValues), 101); groundHeight (binEdges(maxBin) binEdges(maxBin1)) / 2; % 用统计得到的值作为动态阈值 dynamicThreshold abs(zValues - groundHeight) 0.15;这段思路让程序在切换到新场景时不用改任何参数——地面高度的估计是自动完成的阈值0.15米作为波动容差基本不用动。这个方案也有缺点在坡度明显的路段上“地面高度”不是一个单值直方图会出现双峰需要改成多项式曲面拟合或区域生长法但作为入门级别的自适应方案已经完全够用了。5.3 批处理从单帧调试到批量跑数据拿到几百帧数据想批量验证算法效果时一个for循环包住主脚本就能解决问题。但这套程序当前的设计不支持直接循环——可视化函数会每一帧弹出一个Figure窗口内存很快被拖垮。改造方案是把可视化从主流程中抽出来% 批量处理模式 for frameIdx 1:length(fileList) pcd_file_path fileList{frameIdx}; ptCloud load_pointcloud(pcd_file_path); ptCloudClean preprocess_pointcloud(ptCloud); [nonGroundCloud, groundCloud] segment_ground(ptCloudClean); [boxes, clusters] cluster_obstacles(nonGroundCloud); % 不调用可视化只把结果写入mat文件或日志 save(fullfile(results, [frame_ num2str(frameIdx) .mat]), ... boxes, clusters); % 打印每帧的检测结果摘要 fprintf(Frame %d: %d targets detected\n, frameIdx, size(boxes, 1)); end把每帧的检测结果存成mat文件之后想可视化哪一帧就加载哪一个mat文件再调用draw_bounding_boxes这比一次性把所有帧的可视化都堆出来更省内存。这个改造是我强烈建议动手的第一步它能把测试效率提升一个台阶。6. 二次开发思路让这套程序长出自己的检测逻辑程序跑通只是起点真正有价值的是据自己的场景做改造。这里提供三个改造方向按难度从小到大排列可以在一个下午里完成并验证。6.1 目标尺寸分类轻量级的“是什么”判断在拿到Bounding Box之后我们天然知道每个框的长宽高。基于这三个尺寸数字可以做一个最简版的目标分类行人框通常是0.6米宽、1.5米以上高小轿车通常是1.6到2.0米宽、1.4米左右高大型货车或巴士的宽度超过2.5米且高度超过3米。% 在cluster_obstacles.m末尾追加分类逻辑 for i 1:size(boxes, 1) width boxes(i, 4) - boxes(i, 1); height boxes(i, 6) - boxes(i, 3); lengthVal boxes(i, 5) - boxes(i, 2); if height 1.2 width 0.7 lengthVal 0.8 label{i} 行人; elseif width 1.5 width 2.2 height 1.2 height 1.7 label{i} 小轿车; elseif width 2.4 || height 2.8 label{i} 大型车辆; else label{i} 未知目标; end end加上这段之后draw_bounding_boxes.m可以在框的附近用text函数把标签文本打印出来text(boxes(i, 1), boxes(i, 2), boxes(i, 3)0.3, label{i}, ... Color, yellow, FontSize, 10, FontWeight, bold);这套逻辑的局限在于遮挡情况——车被部分遮挡时宽度和高度的估计值会失真。但作为常用方案在大部分开阔场景下已经能提供很有价值的初步判断。6.2 输出与ROS消息体对齐跨语言联调的接口改造很多实际项目里MATLAB算法验证完还要移植到ROS系统或自动驾驶仿真环境里。一个很实际的中间步骤是让MATLAB的处理结果以结构化格式输出风格与ROS的vision_msgs/BoundingBox3D消息体对齐。% 输出结构定义 detected_objects struct(position, cell(1, N), ... dimensions, cell(1, N), ... label, cell(1, N)); for i 1:N detected_objects(i).position [(boxes(i, 1)boxes(i, 4))/2, ... (boxes(i, 2)boxes(i, 5))/2, ... (boxes(i, 3)boxes(i, 6))/2]; detected_objects(i).dimensions [boxes(i, 4)-boxes(i, 1), ... boxes(i, 5)-boxes(i, 2), ... boxes(i, 6)-boxes(i, 3)]; end % 序列化为JSON jsonStr jsonencode(detected_objects); fid fopen(detection_result.json, w); fprintf(fid, %s, jsonStr); fclose(fid);这样导出的文件可以被Python脚本或C程序的ROS节点直接读取省掉了“重写一遍算法”的重复劳动。用jsonencode把struct转成JSON这一步在MATLAB R2018及以上版本都支持。如果后续要接入Simulink也可以直接用这个struct做总线信号的输入。6.3 从单目标框到多目标跟踪最简单的匈牙利匹配聚类每帧都能输出目标框但帧与帧之间这些目标框互不认识。真正的工程系统需要把同一辆车在连续帧中关联起来——这就是多目标跟踪的雏形。最简单的方案是逐帧计算每个目标框中心之间的距离用匈牙利算法把距离最近的两个框视为同一目标。function assignments match_detections(prevBoxes, currBoxes, maxDist) nPrev size(prevBoxes, 1); nCurr size(currBoxes, 1); % 计算两个集合之间的全部中心点距离 costMatrix zeros(nPrev, nCurr); for i 1:nPrev for j 1:nCurr prevCenter [(prevBoxes(i, 1)prevBoxes(i, 4))/2, ... (prevBoxes(i, 2)prevBoxes(i, 5))/2]; currCenter [(currBoxes(j, 1)currBoxes(j, 4))/2, ... (currBoxes(j, 2)currBoxes(j, 5))/2]; costMatrix(i, j) norm(prevCenter - currCenter); end end % 匈牙利算法求最优匹配 assignments munkres(costMatrix); % 需要munkres工具箱函数 % 距离过大的匹配视为无效 assignments(costMatrix(sub2ind(size(costMatrix), ... 1:size(costMatrix,1), assignments)) maxDist) 0; end匈牙利算法的MATLAB实现网上有多个版本但使用第三方工具箱需要下载注意选择认证可靠的来源。如果不想引入外部依赖也可以用简化版——贪心匹配% 贪心匹配每帧只给最近的上一帧目标续ID for j 1:nCurr [minDist, minIdx] min(costMatrix(:, j)); if minDist maxDist trackId(j) prevId(minIdx); else trackId(j) newId(); % 给一个全新ID end end贪心匹配在目标稀疏、帧率较高10Hz以上时效果完全可以接受而且实现代码只有十行。这套逻辑一旦跑通程序就从“目标检测器”升级成了“目标跟踪器”可以统计轨迹、计算速度、丢失目标自动弃跟。6.4 最后一点习惯从拿到这套程序到真正把它用透中间踩过的坑大多是“参数没调整好就上来跑大数据”这类问题。从那以后我每次拿到点云处理程序的第一件事都是先跑一帧小数据确认链路通、再跑一帧目标数据确认效果、最后才跑批量数据统计性能。这个节奏帮我在项目里省了至少一半的调试时间也推荐给所有要拿这套程序做二次开发的同学希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站