NVIDIA-TLT训练行人检测模型(一)----算法模型的训练(finetuning)

前言
在博客阅读前需要说明,本博文为系列文章,通过阅读文章,您将会学习到如下内容:
  • 使用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.打开终端输入如下指令,根据你的网络情况,耐心等待安装即可;
    docker pull nvcr.io/nvidia/tlt-streamanalytics:v2.0_py3
    
    2.下载成功后,启动镜像
    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.txtdetectnet_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不够】

结尾
本篇博客所做的事到这里已经结束了,希望能对大家有所帮助,互相学习,共同进步!!
本篇博客虽然只训练了一个类别,当然按照这个流程,您也可以训练多类别的,博客主要给您介绍一下这个工具的使用流程,中间的细节部分可能没有讲解清楚,可以留言交流!!
本人的文笔有限 ,要是没有描述清楚的问题,多多谅解,可以留言交流!! 欢迎点赞支持!!
下一篇博客会尽快写好,谢谢!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值