一些代码问题
开始阅读代码
代码地址:https://github.com/longcw/yolo2-pytorch
论文地址:https://arxiv.org/abs/1612.08242
关于YOLOv2代码的阅读,因为一些开源的代码实现年代久远,某些工具版本不匹配,难以调试,增加了学习难度。So,我计划重点学习一下YOLOv3以及YOLOv5的代码。
1.关于路径问题
1 | ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) |
这里使用os
模块是为了跨平台兼容性,os.path
模块确保路径在 Windows 和 Unix 系统上都能正确处理,可以学习一下。
__file__
:
- 这是一个 Python 的内置变量,表示当前脚本的文件路径。例如,如果你在运行一个名为 script.py 的文件,
__file__
的值可能是/home/user/project/script.py
(具体路径取决于文件位置)。
os.path.dirname(__file__)
:
os.path.dirname()
函数会返回给定路径的目录部分。
结合__file__
,它提取的是当前脚本所在的目录。例如,如果 file__ 是 /home/user/project/script.py
,那么 os.path.dirname(__file__ )
的结果就是 /home/user/project
。
os.path.join
:就是拼接路径,将上面的os.path.dirname()
得到的路径与..
拼接在一起就代表的是os.path.dirname()
的上一级路径,即/home/user
os.path.abspath()
: 该函数会将相对路径转换为绝对路径,并规范化路径(比如处理掉多余的 … 或 .)也就是说os.path.join
得到的结果是:/home/user/project/..
,然后使用os.path.abspath()
函数将..
解析为上一级路径得到最终的路径。
2.牛逼的操作,学习一下
一行代码可将种类变为种类与ID对应的格式:
1 | classes = ('aeroplane', 'bicycle', 'bird', 'boat', |
只需要下面一行代码:
1 | class_to_ind = dict(list(zip(classes, list(range(len(classes)))))) |
解释:
1) list(range(len(classes)))
该用法会获取class的种类数量和,并且生成0-[len(class)-1]的列表,或者tuple(range(len(classes)))
生成元组也是可以的,因为下面的zip()
函数可以将不同种类的可迭代对象的元素进行配对。
2)使用zip()
函数,将种类和其对应的ID拿到一起。zip()
函数是一个非常有用的内置函数,它的主要作用是将多个可迭代对象(如列表、元组、字符串等)的元素“配对”起来,生成一个由元组组成的迭代器。也就是说zip()
函数返回的是一个迭代器,包含配对后的元组。
3)使用list()
函数将其变为列表,最后使用dict()
函数将其变为最终的字典形式。
3.关于半精度问题
今天在学习YOLOv5+Flask搭建Web服务的时候,遇到了一个有意思的trick。
1 | weights = 'yolov5s.pt' |
attempt_load()
是YOLOv5实现的一个工具API,其作用是:
加载 YOLOv5 预训练模型(官方提供的 .pt
权重文件,如 yolov5s.pt
)。
加载自定义训练的 YOLOv5 权重(如 best.pt
)
自动下载官方模型(如果本地不存在)。
支持不同的计算设备(CPU、GPU、FP16、ONNX、TensorRT 等)。
返回一个 YOLOv5 的 DetectMultiBackend
对象,可以用于推理。
其参数:attempt_load(weights, device=None, inplace=True, fuse=True)
weights
: 支持多种格式:.pt
(PyTorch 格式),.onnx
(ONNX 格式),.tflite
(TensorFlow Lite 格式),.engine
(TensorRT 格式),.xml
(OpenVINO 格 式)
device
: 指定设备:cpu or gpu。
inplace
:True
:启用 inplace
计算,节省显存,提高推理效率。
False
:禁用 inplace
计算,可能稍微增加显存占用。
fuse
:
类型:bool
,默认 True
作用:是否对 BatchNorm 和 Conv 进行融合(仅适用于 PyTorch 格式)。
好处:可以提高推理速度。
下面这个小细节:
1 | model.half() # 这里 |
将模型加载进来以后转换为半精度。half()
是 PyTorch 中的一个方法,适用于 nn.Module(神经网络模型)对象。它将模型的所有参数(权重)和计算从默认的单精度浮点数(FP32,32位浮点数)转换为半精度浮点数(FP16)。FP16 相比 FP32 使用更少的位数表示浮点数,因此占用的内存更少,计算速度更快,但精度稍低。
所以,一般在推理的时候,会将模型转化为半精度,推理速度较快。另外,需要注意的是:
如果模型转为 FP16,那么输入数据(例如图像张量)也需要转为 FP16,否则会因为数据类型不匹配而报错:
1 | img = img.to(device).half() # 输入张量也转为 FP16 |
4.关于模型model的names属性
1 | names = model.module.names if hasattr(model, 'module') else model.names |
上面这个代码是在模型加载之后,从模型中获取各类别名称,其中使用了判断语句:
1 | if hasattr (mode,'module'): |
hasattr(object, name))
:是 Python 内置函数,用于检查一个对象是否具有指定的属性或方法。object
:要检查的对象。name
:属性或方法的名称(字符串)。返回值为:True
或者False
。
为什么要进行判断呢?
因为,如果模型在训练时使用了 DataParallel 或 DistributedDataParallel(多 GPU 并行),保存的权重文件中,模型会被包裹在一个 module 子对象中。所以需要从model.module.names
来获取种类。正常单GPU训练的时候得到的模型,直接使用model.names
即可。 不过,通常情况下,在 YOLOv5 的官方推理代码(例如 detect.py)中,attempt_load 通常返回解包后的模型,因此直接用 model.names 就足够。
那么关于模型model还有那些常见的属性呢?
model
对象的常见属性表格
属性名 | 类型 | 描述 | 示例值 |
---|---|---|---|
model.names |
list |
模型训练时的类别名称列表,用于将预测的类别索引转换为人类可读的标签。 | ['person', 'car', 'dog', ...] |
model.stride |
torch.Tensor 或 int |
模型的最大下采样步幅,通常为 32,用于确保输入图像尺寸是步幅的倍数。 | tensor([8, 16, 32]) 或 32 |
model.yaml |
dict |
模型的配置文件,包含网络结构信息,例如类别数(nc )、深度因子(depth_multiple )等。 |
{'nc': 80, 'depth_multiple': 1.0, ...} |
model.nc |
int |
类别数量(number of classes),表示模型支持的检测类别总数。 | 80 (COCO 数据集) |
model.device |
torch.device |
模型当前所在的设备(CPU 或 GPU)。 | torch.device('cuda:0') 或 'cpu' |
model.module |
nn.Module |
如果模型被 DataParallel 或 DistributedDataParallel 包装,包含实际的模型对象。 |
<yolo.Model object> (视情况存在) |
model.anchors |
torch.Tensor |
模型的锚框(anchors),用于目标检测的边界框预测。 | tensor([[10, 13], [16, 30], ...]) |
model.n_layers |
int |
模型的层数(视实现而定,可能不直接暴露)。 | 103 (YOLOv5s 的层数示例) |
model.backbone |
nn.Module |
模型的主干网络(backbone),负责特征提取,可能是一个子模块。 | <torch.nn.Sequential> |
model.head |
nn.Module |
模型的检测头(head),负责生成最终的检测预测,可能是一个子模块。 | <torch.nn.Module> |
model.training |
bool |
表示模型当前是否处于训练模式(True )或评估模式(False )。 |
False (推理时通常为 False) |
model.parameters() |
Generator | 返回模型的所有参数(权重),用于计算参数量或优化。 | <generator object> |
model.state_dict() |
dict |
模型的状态字典,包含所有权重和偏置的参数名和值。 | {'conv1.weight': tensor(...), ...} |
model.eval() |
方法 | 将模型切换到评估模式,禁用 dropout 和 batch normalization 的训练行为。 | 无返回值,直接修改模型状态 |
model.half() |
方法 | 将模型参数和计算转换为半精度(FP16),优化推理性能(需 GPU 支持)。 | 无返回值,直接修改模型 |
model.to(device) |
方法 | 将模型移动到指定设备(例如 'cuda' 或 'cpu' )。 |
无返回值,直接修改模型 |
4.关于图像格式
在 OpenCV (cv2
) 中,cv2.imread()
读取的图像默认是 BGR(蓝-绿-红)格式,也就是说,需要使用OpenCV来完成的任务都是需要BGR格式,例如:letterbox()
和 plot_one_box()
是 YOLOv5 的工具函数,它们通常与 OpenCV 配合,期望输入是 BGR 格式。
需要注意的是Matplotlib 处理的图像是RGB格式,需要进行通道的转换。
需要弄清楚一个概念,我们通常所说的图片的是[C,H,W]格式,还是[H,W,C]格式,其中的C就是三通道,也就是RGB,我们在使用OPENCV时,要注意这个C的顺序,要为BGR。但是我们在Pytorch中,要注意的是PyTorch 模型通常遵循深度学习领域的惯例,即输入是 [C, H, W] 格式,且通道顺序为 RGB。
1 | img = img[:, :, ::-1].transpose(2, 0, 1) |
上面这行代码中:
img = img[:, :, ::-1]
这是对图像的最后一个维度进行操作,因为OPENCV读取的图片是**[H,W,C] **格式,所以是对C进行操作,即将RGB->BGR
或者BGR->RGB
。
transpose(2, 0, 1)
是对图形的维度进行操作,将[H,W,C]->[C,H,W]
,变为Pytorch使用的格式。
5.一个小项目
Ummm.这两天,接触了Flask,并且结合YOLOv5做了一个小玩意,虽然都是老掉牙的东西,但至少自己学会了如何利用Flask。
项目地址:https://github.com/bighammer-link/YOLOv5-Flask
接下来,开始学习YOLOv3吧,v2的代码实在看不下去,这几天心情不是很好,出去骑行,导致膝盖有点疼,安心学习吧。