简介:面向自动驾驶与数学建模学习者的MATLAB源码包,聚焦环境感知、路径规划、决策控制等核心环节,内置强化学习、CNN车辆检测等典型算法脚本。包内共6个文件,以.m脚本为主(4个),辅以README说明与配置管理文件,整体仅5KB,轻量易读。已有739人学习。源码模块涵盖智能体决策训练、视觉目标检测、仿真数据生成与结果可视化,能够帮助读者串联数据处理、模型训练到控制验证的完整流程。通过研读代码可理解自动驾驶中机器学习与数学建模的落地实现,适合作为课程设计、竞赛准备或入门实践的参考基础。
1. MATLAB 人工智能与自动驾驶源代码:从能跑的 Demo 到能交付的工程,关键在整条链路
搜索“MATLAB 人工智能与自动驾驶源代码”的人,多半不是来听概念,而是带着一个具体交付任务来的:课程设计要验收,毕业设计要答辩,横向项目要在仿真里跑通一版感知或控制演示。真正做过一遍的人都知道,难点不在深度学习理论,而在于把自动驾驶数据集、标注格式、模型训练和仿真验证串成一条能稳定复现的流水线——这也是 MATLAB 这套生态和纯 Python 项目最大的差异。下文按我常用的落地路径拆成四段:先把自动驾驶数据集转成 MATLAB 能识别的结构,再实现目标检测与车道线分割两个 AI 模块,接着放进驾驶场景做闭环验证,最后把源码整理成能继续迭代的 OOP 工程。新手可以对着步骤照抄,熟手可以直接跳到第 4 章的翻车清单,那部分全是血泪经验。
2. 把自动驾驶数据集转成 MATLAB 能吃的格式:标签、表格与 datastore
2.1 用什么数据集起步:KITTI、BDD100K 还是自定义标注
很多初学者第一步就卡在数据上:网上找的自动驾驶数据集大多是 KITTI 或 BDD100K 的原始结构,直接丢给 MATLAB 的检测器训练函数会报一堆“标签类型不支持”“表字段缺失”之类的错。这不是 MATLAB 不好用,而是它要求先拿到一个固化的表格结构:每一行是一张图,第二列是这个图上的所有矩形框,第三列是这些框对应的类别。所有官方训练接口都认这个结构,所以第一步不是写网络,而是把任意数据集“掰”成这种形状。
| 数据集 | 标注格式 | 我常用在哪个环节 | MATLAB 接入成本 |
|---|---|---|---|
| KITTI | 纯文本,逐行给出类别、截断度、遮挡、2D 框和 3D 信息 | 早期验证目标检测与相机几何 | 低,写一个解析函数即可 |
| BDD100K | JSON,带天气、时段、区域属性,共 10 万帧 | 多类别、多气候迁移验证 | 中,需要筛选类别并处理 JSON 层级 |
| 自采图像 + imageLabeler | MATLAB 原生 groundTruth | 固定场景专用模型 | 低,但样本量小容易过拟合 |
KITTI 的 label 文件看起来像这样:每行前面是Car 0.00 0 1.67 -2.13,后面跟着 8 个浮点数,其中第 5 到第 8 个是 2D 框的左上角和右下角坐标。BDD100K 则是层层嵌套的 JSON,一个文件里既有目标框,也有可行驶区域和车道线标注,对新手来说解析成本略高。我的建议很直接:如果只是为了先跑通,优先 KITTI;如果要做“恶劣天气下模型不崩”这类说服力更强的验证,再上 BDD100K。自采数据适合做特定厂区、收费站、停车场这类封闭场景,缺点是标 200 张图就要半天,且很容易过拟合。
2.2 用转换脚本把 KITTI 标签喂给 MATLAB 的 groundTruth
常见做法是写一个独立的解析函数,把 KITTI 文本标签转成两个 MATLAB table,再合并成一个boxLabelDatastore能直接消费的训练表。下面这个函数是我反复用过很多次的骨架,按你自己的目录结构改路径即可。
function [imgTbl, boxTbl] = kitti2matlab(imgDir, labelDir, targetClasses) imds = imageDatastore(imgDir); n = numel(imds.Files); boxesCell = cell(n, 1); labelsCell = cell(n, 1); parfor k = 1:n [~, name, ~] = fileparts(imds.Files{k}); f = fullfile(labelDir, [name '.txt']); boxes = zeros(0, 4); labels = categorical.empty(0, 1); if exist(f, 'file') fid = fopen(f, 'r'); C = textscan(fid, '%s %f %f %f %f %f %f %f', ... 'Delimiter', ' ', 'MultipleDelimsAsOne', 1); fclose(fid); cls = C{1}; x1 = C{5}; y1 = C{6}; x2 = C{7}; y2 = C{8}; hit = ismember(cls, targetClasses); if any(hit) boxes = [x1(hit), y1(hit), x2(hit) - x1(hit), y2(hit) - y1(hit)]; labels = categorical(cls(hit), targetClasses); end end boxesCell{k} = boxes; labelsCell{k} = labels; end imgTbl = table(imds.Files, 'VariableNames', {'imageFilename'}); boxTbl = table(boxesCell, labelsCell, 'VariableNames', {'box', 'label'}); end逻辑说明:imageDatastore负责把文件夹里所有图片路径管理起来,后续训练接口直接从imds.Files读文件,不需要手动拼路径。用fileparts取出当前图片名,再按同名规则找到 KITTI 的 txt 标签文件。textscan按空格拆分每行,%s取类别字符串,后面 7 个%f依次吸收截断度、遮挡、角度和四个坐标值,所以C{5}到C{8}就是左上角和右下角的 x、y。KITTI 的框是“左上右下”格式,而 MATLAB 的检测器统一要[x y w h],因此必须做x2-x1和y2-y1换算。
参数说明:targetClasses用 cell array 传,比如{'Car', 'Pedestrian'},只保留你关心的类别,其余类别直接丢掉。categorical(cls(hit), targetClasses)里的第二个参数必须和后面网络numClasses的数量、顺序完全一致,否则训练阶段会出现“找不到类别”的玄学报错。parfor在有 Parallel Computing Toolbox 时可以加速,文件量大但每张图处理很轻时特别合适;如果担心并行导致机器卡死,改成普通for也完全没问题。
2.3 用 boxLabelDatastore 做批处理:别让自己手写数据循环
数据转换完以后,不要把表格存成一堆mat文件就以为完事了。接下来要把合并后的表包进boxLabelDatastore,它的作用是:训练时按批次读图、解析框、对齐类别,并且自动过滤掉异常行。人工写循环逐张读图再喂给trainYOLOv2ObjectDetector是很常见的错误做法,性能差且容易漏样本。
[imgTbl, boxTbl] = ... kitti2matlab('data/image_2', 'data/label_2', {'Car', 'Pedestrian'}); fullTbl = [imgTbl, boxTbl]; % 去掉完全没有标注框的样本,减少无效负样本 hasBox = cellfun(@(b) ~isempty(b), fullTbl.box); fullTbl = fullTbl(hasBox, :); % 看一眼类别分布,防止 Car 和 Pedestrian 比例严重失衡 summary(fullTbl.label) blds = boxLabelDatastore(fullTbl);逻辑说明:cellfun检测每张图对应的框矩阵是否为空,空样本在检测训练里基本不提供正梯度,数量多了还会拖慢收敛。summary能快速列出每个类别的样本数,如果 Pedestrian 只有 50 个框,而 Car 有 5000 个,后面训练出来的模型基本等于瞎子,这时要先做类别重采样或增加对应类别样本。
参数说明:boxLabelDatastore要求box列必须是数值矩阵,label列必须是categorical类型。表格里不能混入NaN或负数坐标,KITTI 偶尔会标出完全在图外的框,如果不放心,可以在喂给 datastore 之前加一步过滤:fullTbl.box{k} = fullTbl.box{k}(fullTbl.box{k}(:,3) > 0, :),把宽度或高度小于等于零的框清掉。这个坑我踩过一次,当时怎么查都找不到原因,最后是用dbstop if error一行行定位到某张图的框宽为零。
3. 写自动驾驶感知代码:YOLO 检测与车道线分割的最小实现
3.1 感知任务怎么分工
自动驾驶里的“人工智能”落到 MATLAB 代码上,最少需要两条感知线:目标检测用来找车、人、交通标志,语义分割用来找车道线和可行驶区域。检测输出的是离散框,适合做避障和跟车决策;分割输出的是逐像素类别,适合做车道保持和路径规划。做课程设计或毕设演示时,我一般先做检测,因为它对标注噪声容忍度更高,复现出来“能看到框在动”,汇报效果更直观。
| 任务 | 典型模型 | 输出形式 | 在自动驾驶里的用途 |
|---|---|---|---|
| 目标检测 | YOLO v2 / SSD / Faster R-CNN | 矩形框 + 类别 + 置信度 | 车辆识别、行人预警、标志检测 |
| 语义分割 | DeepLabv3+ / 自定义分割网络 | 每个像素一个类别 ID | 车道线、可行驶区域、障碍物轮廓 |
| 实例分割 | Mask R-CNN(需额外工具箱) | 每个实例独立掩码 | 精确避障,但实现成本高 |
MATLAB 的 Computer Vision Toolbox 对 YOLO v2 和 DeepLabv3+ 都有官方支持层,不需要自己从零写卷积和反向传播。这一点比很多人想象中省事:你要写的是数据整理、参数配置、训练循环和结果验证这几段“胶水代码”,而不是重新发明网络结构。
3.2 用 YOLO v2 训练目标检测:最小可跑源代码
下面这段是我在本地跑通的最小实现。它假设你已经生成了fullTbl并包好了boxLabelDatastore,接下来直接搭网络并训练。
% 从上一章的数据表生成 datastore data = load('kitti_train_table.mat'); blds = boxLabelDatastore(data.fullTbl); % 网络输入尺寸要能被 32 整除,416x416 是速度和精度的折中 inputSize = [416 416 3]; numClasses = 2; % 特征层选 resnet50 中语义信息够但不算最深的层 featureLayer = 'activation_40_relu'; % 先用一组粗 anchor 起步,后面用统计方法重新聚类 anchorBoxes = [30 30; 60 45; 90 80; 160 120; 220 180]; lgraph = yolov2Layers(inputSize, numClasses, ... 'resnet50', featureLayer, anchorBoxes); options = trainingOptions('sgdm', ... 'MiniBatchSize', 8, ... 'InitialLearnRate', 1e-4, ... 'MaxEpochs', 15, ... 'Shuffle', 'every-epoch', ... 'VerboseFrequency', 50, ... 'Plots', 'training-progress'); detector = trainYOLOv2ObjectDetector(blds, lgraph, options); save('detector.mat', 'detector');逻辑说明:yolov2Layers会自动读入预训练的 ResNet-50 作为骨干,从activation_40_relu这一层开始接入检测头。选这个层而不是更浅层,是因为浅层特征关注边缘纹理,适合小目标但不适合中等大小的车辆;选更深层又会导致特征图太小,小目标直接消失。trainYOLOv2ObjectDetector内部完成 anchor 匹配、正负样本采样、loss 计算和反向传播,训练过程在Plots里可以实时看到 loss 下降曲线。
参数说明:MiniBatchSize=8在 8G 显存上比较稳妥,如果显存只有 4G,改成 4 或 2,否则第一步就报“Out of memory on GPU”。InitialLearnRate=1e-4是迁移学习的常用起点,因为骨干网络权重已经是训练好的,学习率太大会把前面的特征全冲乱。featureLayer的具体名称可以用analyzeNetwork(lgraph)查看,不同 MATLAB 版本或不同预训练模型,层名可能有差异。
3.3 用 DeepLabv3+ 训练车道线分割:最小可跑源代码
车道线分割需要逐像素标注数据。如果你没有现成 mask,可以用imageLabeler打开图片,用Pixel Label工具把路面刷成road类,其余刷成background,导出成一个像素标签 datastore。下面代码假设你已经拿到了pxds。
% 假设像素类别是 road 和 background,labelID 分别是 1 和 2 pxds = pixelLabelDatastore('train_img_list.txt', ... {'road', 'background'}, [1 2]); % DeepLabv3+ 在 resnet50 上搭 ASPP 模块 lgraph = deeplabv3plusLayers([480 640 3], 2, 'resnet50'); opts = trainingOptions('adam', ... 'InitialLearnRate', 1e-3, ... 'MaxEpochs', 12, ... 'MiniBatchSize', 4, ... 'Shuffle', 'every-epoch', ... 'Plots', 'training-progress'); net = trainNetwork(pxds, lgraph, opts); save('lane_net.mat', 'net');逻辑说明:deeplabv3plusLayers的第二个参数2是类别总数,必须和pixelLabelDatastore里类别列表的长度一致。这里用trainNetwork而不是trainYOLOv2ObjectDetector,因为分割任务是逐像素分类,接口更通用。DeepLabv3+ 内部结构是编码器-解码器,ASPP 用不同膨胀率的空洞卷积抓多尺度上下文,非常适合车道线这种细长目标。
参数说明:输入尺寸[480 640 3]我一般按实际相机分辨率的一半取,太大显存吃不住,太小车道线会断成虚线。MiniBatchSize=4是因为分割每张图的内存占用比检测高很多,8G 显存也只能勉强跑这个数。adam优化器在分割任务里收敛比sgdm快,学习率可以给到1e-3,前提是骨干网络用了预训练权重。
3.4 训练参数调整的底层逻辑:anchor、学习率、批次之间的关联
很多人训练翻车后第一反应是换网络,其实 80% 的问题出在 anchor 和学习率上。anchor 应该从你训练集的真实框尺寸统计出来,而不是胡乱猜。MATLAB 提供了estimateAnchorBoxes,它会基于所有标注框的宽高做聚类,得到一组和你的数据分布匹配的初始框。
% 从 boxLabelDatastore 读出所有框,统计宽高并聚类出 anchor tbl = readall(blds); boxData = tbl.box; if ~isempty(boxData) anchorBoxes = estimateAnchorBoxes(boxData, 5); disp(anchorBoxes); end逻辑说明:readall会把整个训练集的框一次性读进内存,返回的是一个 cell 数组,每个 cell 对应一张图上的所有框。estimateAnchorBoxes内部对这些框的宽高做 k-means 聚类,聚成几个簇,每个簇的中心就是一个 anchor。把这个结果替换掉 3.2 里的手写 anchor,训练会更稳,尤其当你的目标主要是行人这种瘦长形状时,默认正方形 anchor 会非常吃亏。
参数说明:第二个参数5表示聚成 5 类。类别多、目标尺寸跨度大时可以加到 9 或 12,但 anchor 太多会拖慢训练,因为每个 anchor 都要参与正负样本匹配。调 anchor 时,记得同步调trainingOptions里的InitialLearnRate和MaxEpochs:anchor 越贴近真实分布,可以用稍大的学习率;anchor 不准时,学习率大了容易震荡,表现为 loss 前期下降快,后期突然弹回去。
4. 常见问题排查与避坑:自动驾驶 MATLAB 源码的五处高频翻车点
4.1 现象:检测框全部堆在图片左上角,且宽度高度明显不对
我刚转 KITTI 数据时遇到过这个现象:训练出的模型在每张图左上角画出一堆边长一两个像素的小框。原因几乎都是坐标列读错了。KITTI 每行的第 5、6 列是 x1、y1,第 7、8 列才是 x2、y2,如果按C{4}到C{7}解析,读进去的是角度和截断度,数值范围完全对不上,训练时模型只能学到“框永远在左上角附近最安全”。解决方法是先随机找一张图,disp(C)把整个 cell 内容打印出来,人工确认第 5 到第 8 个字段是不是符合 2D 框坐标的整数范围,再继续往下跑。
4.2 现象:训练 loss 一开始就是 NaN,或者第一轮就中断
这类报错在换数据集时特别常见。原因通常是类别列表顺序和标注里的字符串对不上,比如targetClasses写的是{'Car','Pedestrian'},但 KITTI 文件里写的是Pedestrian和Car,categorical函数依然会生成类别,可排序后和网络分类头的顺序不一致,logits 和 label 对不上,loss 计算直接溢出。解决方法是训练前先执行一次unique(fullTbl.label),打印出所有类别,确认和yolov2Layers里的numClasses、以及targetClasses的顺序完全一致。这个检查看起来笨,但能省掉半天排查时间。
4.3 现象:GPU 显存报错,或者 CPU 训练“一晚上一个 epoch 都没跑完”
显存不够的经典组合是输入图片 1280 宽、MiniBatchSize=16、Plots='training-progress'同时开。训练曲线绘图本身也会占用额外显存,而且每轮都刷新图形界面,CPU 模式下尤其拖慢速度。解决方法是先降MiniBatchSize到 4,输入尺寸改成 416 或 224,再关掉Plots改看VerboseFrequency打印的 loss。如果机器完全没 GPU,ExecutionEnvironment设成'cpu',同时把MaxEpochs降到 5 先验证代码通路,等逻辑通了再上大机器训练。我一般会在代码里加一行gpuDevice判断,有卡才走 GPU 分支,没卡也不至于崩。
4.4 现象:DeepLabv3+ 分割结果像马赛克,路面区域空洞多
分割模型跑出来乱码,先检查像素标签 datastore 的 labelID 映射。pixelLabelDatastore的第三个参数[1 2]是期望的像素值,但如果你的标注图里用的是[0 255]或类别顺序反了,网络就会学到完全错误的映射。常见的还有灰度图输入:有的样例数据是单通道灰度,而deeplabv3plusLayers默认输入是 RGB 三通道,少一个维度直接报“输入通道不匹配”。解决方法是训练前先preview(pxds),把读取到的像素标签可视化一遍,确认类别颜色和语义对得上再开训练。
4.5 现象:仿真里集成检测器,结果detect返回空矩阵
训练时表现不错的模型,放进drivingScenario仿真后经常一帧都检测不到。原因有两类:一是仿真相机渲染的图像和训练集风格差距太大,模型没见过这种“干净”的网格路面,特征分布偏移;二是detect默认置信度阈值 0.5 太高,仿真里物体纹理简单,置信度普遍偏低。解决方法是先显式设置'Threshold', 0.1,看有没有低置信度输出,如果 0.1 都没有,再去查仿真相机的分辨率、焦距和安装高度是否和训练图尺度接近。这一步能用insertObjectAnnotation把框直接画到仿真图像上,每帧实时看,比盯着 loss 曲线直观得多。
5. 代码跑起来之后:仿真回环验证与检测指标怎么量
5.1 用 drivingScenario 快速搭一条可重复的验证路
模型训练完成后,不要只在测试集上看几张图就收工。我会把它塞进 MATLAB 的drivingScenario里做仿真验证,原因是仿真场景完全可控,可以设置同一路段、不同车距、不同光照,得到一套可重复测试数据集。下面是最小的建场景代码:
sc = drivingScenario; % 一条 180 米的双向直路,两个车道 roadCenters = [0 0 0; 180 0 0]; road(sc, roadCenters, 'Lanes', 2); % 自车放到起点,前方 40 米放一辆目标车 ego = vehicle(sc, 'Position', [5 0 0], 'Yaw', 0); target = vehicle(sc, 'Position', [40 0 0], 'Velocity', [10 0 0]); % 给自车装一个前视相机,模拟真实安装位 cam = camera(ego, 'Position', [1.5 0 1.4], ... 'Orientation', [0 0 0], 'FocalLength', 600); plot(sc, 'Waypoints', 'on');逻辑说明:drivingScenario是 MATLAB 自动驾驶仿真回环的地基,road函数只要给一系列路径点就能生成路段,vehicle用来定义自车和目标车,camera把相机挂在自车坐标系的某个安装点上。这里的关键是camera的参数要和真实采集系统的内外参接近,否则仿真图像训练出来的模型迁移不到实车。
参数说明:FocalLength=600是像素单位,相当于一个中等焦距的前视相机,视角不够宽但远处目标清晰。如果你想验证近距离行人,可以把焦距降到 400 左右,视野更宽但目标变小。Position的 y 坐标决定车辆在哪个车道,两条车道分别是 y=±1.75,我习惯把自车放 y=0,目标车放 y=0,走同一车道做追尾场景。
5.2 纯 MATLAB 闭环:检测结果接一个最简单的转向控制
仿真里只看到框还不够,我会把检测结果接一个最简单的 Pure Pursuit 控制,让车能跟着前车或车道走。下面的代码演示了在仿真推进循环里做检测、画框、计算前轮转角的最小闭环:
detector = coder.loadDeepLearningNetwork('detector.mat'); while advance(sc) img = snapshot(cam); % 当前相机帧 [bboxes, scores, labels] = detect(detector, img, 'Threshold', 0.3); % 只保留置信度大于 0.5 的目标,避免噪声框 keep = scores > 0.5; bboxes = bboxes(keep, :); labels = labels(keep); % 取最近的检测目标,用它的横向位置计算转向指令 if ~isempty(bboxes) nearestIdx = find(abs(bboxes(:,1) + bboxes(:,3)/2 - size(img,2)/2) == ... min(abs(bboxes(:,1) + bboxes(:,3)/2 - size(img,2)/2))); steerCmd = - (bboxes(nearestIdx(1),1) + bboxes(nearestIdx(1),3)/2 - size(img,2)/2) ... / size(img,2) * 0.5; else steerCmd = 0; end % 可视化当前帧 img = insertObjectAnnotation(img, 'rectangle', bboxes, labels); imshow(img); title(sprintf('Steer Cmd: %.2f', steerCmd)); drawnow; end逻辑说明:coder.loadDeepLearningNetwork加载保存的检测器,比每次循环都重新load快很多,它只加载一次网络结构,后续detect调用都走同一份内存。snapshot(cam)拿到当前仿真帧,detect返回框、分数和类别,再按分数过滤。转向指令用“目标中心离图像中心的偏差”计算,属于比例控制的最简写法,不做动力学建模,只验证感知到控制这条链路通不通。
参数说明:Threshold在仿真里建议给 0.3 而不是 0.5,因为仿真渲染的图像纹理偏干净,模型输出的置信度普遍没有真实数据高。steerCmd的 0.5 是比例增益,这个值取决于你想要的转向灵敏度,200ms 仿真步长下 0.5 比较稳,改成 1.0 会出现明显蛇形摆动。
5.3 mAP 和 IoU:怎么量化一个检测模型是否真的合格
闭环跑通之后,还要回答一个问题:这模型到底行不行?我一般用evaluateDetectionPrecision计算测试集 mAP,同时单独看几个典型难例。它计算的是每个类别的平均精度,代码量很小:
% testGTruth 是测试集的 groundTruth 表,结构和训练表一致 results = detect(detector, testImds, 'Threshold', 0.01); metrics = evaluateDetectionPrecision(results, testGTruth, 0.5); disp(metrics.ClassMetrics); fprintf('mAP@0.5 = %.3f\n', mean(metrics.ClassMetrics.AP));逻辑说明:detect的Threshold要设得极低,比如 0.01,否则高阈值会拦掉一部分漏检框,mAP 会被虚高。evaluateDetectionPrecision会逐个类别绘制 PR 曲线,返回的ClassMetrics.AP是每个类别的平均精度。这里的 0.5 是 IoU 阈值,意思是检测框和真值框的交并比超过 0.5 才算命中了。
参数说明:IoU 阈值从 0.5 改到 0.75,mAP 往往会掉 10 个百分点以上,这是正常现象。如果你要汇报“模型达到多少 mAP”,必须写清楚 IoU 阈值,否则数字没有可比性。更关键的是测试集不能从训练集里抽,我见过有人把训练数据 shuffle 后直接拿来评估,mAP 高达 0.95,换到仿真场景立刻归零,这个“好评测”没有任何交付价值。测试集要么专门留出一段未参与训练的视频,要么用仿真场景生成全新数据。
6. 把源代码变成能继续迭代的工程:OOP 封装、参数隔离与模型管理
6.1 用 handle 类把感知链路包起来
项目做到第三天就会意识到,直接裸写脚本是在给自己埋雷:检测器、阈值、类别列表散落在十几个脚本里,改一个参数要在全部代码里搜索替换。我现在会先用一个 handle 类把感知模块封装成只有step接口的黑匣子,调用方完全不关心内部是 YOLO 还是语义分割,只负责喂图和取结果。
classdef Perception < handle properties (Access = private) Detector ConfThr end methods function obj = Perception(modelFile, confThr) obj.Detector = coder.loadDeepLearningNetwork(modelFile); obj.ConfThr = confThr; end function out = step(obj, img) [bboxes, scores, labels] = detect(obj.Detector, img, ... 'Threshold', obj.ConfThr); out = struct('Boxes', bboxes, 'Scores', scores, 'Labels', labels); end end end逻辑说明:handle类之所以重要,是因为 MATLAB 默认做值拷贝,普通的classdef对象在传入函数时会把整个网络复制一份,训练好的模型动辄几十 MB,每次调用都会卡。handle类是引用语义,所有地方共享同一个对象,消灭了隐性拷贝。Access = private把模型内部字段藏起来,调用方只能通过step获取结果,防止有人手欠改了模型参数。
6.2 把 anchor、阈值、路径全部收进一个配置结构
训练参数是项目里最容易失控的部分。我现在规定所有可调参数都必须登记在一个配置函数里,任何实验都对应一份参数快照,跑完直接存成params_20250601.mat。这件事做起来很简单,但对后续复现实验是救命级的。
function p = defaultParams() p.inputSize = [416 416 3]; p.numClasses = 2; p.anchorBoxes = [30 30; 60 45; 90 80; 160 120; 220 180]; p.confThr = 0.3; p.iouThr = 0.5; p.trainDataDir = 'data/kitti_train/'; p.valDataDir = 'data/kitti_val/'; end这样做的直接收益是复现实验只需要改defaultParams一行,而不用翻遍所有脚本。我吃过最深刻的教训是调大 anchor 后忘了同步yolov2Layers里的参数,网络结构里用的还是旧 anchor,训练了两天,精度毫无提升,最后发现是这种低级失误。从那以后,所有涉及训练的参数一律先进配置结构体,再传给网络函数,训练命令永远只写一行:trainFromParams(defaultParams()),出了问题先看参数,再查代码。希望这篇笔记能帮你绕开我走过的弯路,也希望你跑通之后能再往前一步——把源码整理成一份能交给下一个人的工程,才算是真正把这个方向吃透了。
本文还有配套的精品资源,点击获取