Yolo v5的工程代码实现:detect.py
Yolo简史
Yolo系列可谓是无数CVer的入门之作,也是广大研究生,小厂商的救命稻草。我原先也精读过Yolo v1的论文,不过今天先逐步解析一下Yolo v5的工程代码。
Yolo v1-v3都是由原作者Joseph Redmon开发,2020年,他宣称拒绝该技术被美国军方使用,停止了个人开发。后续的开发由各个企业/开发者维系。
Yolo v4由Yolo Darknet的维护者Alexy Bochkoviskiy发布;Yolo v5/v8由西班牙公司Ulterlytics开发;Yolo v6由美团发布,Yolo v7则继续由Alexy Bochkoviskiy发布。事实上,Alexy Bochkoviskiy目前被广泛认为是Yolo的官方开发者。
代码架构
Yolo v5的官方仓库中主要由以下架构组成:
1 |
|
今天分析的是其中的detect.py
,这是yolo v5的推理代码,总共只有200多行代码,所以并不复杂。
依赖包
首先是导入依赖包:
1 |
|
这里值得学习的是路径的导入:通过当前文件的路径找到项目母路径,添加到系统变量,并转化为相对路径。这时,ROOT就变成了项目根目录的相对路径,引用的时候需要从根目录考虑位置。
接下来是导入自定义的库:
1 |
|
这些自定义库的内容如下:
- models.common.py: 这个文件定义了模型的层结构,以及一些通用的函数和类,比如图像的处理、非极大值抑制等等。
- utils.dataloaders.py: 这个文件定义了dataloader和dataset。其中定义了一些常用的类:LoadStream,LoadImages,LoadScreenshots,LoadImagesAndLabels,这些是用来导入数据的类。
- utils.general.py: 这个文件定义了一些常用的工具函数,比如判断语句、检查文件是否存在、检查图像大小是否符合要求、打印命令行参数等等。
- utils.plots.py: 这个文件定义了Annotator类,可以在图像上绘制矩形框和标注信息。
- utils.torch_utils.py: 这个文件定义了一些与PyTorch有关的工具函数,比如选择设备、同步时间等等。
配置参数
1 |
|
初始设置
接下来进入代码块,第一个部分是推理的基础设置,代码如下:
1 |
|
图片和视频的格式分别如下:
1 |
|
根据source的类型,会确定输入数据的类型:
- 如果source的后缀是图像或视频格式之一,那么将is_file设置为True;
- 如果source以rtsp等开头,那么将is_url设置为True;
- 如果source是数字或以.txt结尾或是一个URL,那么将webcam设置为True;
- 如果source既是文件又是URL,那么会调用check_file函数下载文件。
check_file(source)
的函数代码如下,如果输入不是url地址,可以忽略:
1 |
|
check_suffix(file, suffix)
代码如下,这个函数的主要作用是判断文件后缀是否符合指定的后缀列表。如果符合就不会返回任何信息,否则会报错:
1 |
|
接下来是创建保存输出结果文件夹的代码:
1 |
|
这里用到的increment_path
的函数如下,其实就是创建组合一下创建文件夹:
1 |
|
加载模型
接下来是加载模型:
1 |
|
上面这段代码主要是使用DetectMultiBackend类来加载模型,从模型中提取了三个参数是:
- stride:推理时所用到的步长,默认为32, 大步长适合于大目标,小步长适合于小目标
- names:保存推理结果名的列表,比如默认模型的值是[‘person’, ‘bicycle’, ‘car’, …]
- pt: 加载的是否是pytorch模型(也就是pt格式的文件)
最后确保输入图片的尺寸imgsz能整除stride=32 如果不能则调整为能被整除并返回。
看一下DetectMultiBackend
的源码:
1 |
|
加载数据配置文件的方法是:
1 |
|
对应的COCO128.yaml文件长这样:
1 |
|
check_img_size
的源码如下:
1 |
|
加载预测数据
1 |
|
这段代码根据输入的 source 参数来判断是否是通过 webcam 摄像头捕捉视频流:
- 如果是,则使用 LoadStreams 加载视频流
- 如果是截屏,则使用LoadScreenshots加载截屏
- 否则,使用 LoadImages 加载图像
bs 表示 batch_size(批量大小),这里是 1 或视频流中的帧数。vid_path 和 vid_writer 分别是视频路径和视频编写器,初始化为长度为 batch_size 的空列表。
vid_path 和 vid_writer 分别是视频路径和视频编写器,初始化为长度为 batch_size 的空列表。
其中用到的LoadImages
函数是这样的:
1 |
|
其中使用了一个填充函数letterbox
,例如im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0]
:
1 |
|
返回到主体函数即为 640 * 480 * 3
。
推理代码
推理代码作为核心部分,会通过for循环对加载的数据进行遍历,一帧一帧地推理,进行NMS非极大值抑制、绘制bounding box、预测类别。
1 |
|
Profile()
是ultralytics定义的一个类,是一个简单的上下文管理器(Context Manager)装饰器(Decorator),用于在代码中测量运行时间。内容如下:
1 |
|
核心代码中,包含一个预热函数warmup
:
1 |
|
接下来是保存为csv文件的代码,只有当前面选择save_csv
时才会执行。
1 |
|
继续处理推理信息:
1 |
|
上面这段代码,实际上是在迭代pred
这个对象。pred
是什么呢?就是模型输出的一个batch里的预测数据,当bs为1,或者只有一张图片时,这个pred就只是由一个多维张量组成的列表。
1 |
|
绘制预测图
前面得到了预测的结果det
,也就是一个多张量的列表,其中每个子列表为一个预测框的信息(x1,y1,x2,y2,confidence,class),接下来就需要遍历这个列表里面的每一个预测框,在一张图片上绘图。
这里用到了reversed
这个方法,用来将一个张量里的所有元素都进行倒序排列。我理解这样可以进行后续的操作for *xyxy, conf, cls in reversed(det)
,可以直接拿到最后两位作为置信度和分类标签。
1 |
|
打印信息
最后输出打印的信息:
1 |
|
参数导入
这里没啥好说的,单纯导入参数:
1 |
|
整体看下来,代码逻辑是比较清晰易懂的。记得我第一次看这个代码的时候,被里面很多python的用法都吓到了,现在回过头看看,并不是很复杂,难点主要在怎么对张量进行变换。
2024/1/21 于苏州家中