极本穷源:YOLO篇Ⅲ

本文最后更新于:2022年3月7日 下午

极本穷源:YOLO篇Ⅲ

YOLOV4-tiny源码解读

有了基本概念的理解,接着深入源码一步一步学习。在开始之前先做下回顾,小目标&&配置文件参数解读

小目标识别检测中”小目标”尺寸占原始图片的比例:

ubuntu下安装个labelImg,查看下标注目标时的尺寸。labelImg图片标注工具安装 从 PyPI 获取,但只有 python3.0 或更高版本,这是现代Linux发行版(如Ubuntu和Fedora)上最简单的(单命令)安装方法。

1
2
3
pip3 install labelImg
labelImg
labelImg [IMAGE_PATH] [PRE-DEFINED CLASS FILE]

经不完全统计。本次数据集目标尺寸大致如下:

大部分尺寸 最大 最小 肉眼看不见
目标尺寸 170x151 1200x2356 47x82 167x95
图片尺寸 5472x3648 5472x3648 5472x3648 5472x3648

yolov4-tiny.cfg配置文件解读

训练开始之前根据自己的显卡性能调整btach以及subdivisions的大小,普遍以batch = 64,subdivisions = 16设置。显卡差点的subdivisions设置成64。

整个训练样本会被分成若干个batch,网络中每个batch积累64个样本后会进行一次正向传播(在训练的过程中将一次性加载64张图片进内存)。subdivisions表示每个batch会分16次完成前向传播,前向传播的循环过程中累加loss求平均,待64张图片都完成前向传播后,再一次性后传更新参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[net]
# Testing
# 图片测试
#batch=1
#subdivisions=1
# Training
# 数据训练
batch=64 #
subdivisions=16 #

width=416 #输入图像的宽
height=416
channels=3 #图像通道数,RGB三色图片
momentum=0.9 #动量系数
decay=0.0005 #权重衰减正则项,防止过拟合

#图像增强,通过旋转图片,改变饱和度等操作增加训练样本数
angle=0 #角度
saturation = 1.5 #饱和度
exposure = 1.5 #曝光量
hue=.1 #色调

#学习率的调整
learning_rate=0.00261 #学习率
burn_in=1000 #应该是在1000轮后产生计算map

max_batches = 3000 #训练论次
policy=steps #调整学习率的policy,有如下policy:CONSTANT, STEP, EXP, POLY, STEPS, SIG, RANDOM

steps=1600000,1800000 #根据batch_num调整学习率
scales=.1,.1 #学习率变化比例,累计相乘

convolutional

在训练数据前要修改每个yolo下的classes类别数,以及每个yolo上的第一个filters卷积核个数(等于输出的特征图的维度):filters=(classes + 5)x3。

q:公式中的5是怎么来的?

这5个数分别包括:pc,bx, by, bh, bw。其中pc 为标志位,代表检测框中是否包含对象,框中包含任一目标对象 时pc = 1,反之只有背景没有目标时pc = 0。后面四个为bounding box的边界参数,分别是对象的中心位置x和y 坐标,对象的高和宽。参考博客理解,后面乘以3就很容易理解,rgb三个通道的意思吧,红绿蓝三个通道各卷 一次。

每个卷积层之后包含一个批量归一化处理(BN)和一个激活函数(Leaky),YOLOv4的主干网络CSPDarknet53中,使用Mish代替了原来的Leaky ReLU。常见的激活函数:Sigmoid、tanh、Leaky ,ReLU、Mish。

batch、上采样(下采样)、卷积核和激活函数的概念,卷积核(filters)和滤波器(kernels)的区别。详细参考极本穷源2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#卷积层

[convolutional]

batch_normalize=1 #是否做归一化处理,传统的归一化公式 (number - mean) / std, mean表示均值, std表示标准差

filters=18 #卷积核个数(输出的特征图的维度)只修改每个yolo上的第一个conv层

size=3 #卷积核尺寸3×3

stride=2 #卷积运算时的步长

pad=1 #如果pad为0,padding由 padding参数指定。如果pad为1,padding大小为size/2

activation=leaky #激活函数

route

route应该就是全连接层,起到连接的作用,将不同卷积层输出的特征进行连接。本质上它是一个融合层,它的作用是在当前层引出之前卷积所得到的特征层。全连接层的作用就是将网络学到分布式特征映射到样本标记空间。

参考YOLO中的route层

layers = -6,-1表示将向前数的第6层与第1层相连接,完成特征的传递。

layer = -2 ,表示引出前两层的conv输出的特征图。

1
2
3
[route]

layers = -6,-1

maxpool (池化层)

1
2
3
4
5
[maxpool]

size=2

stride=2

网络模型的构建

当然,只有cfg配置文件是远远不够的。配置文件本质上调用了一大堆定义好的函数,改变传入函数的参数实现不同的功能及效果。这些函数主要包括 /src下的

我们从最常用的 detector命令切入,简单分析下调用规则。从以下命令不难发现,这些命令是运行了当前文件夹下的darknet.c文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#训练
./darknet detector train

#map计算
./darknet detector map

#图片检测
./darknet detector test

#聚类先验框
./darknet detector calc_anchors

#视频检测
./darknet detector demo

打darknet.c看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.先看main函数
#先是一堆#ifndef防止多重定义,接下来就是一大堆if else if

else if (0 == strcmp(argv[1], "detector")){
run_detector(argc, argv);//492行

当./darknet后面的命令为 detector 时,调用函数run_dector()

2.右键这个函数转到定义:
void run_detector(int argc, char **argv)//1966行
和main.c一样先定义一大堆,然后就是if else if

直接找到关键词 test train map...
if (0 == strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, dont_show, ext_output, save_labels, outfile, letter_box, benchmark_layers);//2038
else if (0 == strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear, dont_show, calc_map, thresh, iou_thresh, mjpeg_port, show_imgs, benchmark_layers, chart_path);
else if (0 == strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile);
else if (0 == strcmp(argv[2], "recall")) validate_detector_recall(datacfg, cfg, weights);
else if (0 == strcmp(argv[2], "map")) validate_detector_map(datacfg, cfg, weights, thresh, iou_thresh, map_points, letter_box, NULL);
else if (0 == strcmp(argv[2], "calc_anchors")) calc_anchors(datacfg, num_of_clusters, width, height, show);

果然都能找到定义了好的detector函数:(参考已有资料理解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//detector.c
3.右键test_detector找到函数定义:

void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh,float hier_thresh, int dont_show, int ext_output, int save_labels, char *outfile, int letter_box, int benchmark_layers)//1626行

void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear, int dont_show, int calc_map, float thresh, float iou_thresh, int mjpeg_port, int show_imgs, int benchmark_layers, char* chart_path)//26行

float validate_detector_map(char *datacfg, char *cfgfile, char *weightfile, float thresh_calc_avg_iou, const float iou_thresh, const int map_points, int letter_box, network *existing_net)//940行

void calc_anchors(char *datacfg, int num_of_clusters, int width, int height, int show)//1443行

//视频检测比较特殊,单独定义在 /src/demo.c

void validate_detector(char *datacfg, char *cfgfile, char *weightfile, char *outfile)//643行

以 train_detector()为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
//detector.c

//读取cfg文件
list *options = read_data_cfg(datacfg); //28行

//根据train.txt找到训练的图片
char *train_images = option_find_str(options, "train", "data/train.txt");

//用训练集合作为验证图片
char *valid_images = option_find_str(options, "valid", train_images);

//训练好的权重文件存储在/backup/
char *backup_directory = option_find_str(options, "backup", "/backup/");

大概就是从这里调的cfg配置文件吧,其他命令也是类似的。参考yolov3网络模型构建