☰
YOLOv3目标检测实战:行人自行车机动车识别与训练指南
2026/9/28 11:54:41 网站建设 项目流程

简介:面向目标检测入门与课程实训的YOLOv3完整方案,针对行人、自行车与机动车识别场景,提供模型代码、实验报告与数据集说明,适合高校学生、毕业设计或想快速上手PaddlePaddle实现YOLO的开发者。资源包共12个文件:3个Python脚本承担模型训练、推理与检测结果处理,7张PNG图展示精度曲线、mAP及检测效果,另有说明文档与类别配置文件,整体仅158KB,便于下载。已有693人学习使用。资料从YOLO算法原理、网络结构讲到参数调整与模型优化,并给出YOLO与YOLO-tiny对比、挑战集测试分析和mAP性能评估;结合训练与推理脚本及报告目录,读者可复现训练流程、理解调参思路,也可参考实验报告结构完成自己的课程作业。

1. 基于YOLOv3的目标检测神经网络与数据集:先认清“识别”和“检测”的距离

每个学期都会有一批人拿着同一个标题来问:基于YOLOv3的目标检测神经网络和数据集,识别行人、自行车与机动车。这不是冷门课题,而是计算机视觉课程作业的标配。很多人卡在第一层认知上——识别和检测是两码事。识别只告诉你“这是一个人”,检测要在画面里把那个人的框画出来,顺便标出类别。YOLOv3做的就是后一件事:它把目标检测建模成一个卷积神经网络的回归问题,一张图里有多少行人、多少自行车、多少机动车,一次性全部输出。这篇文章就是给卡在课程作业的人写的,从YOLOv3的输出逻辑讲到数据集准备、训练参数和常见坑。不需要复现论文,也不需要顶配显卡,一台能跑CUDA的普通机器足够。

2. 理解YOLOv3的工作原理:在跑数据之前先看懂输出逻辑

2.1 一张图如何变成一堆框:YOLOv3的输入输出拆解

很多新手一上来就复制训练命令,结果改了参数也不知道为什么。我的建议是先花半小时把输入输出关系搞清楚。YOLOv3会把任意尺寸的输入图resize到固定大小,默认是416×416。经过Darknet-53骨干网络提取特征后,输出三个尺度的特征图。浅层特征图负责小目标,比如远处行人;深层特征图负责大目标,比如近处的卡车。三层各有自己的网格划分,比如13×13、26×26、52×52。

在每一个网格里,YOLOv3预设了3个不同形状的锚框,每个锚框需要回归5个量:4个坐标和1个物体置信度,再加上类别概率。你的任务是三类目标,因此每个网格最终输出 3×(4+1+3)=24 个值。这个24就是后面改cfg时filters参数的由来。理解这一点很重要,因为很多人后面把classes改成3,却忘了把filters从255改成24,训练直接报错。

从数学上讲,YOLOv3把定位和分类放在同一个回归任务里。网格负责回答“物体在哪一片区域”,锚框负责回答“物体的形状接近哪种预设”,类别概率负责回答“这是人、自行车还是机动车”。课程作业里不需要你手写这些前向计算,但需要你能解释清楚这个流程。否则答辩时老师问一句“你的输出张量长什么样”,你只能站在那里干瞪眼。

2.2 锚框、IOU与NMS:训练和预测时真正起作用的三个机制

锚框是YOLOv3最核心的设计之一。YOLOv3在COCO数据集上聚类出9组预设尺寸,按照由小到大分配到三个尺度层。模型不是直接预测目标的绝对宽高,而是预测相对锚框的偏移量。这样做的好处是降低学习难度:小型数据集只要在预设形状附近微调即可,而不是从零学所有可能的物体形状。这也是YOLOv3能在少量数据上收敛的重要原因。

IOU(交并比)用于训练时的正负样本匹配。每一个标注框会和同网格里的锚框计算重叠程度,重叠高的锚框被当作正样本,负责预测这个目标;重叠很低的锚框则作为负样本,强迫网络学会“这里没有物体”。课程作业里不需要手动干预这个匹配过程,但标注框画得是否贴合,会直接影响IOU计算,进而影响哪些锚框被选中。标注框比物体大一圈,看起来无所谓,实际会让模型学到一堆错误的偏移量。

NMS(非极大值抑制)是预测时的后处理。模型在三个尺度上会生成大量候选框,同一个物体可能被多个网格同时检测到。NMS把这些候选框按置信度排序,保留最高分框,抑制掉和它重叠度高的其他框。如果训练结束后检测结果里一个物体上叠着五六个框,多半是NMS阈值设得不合适,这个在后文排查章节会展开。

2.3 课程作业要不要上YOLOv3:和Faster R-CNN、YOLOv8对比后的选择

选型这件事,课程作业和工业项目逻辑完全不同。工业项目追求精度和部署效率,课程作业追求的是“在一个学期内跑通、能解释、出问题搜得到答案”。Faster R-CNN在精度上确实不输,但两阶段结构对显存和训练时间都不友好,而且实现版本杂乱,新手很容易在环境配置上耗掉一半时间。YOLOv8系列虽然更好用,但它带来的配置项比YOLOv3多一个数量级,很多参数在课程作业里根本用不到,反而增加理解负担。

YOLOv3有一个其他版本替代不了的优势:Darknet官方实现只有一套流程,网上能搜到的踩坑记录几乎覆盖了所有会出错的地方。你遇到“loss不降”或者“mAP为0”,大概率已经有人把原因写清楚了。课程作业要求的是把目标检测的整体链路讲明白,YOLOv3的结构恰好是“骨干网络加深、多尺度预测、锚框机制”三件套,展示出来的知识量足够撑起一份像样的实验报告。所以我的结论是:这个标题下,YOLOv3就是当前性价比最高的选择,不需要纠结。

3. 准备行人/自行车/机动车数据集:从标注到划分的完整流程

3.1 标注工具选型与最少操作:LabelImg、VOC/YOLO两种格式的取舍

数据集是这个课程作业的大头。找现成的COCO或者UA-DETRAC虽然省事,但类别定义可能和你的作业要求对不上,而且交作业时老师常会问“数据是你自己标还是下载的”。自己标一批图更稳妥,也更能讲清楚每一类的边界。常用工具是LabelImg,安装很直接:

pip install labelimg labelimg

启动后先设置图片目录和保存目录。我一般把工程结构建在项目根目录下,不要散落到别处:

data/ images/ img_001.jpg img_002.jpg labels/ img_001.txt img_002.txt

LabelImg默认保存成Pascal VOC格式的XML文件,但Darknet训练需要的是YOLO格式的txt。界面上方有一个按钮可以切换成YOLO模式,保存后会直接生成同名txt。我建议课程作业直接用YOLO模式,省掉一次VOC转YOLO的脚本。因为转换脚本一旦写错,类别编号错位或者坐标归一化搞反,你后面所有训练结果都是错的,排查起来比多花十分钟标注痛苦得多。

YOLO格式的每一行是:类别编号 x_center y_center width height,后四个值都是归一化比例,范围在0到1之间。例如一行2 0.5 0.4 0.3 0.25,表示类别2(机动车),中心点在图片的(50%, 40%)处,宽高分别占整张图的30%和25%。这个格式只要记住一次就够了,后面看训练日志和检测结果都用得上。

注意:类别名称不要用中文写在标注文件里。Darknet读取的是obj.names按行号对应的类别名,txt第一列只写数字,不要写“行人”两个字,否则训练会得到一堆无法解析的样本。

3.2 三类目标的标注规范:行人、自行车与机动车的边界

标注规范比很多人想象得更重要。同一个物体在不同人手里标出来可能差很多,比如骑自行车的人,到底算行人还是自行车?我的建议是:如果人坐在自行车上、自行车占画面主体,整框标注为bicycle,不再额外单独标一个人;如果人推着自行车走,人和车可以分两个框,也可以只标人,取决于你的场景定义。课程作业一般拍的是校园道路,骑车人占多数,统一按“整辆自行车含骑手”标成bicycle最稳定。

行人框要从头部顶到脚底,左右贴近身体边缘,不要从人头顶上方留一大片天空。机动车包含轿车、SUV、卡车和公交车,按车体外轮廓标注,后视镜这种凸出部件可以忽略。最容易翻车的是把电动车或摩托车当作自行车,或者把两辆并排停靠的车标成一个框。前一种会让bicycle类别内部形状分裂,后一种会让模型学到一个框同时包含两个目标,NMS阶段很难处理。

数据量方面,课程作业我一般建议每类至少150个实例,三类合计500个左右。如果数据集里全是单目标图,模型泛化会很差。尽量拍一些一个画面同时出现两类甚至三类的照片,让模型学到“人和自行车可以共处一个画面”的上下文,而不是只学单目标模板。标注完成后随机抽20张图,用LabelImg的“下一张”快速翻一遍,检查有没有漏标和框错位,这一步花十分钟能省掉后面好几轮无效训练。

3.3 一键划分训练集与验证集:带类别统计的Python脚本

标注完成后,Darknet需要两个文本文件:train.txt和valid.txt,每行一张训练图片的绝对路径。同时我们要确认三类目标的样本分布是不是均衡。用下面这个脚本可以一次完成划分和统计:

import os import random from collections import Counter random.seed(42) img_dir, label_dir = 'data/images', 'data/labels' imgs = [f for f in os.listdir(img_dir) if f.endswith(('.jpg', '.jpeg', '.png'))] random.shuffle(imgs) train_ratio = 0.9 split = int(len(imgs) * train_ratio) train_imgs, valid_imgs = imgs[:split], imgs[split:] with open('data/train.txt', 'w') as ft, open('data/valid.txt', 'w') as fv: for name in train_imgs: ft.write(os.path.join(os.path.abspath(img_dir), name) + '\n') for name in valid_imgs: fv.write(os.path.join(os.path.abspath(img_dir), name) + '\n') cnt = Counter() for name in train_imgs: label_path = os.path.join(label_dir, os.path.splitext(name)[0] + '.txt') if not os.path.exists(label_path): continue with open(label_path) as f: for line in f: cnt[int(line.split()[0])] += 1 print('train images:', len(train_imgs), 'valid images:', len(valid_imgs)) for cls, num in sorted(cnt.items()): print('class', cls, 'instances:', num)

这个脚本的关键点是os.path.abspath,它把相对路径转成绝对路径写进train.txt。Darknet在读取训练列表时如果遇到相对路径,会以它自己的工作目录为基准拼接,很容易出现找不到图片的错误。随机种子固定成42,保证每次划分结果一致,这样你训练两个版本对比时,可以排除“验证集内容不同”这个干扰因素。

参数方面,train_ratio=0.9适合几百张的小数据集,留10%大概几十张做验证足够。类别编号按obj.names里的顺序从0开始,0对应person,1对应bicycle,2对应car。如果统计出来某类只有其他类的一半,最好是回去补拍,不要试图靠后续数据增强硬撑。

3.4 小数据集增强配置:Darknet在cfg里自带的数据增强参数

在准备数据阶段,最后一件要做的事是给训练配置数据增强。Darknet自带了一套基于cfg的数据增强,不需要额外写Python代码去离线生成图片。在yolov3.cfg文件的顶部,模型定义里就写着:

flip=1 jitter=0.3 saturation=1.5 exposure=1.5 hue=0.1

flip=1表示允许训练时水平翻转,能让数据量近似翻倍。对行人和自行车这类左右对称的目标来说非常安全。jitter=0.3是输入图片的随机缩放和长宽比扰动,让模型对透视变形不那么敏感,不过数值过大会导致车看起来比例失真。saturation和exposure控制饱和度和曝光随机变化,目的是让模型适应不同光照条件。

课程作业里我一般不会去加大这些参数,保持官方默认值就够了。如果训练集里傍晚场景特别少,与其把exposure调大,不如直接补拍几十张傍晚的图进数据集。数据增强只能增加颜色和几何变化,补偿不了场景分布上的缺失。这一点在后面遇到“换场景就漏检”时还会再提到。

4. 把YOLOv3训练跑起来:从Darknet编译到自定义参数调整

4.1 为什么用Darknet而不是PyTorch复现:课程作业最稳的编译流程

YOLOv3最原始的官方实现就是Darknet框架。PyTorch复现版很多,但版本差异会造成训练命令、权重格式、配置文件写法都不完全一致。课程作业最怕的就是教程对不上代码,所以我一般建议直接用Darknet,因为它只有一套标准流程,遇到问题搜到的答案基本都能复用。

编译的第一步是拉取代码和修改Makefile:

git clone https://github.com/pjreddie/darknet.git cd darknet vim Makefile

打开Makefile后,把这几行的值从0改成1:

GPU=1 CUDNN=1 OPENCV=1

然后保存退出,执行:

make

编译成功的标志是darknet目录下出现一个可执行文件darknet。如果机器没有NVIDIA显卡,或者CUDA没有装好,可以把GPU保持为0,用CPU模式编译。CPU模式也能完成训练,但速度会慢上好几倍,一个小数据集在GPU上跑一晚上的训练量,CPU可能要跑两三天。所以只要机器有显卡,尽量把GPU打开。

参数说明:OPENCV=1不是必须的,但建议打开。它在检测时会自动保存输出图,方便你在测试阶段直接看效果。如果编译时报错找不到opencv的头文件,先把OPENCV设回0,训练不受影响,只是检测后不会自动生成图片,需要手动用代码保存结果。

4.2 四个训练配置文件:obj.names、obj.data、train.txt与cfg改法

从这一步开始,所有操作都围绕四个文件展开。第一个是data/obj.names,内容就是类别名,一行一个:

person bicycle car

第二个是data/obj.data,它告诉Darknet训练数据从哪来、验证数据从哪来、类别数是多少:

classes = 3 train = data/train.txt valid = data/valid.txt names = data/obj.names backup = backup/

第三个是上一步生成的train.txt,里面是每张训练图片的绝对路径。第四个是cfg文件。Darknet官方自带的是cfg/yolov3.cfg,我们复制一份改成自己的:

cp cfg/yolov3.cfg cfg/yolo-obj.cfg mkdir backup

然后开始修改yolo-obj.cfg。这一步是整篇作业最容易出错的地方。需要改的参数集中在文件顶部和三个[yolo]层里。顶部要改的分辨率和批大小:

batch=64 subdivisions=16 width=416 height=416

三个[yolo]层分布在文件不同位置,搜索[yolo]能找到,把每个yolo层里的classes从80改成3:

classes = 3

同时,每个[yolo]层前面紧挨着一个卷积层,它的filters要改成24,公式是3 × (classes + 5),即3 × (3 + 5)。很多人在这一步只改了classes,忘了改filters,启动训练后直接报尺寸不匹配的错误。

最后是训练轮数相关参数。YOLOv3的官方建议是每类训练2000轮,三类就是6000轮。在cfg文件搜索max_batches:

max_batches=6000 policy=steps steps=4800,5400

steps表示学习率在4800轮和5400轮时分别下降一次,比例是max_batches的80%和90%。这样做的好处是前期大学习率让模型快速逼近,后期小学习率精调边框位置。如果显卡不行或者数据集很小,可以把max_batches降成4500,steps对应改成3600,4050,但不要低于3000,否则模型还没收敛就结束了。

4.3 开始训练与续训:命令、日志解读和三个最关键的观察指标

训练前先下载YOLOv3在ImageNet上预训练好的骨干权重:

wget https://pjreddie.com/media/files/darknet53.conv.74

使用预训练权重能让模型不再从零学习特征,这是课程作业数据集小却仍能收敛的关键。训练命令如下:

./darknet detector train data/obj.data cfg/yolo-obj.cfg darknet53.conv.74 -map -dont_show

-map表示每训练若干轮后在验证集上计算mAP,日志里会周期打印出来。-dont_show表示不弹训练窗,避免远程连接时界面卡死。如果一切正常,屏幕上会不断刷新一行行训练日志,重点关注三个值:avg loss、iou avg和map。

avg loss从2到3开始是正常的,随着训练推进慢慢往下降,最终可能到0.5甚至更低。如果loss不是缓慢下降,而是卡在2以上震荡,多半是配置有问题,去排查filters和预训练权重。iou avg反映预测框和标注框的重合度,稳定在0.7以上算正常,如果一直很低,回去检查标注框是否太随意。map是最直观的指标,数字越大越好,但小数据集下波动很正常,不要看到某一次validate掉了10个点就着急。

如果训练中途断掉,续训一定不要重新用darknet53.conv.74,而是用backup目录里保存的权重:

./darknet detector train data/obj.data cfg/yolo-obj.cfg backup/yolo-obj_last.weights -map -dont_show

backup目录里会有定期保存的多个weights文件,last文件是最近一轮的完整状态,用它继续训练即可。很多新手在这里翻车,中断后重新用预训练权重跑,损失回到初始值,白白浪费之前所有训练时间。

提示:如果训练报CUDA out of memory,把cfg顶部的batch改成32,subdivisions改成64,width和height改成416。显存不足大多数情况下不是显卡太差,而是batch和分辨率叠一起顶爆了显存。

5. 踩坑记录:YOLOv3训练与检测中排查最多的问题

5.1 训练阶段的三种典型故障:loss不降、显存溢出、续训退化

训练阶段遇到的第一个典型故障是loss卡在2左右不动,mAP始终保持0。现象是训练日志每一行的avg loss都在2.3到2.6之间来回震荡,跑了几百轮也不往下走,验证时mAP要么不显示要么一直是0。原因一般出在两个地方:一是改cfg时只改了classes没改对应的filters,二是训练时直接从随机初始化开始,没有加载darknet53.conv.74。YOLOv3在小型数据集上从头训练几乎不可能收敛,只有预训练骨干能提供足够好的底层特征。解决方法是先搜索cfg里所有[yolo]层,确认每个yolo层前面的filters都是24,然后重新用预训练权重开始训练,不要相信“已经跑了100轮再补救”这种侥幸心理。

第二个常见故障是训练启动后很快报CUDA out of memory。现象是make成功,命令一执行,前几秒还有日志输出,然后整个进程被显存溢出中断。原因通常是batch=64、subdivisions=16、width=608这组参数叠加后的显存峰值超过了显卡容量。解决方法是把width和height从608调回416,再改batch=32、subdivisions=64。注意subdivisions的作用是把一个大batch拆成多个小份依次forward,调大它能降低单次占用的显存峰值,但也意味着训练实际速度变慢,所以不要为了省显存无限调大。

第三个故障是中断后续训,loss反而比中断前更高,像是从头开始训练。现象是你明明加载了backup里的权重,损失却从2.5到3重新起步。原因不是模型坏了,而是“加载错文件”。Darknet在训练命令里接受权重路径参数,如果路径写成了darknet53.conv.74这个预训练文件,自然会把之前学到的知识全部丢掉。解决方法是使用后文这一条命令,务必确认路径指向backup/yolo-obj_last.weights,而不是预训练文件。养成这个习惯后,中途断训就只剩“等它继续跑”这一件事了。

5.2 测试阶段的三类翻车:重复框、小目标漏检、换场景失效

训练完成后进入测试阶段,第一类翻车是同一辆车被标出四五个框。现象是置信度都在0.3以下,NMS似乎没有生效。原因一般是测试时没有显式设置置信度阈值,模型会把大量低置信度候选框全部保留下来;另外标注框如果互相重叠严重,多个锚框会同时学习到同一个目标,NMS压不掉这些重叠框。解决方法是给检测命令加-thresh 0.25或-thresh 0.3,把低于这个置信度的框过滤掉。如果仍然出现大量重复,回到标注阶段检查有没有把一张图上紧挨着的两个目标画成了一个框。

第二类翻车是最典型的:机动车检测正常,行人几乎漏检,自行车也偶尔丢。现象是远距离行人完全检测不到,近距离行人也只有一半概率被召回。原因是行人属于小目标,在416×416的训练分辨率下,远行人可能只有十来个像素,特征经过多次下采样后已经消失。解决思路有两步:第一步把cfg里的width和height改成608,给浅层特征图更多像素信息;第二步是补拍一些行人离镜头近的照片,让模型看到足够大的行人特征。注意这两步都会让训练变慢,但行人检测本来就是三类目标里最吃分辨率的。

第三类翻车是训练集里全是白天的校园场景,换到傍晚或者阴天测试后漏检率飙升。现象是同一组权重,白天照片表现不错,导入几张傍晚照片后框变少很多。这其实不算模型坏了,而是训练集分布覆盖不够。数据增强里的saturation和exposure虽然能模拟部分光照变化,但无法弥补场景背景、色温、阴影分布上的差异。解决方法是把实际测试会遇到的天气和时间段各拍几十张,补进数据集重新微调。课程作业里最经济的做法是先用现有模型预测这些新照片,选出漏检部分,再标注这些漏检图补训,而不是全部重新标一遍。

6. 验证模型与提升精度的技巧:mAP、阈值与简单批量检测

6.1 用mAP验证模型:数字比肉眼更可信

训练结束时backup目录里会有一批weights文件,其中yolo-obj_final.weights是最后一轮的模型。用它跑一遍验证集就能得到量化指标:

./darknet detector map data/obj.data cfg/yolo-obj.cfg backup/yolo-obj_final.weights

命令结束后会打印mAP@0.50、mAP@0.75等数值。课程作业看mAP@0.50就够,这个值反映“框的大致位置对不对”;mAP@0.75如果明显偏低,说明预测框和标注框的贴合度不够,问题多半在标注阶段框画得不够紧,而不是模型需要继续训。

6.2 一个对自己友好的批量检测习惯

单张图检测可以用下面的命令,-ext_output会把目标的坐标和置信度逐行列出来:

mkdir -p predictions for img in test_images/*.jpg; do name=$(basename "$img") ./darknet detector test data/obj.data cfg/yolo-obj.cfg backup/yolo-obj_final.weights "$img" -thresh 0.25 -ext_output 2>/dev/null | tee predictions/"$name".txt done

这个循环会把每张测试图的检测日志存成一份txt,方便你统计每一类检测到了多少个目标,也方便交作业时附一份机器可读的检测结果。我自己做类似作业时,会先把旧权重单独备份到一个固定目录,每次调整数据集或参数后都保留一个版本,再用上面这个批量命令过一遍固定测试图。效果好的版本留下,翻车的版本也能回头排查是哪一次改动导致的。

这套流程走下来,课程作业就不再是“跑通一个模型”那么简单了:你能说清楚mAP是多少、哪一类拖了后腿、标注规范里哪个约定影响最大。这些细节在答辩时比代码本身更有说服力。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询