资讯动态

Darknet YOLOv3测试全流程与mAP计算方法详解

发布时间:2026/10/15 12:10:15 来源:尧图企业网站定制
聊到 darknet 和 YOLOv3绕不开两件事怎么把检测跑起来怎么告诉别人检测得好。我第一次在一个目标检测项目里用 darknet 训练完 YOLOv3 时觉得 loss 已经降得挺像样了结果拿验证集一测输出的一堆left_x / top_y / width / height让我一头雾水。后来花了不少时间才把 darknet 的测试命令、检测输出解读、mAP 计算路径和那些容易翻车的细节一一串起来。这篇东西算是我自己的实操沉淀覆盖 darknet 的 yolov3 测试完整流程、评价指标里最关键的概念以及踩过的一些坑适合刚跑通训练但还搞不清测试结果的新手也适合想系统梳理 mAP 和 PR 曲线的同学。1. 跑通 detector test 之前我建议你先搞定这三件事1.1 编译选项直接决定你用 GPU 还是 CPU 硬扛darknet 不是装完就能直接到处用的它最开始是一个纯 C 项目编译参数决定了很多事。如果你用 CPU 测试单张图片一张 416x416 的输入可能得等一两秒这还能忍但批量测试几百张图片的时候CPU 推理就是灾难。我见过有人在笔记本上用 CPU 跑完 5000 张验证集整整跑了一个通宵。所以在编译阶段至少把 Makefile 里这几个开关打开GPU1 CUDNN1 OPENCV1然后再执行make。GPU 和 CUDNN 会明显提速OPENCV 则影响图片读取和展示没有 OPENCV 的话predictions.jpg可能不会正常保存。另一个容易被忽略的是LIBSO1如果你后面打算用 Python 调 darknet 的接口这个选项最好也开着。提示改完 Makefile 后建议先make clean再重新编译不要直接make否则旧的对象文件会捣乱出现各种诡异的段错误。1.2 单张测试与批量测试命令行的差别比你想的大darknet 测试 YOLOv3 的标准命令是 load 完所有输入后从命令行指定图片路径。下面这条命令是最基础的姿势./darknet detector test cfg/coco.data cfg/yolov3.cfg weights/yolov3.weights data/dog.jpg如果一切正常终端会打印类似这样的输出dog: 62% left_x: 47 top_y: 260 width: 191 height: 213同时当前目录下生成predictions.jpg。但你很快会遇到一个问题单张图片测试只能一次喂一张真正验证模型性能时不可能手动一张张敲图片路径。我的做法是准备一个valid.txt每行一条图片绝对路径然后通过标准输入喂给 darknet./darknet detector test cfg/obj.data cfg/yolov3.cfg backup/yolov3_last.weights -dont_show -ext_output valid.txt注意这里-dont_show表示不弹图片窗口适合服务器环境-ext_output会让检测结果以更适合文本解析的格式输出比如每行包含图片名、坐标、置信度。如果你把标准输出重定向到文件就能得到一份完整的检测结果记录。如果想让 darknet 直接输出 JSON方便后面算 mAP就加上-out参数./darknet detector test cfg/obj.data cfg/yolov3.cfg backup/yolov3_last.weights -dont_show -ext_output -out result.json valid.txt这里有个很关键的细节-out result.json只有在配合-ext_output且从标准输入读路径列表时才生效单独敲命令加-out经常不产出文件我第一次就是卡在这里。1.3 批量测试时的显存问题不要忽略GPU 显存不够大时批量测试一样会爆。YOLOv3 416x416 的单张推理显存占用大概在 1GB 到 2GB 之间看模型输入大小和 batch。darknet 的 test 逻辑默认不按 batch 循环而是逐张处理看起来吃得消但如果你的模型是 608x608 甚至更大或者你开了benchmark之类的参数显存压力会明显上升。我的建议是先跑 100 张图片试一下显存占用如果过载就在命令里加-dont_show减少额外开销或者把验证集拆成几个子文件分批跑最后合并 JSON。不要天真地认为测试比训练省显存后处理阶段的图像存储也会占显存稍微不留神就报CUDA Error: out of memory。2. 读懂检测输出一个框是怎么从网络变成文字的2.1 那串数字不是中心点是左上角和宽高很多第一次接触 darknet 输出的人会犯一个理解错误以为left_x / top_y / width / height是中心点坐标和半径之类的。实际上darknet 的检测输出定义非常直白字段含义left_x检测框左上角 x 坐标top_y检测框左上角 y 坐标width检测框宽度height检测框高度score该类别置信度0 到 1 之间这个坐标系以图片左上角为原点向右为 x 正方向向下为 y 正方向。很多标注工具交互时爱用中心点center_x, center_y, w, h所以当你把 YOLO 标签转成检测框时免不了要做一次换算left_x center_x - width / 2.0 top_y center_y - height / 2.0我早期调试时因为相信了输出是中心点直接画框画得乱七八糟后来一检查发现是自己对坐标系的定义搞混了。你如果打算自己写可视化函数请务必先确认这一点。2.2 置信度、NMS 和重叠框框多不代表检测好YOLOv3 的输出层本质上是若干个预测框的集合。每个网格会预测多个 anchor box每个 anchor box 会输出 4 个坐标值 1 个 objectness 分数 每个类别的条件概率。最终整合时darknet 会做两件事用置信度过滤掉低于阈值的框对同一类别的重叠框做 NMS非极大值抑制如果你在测试命令里加-thresh 0.5darknet 只输出置信度大于 0.5 的框。如果-thresh 0.1会输出非常多的候选框NMS 随后再压缩。很多刚开始用 YOLOv3 的人会看到-thresh 0.1下出现大量重叠框第一反应是“模型坏了”其实只是阈值太低NMS 又不够激进而已。提示算 mAP 时我不建议把-thresh设得太高。mAP 的本质是评价模型在所有置信度下的排序能力你只保留 0.5 以上的框前面的高精度区间也许覆盖不到最终 AP 会失真。通常我算 mAP 用-thresh 0.001或0.01让模型尽量输出所有可能的候选框后续再按置信度排序算 PR 曲线。2.3 训练 loss 和测试效果为什么经常不一致这个问题几乎每个训练过 YOLOv3 的人都会遇到训练 loss 已经很低了验证集看一眼却乱七八糟。原因往往不是模型本身而是你没有真正理解 YOLOv3 的 loss 组成。YOLOv3 的 loss 由坐标损失、objectness 损失、类别损失三块加权组成。训练时这些损失在某个尺度下被平均而验证时的“检测效果”是由最终预测框和真实框之间的 IoU 决定的。一个模型可能坐标预测得很准但类别置信度分布有问题也可能整体 loss 不高却在少数被漏检的类别上表现很差。所以你不能只看 loss 曲线就说“训练完成”必须用独立的验证集跑测试并用评价指标量化。这也是为什么 YOLOv3 的测试流程和评价指标不只是“跑个 demo”而是训练闭环里最不可缺少的一步。3. mAP 不是玄学从 IoU 到 PR 曲线的完整推导3.1 IoU 是什么两个框的重合度怎么算IoUIntersection over Union是检测框和真实标注框之间重叠程度的度量。公式很简单IoU 两个框交集面积 / 两个框并集面积如果两个框刚好完全重合IoU 等于 1如果完全不相交IoU 等于 0。实际判断“检测是否正确”时通常设定一个阈值VOC 风格常用 0.5也就是 IoU 大于等于 0.5 且类别正确就认为这个检测框是正确检测。举个例子检测框和真实框的交集面积是 40并集面积是 80那么 IoU 0.5刚好踩线在很多评测标准里就被判为正确。如果你用的阈值是 0.75那这个检测框会被当成误检。同样是 YOLOv3用 mAP0.5 和 mAP0.75 得出的分数可能差很多这并不奇怪只是判定标准不同。3.2 TP、FP、FN先把“检测对没对”定义清楚在目标检测里我们把模型输出的每个框和真实标注框做匹配最终会得到四类结果缩写全称含义TPTrue Positive正确检测到的目标IoU 达标且类别正确FPFalse Positive误检检测到背景或类别错误FNFalse Negative漏检真实目标没有被检测到TNTrue Negative背景被正确地不检测目标检测一般不讨论匹配规则比较关键。通常每个真实框只能被匹配一次多个检测框攻击同一个真实框时IoU 最高且超过阈值的那个框算 TP其余算 FP如果某个真实框没有任何检测框匹配就记一个 FN。这个逻辑决定了你光看输出数量没有用必须真正和 GT 对齐。举一个小例子某张图有 3 个人模型输出了 5 个框。前 3 个框分别匹配到 3 个人IoU 都大于 0.5所以产生了 3 个 TP剩下 2 个框全部落空是 2 个 FPGT 里 3 个目标都被覆盖FN 0。如果模型只输出 1 个框匹配到其中 1 个人则 TP 1FP 0FN 2。3.3 AP 和 mAP最终看的其实是排序质量有了 TP、FP、FNprecision 和 recall 就好算了precision TP / (TP FP) recall TP / (TP FN)precision 衡量“模型输出的框中有多少是对的”recall 衡量“真实目标中有多少被找出来了”。但这两个数字不是固定值它们和置信度阈值相关。把阈值从高往低调输出的框越来越多recall 一般会上升precision 可能会下降。APAverage Precision就是把不同阈值下的 precision 和 recall 画成 PR 曲线再算曲线下的面积。mAP 是所有类别的 AP 取平均。所以 mAP 高意味着模型把正确目标排在前面、错误框排在后面整体排序质量好。用一个小例子更好理解某个类别有 3 个真实目标按置信度从高到低排列模型输出的 5 个框并标记是否为 TP序号置信度是否TP累计 TP累计 FPprecisionrecall10.95TP101.00.3320.86FP110.50.3330.78TP210.670.6740.65FP220.50.6750.50TP320.61.0AP 的计算方式有不同流派VOC 早期用的是 11 点插值在 recall 从 0 到 1 的 11 个等分点上取最大 precision 再平均更常见的是对每个 recall 变化点做精确积分。无论用哪种目的都是量化 PR 曲线下的面积。这个例子算下来AP 大概在 0.7 上下比“平均 precision 直接求平均”要略高也更能反映排序特性。3.4 darknet 常用指标边界0.5 阈值与不同评测风格darknet 社区里最常说的 mAP默认是 IoU0.5 的 AP 均值也就是mAP0.5。与之相对的是 COCO 风格的mAP[.5:.95]后者把 IoU 从 0.5 到 0.95 按步长 0.05 取 10 个阈值分别计算 mAP 再平均。同一个模型在 COCO 风格下分数通常显著低于 VOC 风格这不代表模型变差了只是评价标准更苛刻。所以拿到一个 mAP 数字先问清楚计算口径。你如果跟别人对比模型效果一定要确认两边用的是同一套评测脚本、同一个验证集否则对比没有意义。我自己遇到过类似情况自己按 VOC 口径算出来 0.68对方按 COCO 口径报 0.41一开始还以为模型被吊打后来才发现是标准不同。4. 在自己的数据集上算 mAP两条清晰可走的路4.1 验证集标注文件的格式检查清单darknet 使用的标注格式是 YOLO 风格每张图片对应一个同名 txt一行一个目标内容是类别编号 中心点x 中心点y 宽度 高度所有坐标都归一化到 0 到 1。比如0 0.5 0.45 0.2 0.3 1 0.7 0.8 0.1 0.15第一行的含义是类别 0 的目标中心点在图像相对位置 (0.5, 0.45)宽高分别占整张图片的 0.2 和 0.3。算 mAP 前你还要准备一个obj.data文件内容大概是classes1 traintrain.txt validvalid.txt namesobj.names backupbackup/其中valid.txt写的是验证集图片路径每行一条。注意别把训练集图片混进valid.txt否则模型见过这些图片mAP 会虚高。还有一个容易漏掉的细节没有目标的图片其同名 txt 也必须存在内容为空。我遇到过验证集里有两张纯背景图没生成 txt结果脚本跑一半报错。4.2 路子一darknet 直接输出 JSON再用脚本算 mAP如果你用的 darknet 版本比较新可能会有detector map这样的命令一步到位输出 mAP。但我觉得最可控的方式仍然是上一节提到的-out result.json把检测结果导出来再用脚本解析。JSON 文件大致结构是一个数组每个元素包含图片 ID、类别 ID、bbox 坐标和置信度。拿到这个 JSON 后配合验证集的 GT 标注就可以算每一类的 AP 和整体 mAP。你可以用公开的 mAP 计算脚本也可以自己写一个短的 Python 脚本。核心流程是读取valid.txt对应的所有 GT 标注按图片分组读取 JSON 检测结果按图片分组对每一类分别处理把该类的所有检测框按置信度降序排列依次把检测框与当前图片内的同类 GT 做 IoU 匹配匹配到未使用的 GT 且 IoU 达标的计为 TP否则计为 FP没有被匹配到的 GT 计为 FN根据 TP/FP/FN 序列计算 precision、recall 和 AP所有类别 AP 取平均得到 mAP这个流程听起来不难但实际写起来细节不少比如同一 GT 在多个检测框之间的分配策略、空标签图片的处理、类别编号要与obj.names对应。我建议第一次跑的时候打印几行中间结果手动核对一两个类别避免脚本里出现计算错误你都不知道。4.3 路子二把检测结果转成标准格式用现成评测库算如果你不太想自己写 PR 计算逻辑另一种做法是把 darknet 的检测结果转成 COCO 风格的 JSON或者转换成 PASCAL VOC 的 XML/txt 格式再用开源评测库来计算。darknet 的 bbox 是left_x, top_y, width, heightCOCO 风格也是left_x, top_y, width, height所以这个转换相对直接。但要注意类别编号要对齐。darknet 的类别编号从 0 开始COCO 评测里可能要求从 1 开始或者有一套默认的 80 类映射表。如果你用的是自定义数据集建议直接把类别编号加 1再映射成你自己的类别名称列表避免阴差阳错地跟 COCO 类别混淆。我个人偏向路径一因为 JSON 保留的信息更完整后期排查错误检测框、按阈值调结果、分析 FP/FN 分布都比较方便。现成评测库虽然省事但一旦结果异常你很难判断是检测问题还是评测脚本问题。4.4 一个小建议先跑通单类别再跑全类别刚开始算 mAP 时不要一上来就在全类别数据集上跑。我的做法是先把obj.data里的classes临时改成 1验证集也只保留一个类别的标签跑通完整流程。这样核对数字容易得多自己手动挑几张图算一遍 IoU 和 AP和脚本输出对照确认无误后再放开全类别。这一步能帮你省掉大量排查时间尤其是当你用的计算脚本是别人写的时候。5. 测试阶段最容易翻车的几个点与我的排查顺序5.1 训练 cfg 和测试 cfg 不一致filters 和 classes 的隐蔽坑YOLOv3 的模型结构完全由 cfg 文件决定。如果你训练时用的是yolov3_custom.cfg测试时必须用同一个 cfg否则权重结构对不上。最典型的错误是把classes改成了自己的类别数但忘了改输出层前面的filters导致加载权重直接报错。YOLOv3 最后一层卷积的 filters 数量和类别数有关有个常用公式filters 3 * (classes 5)因为每个网格预测 3 个 anchor每个 anchor 需要 4 个坐标 1 个 objectness classes 个类别概率。假设你有 5 个类别最后一级的 filters 就是3 * (5 5) 30。yolov3 有三个 yolo 输出层不是只有最后一层要改三个尺度对应的前置卷积都要匹配。更隐蔽的是训练 cfg 里改了random、augment等参数测试时用了另一份“看起来差不多但字面上不一样”的 cfg最终结果出现细微偏差。我的建议是训练完备份一份完整的 cfg 到backup/目录测试时直接用它不要从网上重新下一份来拼。5.2 输入分辨率与 anchors为什么同一个模型换个尺寸结果差很多YOLOv3 的输入尺寸由 cfg 中的width和height决定比如 416x416 或 608x608。测试时图片会被拉伸或填充到固定尺寸。如果你训练用 416测试时换到 608模型的感受野变化会让小目标的检测效果突变因为 YOLOv3 的三个尺度对不同大小目标的敏感度不一样。锚点框也不是随便写的。darknet 自带的anchors是 COCO 数据集上聚类出来的用在自定义数据集上并不一定最优。我在一个项目里训练的是大量长条形物体COCO anchors 显然不适合测试时小物体漏检非常严重。后来重新用 k-means 聚类了自己的 anchors再测试mAP 明显提升。所以当你发现测试指标差先别急着怀疑模型确认一下你用的 anchors 是否与数据分布匹配。anchors 和输入分辨率、特征图尺度是有联动的单一变量改变都会影响最终输出。5.3 算 mAP 时别乱调 thresh可视化再另说很多人跑测试时习惯加-thresh 0.5甚至-thresh 0.8觉得只有高置信度的结果才展示出来好看。但算 mAP 时这种做法会把低置信度的正确检测全部丢进 FN导致 mAP 偏低。我踩过的坑是一开始算 mAP 时用了-thresh 0.25结果 AP 只有 0.4 左右总觉得模型训练不到位。后来才发现darknet 后处理在低阈值情况下会保留很多候选框再由评测脚本按置信度排序计算 PR 曲线。阈值设太高等于提前剪掉了很多可能成为 TP 的框计算出来的 AP 当然失真。而做可视化时则正好相反我通常会用-thresh 0.5甚至针对某些误检多的类别提到0.6让可视化结果适合肉眼分析。简单说算指标低阈值看图高阈值两件事不要用同一个参数混着来。5.4 我的建议排查流程可视化先行指标殿后如果评价指标不好我建议按下面的顺序排查而不是盯着 mAP 数字瞎猜先在小范围验证集上跑可视化保存检测框叠加图看模型到底在哪些图上表现异常把检测结果按置信度排序找到高置信度的 FP看看模型误检的共性统计 GT 数量与检测框数量如果一个类别的召回率偏低很可能是漏检而不是误检针对疑似问题检查训练集是不是存在标注错误、标签不平衡、类别混淆等问题最后再回到 mAP结合 AP 的类别差异判断是整体弱还是局部弱有一次我负责的模型 mAP 卡在 0.55 上不去单看训练 loss 很正常。后来可视化一看大量检测框都落在小目标上而且模型把一些纹理相近的背景当成了目标。回查标注发现训练集里小目标比例太高而且不少人眼都容易混淆的样例。调整了训练样本分布和标注清洗之后同样的测试流程 mAP 涨了好几个点。这就是先可视化再盯指标带来的价值。最后关于测试这件事我的个人体会我测试 YOLOv3 模型时最常用的一条基础命令还是./darknet detector test cfg/obj.data cfg/yolov3.cfg backup/yolov3_last.weights -dont_show -ext_output valid.txt先看输出和可视化再算 mAP最后反推优化方向。darknet 的使用门槛不高但它给出的信息很原始很多细节都需要你自己去解析和判断。如果你正在被测试结果折磨不妨先停下来读一遍输出文本搞清楚每一列的含义再动手写脚本算指标。我发现大部分所谓“模型不行”的情况最后追踪下来都是数据处理或者评测流程出了问题。另外一个小技巧把每次测试的 json、cfg、thresh、mAP 结果都存档命名带上日期和模型迭代编号。等你回头调参时才发现能对比历史结果的心态和效率完全不一样。我自己就是吃了没记录对比的亏同一个模型不同参数跑出来的结果对数都找不到又要重新跑一遍。如果你也觉得测试和评价指标比较混乱按我说的这套流程走一遍应该能理清大半。

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

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

免费获取报价 →
↑