当前位置: 代码网 > it编程>编程语言>其他编程 > YOLOv5+Deepsort训练自己的数据集实现多目标跟踪

YOLOv5+Deepsort训练自己的数据集实现多目标跟踪

2024年07月28日 其他编程 我要评论
本文利用yolov5-6.1版本完成目标检测模块,利用deepsort跟踪算法实现目标跟踪模块,将二者集成,在自己的数据集上形成一套行之有效的目标检测+跟踪模型。yolov5-6.1版本代码下载地址:yolov5 deepsort- CSDN搜索 (github.com)deepsort代码下载地址:root下的models和utils是使用的yolov5的v6.1版本的代码,如使用其它版本可以用相应版本的models和utils代码替换。

引言

本文利用yolov5-6.1版本完成目标检测模块,利用deepsort跟踪算法实现目标跟踪模块,将二者集成,在自己的数据集上形成一套行之有效的目标检测+跟踪模型。

yolov5-6.1版本代码下载地址:yolov5 deepsort- csdn搜索 (github.com)https://github.com/ultralytics/yolov5deepsort代码下载地址:https://github.com/mikel-brostrom/yolov5_deepsort_pytorchhttps://github.com/mikel-brostrom/yolov5_deepsort_pytorch注意:root下的models和utils是使用的yolov5的v6.1版本的代码,如使用其它版本可以用相应版本的models和utils代码替换。

 1 代码整体框架

其中分为yolov5-6.1模块,deepsort模块。

2 目标检测模块

2.1 环境依赖

yolov5的代码是开源的,因此我们可以从github上克隆其源码。该项目利用yolov5-6.1版本来作为讲解。下载yolov5-6.1代码,其目录结构如下:

data:主要是存放一些超参数的配置文件,是用来配置训练集和测试集还有验证集的路径的。如果是训练自己的数据集的话,那么就需要修改其中的yaml文件。

models:里面主要是一些网络构建的配置文件和函数。如果训练自己的数据集的话,就需要修改这里面相对应的yaml文件来训练自己模型。

 utils:存放的是工具类的函数,里面有loss函数,metrics函数,plots函数等。

weights:放置训练好的权重参数。

detect.py:利用训练好的权重参数进行目标检测。

 train.py:训练自己的数据集的函数。

 test.py:测试训练的结果的函数。

requirements.txt:yolov5-6.1的环境依赖包。利用以下命令完成环境依赖安装。

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

2.2 数据集准备

       这里需要将voc(xml)格式的数据集转换成yolo所需的txt格式,需要对xml格式的标签文件转换为txt文件。将标注好的数据集按照以下形式存放。

 运行代码:

import xml.etree.elementtree as et
import pickle
import os
from os import listdir, getcwd
from os.path import join
import random
from shutil import copyfile
 
classes = ["hat", "person"]
#classes=["ball"]
 
train_ratio = 80
 
def clear_hidden_files(path):
    dir_list = os.listdir(path)
    for i in dir_list:
        abspath = os.path.join(os.path.abspath(path), i)
        if os.path.isfile(abspath):
            if i.startswith("._"):
                os.remove(abspath)
        else:
            clear_hidden_files(abspath)
 
def convert(size, box):
    dw = 1./size[0]
    dh = 1./size[1]
    x = (box[0] + box[1])/2.0
    y = (box[2] + box[3])/2.0
    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):
    in_file = open('vocdevkit/voc2007/annotations/%s.xml' %image_id)
    out_file = open('vocdevkit/voc2007/yololabels/%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')
    in_file.close()
    out_file.close()
 
wd = os.getcwd()
wd = os.getcwd()
data_base_dir = os.path.join(wd, "vocdevkit/")
if not os.path.isdir(data_base_dir):
    os.mkdir(data_base_dir)
work_sapce_dir = os.path.join(data_base_dir, "voc2007/")
if not os.path.isdir(work_sapce_dir):
    os.mkdir(work_sapce_dir)
annotation_dir = os.path.join(work_sapce_dir, "annotations/")
if not os.path.isdir(annotation_dir):
        os.mkdir(annotation_dir)
clear_hidden_files(annotation_dir)
image_dir = os.path.join(work_sapce_dir, "jpegimages/")
if not os.path.isdir(image_dir):
        os.mkdir(image_dir)
clear_hidden_files(image_dir)
yolo_labels_dir = os.path.join(work_sapce_dir, "yololabels/")
if not os.path.isdir(yolo_labels_dir):
        os.mkdir(yolo_labels_dir)
clear_hidden_files(yolo_labels_dir)
yolov5_images_dir = os.path.join(data_base_dir, "images/")
if not os.path.isdir(yolov5_images_dir):
        os.mkdir(yolov5_images_dir)
clear_hidden_files(yolov5_images_dir)
yolov5_labels_dir = os.path.join(data_base_dir, "labels/")
if not os.path.isdir(yolov5_labels_dir):
        os.mkdir(yolov5_labels_dir)
clear_hidden_files(yolov5_labels_dir)
yolov5_images_train_dir = os.path.join(yolov5_images_dir, "train/")
if not os.path.isdir(yolov5_images_train_dir):
        os.mkdir(yolov5_images_train_dir)
clear_hidden_files(yolov5_images_train_dir)
yolov5_images_test_dir = os.path.join(yolov5_images_dir, "val/")
if not os.path.isdir(yolov5_images_test_dir):
        os.mkdir(yolov5_images_test_dir)
clear_hidden_files(yolov5_images_test_dir)
yolov5_labels_train_dir = os.path.join(yolov5_labels_dir, "train/")
if not os.path.isdir(yolov5_labels_train_dir):
        os.mkdir(yolov5_labels_train_dir)
clear_hidden_files(yolov5_labels_train_dir)
yolov5_labels_test_dir = os.path.join(yolov5_labels_dir, "val/")
if not os.path.isdir(yolov5_labels_test_dir):
        os.mkdir(yolov5_labels_test_dir)
clear_hidden_files(yolov5_labels_test_dir)
 
train_file = open(os.path.join(wd, "yolov5_train.txt"), 'w')
test_file = open(os.path.join(wd, "yolov5_val.txt"), 'w')
train_file.close()
test_file.close()
train_file = open(os.path.join(wd, "yolov5_train.txt"), 'a')
test_file = open(os.path.join(wd, "yolov5_val.txt"), 'a')
list_imgs = os.listdir(image_dir) # list image files
prob = random.randint(1, 100)
print("probability: %d" % prob)
for i in range(0,len(list_imgs)):
    path = os.path.join(image_dir,list_imgs[i])
    if os.path.isfile(path):
        image_path = image_dir + list_imgs[i]
        voc_path = list_imgs[i]
        (namewithoutextention, extention) = os.path.splitext(os.path.basename(image_path))
        (voc_namewithoutextention, voc_extention) = os.path.splitext(os.path.basename(voc_path))
        annotation_name = namewithoutextention + '.xml'
        annotation_path = os.path.join(annotation_dir, annotation_name)
        label_name = namewithoutextention + '.txt'
        label_path = os.path.join(yolo_labels_dir, label_name)
    prob = random.randint(1, 100)
    print("probability: %d" % prob)
    if(prob < train_ratio): # train dataset
        if os.path.exists(annotation_path):
            train_file.write(image_path + '\n')
            convert_annotation(namewithoutextention) # convert label
            copyfile(image_path, yolov5_images_train_dir + voc_path)
            copyfile(label_path, yolov5_labels_train_dir + label_name)
    else: # test dataset
        if os.path.exists(annotation_path):
            test_file.write(image_path + '\n')
            convert_annotation(namewithoutextention) # convert label
            copyfile(image_path, yolov5_images_test_dir + voc_path)
            copyfile(label_path, yolov5_labels_test_dir + label_name)
train_file.close()
test_file.close()

       在vocdevkit目录下生成images和labels文件夹,文件夹下分别生成了train文件夹和val文件夹,里面分别保存着训练集的照片和txt格式的标签,还有验证集的照片和txt格式的标签。images文件夹和labels文件夹就是训练yolov5模型所需的训练集和验证集。

2.3 预训练权重

 为了缩短训练时间,可以从下面网址中获取预训练权重。ultralytics/yolov5: yolov5 🚀 in pytorch > onnx > coreml > tflite (github.com)https://github.com/ultralytics/yolov5/releases

2.4 参数修改

修改data目录下的相应的yaml文件 。也可以创建新的mob.yaml文件。train和val改为自己数据集路径。nc改为自己要训练的类别数量。names改为自己的类别名称。

修改models下的yaml文件。我这里使用的是yolov5n.pt预训练权重,将nc改为自己类别数量。

2.5 模型训练

在根目录下找到train.py文件,根据自己文件路径修改--weights --cfg --data。

def parse_opt(known=false):
    parser = argparse.argumentparser()
    parser.add_argument('--weights', type=str, default=root / 'weights/yolov5n.pt', help='initial weights path')
    parser.add_argument('--cfg', type=str, default='models/yolov5n.yaml', help='model.yaml path')
    parser.add_argument('--data', type=str, default=root / 'data/mob.yaml', help='dataset.yaml path')
    parser.add_argument('--hyp', type=str, default=root / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
    parser.add_argument('--epochs', type=int, default=200)
    parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all gpus, -1 for autobatch')
    parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
    parser.add_argument('--rect', action='store_true', help='rectangular training')
    parser.add_argument('--resume', nargs='?', const=true, default=false, help='resume most recent training')
    parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
    parser.add_argument('--noval', action='store_true', help='only validate final epoch')
    parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor')
    parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
    parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
    parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"')
    parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
    parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
    parser.add_argument('--optimizer', type=str, choices=['sgd', 'adam', 'adamw'], default='sgd', help='optimizer')
    parser.add_argument('--sync-bn', action='store_true', help='use syncbatchnorm, only available in ddp mode')
    parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per rank in ddp mode)')
    parser.add_argument('--project', default=root / 'runs/train', help='save to project/name')
    parser.add_argument('--name', default='exp', help='save to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    parser.add_argument('--quad', action='store_true', help='quad dataloader')
    parser.add_argument('--cos-lr', action='store_true', help='cosine lr scheduler')
    parser.add_argument('--label-smoothing', type=float, default=0.0, help='label smoothing epsilon')
    parser.add_argument('--patience', type=int, default=20, help='earlystopping patience (epochs without improvement)')
    parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='freeze layers: backbone=10, first3=0 1 2')
    parser.add_argument('--save-period', type=int, default=-1, help='save checkpoint every x epochs (disabled if < 1)')
    parser.add_argument('--local_rank', type=int, default=-1, help='ddp parameter, do not modify')

    # weights & biases arguments
    parser.add_argument('--entity', default=none, help='w&b: entity')
    parser.add_argument('--upload_dataset', nargs='?', const=true, default=false, help='w&b: upload data, "val" option')
    parser.add_argument('--bbox_interval', type=int, default=-1, help='w&b: set bounding-box image logging interval')
    parser.add_argument('--artifact_alias', type=str, default='latest', help='w&b: version of dataset artifact to use')

    opt = parser.parse_known_args()[0] if known else parser.parse_args()
    return opt

运行train.py。将数据加载到内存并且冻结前8层网络以加快训练速度。

python train.py --cache --freeze=8

启用tensorbord查看。

tensorbord --logdir ./runs

3 跟踪模块

3.1 跟踪数据集准备

deep_sort\deep_sort\deep\checkpoint下的权重ckpt.t7是deepsort在行人reid数据 集训练出来,用于提取行人的外观特征 market 1501数据集 market-1501 数据集是在清华大学校园中采集于2015年构建并公开。

数据集目录结构

market-1501-v15.09.15

bounding_box_test

bounding_box_train

gt_bbox

gt_query

query

这里我们制作自己的数据集进行跟踪权重的训练。将图像中的检测目标扣出,作为跟踪数据集。

import cv2
import xml.etree.elementtree as et
import numpy as np
 
import xml.dom.minidom
import os
import argparse
 
 
def main():
    # jpg文件的地址
    img_path = 'xxxxxxxx'
    # xml文件的地址
    anno_path = 'xxxxxxxxx'
    # 存结果的文件夹
    cut_path = 'xxxxxxxxxxx'

    if not os.path.exists(cut_path):
        os.makedirs(cut_path)
    # 获取文件夹中的文件
    imagelist = os.listdir(img_path)
    # print(imagelist
    for image in imagelist:
        image_pre, ext = os.path.splitext(image)
        img_file = img_path + image
        img = cv2.imread(img_file)
        xml_file = anno_path + image_pre + '.xml'
        # domtree = xml.dom.minidom.parse(xml_file)
        # collection = domtree.documentelement
        # objects = collection.getelementsbytagname("object")
 
        tree = et.parse(xml_file)
        root = tree.getroot()
        # if root.find('object') == none:
        #     return
        obj_i = 0
        for obj in root.iter('object'):
            obj_i += 1
            print(obj_i)
            cls = obj.find('name').text
            xmlbox = obj.find('bndbox')
            b = [int(float(xmlbox.find('xmin').text)), int(float(xmlbox.find('ymin').text)),
                 int(float(xmlbox.find('xmax').text)),
                 int(float(xmlbox.find('ymax').text))]
            img_cut = img[b[1]:b[3], b[0]:b[2], :]
            path = os.path.join(cut_path, cls)
            # 目录是否存在,不存在则创建
            mkdirlambda = lambda x: os.makedirs(x) if not os.path.exists(x) else true
            mkdirlambda(path)
            try:
                cv2.imwrite(os.path.join(cut_path, cls, '{}_{:0>2d}.jpg'.format(image_pre, obj_i)), img_cut)
            except:
                continue
 
            print("&&&&")
 
 
if __name__ == '__main__':
    main()

运行上述代码,得到扣出的目标数据集。

将上述数据集进行分类,这里检测目标只要person,但是类别有31个。分好后train和test文件夹下分别有31个文件夹,代表31个类别。将train和test移动到deep_sort/deep目录下。

修改train.py中dataset的预处理如下。

transform_train = torchvision.transforms.compose([
    torchvision.transforms.resize((128, 64)),
    torchvision.transforms.randomcrop((128, 64), padding=4),
    torchvision.transforms.randomhorizontalflip(),
    torchvision.transforms.totensor(),
    torchvision.transforms.normalize(
        [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

 在model.py中修改类别为31。

class net(nn.module):
    def __init__(self, num_classes= 31 ,reid=false):
        super(net,self).__init__()
        # 3 128 64
        self.conv = nn.sequential(
            nn.conv2d(3,64,3,stride=1,padding=1),
            nn.batchnorm2d(64),
            nn.relu(inplace=true),
            # nn.conv2d(32,32,3,stride=1,padding=1),
            # nn.batchnorm2d(32),
            # nn.relu(inplace=true),
            nn.maxpool2d(3,2,padding=1),
        )

运行train.py训练。

python train.py

并将训练好的ckpt.t7跟踪权重文件放到checkpoint目录下,将训练好的yolo权重mob_yolov5_6.1.pt放到weights目录下。

修改objdetector.py

obj_list = ['person']
detector_path = 'weights/mob_yolov5_6.1.pt'

修改deep_sort/configs/deep_sort.yaml文件

deepsort:
  reid_ckpt: "deep_sort/deep_sort/deep/checkpoint/ckpt.t7"
  max_dist: 0.2
  min_confidence: 0.3
  nms_max_overlap: 0.5
  max_iou_distance: 0.7
  max_age: 70
  n_init: 3
  nn_budget: 100

4 跟踪效果

运行demo.py查看跟踪效果。

 至此,大致流程训练完毕!本项目实现了稳定跟踪,但对reid效果暂未考虑,后续将继续完善!欢迎交流!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com