开始阅读代码

代码地址: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
2
3
4
5
6
7
classes = ('aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair',
'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor')
# 变为:
{'aeroplane': 0, 'bicycle': 1, 'bird': 2, 'boat': 3, 'bottle': 4, 'bus': 5, 'car': 6, 'cat': 7, 'chair': 8, 'cow': 9, 'diningtable': 10, 'dog': 11, 'horse': 12, 'motorbike': 13, 'person': 14, 'pottedplant': 15, 'sheep': 16, 'sofa': 17, 'train': 18, 'tvmonitor': 19}

只需要下面一行代码:

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
2
3
4
5
weights = 'yolov5s.pt'
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = attempt_load(weights, map_location=device)
model.to(device).eval()
model.half() # 这里

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。

inplaceTrue:启用 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
2
3
4
if hasattr (mode,'module'):
names = model.module.names
else:
names = model.names

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.Tensorint 模型的最大下采样步幅,通常为 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 如果模型被 DataParallelDistributedDataParallel 包装,包含实际的模型对象。 <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的代码实在看不下去,这几天心情不是很好,出去骑行,导致膝盖有点疼,安心学习吧。