0.引言
YOLOv7作為YOLO系列的又一大巔峰之作,下面將介紹利用自己的數(shù)據(jù)集訓練YOLOv7模型。
github代碼鏈接:https://github.com/WongKinYiu/yolov7 目前版本為v0.1
運行環(huán)境如下:
- ubuntu20.04
- cuda11.0
- cudnn8.0.4
- python3.8
- torch1.12.0
- torchvision0.11.0
1.數(shù)據(jù)集準備
(1)把yolov7克隆到本地
git clone https://github.com/WongKinYiu/yolov7.git
(2)指定格式存放數(shù)據(jù)集
在data目錄下新建Annotations, images, ImageSets, labels 四個文件夾
images目錄下存放數(shù)據(jù)集的圖片文件
Annotations目錄下存放圖片的xml文件(labelImg標注)
目錄結構如下所示
.
├── ./data
│ ├── ./data/Annotations
│ │ ├── ./data/Annotations/fall_0.xml
│ │ ├── ./data/Annotations/fall_1000.xml
│ │ ├── ./data/Annotations/fall_1001.xml
│ │ ├── ./data/Annotations/fall_1002.xml
│ │ ├── ./data/Annotations/fall_1003.xml
│ │ ├── ./data/Annotations/fall_1004.xml
│ │ ├── ...
│ ├── ./data/images
│ │ ├── ./data/images/fall_0.jpg
│ │ ├── ./data/images/fall_1000.jpg
│ │ ├── ./data/images/fall_1001.jpg
│ │ ├── ./data/images/fall_1002.jpg
│ │ ├── ./data/images/fall_1003.jpg
│ │ ├── ./data/images/fall_1004.jpg
│ │ ├── ...
│ ├── ./data/ImageSets
│ └── ./data/labels
│ ├── ./data/coco.yaml
│ ├── ./data/hyp.scratch.p5.yaml
│ ├── ./data/hyp.scratch.p6.yaml
│ ├── ./data/hyp.scratch.tiny.yaml
├── ./cfg
├── ./detect.py
├── ./figure
├── ./hubconf.py
├── ./inference
├── ./models
├── ./README.md
├── ....
(3)按比例劃分數(shù)據(jù)集
在yolov7根目錄下新建一個文件splitDataset.py
隨機分配訓練/驗證/測試集圖片,代碼如下所示:
import os
import random
trainval_percent = 0.9
train_percent = 0.9
xmlfilepath = 'data/Annotations'
txtsavepath = 'data/ImageSets'
total_xml = os.listdir(xmlfilepath)
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
ftrainval = open('data/ImageSets/trainval.txt', 'w')
ftest = open('data/ImageSets/test.txt', 'w')
ftrain = open('data/ImageSets/train.txt', 'w')
fval = open('data/ImageSets/val.txt', 'w')
for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftrain.write(name)
else:
fval.write(name)
else:
ftest.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
(4)將xml文件轉(zhuǎn)換成YOLO系列標準讀取的txt文件
在同級目錄下再新建一個文件XML2TXT.py
注意classes = [“…”]一定需要填寫自己數(shù)據(jù)集的類別,在這里我是一個類別"fall",因此classes = [“fall”],代碼如下所示:
如果數(shù)據(jù)集中的類別比較多不想手敲類別的,可以使用(5)中的腳本直接獲取類別,同時還能查看各個類別的數(shù)據(jù)量,如果不想可以直接跳過(5)。
# -*- coding: utf-8 -*-
# xml解析包
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
sets = ['train', 'test', 'val']
classes = ['fall']
# 進行歸一化操作
def convert(size, box): # size:(原圖w,原圖h) , box:(xmin,xmax,ymin,ymax)
dw = 1./size[0] # 1/w
dh = 1./size[1] # 1/h
x = (box[0] + box[1])/2.0 # 物體在圖中的中心點x坐標
y = (box[2] + box[3])/2.0 # 物體在圖中的中心點y坐標
w = box[1] - box[0] # 物體實際像素寬度
h = box[3] - box[2] # 物體實際像素高度
x = x*dw # 物體中心點x的坐標比(相當于 x/原圖w)
w = w*dw # 物體寬度的寬度比(相當于 w/原圖w)
y = y*dh # 物體中心點y的坐標比(相當于 y/原圖h)
h = h*dh # 物體寬度的寬度比(相當于 h/原圖h)
return (x, y, w, h) # 返回 相對于原圖的物體中心點的x坐標比,y坐標比,寬度比,高度比,取值范圍[0-1]
# year ='2012', 對應圖片的id(文件名)
def convert_annotation(image_id):
'''
將對應文件名的xml文件轉(zhuǎn)化為label文件,xml文件包含了對應的bunding框以及圖片長款大小等信息,
通過對其解析,然后進行歸一化最終讀到label文件中去,也就是說
一張圖片文件對應一個xml文件,然后通過解析和歸一化,能夠?qū)男畔⒈4娴轿ㄒ灰粋€label文件中去
labal文件中的格式:calss x y w h 同時,一張圖片對應的類別有多個,所以對應的bunding的信息也有多個
'''
# 對應的通過year 找到相應的文件夾,并且打開相應image_id的xml文件,其對應bund文件
in_file = open('data/Annotations/%s.xml' % (image_id), encoding='utf-8')
# 準備在對應的image_id 中寫入對應的label,分別為
# <object-class> <x> <y> <width> <height>
out_file = open('data/labels/%s.txt' % (image_id), 'w', encoding='utf-8')
# 解析xml文件
tree = ET.parse(in_file)
# 獲得對應的鍵值對
root = tree.getroot()
# 獲得圖片的尺寸大小
size = root.find('size')
# 如果xml內(nèi)的標記為空,增加判斷條件
if size != None:
# 獲得寬
w = int(size.find('width').text)
# 獲得高
h = int(size.find('height').text)
# 遍歷目標obj
for obj in root.iter('object'):
# 獲得difficult ??
difficult = obj.find('difficult').text
# 獲得類別 =string 類型
cls = obj.find('name').text
# 如果類別不是對應在我們預定好的class文件中,或difficult==1則跳過
if cls not in classes or int(difficult) == 1:
continue
# 通過類別名稱找到id
cls_id = classes.index(cls)
# 找到bndbox 對象
xmlbox = obj.find('bndbox')
# 獲取對應的bndbox的數(shù)組 = ['xmin','xmax','ymin','ymax']
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
float(xmlbox.find('ymax').text))
print(image_id, cls, b)
# 帶入進行歸一化操作
# w = 寬, h = 高, b= bndbox的數(shù)組 = ['xmin','xmax','ymin','ymax']
bb = convert((w, h), b)
# bb 對應的是歸一化后的(x,y,w,h)
# 生成 calss x y w h 在label文件中
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
# 返回當前工作目錄
wd = getcwd()
print(wd)
for image_set in sets:
'''
對所有的文件數(shù)據(jù)集進行遍歷
做了兩個工作:
1.將所有圖片文件都遍歷一遍,并且將其所有的全路徑都寫在對應的txt文件中去,方便定位
?。玻瑫r對所有的圖片文件進行解析和轉(zhuǎn)化,將其對應的bundingbox 以及類別的信息全部解析寫到label 文件中去
最后再通過直接讀取文件,就能找到對應的label 信息
'''
# 先找labels文件夾如果不存在則創(chuàng)建
if not os.path.exists('data/labels/'):
os.makedirs('data/labels/')
# 讀取在ImageSets/Main 中的train、test..等文件的內(nèi)容
# 包含對應的文件名稱
image_ids = open('data/ImageSets/%s.txt' % (image_set)).read().strip().split()
# 打開對應的2012_train.txt 文件對其進行寫入準備
list_file = open('data/%s.txt' % (image_set), 'w')
# 將對應的文件_id以及全路徑寫進去并換行
for image_id in image_ids:
list_file.write('data/images/%s.jpg\n' % (image_id))
# 調(diào)用 year = 年份 image_id = 對應的文件名_id
convert_annotation(image_id)
# 關閉文件
list_file.close()
(5)查看自定義數(shù)據(jù)集標簽類別及數(shù)量
在同級目錄下再新建一個文件ViewCategory.py,將代碼復制進去
import os
from unicodedata import name
import xml.etree.ElementTree as ET
import glob
def count_num(indir):
label_list = []
# 提取xml文件列表
os.chdir(indir)
annotations = os.listdir('.')
annotations = glob.glob(str(annotations) + '*.xml')
dict = {} # 新建字典,用于存放各類標簽名及其對應的數(shù)目
for i, file in enumerate(annotations): # 遍歷xml文件
# actual parsing
in_file = open(file, encoding='utf-8')
tree = ET.parse(in_file)
root = tree.getroot()
# 遍歷文件的所有標簽
for obj in root.iter('object'):
name = obj.find('name').text
if (name in dict.keys()):
dict[name] += 1 # 如果標簽不是第一次出現(xiàn),則+1
else:
dict[name] = 1 # 如果標簽是第一次出現(xiàn),則將該標簽名對應的value初始化為1
# 打印結果
print("各類標簽的數(shù)量分別為:")
for key in dict.keys():
print(key + ': ' + str(dict[key]))
label_list.append(key)
print("標簽類別如下:")
print(label_list)
if __name__ == '__main__':
# xml文件所在的目錄,修改此處
indir = 'data/Annotations'
count_num(indir) # 調(diào)用函數(shù)統(tǒng)計各類標簽數(shù)目
至此數(shù)據(jù)集的準備已經(jīng)就緒,索引文件在data目錄下的train.txt/val.txt/test.txt
2.訓練配置準備
(1)安裝requirements
首先需要先利用終端進入yolov7文件夾,創(chuàng)建python環(huán)境,這里以Anaconda舉例
cd yolov7
conda create -n yolov7 python=3.8
conda activate yolov7
pip install -r requirements.txt
(2)修改模型配置文件
進入cfg/training文件夾,選擇需要訓練的模型配置文件,這里選擇yolov7.yaml,將其中的nc修改為自己的類別數(shù)量,這里修改為1
# parameters
nc: 1 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
# anchors
anchors:
- [12,16, 19,36, 40,28] # P3/8
- [36,75, 76,55, 72,146] # P4/16
- [142,110, 192,243, 459,401] # P5/32
# yolov7 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [32, 3, 1]], # 0
[-1, 1, Conv, [64, 3, 2]], # 1-P1/2
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [128, 3, 2]], # 3-P2/4
[-1, 1, Conv, [64, 1, 1]],
[-2, 1, Conv, [64, 1, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 11
[-1, 1, MP, []],
[-1, 1, Conv, [128, 1, 1]],
[-3, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 16-P3/8
[-1, 1, Conv, [128, 1, 1]],
[-2, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1]], # 24
[-1, 1, MP, []],
[-1, 1, Conv, [256, 1, 1]],
[-3, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 29-P4/16
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [1024, 1, 1]], # 37
[-1, 1, MP, []],
[-1, 1, Conv, [512, 1, 1]],
[-3, 1, Conv, [512, 1, 1]],
[-1, 1, Conv, [512, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 42-P5/32
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [1024, 1, 1]], # 50
]
# yolov7 head
head:
[[-1, 1, SPPCSPC, [512]], # 51
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[37, 1, Conv, [256, 1, 1]], # route backbone P4
[[-1, -2], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 63
[-1, 1, Conv, [128, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[24, 1, Conv, [128, 1, 1]], # route backbone P3
[[-1, -2], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1]],
[-2, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1]], # 75
[-1, 1, MP, []],
[-1, 1, Conv, [128, 1, 1]],
[-3, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 2]],
[[-1, -3, 63], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 88
[-1, 1, MP, []],
[-1, 1, Conv, [256, 1, 1]],
[-3, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 2]],
[[-1, -3, 51], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1]],
[-2, 1, Conv, [512, 1, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1]], # 101
[75, 1, RepConv, [256, 3, 1]],
[88, 1, RepConv, [512, 3, 1]],
[101, 1, RepConv, [1024, 3, 1]],
[[102,103,104], 1, IDetect, [nc, anchors]], # Detect(P3, P4, P5)
]
(3)修改數(shù)據(jù)加載配置文件
進入data/文件夾,新建fall.yaml,內(nèi)容如下:
train: ./data/train.txt
val: ./data/val.txt
test: ./data/test.txt
# number of classes
nc: 1
# class names
names: ['fall']
至此,配置文件修改完成
3.訓練檢測模型
yolov7倉庫中有兩個訓練腳本,一個叫train.py,一個叫train_aux.py,前者是訓練P5的模型,包含yolov7-tiny、yolov7-tiny-silu、yolov7、yolov7x,后者是訓練P6的模型,包含yolov7-w6、yolov7-e6、yolov7-d6、yolov7-e6e。
# 訓練P5模型
python train.py --weights yolov7.pt --data data/fall.yaml --epochs 300 --batch-size 8 --cfg cfg/training/yolov7.yaml --workers 0 --device 0 --img-size 640 640
# 訓練P6模型
python train_aux.py --weights yolov7-e6e.pt --data data/fall.yaml --epochs 300 --batch-size 8 --cfg cfg/training/yolov7-e6e.yaml --workers 0 --device 0 --img-size 1280 1280
其中,–weights是指預訓練模型權重,可以去yolov7官方鏈接下載,指定到相應目錄下(推薦),如果沒有配置網(wǎng)絡可能會存在git不了權重文件
–data是指數(shù)據(jù)加載文件路徑
–epoch是指模型訓練輪次
–batch-size是指一批次輸入多少數(shù)據(jù)一起訓練,根據(jù)自己顯卡的顯存決定
–cfg是指模型加載文件路徑,關于–cfg中的training和deploy可以參考這篇文章:training和deploy的區(qū)別
–workers是指dataloader同時讀取多少個進程,如果num_worker設為0,意味著每一輪迭代時,dataloader不再有自主加載數(shù)據(jù)到RAM這一步驟(因為沒有worker了),而是在RA中找batch,找不到時再加載相應的batch。缺點當然是速度慢。設置為0可以避免一些錯誤發(fā)生
–device是指選用幾號GPU
–img-size是指訓練集和測試集圖像大小,可選640或1280等
–rect是指是否采用矩陣推理的方式去訓練模型,采用矩陣推理就不要求送入的訓練的圖片是正方形
–resume斷點續(xù)訓
–evolve超參數(shù)進化,模型提供的默認參數(shù)是通過在COCO數(shù)據(jù)集上使用超參數(shù)進化得來的
–linear-lr利用余弦函數(shù)對訓練中的學習率進行調(diào)整
如果計算機上存在多張GPU卡,則可以使用分布式訓練方法:
# 訓練P5模型
python -m torch.distributed.launch --nproc_per_node 4 --master_port 9527 train.py --workers 8 --device 0,1,2,3 --sync-bn --batch-size 8 --data data/fall.yaml --img 640 640 --cfg cfg/training/yolov7.yaml --weights '' --name yolov7 --hyp data/hyp.scratch.p5.yaml
# 訓練P6模型
python -m torch.distributed.launch --nproc_per_node 8 --master_port 9527 train_aux.py --workers 8 --device 0,1,2,3,4,5,6,7 --sync-bn --batch-size 8 --data data/fall.yaml --img 1280 1280 --cfg cfg/training/yolov7-e6e.yaml --weights '' --name yolov7-w6 --hyp data/hyp.scratch.p6.yaml
4.測試模型性能
使用test.py文件可以對訓練出的模型進行測試,與訓練命令基本類似,但是這里需要注意的是,需要把:
# 訓練P5模型
python test.py --weights exp/best.pt --data data/fall.yaml --batch-size 8 --device 0 --img-size 640 640
5.實戰(zhàn)檢測模型性能
# 使用攝像頭進行目標檢測,--source可以設置為圖片文件夾/rtsp流地址/視頻地址等
python detect.py --weights runs/train/exp/weights/best.pt --source 0
6.導出模型
使用export.py腳本,可以導出成中間格式的onnx模型
python export.py --weights runs/train/exp/best.pt
–weights是通過yolov7訓練得到的pt文件,一般存在于runs/train下面的文件夾中,導出后,方便后續(xù)部署(onnx/tensorrt/openvino/coreml)
7.后續(xù)
導出模型后,GPU環(huán)境下可以使用tensorrt進行部署,詳情可以參考我這篇文章:
YOLOv7系列教程:二、使用onnx導出成tensorrt模型,實現(xiàn)高性能部署,包含opencv多線程、圖像隊列存取、自動保存xml和jpg等模塊
CPU環(huán)境下可以使用openvino進行部署,詳情可以參考我這篇文章:
YOLOv7系列教程:三、使用onnx導出成openvino模型,并調(diào)用接口進行攝像頭推理預測
不同模型對比結果可以看這篇:
YOLOv8(n/s/m/l/x)&YOLOv7(yolov7-tiny/yolov7/yolov7x)&YOLOv5(n/s/m/l/x)不同模型參數(shù)/性能對比(含訓練及推理速度)文章來源:http://www.zghlxwxcb.cn/news/detail-816005.html
參考文章:
YOLOv5 實現(xiàn)目標檢測(訓練自己的數(shù)據(jù)集實現(xiàn)貓貓識別)
YoloV7:訓練自己得數(shù)據(jù)集詳細教程文章來源地址http://www.zghlxwxcb.cn/news/detail-816005.html
到了這里,關于YOLOv7教程系列:一、基于自定義數(shù)據(jù)集訓練專屬于自己的目標檢測模型(保姆級教程,含數(shù)據(jù)集預處理),包含對train.py/test.py/detect.py/export.py詳細說明的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!