0


更新版yolov5_deepsort_pytorch实现目标检测和跟踪

由于mikel-brostrom在github上发布的Yolov5_DeepSort_Pytorch更新,使整个代码封装性更好,进而允许采用多种REID特征识别模型,完善了deepsort在检测跟踪方面的性能。本博文记录如何使用此版本Yolov5_DeepSort_Pytorch的过程,同时给出ZQPei REID模型的修改方法,以适应mikel-brostrom更新版本。

使用Yolov5_DeepSort_Pytorch默认的osnet REID实现跟踪track.py

Yolov5_DeepSort_Pytorch中包含了两个链接目录yolov5和reid,不能一次性把github中的代码克隆下来,因此,需分别将三个github代码克隆到本地。
Yolov5_DeepSort_Pytorch:

  1. git clone https://github.com/mikel-brostrom/Yolov5_DeepSort_Pytorch

Yolov5:

  1. git clone https://github.com/ultralytics/yolov5

REID:

  1. git clone https://github.com/KaiyangZhou/deep-person-reid

假定你的deepsort目录为your_dir,是第一个克隆下来的目录。第二个克隆目录是yolov5,将yolov5目录放在your_dir目录下,即your_dir/yolov5。第三个克隆目录是reid,放到your_dir/deep_sort/deep目录下,your_dir/deep_sort/deep/reid。
假定已经安装了conda和虚拟环境,且安装好运行Yolov5_DeepSort_Pytorch所需的模块。进入reid目录,运行

  1. python setup.py develop

如此,即安装好KaiyangZhou的REID环境。
下载yolov5模型权重,放入目录your_dir/yolov5/weights
从KaiyangZhou的github中,Model zoo里下载权重文件,例如osnet_x1_0.pth,放到checkpoint目录:your_dir/deep_sort/deep/checkpoint。

(1)修改deep_sort/configs/deep_sort.yaml

  1. DEEPSORT:
  2. MODEL_TYPE: "osnet_x1_0"
  3. REID_CKPT: '~/your_dir/deep_sort/deep/checkpoint/osnet_x1_0_imagenet.pth'
  4. MAX_DIST: 0.1# 0.2 The matching threshold. Samples with larger distance are considered an invalid match
  5. MAX_IOU_DISTANCE: 0.7# 0.7 Gating threshold. Associations with cost larger than this value are disregarded.
  6. MAX_AGE: 90# 30 Maximum number of missed misses before a track is deleted
  7. N_INIT: 3# 3 Number of frames that a track remains in initialization phase
  8. NN_BUDGET: 100# 100 Maximum size of the appearance descriptors gallery
  9. MIN_CONFIDENCE: 0.75
  10. NMS_MAX_OVERLAP: 1.0

这里,增加REID_CKPT,把某些参数设置放到yaml文件中,尽可能减少track.py命令行中的输入参数。

(2)修改track.py中DeepSort类实例的参数定义

  1. deepsort = DeepSort( cfg.DEEPSORT.MODEL_TYPE,
  2. cfg.DEEPSORT.REID_CKPT,# 添加checkpoint路径
  3. device,
  4. max_dist=cfg.DEEPSORT.MAX_DIST,
  5. max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE,
  6. max_age=cfg.DEEPSORT.MAX_AGE,
  7. n_init=cfg.DEEPSORT.N_INIT,
  8. nn_budget=cfg.DEEPSORT.NN_BUDGET,)

此处增加了一个reid权重文件路径参数,故也需在DeepSort类定义中增加该参数model_path,修改deep_sort/deep_sort.py,

  1. __init__()

:

  1. classDeepSort(object):def__init__(self, model_type, model_path, device, max_dist=0.2, min_confidence=0.3, nms_max_overlap=1.0, max_iou_distance=0.7, max_age=70, n_init=3, nn_budget=100, use_cuda=True):
  2. self.min_confidence = min_confidence
  3. self.nms_max_overlap = nms_max_overlap
  4. self.extractor = FeatureExtractor(
  5. model_name=model_type,
  6. model_path = model_path,
  7. device=str(device))
  8. max_cosine_distance = max_dist
  9. metric = NearestNeighborDistanceMetric("cosine", max_cosine_distance, nn_budget)
  10. self.tracker = Tracker(
  11. metric, max_iou_distance=max_iou_distance, max_age=max_age, n_init=n_init)

注:mikel好像又改了有关model_path的引入方法,我感觉太复杂,故还是用以上的修改办法,其目的就是从deep_sort/deep/checkpoint中找到权重文件路径,避免从网上下载权重文件,或者从本地缓存.torch中去找权重。

(3)运行deepsort跟踪程序,命令行选项中给出一种比较全的选项

  1. python track.py --yolo_model ~/your_dir/yolov5/weights/yolov5s.pt \ // yolov5权文件
  2. --source ~/your_dir/video_demo.mp4 \ // 输入视频文件
  3. --show-vid \ // 显示跟踪视频
  4. --classes 02\ // 0= 行人类别, 2=小汽车类别。
  5. --save-txt \ // 输出兼容MOT16格式文件
  6. --save-vid \ // 保存跟踪视频

其中,classes 0 表示yolov5检测对象为行人,类型号0。

更改ZQPei REID模型文件

此模型文件命名为model_ZQP.py,放入目录 deep_sort/deep/reid/torchreid/models
模型更改只需添加一个定义函数 def ZQP()

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. classBasicBlock(nn.Module):def__init__(self, c_in, c_out, is_downsample=False):super(BasicBlock, self).__init__()
  5. self.is_downsample = is_downsample
  6. if is_downsample:
  7. self.conv1 = nn.Conv2d(
  8. c_in, c_out,3, stride=2, padding=1, bias=False)else:
  9. self.conv1 = nn.Conv2d(
  10. c_in, c_out,3, stride=1, padding=1, bias=False)
  11. self.bn1 = nn.BatchNorm2d(c_out)
  12. self.relu = nn.ReLU(True)
  13. self.conv2 = nn.Conv2d(c_out, c_out,3, stride=1,
  14. padding=1, bias=False)
  15. self.bn2 = nn.BatchNorm2d(c_out)if is_downsample:
  16. self.downsample = nn.Sequential(
  17. nn.Conv2d(c_in, c_out,1, stride=2, bias=False),
  18. nn.BatchNorm2d(c_out))elif c_in != c_out:
  19. self.downsample = nn.Sequential(
  20. nn.Conv2d(c_in, c_out,1, stride=1, bias=False),
  21. nn.BatchNorm2d(c_out))
  22. self.is_downsample =Truedefforward(self, x):
  23. y = self.conv1(x)
  24. y = self.bn1(y)
  25. y = self.relu(y)
  26. y = self.conv2(y)
  27. y = self.bn2(y)if self.is_downsample:
  28. x = self.downsample(x)return F.relu(x.add(y),True)defmake_layers(c_in, c_out, repeat_times, is_downsample=False):
  29. blocks =[]for i inrange(repeat_times):if i ==0:
  30. blocks +=[BasicBlock(c_in, c_out, is_downsample=is_downsample),]else:
  31. blocks +=[BasicBlock(c_out, c_out),]return nn.Sequential(*blocks)classNet(nn.Module):def__init__(self, num_classes=751, pretrained=True, loss ='softmax',**kwargs):# market1501=751, dukemtmcreid=702super(Net, self).__init__()# Net# 3 128 64
  32. self.conv = nn.Sequential(
  33. nn.Conv2d(3,64,3, stride=1, padding=1),
  34. nn.BatchNorm2d(64),
  35. nn.ELU(inplace=True),)
  36. self.layer1 = make_layers(64,64,2,False)
  37. self.layer2 = make_layers(64,128,2,True)
  38. self.layer3 = make_layers(128,256,2,True)
  39. self.layer4 = make_layers(256,512,2,True)#self.avgpool = nn.AvgPool2d((8,4),1)
  40. self.adaptiveavgpool = nn.AdaptiveAvgPool2d(1)
  41. self.classifier = nn.Sequential(
  42. nn.Linear(512,256),
  43. nn.BatchNorm1d(256),
  44. nn.ReLU(inplace=True),
  45. nn.Dropout(),
  46. nn.Linear(256, num_classes),)defforward(self, x):
  47. x = self.conv(x)
  48. x = self.layer1(x)
  49. x = self.layer2(x)
  50. x = self.layer3(x)
  51. x = self.layer4(x)#x = self.avgpool(x)
  52. x = self.adaptiveavgpool(x)#
  53. x = x.view(x.size(0),-1)# B x 128ifnot self.training:
  54. x = x.div(x.norm(p=2,dim=1,keepdim=True))return x
  55. # classifier
  56. x = self.classifier(x)return x
  57. defZQP(num_classes=751, pretrained=True, loss='softmax',**kwargs):
  58. model = Net(
  59. num_classes=num_classes,
  60. pretrained = pretrained,
  61. loss ='softmax',**kwargs
  62. )return model
  63. if __name__ =='__main__':
  64. net = Net(pretrained=True)# Net
  65. x = torch.randn(4,3,256,256)# 128, 64
  66. y = net(x)

下面将模型ZQP添加到REID的定义文件中

  1. deep_sort/deep/reid/torchreid/models/__init__.py

中引入ZQP模型文件,添加:

  1. from.model_ZQP import*

在字典__model_factory中添加模型名称ZQP:

  1. __model_factory ={# image classification models'resnet18': resnet18,'resnet34': resnet34,'resnet50': resnet50,......'ZQP': ZQP

现在,只要修改deep_sort/configs/deep_sort.yaml中的MODEL_TYPE和REID_CKPT路径,就可运行ZQPei的reid模型ckpt.t7。

  1. MODEL_TYPE: "ZQP"
  2. REID_CKPT: '~/your_dir/deep_sort/deep/checkpoint/ckpt.t7'

另外,由于ZQPei reid模型训练中resize为128x64(hxw),故需修改deep_sort/deep/reid/torchreid/utils/feature_extractor.py中的image_size

  1. def__init__(
  2. self,
  3. model_name='',
  4. model_path='',
  5. image_size=(128,64),#(256, 128) (h, w)
  6. pixel_mean=[0.485,0.456,0.406],
  7. pixel_std=[0.229,0.224,0.225],
  8. pixel_norm=True,
  9. device='cuda',
  10. verbose=True):

至此,我们完成了添加ZQP reid模型到KaiyangZhou REID模型中的整个过程,并可以用同样的命令行参数,运行track.py

reid模型训练

deepsort中有两个神经网络模型,一个是目标检测模型yolov5,另一个是特征识别模型reid。yolov5模型训练有很多文章可参考,省略,此处侧重谈谈reid模型的训练。KaiyangZhou给出识别行人特征的reid模型训练方法,训练程序deep_sort/deep/reid/scripts/main.py。
训练可分别采用两个数据集:Market-1501和DukeMTMC-reID。
Market-1501数据集:
训练数据集”bounding_box_train“有751个行人ID,包含 12,936 张图像,平均每人有17.2张训练数据;
测试集“bounding_box_test”有750个行人ID,包含19,732张图像,平均每人有26.3张测试数据。
查询集gally从测试集中挑选出750个行人在6个摄像头下的图片,共3368 张查询图像。
dukemtmc-reid数据集:
“bounding_box_test”——用于测试集的 702 人,包含 17,661 张图像(随机采样,702 ID + 408 distractor ID)
“bounding_box_train”——用于训练集的 702 人,包含 16,522 张图像(随机采样)
“query”——为测试集中的 702 人在每个摄像头中随机选择一张图像作为 query,共有 2,228 张图像
只要从网上下载这两个数据集,使用其安排好的目录结构无需更改,只要指出数据集的根目录。
以下给出ZQPei模型在dukemtmc-reid数据集的训练方法
构造配置文件:deep_sort/deep/reid/configs/ZQP_128x64.yaml

  1. model:
  2. name:'ZQP'
  3. pretrained:True#True
  4. data:type:'image'
  5. sources:['dukemtmcreid']
  6. targets:['dukemtmcreid']# market1501, dukemtmcreid
  7. height:128
  8. width:64
  9. combineall:False
  10. transforms:['random_flip']#random_flip random_erase color_jitter
  11. save_dir:'deep_sort/deep/reid/log/ZQP'
  12. loss:
  13. name:'softmax'
  14. softmax:
  15. label_smooth:True
  16. train:
  17. optim:'amsgrad'
  18. lr:0.0015
  19. max_epoch:40#150
  20. batch_size:64
  21. fixbase_epoch:10#10
  22. open_layers:['classifier']#'classifier'
  23. lr_scheduler:'cosine'# stepsize: [60]
  24. test:
  25. batch_size:300
  26. dist_metric:'euclidean'
  27. normalize_feature:False
  28. evaluate:False#test only
  29. eval_freq:-1
  30. rerank:False

运行训练程序main.py

  1. python main.py \
  2. --config-file deep_sort/deep/reid/configs/ZQP_128x64.yaml \
  3. --root ~/your_datasets/dukemtmc_reid \
  4. model.load_weights ~/your_dir/deep_sort/deep/checkpoint/ckpt.t7

这里,在配置文件ZQP_128x64.yaml中pretrained = True,表示需加载预先训练的权重ckpt.t7。如果从头开始训练,则pretrained = False,并在命令行中删除model.load_weights项。命令行指定配置文件ZQP_128x64.yaml,数据集根目录dukemtmc_reid。
此外,采用dukemtmc训练数据集行人ID数为702,所以,需更改特征类型为702, 即模型文件model_ZQP.py中num_classes=702。

数据集目录组成应为:
~/your_datasets/dukemtmc_reid/dukemtmc-reid/DukeMTMC-reID/bounding_box_test, bounding_box_train, query
用逗号分开的项目表示在DukeMTMC-reID目录下有三个子目录bounding_box_test、bounding_box_train和query。

用自己的数据集训练reid

要利用KaiyangZhou reid训练程序,需要将数据集构造成market-1501的结构形式,即bounding_box_train, bounding_box_test, guery。
下面是一个例子,用veri-wild提供的小汽车数据集来构造符合market-1501构成形式的数据集。
veri-wild提供了包含40多万张4万辆汽车的id图片,每个汽车ID目录下有多张不同摄像机和不同时刻获取的汽车图片。假定选择800个汽车ID组成训练集,另外800个汽车ID组成测试集,从测试集中取出每个汽车ID在每个摄像头下的图片,组成query。
(1)从veri-wild中分别抽取800个ID放入train_800和test_800,每个ID的图片在20-30张之间。
(2)对train_800和test_800图片进行resize,压缩数据集容量。
(3)按照market-1501文件命名规则更改数据集图片名称,并从test数据集挑选图片放入query。
由此,仿照market-1501构成训练reid的小汽车数据集,进行汽车特征的reid特征识别模型。

参考程序如下:

  1. import os
  2. from shutil import copyfile, copytree, rmtree
  3. from torch.functional import broadcast_shapes
  4. from PIL import Image
  5. import matplotlib.pyplot as plt
  6. import random
  7. import shutil
  8. defget_cam_n(f_list):# 构造列表[file_name, camera_ID],列表抽取,每个camera_ID只取一项。
  9. A =[]for cam_n inrange(1,7):for j inrange(len(f_list)):
  10. B = f_list[j][1]if(f_list[j][1]== cam_n):
  11. A.append(f_list[j])breakreturn A
  12. defmake_fileprefix(src_dir, tar_dir, gallary_dir):# src_dir ID子目录的数据集目录,ID子目录下是同一ID的图片。for subdir in os.listdir( src_dir ):# ID子目录名称
  13. src=src_dir+"/"+subdir
  14. f_list =[]for file_name in os.listdir(src):# 提取ID子目录内文件名称
  15. fileprefix= os.path.splitext(file_name)[0]
  16. cam_n = random.randint(1,6)
  17. A1=[fileprefix, cam_n]
  18. f_list.append(A1)
  19. file_name1 = subdir+"_c"+str( cam_n)+"_f"+ fileprefix +".jpg"
  20. copyfile(src+"/"+file_name, tar_dir+"/"+file_name1)#将各子目录文件添加prefix,拷贝到一个目标文件夹tar_dirprint("copyfile: ", file_name1)
  21. f_cam=get_cam_n(f_list)# 列表[file_name, camera_ID],每个camera_ID只有一张图片。for i inrange(len(f_cam)):# test中抽取图片到query
  22. file_prefix = f_cam[i][0]
  23. cam_n = f_cam[i][1]
  24. file_name1 = subdir+"_c"+str( cam_n)+"_f"+ file_prefix +".jpg"
  25. file_name0 = src+"/"+file_prefix+'.jpg'
  26. copyfile(file_name0, gallary_dir+"/"+file_name1)#将各子目录文件添加prefix,拷贝到一个目标文件夹tar_dirreturndefimage_resize(src_dir, tar_dir):for subdir in os.listdir( src_dir ):# ID子目录名称
  27. src=src_dir+"/"+subdir
  28. tar_ID_dir=tar_dir+"/"+subdir
  29. if os.path.isdir(tar_ID_dir):
  30. rmtree(tar_ID_dir)
  31. os.mkdir(tar_ID_dir)for file_name in os.listdir(src):# 提取ID子目录中文件名称
  32. img=Image.open(src+"/"+file_name)
  33. w, h =img.size
  34. #img.show()
  35. imax =max(h,w)if imax <280:
  36. copyfile(src+"/"+file_name, tar_ID_dir+"/"+file_name)else:
  37. rate_hw =280/imax
  38. w,h =int(rate_hw*w),int(rate_hw*h)
  39. new_img = img.resize((w, h),Image.BILINEAR)#new_img.show()
  40. new_img.save(tar_ID_dir+"/"+file_name)print(tar_ID_dir+"/"+file_name)returnif __name__ =='__main__':
  41. src_dir ="~/your_datasets/resize/train_800"
  42. tar_dir ="~/your_datasets/train_box"if os.path.isdir(tar_dir):
  43. rmtree(tar_dir)
  44. os.mkdir(tar_dir)
  45. gallary_dir ="your_datasets/resize/gallary"if os.path.isdir(gallary_dir):
  46. rmtree(gallary_dir)
  47. os.mkdir(gallary_dir)
  48. make_fileprefix(src_dir, tar_dir, gallary_dir)

本文转载自: https://blog.csdn.net/weixin_44238733/article/details/123805195
版权归原作者 王定邦 所有, 如有侵权,请联系我们删除。

“更新版yolov5_deepsort_pytorch实现目标检测和跟踪”的评论:

还没有评论