前言
在博客阅读前需要说明,本博文为系列文章,通过阅读文章,您将会学习到如下内容:
- 使用NVIDIA Transfer Learning Toolkit 工具训练(finetuning)出一个行人检测算法模型;
- 将训练好的算法模型部署到NVIDIA Jetson TX2上,结合deepstream sdk实现模型对多路(16路)rtsp视频流进行实时的行人检测;
思路的简介(目录)
在我们做一件事之前,需要思考;做事的思路,做事的方式,这样我们才能顺利、高效的完成这件事;
本篇博客:
- Docker-ce的安装
- Nvidia-TLT工具的安装
- 训练数据的准备
- 模型训练配置文件的调整
- 算法模型的训练
- 算法模型的评估
下一篇博客: - 算法模型的purn
- purn之后模型的再训练
- 模型导出到tx2
- 使用deepstream sdk进行多路视屏流的行人检测
接下来我们开始上具体的实验流程和代码;
(一)Docker-ce的安装(参考地址)
由于我们使用的TLT工具是一个镜像环境,因此我们需要预先安装好docker。docker的安装很多博客有介绍了,这里本文就直接照搬aliyun上提供的安装步骤来进行安装:
Ubuntu 14.04/16.04(使用 apt-get 进行安装)
# step 1: 安装必要的一些系统工具
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安装GPG证书
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新并安装Docker-CE
sudo apt-get -y update
sudo apt-get -y install docker-ce
# 安装指定版本的Docker-CE:
# Step 1: 查找Docker-CE的版本:
# apt-cache madison docker-ce
# docker-ce | 17.03.1~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# docker-ce | 17.03.0~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# Step 2: 安装指定版本的Docker-CE: (VERSION例如上面的17.03.1~ce-0~ubuntu-xenial)
# sudo apt-get -y install docker-ce=[VERSION]
待完成上述步骤后,可打开终端检查检查一下docker的安装情况,在终端中输入指令 docker
即可(部分信息截图如下):
这一块没有过多的描述,安装好就结束了;
(二)NVIDIA-TLT工具镜像的下载
[ps: 特别提示,安装此工具前您需要将你电脑基本的环境搭建好,cuda+cudnn+nvidia显卡驱动]
镜像的下载需要去NVIDIA NGC官网,NGC官方地址:https://ngc.nvidia.com/catalog/collections
具体步骤如下:
- 如果您之前有注册过NGC官网的账号,请按照提示登陆上您的账号;若是没有注册,第一次使用,那么需要按照注册步骤注册一个账号,并登陆;
- 待登陆上您的账号后,我们在collections页面搜索NVIDIA TLT工具(搜索结果如下图);
- 出现该工具的镜像后,我们点击进去,进去页面后会有关于该镜像的详细信息(页面如下图所示),以及如何download这个镜像的详细步骤(本文将相关步骤描述至图片下方);
- 安装步骤
1.打开终端输入如下指令,根据你的网络情况,耐心等待安装即可;
2.下载成功后,启动镜像docker pull nvcr.io/nvidia/tlt-streamanalytics:v2.0_py3
docker run --gpus all -itd -p 8888:8888 nvcr.io/nvidia/tlt-streamanalytics:v2.0_py3 /bin/bash
- 若镜像启动成功,即可成功进入镜像内,如下图所以(下图为后面再次进入镜像时的截图,初始化安装后的启动未截图保存,拿此图作为展示);
到此步骤,我们所需软件环境的安装流程已经搞定,接下来即可开始算法模型的训练了;
(三)训练数据的准备
在本文实验过程中,采用的是NVIDIA的detectnet_v2算法,backbond为resnet18。因此需要按照该算法框架使用的数据格式来进行数据集的准备;
需要注意的是,算法模型的训练需要采用kitti数据集格式的标签文件,因此,我们需要将用于训练的数据标签格式转换至kitti标签格式;并且,模型训练过程不支持输入任意尺度的照片,因此在训练之前照片尺寸以及标签也需要做调整;
开始之前先简单介绍一下用于训练的数据集。该数据集来源于网络(baipiao),数据格式为VOC格式,一张图对应一个xml文件,如下图所示,由于本文为了快速实验和介绍流程,只从数据集中选择了1000张左右的图片用于本文的实验流程;
ok,开始我们的数据预处理流程,数据预处理的流程如下
- 获取照片的路径,生成照片路径的txt文件:
get_path.py
import os
src_dir = './images'
pic_name = os.listdir(src_dir)
with open('train.txt', 'a') as f:
for pic in pic_name:
full_path = os.path.join(src_dir, pic)
f.writelines(full_path + '\n')
f.close()
生成的train.txt
文件内容示例如下所示(可以用相对路径,也可以使用绝对路径):
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s597.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5402.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5648.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5722.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5365.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5695.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5840.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5384.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5136.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5651.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5882.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5653.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5227.jpg
/home/lxz/data-256/data/tlt-expriments/datasets/test_data/images/s5780.jpg
- 将数据集转换至yolo标签格式:
transfer_voc_label.py
(为什么:万一你想用yolo训练呢,哈哈。当然,如果您不需要yolo格式,可自行编写脚本处理xml文件直接转到kitti标签格式)
# coding: utf-8
# author: HXY
"""
标签格式的装换;
"""
import os
from os.path import join
import xml.etree.ElementTree as ET
def convert(size, box):
dw = 1./(size[0])
dh = 1./(size[1])
x = (box[0] + box[1])/2.0 - 1
y = (box[2] + box[3])/2.0 - 1
w = box[1] - box[0]
h = box[3] - box[2]
x = x*dw
w = w*dw
y = y*dh
h = h*dh
return (x,y,w,h)
def convert_annotation(image_id):
classes = ["person"]
in_file = open('./xml/%s.xml'%(image_id))
out_file = open('./labels/%s.txt'%(image_id), 'w')
tree=ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls = obj.find('name').text
if cls not in classes or int(difficult)==1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
bb = convert((w,h), b)
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
def transfer():
images_txt = "train.txt"
labels_save_path = './labels'
if not os.path.exists(labels_save_path):
os.mkdir(labels_save_path)
with open(images_txt, "r") as f:
for file in f.read().splitlines():
id = file.split('/')[-1].split('.')[0]
convert_annotation(image_id=id)
f.close()
print("标签格式转换成功....")
if __name__ == '__main__':
transfer()
- 将yolo格式的数据标签转换至kitti数据集格式的标签:
yolo_txt_to_kitti.py
# coding: utf-8
# author: hxy
"""
代码用于将yolo的txt标签格式转换到kitti的txt标签格式;
"""
import os
import cv2
import time
# 将txt中坐标还原到原始照片的坐标
def restore_coordinate(yolo_bbox, image_w, image_h):
box_w = float(yolo_bbox[3]) * image_w
box_h = float(yolo_bbox[4]) * image_h
x_mid = float(yolo_bbox[1]) * image_w + 1
y_mid = float(yolo_bbox[2]) * image_h + 1
xmin = int(x_mid - box_w / 2)
xmax = int(x_mid + box_w / 2)
ymin = int(y_mid - box_h / 2) + 3
ymax = int(y_mid + box_h / 2) + 3
return [xmin, ymin, xmax, ymax]
# 获取照片的labels文件和images文件
def restore_results(images_folder, labels_folder, kitti_labels):
labels = os.listdir(labels_folder)
for label in labels:
name = label.split('.')[0]
with open(os.path.join(labels_folder, label), 'r') as f:
infos = f.readlines()
with open(os.path.join(kitti_labels, name + '.txt'), 'w') as f1:
for boxes in infos:
info = boxes.strip('\n')
label = list(info.split(' '))
img = cv2.imread(os.path.join(images_folder, name + '.jpg'))
w = img.shape[1]
h = img.shape[0]
box = restore_coordinate(label, w, h)
# # 将转换的坐标值绘制到原始图片上,验证看看转换的坐标值是否ok;
# cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (0, 255, 255), 2)
# cv2.imshow('Transfer_label', img)
# if cv2.waitKey(100) & 0XFF == ord('q'):
# break
new_info = class_name + ' ' + '0.00' + ' ' + '0' + ' ' + '0.00' + ' ' \
+ str(box[0]) + ' ' + str(box[1]) + ' ' + str(box[2]) + ' ' + str(box[3]) \
+ ' ' + '0.00' + ' ' + '0.00' + ' ' + '0.00' + ' ' + '0.00' + ' ' + '0.00' \
+ ' ' + '0.00' + ' ' + '0.00'
f1.write(new_info + '\n')
f1.close()
f.close()
if __name__ == '__main__':
s = time.time()
print('----数据转换开始---')
class_name = 'person'
restore_results('./images',
'./labels',
'./kitti_labels')
print('---耗时:{:.2f}s'.format(time.time() - s))
print('---数据转换成功---')
这个脚本里面的文件路径解释一下:./images
代表的是原始照片的存储文件夹;./labels
代表的是yolo格式的标签存储文件夹;./kitti_labels
代表的是新转换成功的kitti格式的标签存储文件夹;代码中有部分注释的代码是用于检查标签转换结果可视化的代码;
转换成功后的标签文件如下图所示(上面为kitti格式,下面为yolo格式):
- 将图片进行resize操作:
resize_imgs.py
(由于采用的是resnet18作为backbond,并且根据官方文档的描述,说输入图片的尺寸w、h需要为16的倍数,本文用于训练的照片尺寸为600*800的,因此需要调整)
# coding: utf-8
# author: lxz-hxy
"""
用于将照片进行resize以及将对应的标签文件进行相应的变换
"""
import os
import cv2
def resizeImg(w, h, imgs_floder, save_folder):
imgs = os.listdir(imgs_floder)
for img in imgs:
img_full_path = os.path.join(imgs_floder, img)
ori_img = cv2.imread(img_full_path)
img_r = cv2.resize(ori_img, (w, h))
cv2.imwrite(os.path.join(save_folder, img), img_r)
adjust_labels(img,
w,
h,
'./kitti_labels',
'./resize_kitti_labels',
img_r)
return 0
def adjust_labels(imgName, w, h, ori_labels_folder, new_labels_folder, img):
x_scale_ratio = float(w / 600)
y_scale_ration = float(h / 800)
with open(os.path.join(ori_labels_folder, imgName.split('.')[0] + '.txt'), 'r') as f:
infos = f.readlines()
with open(os.path.join(new_labels_folder, imgName.split('.')[0] + '.txt'), 'w') as w:
for box in infos:
info = box.strip('\n')
label_info = info.split(' ')
xmin = int(label_info[4]) * x_scale_ratio
ymin = int(label_info[5]) * y_scale_ration
xmax = int(label_info[6]) * x_scale_ratio
ymax = int(label_info[7]) * y_scale_ration
# 将装换后的坐标绘制到resize之后的图片上,检测box是否正确;
# cv2.rectangle(img, (int(xmin), int(ymin)), (int(xmax), int(ymax)), (0, 255, 255), 2)
# cv2.imshow('Transfer_label', img)
# if cv2.waitKey(100) & 0xFF == ord('q'):
# break
new_info = label_info[0] + ' ' + '0.00' + ' ' + '0' + ' ' + '0.00' + ' ' \
+ str('{:.2f}'.format(xmin)) + ' ' + str('{:.2f}'.format(ymin)) + ' ' \
+ str('{:.2f}'.format(xmax)) + ' ' + str('{:.2f}'.format(ymax)) \
+ ' ' + '0.00' + ' ' + '0.00' + ' ' + '0.00' + ' ' + '0.00' + ' ' + '0.00' \
+ ' ' + '0.00' + ' ' + '0.00'
w.write(new_info + '\n')
w.close()
f.close()
return 0
if __name__ == '__main__':
print('----进行照片处理以及标签转换----')
# w, h需要%16 == 0
resizeImg(544, 800, './images',
'./resize_images')
print('---处理完成---')
同样,解释一下该脚本中的路径:./images
代表的是原始图片的存储文件夹;./resize_image
代表的是经过处理后的照片存储文件夹;./kitti_labels
代表的是上一步骤生成的kitti格式的标签文件存储文件夹;./resize_kitti_labels
代表的是经过处理后新生成的kitti格式的标签存储文件夹
生成新的图片比较如下所示(左图为原始照片,右图为resize照片):
生成新的kitti标签如下所示(左边为原图kitti标签,右图为resize之后的kitti标签):
到这里,训练数据集的预处理已经完美结束,接下来则可以开始算法模型的训练了;
这里还需要解释一下,也许你看到这里觉得数据处理的代码有些闲得麻烦了,是的,我也是这么想的,但是,这是一篇博客,为了让更多的人看懂这个过程,本人就将数据处理的每一步都写了一个脚本。实际使用中,本人就使用了一个脚本,等你熟悉整个过程后,也可以自行的修改脚本,不需要这么多步骤分开来。
(四)模型训练配置文件的调整
到这一步就可以开始进行算法模型训练的步骤了;
1. 首先我们先启动容器;
docker run --gpus all -itd -p 8888:8888 nvcr.io/nvidia/tlt-streamanalytics:v2.0_py3 /bin/bash
2. 将训练数据从本机拷贝到容器内;
# 从主机到容器内
docker cp ./datasets kind_kirch:workspace/examples/detectnet_v2/
需要注意:这里的datasets目录下的文件为数据处理最后一步得到的resize数据集,包括图片和标签。如下图:
3. 数据复制好后,进入容器内;
docker exec -it kind_kirch /bin/bash
4. 下载预训练模型;
# 在容器的终端内输入下面的指令,则可以下载预训练模型,在这里,我们使用resnet18作为backbond
ngc registry model download-version "nvidia/tlt_pretrained_detectnet_v2:resnet18"
下载成功后如图所示,下载速度根据网速而定:
5. 修改训练参数配置文件;
模型下载好后我们就可以开始修改配置文件了,在这里我们主要修改两个文件,均在/workspace/example/detectnet_v2/specs
目录下:
两个文件分别为:detectnet_v2_tfrecords_kitti_trainval.txt
、detectnet_v2_train_resnet18_kitti.txt
detectnet_v2_tfrecords_kitti_trainval.txt
主要是将数据转换至tfrecord形式,修改如下:
kitti_config {
root_directory_path: "/workspace/examples/detectnet_v2/datasets"
image_dir_name: "images"
label_dir_name: "labels"
image_extension: ".jpg"
partition_mode: "random"
num_partitions: 2
val_split: 14
num_shards: 10
}
image_directory_path: "/workspace/tlt-experiments/data/training"
其中需要注意各部分的路径,需要对应到你数据集的存放路径,修改好后,即可进行数据集的转换,在终端中输入如下指令:
# -d 参数后面接修改好后的detectnet_v2_tfrecords_person_trainval.txt文件,-o 参数后面接生成tfrecord数据存储的文件夹
tlt-dataset-convert -d specs/detectnet_v2_tfrecords_person_trainval.txt -o /workspace/examples/detectnet_v2/tfrecord/
执行完指令后,即可得到如下信息,则表明训练数据生成成功:
数据生成后我们则可开始进行训练了
detectnet_v2_train_resnet18_kitti.txt
为网络训练具体的配置文件,修改后如下所示:
random_seed: 42
dataset_config {
data_sources {
tfrecords_path: "/workspace/examples/detectnet_v2/tfrecord/*"
image_directory_path: "/workspace/examples/detectnet_v2/datasets/"
}
image_extension: "jpg"
target_class_mapping {
key: "person"
value: "person"
}
validation_fold: 0
}
augmentation_config {
preprocessing {
output_image_width: 544
output_image_height: 800
min_bbox_width: 1.0
min_bbox_height: 1.0
output_image_channel: 3
}
spatial_augmentation {
hflip_probability: 0.5
zoom_min: 1.0
zoom_max: 1.0
translate_max_x: 8.0
translate_max_y: 8.0
}
color_augmentation {
hue_rotation_max: 25.0
saturation_shift_max: 0.20000000298
contrast_scale_max: 0.10000000149
contrast_center: 0.5
}
}
postprocessing_config {
target_class_config {
key: "person"
value {
clustering_config {
coverage_threshold: 0.00749999983236
dbscan_eps: 0.230000004172
dbscan_min_samples: 0.0500000007451
minimum_bounding_box_height: 20
}
}
}
}
model_config {
pretrained_model_file: "/workspace/examples/detectnet_v2/tlt_pretrained_detectnet_v2_vresnet18/resnet18.hdf5"
num_layers: 18
use_batch_norm: true
objective_set {
bbox {
scale: 35.0
offset: 0.5
}
cov {
}
}
training_precision {
backend_floatx: FLOAT32
}
arch: "resnet"
}
evaluation_config {
validation_period_during_training: 10
first_validation_epoch: 30
minimum_detection_ground_truth_overlap {
key: "person"
value: 0.5
}
evaluation_box_config {
key: "person"
value {
minimum_height: 20
maximum_height: 9999
minimum_width: 10
maximum_width: 9999
}
}
average_precision_mode: INTEGRATE
}
cost_function_config {
target_classes {
name: "person"
class_weight: 4.0
coverage_foreground_weight: 0.0500000007451
objectives {
name: "cov"
initial_weight: 1.0
weight_target: 1.0
}
objectives {
name: "bbox"
initial_weight: 10.0
weight_target: 10.0
}
}
enable_autoweighting: true
max_objective_weight: 0.999899983406
min_objective_weight: 9.99999974738e-05
}
training_config {
batch_size_per_gpu: 4
num_epochs: 30
learning_rate {
soft_start_annealing_schedule {
min_learning_rate: 5e-06
max_learning_rate: 5e-04
soft_start: 0.10000000149
annealing: 0.699999988079
}
}
regularizer {
type: L1
weight: 3.00000002618e-09
}
optimizer {
adam {
epsilon: 9.99999993923e-09
beta1: 0.899999976158
beta2: 0.999000012875
}
}
cost_scaling {
initial_exponent: 20.0
increment: 0.005
decrement: 1.0
}
checkpoint_interval: 10
}
bbox_rasterizer_config {
target_class_config {
key: "person"
value {
cov_center_x: 0.5
cov_center_y: 1.5
cov_radius_x: 1.0
cov_radius_y: 1.0
bbox_min_radius: 1.0
}
}
deadzone_radius: 0.400000154972
同样,修改配置文件时需要注意配置文件中相关路径的调整【需要注意,这里配置文件的修改,未涉及到超参数的优化,该部分工作大伙们可自行研究,或者留言交流,共同学习,进步】;
tfrecords_path: "/workspace/examples/detectnet_v2/tfrecord/*" # tfrecord格式数据集的保存路径
image_directory_path: "/workspace/examples/detectnet_v2/datasets/" # 训练数据集的保存路径
# 预训练模型的路径
pretrained_model_file: "/workspace/examples/detectnet_v2/tlt_pretrained_detectnet_v2_vresnet18/resnet18.hdf5"
7. 开始训练;
待一切准备就绪,我们就可以开始进行算法模型的训练了,在训练之前简单描述几句,这是一篇博客,为了和大家共同交流这个工具的使用流程和经验,关于优化模型训练过程在博客中未做介绍,只是做了简单(wunao)的修改配置文件来实现整个流程,请大家勿喷,谢谢!
开始训练,在终端内输入如下指令:
tlt-train detectnet_v2 -e specs/detectnet_v2_train_resnet18_person.txt -r /workspace/examples/detectnet_v2/backup/train_model/ -k [你自己的key] -n person --gpus 2
在这里本人使用两快GPU进行训练,并且只训练了30个epoch;
指令中各个参数介绍:
tlt-train detectnet_v2
-e <path_to_spec_file>
-r <path_to_experiment_output>
-k [你自己的key]
-n <自定义那model name>
--gpus <GPU数量>
执行上面的训练指令后,则开始正常训练了,如下图:
经过几分钟的训练,30个epoch结束,如下图:
可以看到我们的训练结束了,最后的精度为0.68;【数据少,训练epoch不够】