??前言:本篇是關(guān)于如何使用YoloV5+Deepsort訓(xùn)練自己的數(shù)據(jù)集,從而實(shí)現(xiàn)目標(biāo)檢測(cè)與目標(biāo)追蹤,并繪制出物體的運(yùn)動(dòng)軌跡。本章講解的為第三部分內(nèi)容:數(shù)據(jù)集的制作、Deepsort模型的訓(xùn)練以及動(dòng)物運(yùn)動(dòng)軌跡的繪制。本文中用到的數(shù)據(jù)集均為自采,實(shí)驗(yàn)動(dòng)物為斑馬魚。
??環(huán)境&配置:RTX 3060、CUDA Version: 11.1、torch_version:1.9.1+cu111、python:3.8
???源碼如下:
GitHub - mikel-brostrom/yolo_tracking: A collection of SOTA real-time, multi-object tracking algorithms for object detectors
GitHub - Sharpiless/Yolov5-Deepsort: 最新版本yolov5+deepsort目標(biāo)檢測(cè)和追蹤,能夠顯示目標(biāo)類別,支持5.0版本可訓(xùn)練自己數(shù)據(jù)集
如果想進(jìn)一步了解Yolov5+Deepsort中的算法,猛戳這里:
【Yolov5+Deepsort】訓(xùn)練自己的數(shù)據(jù)集(1)| 目標(biāo)檢測(cè)&追蹤 | 軌跡繪制
如果想要實(shí)現(xiàn)訓(xùn)練集的采集與劃分,Yolov5模型的訓(xùn)練,猛戳這里:
?Ⅰ Deepsort模型訓(xùn)練
0x00 數(shù)據(jù)集準(zhǔn)備
Deepsort所需要的的數(shù)據(jù)集與前面Yolov5目標(biāo)檢測(cè)的有所不同。
這里需要借助labelimg工具手動(dòng)做出標(biāo)定生成xml文件,再撰寫腳本把圖像中的檢測(cè)目標(biāo)扣出來,作為我們的數(shù)據(jù)集。
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 = 'path'
# XML文件的地址
anno_path = 'path'
# 存結(jié)果的文件夾
cut_path = '/home/zqy/Desktop/yolov5-master/nxm_data/crops/'
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)
# 目錄是否存在,不存在則創(chuàng)建
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()
得到完整的數(shù)據(jù)集后,我們對(duì)數(shù)據(jù)集進(jìn)行劃分?:
import os
from PIL import Image
from shutil import copyfile, copytree, rmtree, move
PATH_DATASET = 'path' # 需要處理的文件夾
PATH_NEW_DATASET = 'path' # 處理后的文件夾
PATH_ALL_IMAGES = PATH_NEW_DATASET + '/all_images'
PATH_TRAIN = PATH_NEW_DATASET + '/train'
PATH_TEST = PATH_NEW_DATASET + '/test'
# 定義創(chuàng)建目錄函數(shù)
def mymkdir(path):
path = path.strip() # 去除首位空格
path = path.rstrip("\\") # 去除尾部 \ 符號(hào)
isExists = os.path.exists(path) # 判斷路徑是否存在
if not isExists:
os.makedirs(path) # 如果不存在則創(chuàng)建目錄
print(path + ' 創(chuàng)建成功')
return True
else:
# 如果目錄存在則不創(chuàng)建,并提示目錄已存在
print(path + ' 目錄已存在')
return False
class BatchRename():
'''
批量重命名文件夾中的圖片文件
'''
def __init__(self):
self.path = PATH_DATASET # 表示需要命名處理的文件夾
# 修改圖像尺寸
def resize(self):
for aroot, dirs, files in os.walk(self.path):
# aroot是self.path目錄下的所有子目錄(含self.path),dir是self.path下所有的文件夾的列表.
filelist = files # 注意此處僅是該路徑下的其中一個(gè)列表
# print('list', list)
# filelist = os.listdir(self.path) #獲取文件路徑
total_num = len(filelist) # 獲取文件長度(個(gè)數(shù))
for item in filelist:
if item.endswith('.jpg'): # 初始的圖片的格式為jpg格式的(或者源文件是png格式及其他格式,后面的轉(zhuǎn)換格式就可以調(diào)整為自己需要的格式即可)
src = os.path.join(os.path.abspath(aroot), item)
# 修改圖片尺寸到128寬*256高
im = Image.open(src)
out = im.resize((128, 256), Image.ANTIALIAS) # resize image with high-quality
out.save(src) # 原路徑保存
def rename(self):
for aroot, dirs, files in os.walk(self.path):
# aroot是self.path目錄下的所有子目錄(含self.path),dir是self.path下所有的文件夾的列表.
filelist = files # 注意此處僅是該路徑下的其中一個(gè)列表
# print('list', list)
# filelist = os.listdir(self.path) #獲取文件路徑
total_num = len(filelist) # 獲取文件長度(個(gè)數(shù))
i = 1 # 表示文件的命名是從1開始的
for item in filelist:
if item.endswith('.jpg'): # 初始的圖片的格式為jpg格式的(或者源文件是png格式及其他格式,后面的轉(zhuǎn)換格式就可以調(diào)整為自己需要的格式即可)
src = os.path.join(os.path.abspath(aroot), item)
# 根據(jù)圖片名創(chuàng)建圖片目錄
dirname = str(item.split('_')[0])
# 為相同車輛創(chuàng)建目錄
# new_dir = os.path.join(self.path, '..', 'bbox_all', dirname)
new_dir = os.path.join(PATH_ALL_IMAGES, dirname)
if not os.path.isdir(new_dir):
mymkdir(new_dir)
# 獲得new_dir中的圖片數(shù)
num_pic = len(os.listdir(new_dir))
dst = os.path.join(os.path.abspath(new_dir),
dirname + 'C1T0001F' + str(num_pic + 1) + '.jpg')
# 處理后的格式也為jpg格式的,當(dāng)然這里可以改成png格式 C1T0001F見mars.py filenames 相機(jī)ID,跟蹤指數(shù)
# dst = os.path.join(os.path.abspath(self.path), '0000' + format(str(i), '0>3s') + '.jpg') 這種情況下的命名格式為0000000.jpg形式,可以自主定義想要的格式
try:
copyfile(src, dst) # os.rename(src, dst)
print('converting %s to %s ...' % (src, dst))
i = i + 1
except:
continue
print('total %d to rename & converted %d jpgs' % (total_num, i))
def split(self):
# ---------------------------------------
# train_test
images_path = PATH_ALL_IMAGES
train_save_path = PATH_TRAIN
test_save_path = PATH_TEST
if not os.path.isdir(train_save_path):
os.mkdir(train_save_path)
os.mkdir(test_save_path)
for _, dirs, _ in os.walk(images_path, topdown=True):
for i, dir in enumerate(dirs):
for root, _, files in os.walk(images_path + '/' + dir, topdown=True):
for j, file in enumerate(files):
if (j == 0): # test dataset;每個(gè)車輛的第一幅圖片
print("序號(hào):%s 文件夾: %s 圖片:%s 歸為測(cè)試集" % (i + 1, root, file))
src_path = root + '/' + file
dst_dir = test_save_path + '/' + dir
if not os.path.isdir(dst_dir):
os.mkdir(dst_dir)
dst_path = dst_dir + '/' + file
move(src_path, dst_path)
else:
src_path = root + '/' + file
dst_dir = train_save_path + '/' + dir
if not os.path.isdir(dst_dir):
os.mkdir(dst_dir)
dst_path = dst_dir + '/' + file
move(src_path, dst_path)
rmtree(PATH_ALL_IMAGES)
if __name__ == '__main__':
demo = BatchRename()
demo.resize()
demo.rename()
demo.split()
0x01?參數(shù)調(diào)整
1.修改model.py
根據(jù)數(shù)據(jù)集中的類別,修改num_classes:
??注:
數(shù)據(jù)集劃分好后train和test文件夾下分別有多少個(gè)子文件夾,就代表有多少個(gè)類別。
即num_classes的數(shù)量。
2.修改train.py
?--data-dir:數(shù)據(jù)集文件,修改數(shù)據(jù)集的路徑。
--lr:學(xué)習(xí)率,可以不用修改。
根據(jù)需求修改epoches的次數(shù):
?可以修改權(quán)重保存的位置以及命名,以免發(fā)生覆蓋:
修改dataset的預(yù)處理:
修改完成后,運(yùn)行train.py開始訓(xùn)練,最終得到的權(quán)重結(jié)果保存在deep/checkpoint中。
至此,Deepsort部分已經(jīng)全部結(jié)束。
Ⅱ 生成視頻&軌跡繪制
0x00 參數(shù)設(shè)置
?將之前yolov5訓(xùn)練后得到的best.pt和Deepsort訓(xùn)練后得到的權(quán)重替換到track.py中:
修改視頻的地址:?
運(yùn)行track.py,得到最終視頻,并在視頻中顯示運(yùn)動(dòng)軌跡。
Ⅲ 常見報(bào)錯(cuò)分析
為了方便新手小白快速上手,解決報(bào)錯(cuò),暫不講解報(bào)錯(cuò)的具體原因,只給出如何解決報(bào)錯(cuò)(給出最簡(jiǎn)單的解決辦法),若想進(jìn)一步了解報(bào)錯(cuò)的具體原因,可以在評(píng)論區(qū)一起交流。
0x00 未修改num_classes
報(bào)錯(cuò):
解決方法:
在model.py中修改num_classes
?0x01 梯度問題
?報(bào)錯(cuò):
?這個(gè)錯(cuò)誤是由于在計(jì)算梯度的過程中,對(duì)一個(gè)葉子節(jié)點(diǎn)(leaf Variable)進(jìn)行了原地操作(in-place operation),導(dǎo)致了運(yùn)行時(shí)錯(cuò)誤。PyTorch中默認(rèn)情況下,autograd不支持對(duì)葉子節(jié)點(diǎn)進(jìn)行原地操作,因?yàn)檫@會(huì)導(dǎo)致梯度計(jì)算不正確。
解決方法:
在models文件夾下的yolo.py文件中:
?添加代碼:
with torch.no_grad():
0x02 顯存不足
報(bào)錯(cuò):
解決方法(這里提供一個(gè)最簡(jiǎn)單的方法):
更改batch_size的大小和epoch的次數(shù)。
?或者釋放內(nèi)存:
if hasattr(torch.cuda, 'empty_cache'):
torch.cuda.empty_cache()
??有更多報(bào)錯(cuò)大家可以寫在評(píng)論區(qū),博主看到后會(huì)盡力幫助大家。
0x03 Wandb問題
報(bào)錯(cuò):
解決方法:
直接關(guān)閉wandb。
在wandb_utils.py中,將開頭部分的代碼:
try:
import wandb
from wandb import init, finish
except ImportError:
wandb = None
?改為:
try:
import wandb
from wandb import init, finish
except ImportError:
wandb = None
wandb = None
0x04?權(quán)重pt文件不匹配
報(bào)錯(cuò):
權(quán)重pt文件和新環(huán)境的YOLOv5的小版本不相同
報(bào)錯(cuò)代碼:
YoloV5:AttributeError: Can‘t get attribute ‘C3‘ on <module ‘models.common‘ from
解決方法:在common.py中加入C3和SPPF模塊:
#在最上面需要引入warnings庫
import warnings
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super(C3, self).__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])
# self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)])
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
class SPPF(nn.Module):
# Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13))
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * 4, c2, 1, 1)
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
y1 = self.m(x)
y2 = self.m(y1)
return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))
0x05?YOLOv5斷后繼續(xù)訓(xùn)練?
YOLOv5自帶斷點(diǎn)保存,可以恢復(fù)訓(xùn)練。
在train.py中,把
?改為:
parser.add_argument('--resume', nargs='?', const=True, default=True, help='resume most recent training')
即default 后改為True。
運(yùn)行程序,可以看到從上次中斷得到地方繼續(xù)訓(xùn)練了。
??END
??因?yàn)樽髡叩哪芰τ邢?,所以文章可能?huì)存在一些錯(cuò)誤和不準(zhǔn)確之處,懇請(qǐng)大家指出!
???參考文獻(xiàn): [1] Simple Online and Realtime Tracking with a Deep Association Metric文章來源:http://www.zghlxwxcb.cn/news/detail-694679.html [1703.07402] Simple Online and Realtime Tracking with a Deep Association Metric (arxiv.org)文章來源地址http://www.zghlxwxcb.cn/news/detail-694679.html |
到了這里,關(guān)于【Yolov5+Deepsort】訓(xùn)練自己的數(shù)據(jù)集(3)| 目標(biāo)檢測(cè)&追蹤 | 軌跡繪制 | 報(bào)錯(cuò)分析&解決的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!