1. 對(duì)原始圖片打標(biāo)
利用LabelImg工具打標(biāo)厘灼,輸出格式選擇為PascalVOC倒庵,得到xml格式的文件
2. 數(shù)據(jù)預(yù)處理
(1)將打標(biāo)后的文件拷貝到當(dāng)前工作目錄脸候,即放在和代碼同一級(jí)目錄下的datasets文件夾中
"""將打標(biāo)好的圖片和xml分別放在img和xml文件夾中"""
import os
import glob
import shutil
root_path = os.getcwd() # 當(dāng)前工作目錄
xml_file_path = r'D:\YOLO\0506\labels\\' # 存放打標(biāo)后xml文件的路徑
png_file_path = r'D:\YOLO\0506\images\\' # 原始圖片數(shù)據(jù)集的路徑
png_name_list = []
xml_name_list = []
for xml_name in glob.glob(xml_file_path + "*.xml"):
filepath, temp_file_name = os.path.split(xml_name) # 將全路徑分割成目錄和文件名
filename, extension = os.path.splitext(temp_file_name) # 分離文件名與擴(kuò)展名
png_name_list.append(filename + '.png')
xml_name_list.append(filename + '.xml')
new_dataset_png = root_path + '\\datasets\\HDA\\img\\' # 新的存放圖片文件的目錄
if not os.path.exists(new_dataset_png):
os.makedirs(new_dataset_png)
new_dataset_xml = root_path + '\\datasets\\HDA\\xml\\' # 新的存放xml文件的目錄
if not os.path.exists(new_dataset_xml):
os.makedirs(new_dataset_xml)
for i in range(len(png_name_list)): # 將源文件的內(nèi)容復(fù)制到目標(biāo)文件
shutil.copy(png_file_path + str(png_name_list[i]), new_dataset_png)
shutil.copy(xml_file_path + str(xml_name_list[i]), new_dataset_xml)
運(yùn)行以上代碼后婿脸,當(dāng)前代碼路徑下的目錄框架如下:
(2)將xml文件轉(zhuǎn)換成txt文件
目標(biāo)檢測(cè)的坐標(biāo)格式有:
VOC(XML)格式:
(Xmin, Ymin, Xmax, Ymax),分別代表左上角和右下角的兩個(gè)坐標(biāo)
YOLO(TXT)格式:
(Xcenter, Ycenter, W, H)烛占,其中x,y,w,h為歸一化后的數(shù)值,分別代表中心點(diǎn)坐標(biāo)和寬沟启、高
COCO(JSON)格式:
(Xmin, Ymin, W, H)忆家,其中x,y,w,h均不是歸一化后的數(shù)值,分別代表左上角坐標(biāo)和寬美浦、高
由于不同格式的坐標(biāo)表示方式不同弦赖,所以需要進(jìn)行轉(zhuǎn)換,將轉(zhuǎn)換后的坐標(biāo)文件存放到txt目錄下
"""將xml格式的坐標(biāo)轉(zhuǎn)換成txt格式浦辨,放入txt文件夾中"""
import xml.etree.ElementTree as ET
import os
import glob
classes = ["A", "T"]
# 將xml格式的坐標(biāo)轉(zhuǎn)換成txt格式的坐標(biāo):(Xmin蹬竖,Ymin沼沈,Xmax,Ymax)–>(X币厕,Y列另,W,H)
def convert(size, box):
dw = 1.0 / size[0]
dh = 1.0 / 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
y = y * dh
w = w * dw
h = h * dh
return x, y, w, h
def convert_annotation(img_name, xml_path, txt_path):
in_file = open(xml_path + img_name[:-3] + 'xml') # xml文件路徑
out_file = open(txt_path + img_name[:-3] + 'txt', 'w') # 轉(zhuǎn)換后的txt文件存放路徑
xml_text = in_file.read()
root = ET.fromstring(xml_text)
in_file.close()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes:
print(cls)
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')
if __name__ == '__main__':
path = os.getcwd()
xml_path = path + '\\datasets\\HDA\\xml\\'
img_path = path + '\\datasets\\HDA\\img\\'
txt_path = path + '\\datasets\\HDA\\txt\\'
if not os.path.exists(txt_path):
os.makedirs(txt_path)
for image_path in glob.glob(img_path + "*.png"):
# 每一張圖片都對(duì)應(yīng)一個(gè)xml文件旦装,這里寫xml對(duì)應(yīng)的圖片的路徑
image_name = image_path.split('\\')[-1]
try:
convert_annotation(image_name, xml_path, txt_path)
except:
print('This picture has not xml file')
運(yùn)行以上代碼后页衙,當(dāng)前代碼路徑下的目錄框架如下所示,可以看到阴绢,多了一個(gè)txt目錄
(3)將數(shù)據(jù)集劃分成訓(xùn)練集店乐、測(cè)試集、驗(yàn)證集
"""
將數(shù)據(jù)集劃分成訓(xùn)練集呻袭、測(cè)試集眨八、驗(yàn)證集
僅僅是把劃分好的png和xml文件名寫入到不同集合的txt文件
"""
import os
import random
root_path = os.getcwd()
xml_file_path = root_path + '\\datasets\\HDA\\xml'
config_txt_save_path = root_path + '\\datasets\\HDA\\config'
if not os.path.exists(config_txt_save_path):
os.makedirs(config_txt_save_path)
train_test_percent = 0.9 # (訓(xùn)練集+驗(yàn)證集) / (訓(xùn)練集+驗(yàn)證集+測(cè)試集)
train_val_percent = 0.9 # 訓(xùn)練集 / (訓(xùn)練集+驗(yàn)證集)
total_xml = os.listdir(xml_file_path)
num = len(total_xml)
train_val_size = int(num * train_test_percent) # 訓(xùn)練集+驗(yàn)證集數(shù)量
test_size = int(num - train_val_size) # 測(cè)試集數(shù)量
val_size = int(train_val_size * train_val_percent) # 驗(yàn)證集數(shù)量
train_size = int(train_val_size - val_size) # 訓(xùn)練集數(shù)量
train_val = random.sample(range(num), train_val_size)
train = random.sample(train_val, val_size)
print("train and valid size:", train_val_size)
print("train size:", train_size)
print("test size:", test_size)
print("valid size:", val_size)
test_txt = open(config_txt_save_path + '\\test.txt', 'w')
train_txt = open(config_txt_save_path + '\\train.txt', 'w')
val_txt = open(config_txt_save_path + '\\val.txt', 'w')
img_test_txt = open(config_txt_save_path + '\\img_test.txt', 'w')
img_train_txt = open(config_txt_save_path + '\\img_train.txt', 'w')
img_val_txt = open(config_txt_save_path + '\\img_val.txt', 'w')
for i in range(num):
txt_name = total_xml[i][:-4] + '.txt' + '\n'
img_name = total_xml[i][:-4] + '.png' + '\n'
if i in train_val:
if i in train:
train_txt.write(txt_name)
img_train_txt.write(img_name)
else:
val_txt.write(txt_name)
img_val_txt.write(img_name)
else:
test_txt.write(txt_name)
img_test_txt.write(img_name)
train_txt.close()
val_txt.close()
test_txt.close()
img_train_txt.close()
img_val_txt.close()
img_test_txt.close()
運(yùn)行以上代碼后,當(dāng)前代碼路徑下的目錄框架如下所示左电,可以看到廉侧,多了一個(gè)config目錄
(4)重構(gòu)數(shù)據(jù)集
"""重構(gòu)數(shù)據(jù)集,根據(jù)txt文件劃分圖片篓足,分別創(chuàng)建訓(xùn)練集段誊、驗(yàn)證集、測(cè)試集的圖片文件夾"""
import os
import shutil
# 獲取分割好的train\test\valid名稱
img_train_txt = []
img_test_txt = []
img_valid_txt = []
label_train_txt = []
label_test_txt = []
label_valid_txt = []
path = os.getcwd() + '\\datasets\\HDA\\config\\'
for line in open(path + "img_train.txt"):
line = line.strip('\n')
img_train_txt.append(line)
for line1 in open(path + "img_test.txt"):
line1 = line1.strip('\n')
img_test_txt.append(line1)
for line2 in open(path + "img_val.txt"):
line2 = line2.strip('\n')
img_valid_txt.append(line2)
for line3 in open(path + "train.txt"):
line3 = line3.strip('\n')
label_train_txt.append(line3)
for line4 in open(path + "test.txt"):
line4 = line4.strip('\n')
label_test_txt.append(line4)
for line5 in open(path + "val.txt"):
line5 = line5.strip('\n')
label_valid_txt.append(line5)
# 建立圖片的3種集合文件夾
new_train_img_dir = 'datasets\\HDA\\split\\images\\train\\'
new_test_img_dir = 'datasets\\HDA\\split\\images\\test\\'
new_valid_img_dir = 'datasets\\HDA\\split\\images\\val\\'
# 建立label的3種集合文件夾
new_train_label_dir = 'datasets\\HDA\\split\\labels\\train\\'
new_test_label_dir = 'datasets\\HDA\\split\\labels\\test\\'
new_valid_label_dir = 'datasets\\HDA\\split\\labels\\val\\'
if not os.path.exists(new_train_img_dir):
os.makedirs(new_train_img_dir)
if not os.path.exists(new_test_img_dir):
os.makedirs(new_test_img_dir)
if not os.path.exists(new_valid_img_dir):
os.makedirs(new_valid_img_dir)
if not os.path.exists(new_train_label_dir):
os.makedirs(new_train_label_dir)
if not os.path.exists(new_test_label_dir):
os.makedirs(new_test_label_dir)
if not os.path.exists(new_valid_label_dir):
os.makedirs(new_valid_label_dir)
# 將圖片從原始目錄移動(dòng)到訓(xùn)練集栈拖、驗(yàn)證集连舍、測(cè)試集目錄
origin_img_dir = 'datasets\\HDA\\img\\'
origin_label_dir = 'datasets\\HDA\\txt\\'
# 小數(shù)據(jù)建議:copy 大數(shù)據(jù)建議:move
for i in range(len(img_train_txt)):
shutil.copy(origin_img_dir + str(img_train_txt[i]), new_train_img_dir)
shutil.copy(origin_label_dir + str(label_train_txt[i]), new_train_label_dir)
for j in range(len(img_test_txt)):
shutil.copy(origin_img_dir + str(img_test_txt[j]), new_test_img_dir)
shutil.copy(origin_label_dir + str(label_test_txt[j]), new_test_label_dir)
for k in range(len(img_valid_txt)):
shutil.copy(origin_img_dir + str(img_valid_txt[k]), new_valid_img_dir)
shutil.copy(origin_label_dir + str(label_valid_txt[k]), new_valid_label_dir)
運(yùn)行以上代碼后,當(dāng)前代碼路徑下的目錄框架如下所示辱魁,可以看到烟瞧,多了一個(gè)split目錄,split目錄下又包括images和labels目錄染簇,分別代表圖片和標(biāo)簽参滴,這2個(gè)目錄下都分別有訓(xùn)練集、驗(yàn)證集和測(cè)試集的目錄锻弓。
至此砾赔,對(duì)原始圖片數(shù)據(jù)集的預(yù)處理完成,可以開始訓(xùn)練了
3.訓(xùn)練
(1)從GitHub下載YOLOv5代碼和pt文件
pt文件也就是PyTorch的模型文件青灼,我使用的是yolov5x.pt
其他類型的model也可以在GitHub找到暴心,進(jìn)入https://github.com/ultralytics/yolov5/releases/tag/v6.1,劃到頁面最下方杂拨,Assets下有各種模型
下載好的YOLOv5項(xiàng)目結(jié)構(gòu)如下所示:
(2)將代碼上傳到服務(wù)器专普,并創(chuàng)建相應(yīng)目錄
將下載好的YOLOv5代碼上傳到有GPU的服務(wù)器
在服務(wù)器的YOLOv5文件夾中新建一個(gè)目錄weights,存放yolov5x.pt
-
在服務(wù)器的YOLOv5文件夾中新建一個(gè)目錄datasets弹沽,并新建一個(gè)當(dāng)前項(xiàng)目的目錄(便于日后針對(duì)不同項(xiàng)目管理)檀夹,存放之前split目錄下的images和labels筋粗,我這里是datasets/0506HDA
注:上傳到服務(wù)器的時(shí)候需要將images和labels打成壓縮包,例如dataset.zip炸渡,然后使用 unzip dataset.zip解壓縮
在models目錄下新建一個(gè)針對(duì)當(dāng)前任務(wù)制定的模型配置文件娜亿,我的是yolov5x_hdl.yaml,具體細(xì)節(jié)參見(3)
在datasets/0506HDA目錄下新建一個(gè)訓(xùn)練所用的yaml文件蚌堵,我的是hdl.yaml买决,具體細(xì)節(jié)參見(3)
(3)回到服務(wù)器中的YOLOv5目錄,然后輸入訓(xùn)練指令:
python train.py --data datasets/0506HDA/hdl.yaml --cfg models/yolov5x_hdl.yaml --weights weights/yolov5x.pt --epoch 400
其中吼畏,--cfg 指定訓(xùn)練所用的模型督赤,--data 指定數(shù)據(jù)文件,默認(rèn)使用data/coco128.yaml宫仗,--weights 指定模型權(quán)重够挂, --epoch 指定訓(xùn)練輪數(shù)
- yolov5x_hdl.yaml是在yolov5x.yaml的基礎(chǔ)上稍作修改的文件,這是由于我的目標(biāo)檢測(cè)任務(wù)是二分類的藕夫,所以將nc從80變成了2,其他部分不變枯冈,內(nèi)容如下:
# Parameters
nc: 2 # number of classes
depth_multiple: 1.33 # model depth multiple
width_multiple: 1.25 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, C3, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3, [1024]],
[-1, 1, SPPF, [1024, 5]], # 9
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
-
hdl.yaml存放訓(xùn)練集毅贮、驗(yàn)證集、測(cè)試集的目錄尘奏,以及類別信息滩褥,這是我在data/coco128.yaml的基礎(chǔ)上修改的
原始的coco128.yaml指定了訓(xùn)練集、驗(yàn)證集炫加、測(cè)試集的路徑瑰煎,以及分類個(gè)數(shù)和名字,內(nèi)容如下:
# YOLOv5 ?? by Ultralytics, GPL-3.0 license # COCO128 dataset https://www.kaggle.com/ultralytics/coco128 (first 128 images from COCO train2017) by Ultralytics # Example usage: python train.py --data coco128.yaml # parent # ├── yolov5 # └── datasets # └── coco128 ← downloads here (7 MB) # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path: ../datasets/coco128 # dataset root dir train: images/train2017 # train images (relative to 'path') 128 images val: images/train2017 # val images (relative to 'path') 128 images test: # test images (optional) # Classes nc: 80 # number of classes names: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'] # class names # Download script/URL (optional) download: https://ultralytics.com/assets/coco128.zip
我根據(jù)自己的需求修改后內(nèi)容如下:
train: ../yolov5/datasets/0506HDA/images/train/ # train images (relative to 'path') 128 images
val: ../yolov5/datasets/0506HDA/images/val/ # val images (relative to 'path') 128 images
test: ../yolov5/datasets/0506HDA/images/test/ # test images (optional)
# Classes
nc: 2 # number of classes
names: ['A', 'T'] # class names
4.檢測(cè)
訓(xùn)練完成后俗孝,模型的各項(xiàng)信息會(huì)保存在yolov5/runs/train/目錄下酒甸,如果是默認(rèn)的話,會(huì)有exp+數(shù)字命名的各種目錄赋铝,此時(shí)進(jìn)入數(shù)字最大的那個(gè)(我的是exp3)插勤,也就是最后一次訓(xùn)練得到的模型,即可看到訓(xùn)練結(jié)果革骨。
訓(xùn)練效果最好的模型參數(shù)保存在exp3/weights/best.pt中
檢測(cè)命令如下:
python detect.py --source /mnt/seqdata2/Public_shared/tmp_cyf/CYCLONE-291/images/20211210171717_LAB256V2_5K_PC28_34_Z3_h49_5C20_J4_AD3_2020SEP_LiuJiaDun --weights ./runs/train/exp3/weights/best.pt --line-thickness 1 --save-txt --save-conf
其中农尖, --source 指定待檢測(cè)的數(shù)據(jù)集目錄,--weights 指定權(quán)重良哲,這里我們使用訓(xùn)練過的最佳模型
檢測(cè)結(jié)果會(huì)存放在yolov5/runs/detect/目錄下