嘿!您似乎在 United States,您想使用我们的 English 网站吗?
Switch to English site
Skip to main content

NVIDIA Jetson Nano應用- Google Colab雲端訓練客製化 YOLOv4物件辨識-下篇

下篇分為幾個重點:

1.找尋欲訓練的數據集

2.使用特定數據集進行Transfer Learning

3.於Colab上進行測試

4.下載至Jetson Nano並進行測試

# 剛剛宣告的 target directory
# trg_dir = 'Apple'
target_cfg = f"cfg/yolov4-tiny-{trg_dir}.cfg"
classes_nums = 1
target_filter = int((classes_nums+5)*3)
max_batches = classes_nums*2000 if classes_nums>=3 else 6000
steps_1, steps_2 = int(max_batches*0.8), int(max_batches*0.9)
info = '\
classes_num : {}\n\
target_filter : {}\n\
max_batches : {}\n\
steps : {},{}\n\
'.format(classes_nums, target_filter, max_batches, steps_1, steps_2)
print(info)
!cp cfg/yolov4-tiny-custom.cfg {target_cfg}
!sed -i '7c subdivisions=16' {target_cfg}
!sed -i '8c width=416' {target_cfg}
!sed -i '9c height=416' {target_cfg}
# classes*2000 = max_batches
!sed -i '20c max_batches={max_batches}' {target_cfg}
# step = max_batches*80% and 90%
!sed -i '22c steps={steps_1},{steps_2}' {target_cfg}
# last layer's filter = ( classes + 5 ) * 3
# classes代表有幾個類別 而這邊5代表的是 class,x1,x2,y1,y2 3代表rgb通道數
!sed -i '212c filters={target_filter}' {target_cfg}
!sed -i '220c classes={classes_nums}' {target_cfg}
!sed -i '263c filters={target_filter}' {target_cfg}
!sed -i '269c classes={classes_nums}' {target_cfg}
print(f'Created {target_cfg}.')

5.補充-如何讓訓練更好?

找尋欲訓練的數據集

去年的文章有帶大家自己標記資料並且使用YOLOv5進行遷移式學習 ( 文章連結 : https://www.rs-online.com/designspark/pytorchx-nvidia-jetsonnano-yolov5-transfer-learning-cn ),這次教大家運用另一個開源的資源 – Open Image,它是一個開源的大型數據集,其中有超過6000個類別,有物件辨識也有語意分割的數據,這次我們使用的是Open Image V6的版本(如下圖)。

image00111_e10d09ea54b2294cc0e14f328122884583a107f3.png

我們可以先點擊 Explore進行探索,點進來後每一次都會random一樣物品給你,你也可以在上方導覽列點擊「Random category」來隨機產生圖片:

image0029_5279ee86721fdb5fd92a901e3b5f8919b85f569b.png

接著你可以在「Type」的位置選擇要哪一種類別的數據,這邊有「Detection」、「Segmentation」、「Relationships」三種可以選擇,預設都是segmentation,點下去後圖片就會改變。

image00311_dcd9eefd52cb7857990eab22ae3b338461c23e0d.png

下圖為同樣是Cucumber但是選擇成Detection的結果:

image00412_a9c089bbfe707095ea1c8619c793f36490372ba4.png

在Type中有一個大家可能比較不熟悉的就是「Relationships」這是表達物件跟物件之間的交互關係,像是圖片中有「人」跟「人」可能就會有「hug」這個動作。

image00510_5782c6f7bf7718dd9f677f90b692c0b787b2a792.png

到這邊稍微簡單的介紹了一下Open Image V6接下來我們就可以進行下載了!

這邊我們使用的資源是網友改良過的「OIDv4 Toolkits」,他是一個可以幫助下載Open Image V4的工具,經過實測V6也可以進行下載,我會選擇改良版本的原因是因為他有一個程式可以幫助我們自動將OpenImage的格式進行轉換,這部分後續會再介紹,現在就讓我們直接開啟一個全新的Colab來下載數據集吧。

首先,由於暫存的空間會在關閉的時候清除資料,所以我們需要綁定雲端硬碟,將數據集下載到自己的雲端雲端硬碟:

from google.colab import drive
drive.mount('/content/gdrive')

執行畫面如下,需要點擊連結取得認證碼:

image00616_916b6aca6d2f2d8a0e8fb3ddc23a7f58e345ca3d.png

輸入完按下Enter就會跳出「Mounted at /content/gdrive」的字樣,接著我們可以移動到工作目錄去,這邊簡單寫了一下防呆,如果沒有工作目錄就直接創建。

import os
working_dir = '/content/gdrive/MyDrive/yolov4'
try:
 os.makedirs(working_dir)
 print('Create directory')
except FileExistsError:
 print('Directory is exists!')
os.chdir(working_dir)
print(f'Move to working space ({os.getcwd()})')

執行完上一步,Colab的位置就會移動到yolov4這個資料夾裡了,接著我們需要將OIDv4 Toolkits下載下來並安裝相關套件:

!git clone https://github.com/theAIGuysCode/OIDv4_ToolKit.git
!pip3 install -r ./OIDv4_ToolKit/requirements.txt

接著就可以使用該Toolkits進行下載了,由於在Colab當中我都會謹慎地在每一個block多做一個路徑的確認,除此之外如果有殘留的舊資料也會先進行刪除,以免混雜在一起,最後我們直接執行main.py這個程式來進行下載,--class 後面可以輸入多個類別,type_csv則是宣告是train還是test的資料夾,limit則是限制下載的數量,接著multiclasses如果設定成True就會把所有的類別放在同一個資料夾當中,最後thread就是線程的數量了。

import os
import shutil
# 確保移動到對的目錄
working_dir = '/content/gdrive/MyDrive/yolov4/OIDv4_ToolKit'
os.chdir(working_dir)
# 如果有舊有的資料集要先刪除
target_dir = 'OID/Dataset/train'
if os.path.exists(target_dir) is True:
 shutil.rmtree(target_dir, ignore_errors=True)
# 於 classes 中找 特定 類別
!python ./main.py downloader --classes 'Apple' --type_csv train --limit 300 --multiclasses 1 --y --n_threads 4

執行畫面如下,下載完之後會在OIDv4_Toolkits/OID/Dataset/train/Apple當中出現圖片以及Label的資料夾存放所有圖片的標籤檔案:

image00712_45463fa037ff81560b24970e8ca391ecae394220.png

此外,這邊順便給大家參考多類別的指令:

!python ./main.py downloader --classes 'Human eye' 'Human mouth' 'Human nose' --type_csv train --limit 500

下載完之後,我們可以用下列的程式稍微檢查一下數據:

import cv2
import matplotlib.pyplot as plt
%matplotlib inline
task = 'Apple'
file_path = f'OID/Dataset/train/{task}'
label_dir = f'OID/Dataset/train/{task}/Label'
# 隨機選一張圖片
import random
file_list = os.listdir(file_path)
trg_id = random.randint(0, len(file_list))
# 取得 圖檔 以及 標籤檔
title, ext = os.path.splitext( file_list[trg_id] )
img_path = os.path.join(file_path, title+'.jpg')
label_path = os.path.join(label_dir, title+'.txt')
# 讀取圖片
img = cv2.imread(img_path)
# 開啟標籤檔 並繪製 bonding box
with open(label_path) as f:
 for line in f.readlines():
 # 取得座標
 x1,y1,x2,y2 = [ round(float(c)) for c in line.rstrip('\n').split(' ')[1:]] 
 # 繪製bbox
 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 1)
# 在 Jupyter Notebook 上需要轉換成 Matplot 顯示才行
fig = plt.gcf()
fig.set_size_inches(18, 10)
plt.axis('off')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()

執行結果如下:

image0088_51da354a4f2d2136d5448edf044546cebb8f1d95.png

稍微確認過後,我們需要將原本Open Image的標籤形式轉換成YOLO可用的形式,原本Open Image的形式是 「<class_name> <left_x> <left_y> <right_x> <right_y>」,如下圖所示:

image0099_fc6b0efb595fc4b32c3b6b969c2df2fc64869701.png

但是YOLO的格式是「<class_id> <center_x> <center_y> <width> <height>」,並且都會進行正規化例如<center_x>= <absolute_x> / <image_width>又或者<height> = <absolute_height> / <image_height>,所以上面那個標籤檔轉換完就會如下圖所示:

image0105_90df29dcd9d192252280b469a1856905137d0c50.png

由於經過正規化的關係所以數值會介於0~1之間,剛剛上述的動作可以自己撰寫程式進行換算,不過我們下載的改良版Toolkits已經有寫好程式,我們可以直接使用下列程式去轉換,不過在轉換之前我們需要先修改OIDv4_Toolkits的classes.txt檔案,在classes.txt當中輸入我們的所有標籤。

image0116_877da320ae97f5a0f0746bb5e259833fa7a57c2b.png

接著就可以進行轉換,執行完這個程式後:

%cd '/content/gdrive/MyDrive/yolov4/OIDv4_ToolKit'
!python ./convert_annotations.py

接著我們需要把整理好的數據複製到對應的資料夾當中:

import shutil
# task = 'custom'
task = 'Apple'
shutil.copytree(f'/content/gdrive/MyDrive/yolov4/OIDv4_ToolKit/OID/Dataset/train/{task}', f'../darknet/data/{task}')
print('Finish')

最後需要刪除舊有的標籤檔案( Label資料夾):

import os
task = 'Apple'
os.chdir('/content/gdrive/MyDrive/yolov4/darknet/')
shutil.rmtree(f'./data/{task}/Label')

到目前我們已經完成數據擊的下載與整理,接著就要進行Trainning了!

使用特定數據集進行Transfer Learning

我的作法會在這邊直接新增一個Colab檔案把下載數據集與訓練分開來,所以需要重新掛接雲端硬碟以及移動到工作目錄:

from google.colab import drive
drive.mount('/content/gdrive')

掛接完就可以進行移動,用跟之前一樣的程式碼即可:

import os
working_dir = '/content/gdrive/MyDrive/yolov4'
try:
 os.makedirs(working_dir)
 print('Create directory')
except FileExistsError:
 print('Directory is exists!')
os.chdir(working_dir)
print(f'Move to working space ({os.getcwd()})')

接著需要下載並且進入該資料夾:

import os
darknet_path = os.path.join(working_dir,'darknet')
if os.path.exists( darknet_path ):
 print('Darknet is exists!')
else:
 !git clone https://github.com/AlexeyAB/darknet.git

緊接著需要建構函式庫,大約3分鐘:

import time
%cd /content/gdrive/MyDrive/yolov4/darknet
print('\n\nBuild Darknet...')
t_start = time.time()
# 修改 Make File
!sed -i 's/OPENCV=0/OPENCV=1/' Makefile
!sed -i 's/GPU=0/GPU=1/' Makefile
!sed -i 's/CUDNN=0/CUDNN=1/' Makefile
!sed -i 's/CUDNN_HALF=0/CUDNN_HALF=1/' Makefile
!sed -i 's/LIBSO=0/LIBSO=1/' Makefile
# 建構環境
!make
t_end = time.time()
print(f'\n\nFinish ({int(t_end-t_start)}s)')

接著需要把握YOLOv4在darknet當中訓練的幾個大原則:

  1. 新增數據集的名稱與類別的名稱的檔案 ( train.txt , test.txt)
  2. 修改模型的配置檔 (config)
  3. 建立訓練用的檔案 (obj.data, obj.names)
  4. 下載預訓練模型開始訓練

1. 新增數據集的名稱與類別的名稱的檔案 ( train.txt , test.txt)

在這一步我們會將所有的檔案名稱彙整到train.txt以集test.txt,可以透過percentage來宣告有多少筆數據要被歸類到test.txt來驗證使用,最後將這兩個檔案存放到data資料夾當中。

import glob, os
# 目標資料夾的名稱 ( 放到 ./data 當中 )
trg_dir = 'Apple'
# 宣告多少筆數據要變成測試資料
percentage_test = 10;
# 確保資料集存在
datasets_path = f'data/{trg_dir}'
if os.path.exists(datasets_path):
 # 取得 train.txt , test_txt 的完整路徑
 train_txt_path = f'data/{trg_dir}_train.txt'
 test_txt_path = f'data/{trg_dir}_test.txt'
 # 確認是否移動到當前目錄
 os.chdir('/content/gdrive/MyDrive/yolov4/darknet')
 print('當前路徑為:', os.getcwd())
 # 宣告資料集路徑
 dataset_dir = f'data/{trg_dir}'
 print('目標資料夾:', dataset_dir)
 # 建立以及開啟 train.txt, test.txt
 file_train = open(train_txt_path, 'w')
 file_test = open(test_txt_path, 'w')
 # 開始輸入訓練資料
 counter = 1
 index_test = round(100 / percentage_test)
 # glob.iglob 可以將該目錄下所有的 .jpg 儲存成一個 List
 for file_path in glob.iglob(os.path.join(dataset_dir, "*.jpg")):
 if counter == index_test:
 counter = 1
 file_test.write(file_path + "\n")
 else:
 file_train.write(file_path + "\n")
 counter = counter + 1
 file_train.close()
 file_test.close()
 print('Finish')
else:
 print(f'Check the target datasets is exists ({datasets_path})')

2. 修改模型的配置檔 (config)

接著我需要修改模型用的配置檔案,我這邊使用的是tiny版本的所以要去修改的是cfg/yolov4-tiny-custom.cfg,這邊有幾個重點要修改:

1.類別的數量 (classes_num),其中每一個YOLO層都要修改類別數量;

2.濾波器的數量 (target_filter) 這個會與類別數量有關,修改每一個YOLO層的前一個Convolution層,其中的filter=255都要都要修改成對應的數字,必須要符合 ( classes + 5 * 3 )這個公式,是YOLO輸出維度的關係,5代表的是<classes_id><center_x><center_y><width><height>,這部分推薦大家去看吳恩達的YOLO介紹,將會很清楚明白原因為何!

3.訓練的總次數 (max_batches),這邊YOLO是建議類別的數量乘上2000,但不能低於6000回,所以這邊我有寫了一個簡單的判斷;

4. 接著就是learning rate的調整,這邊YOLO也是建議使用訓練到八成以及九成的時候在進行調整。

# 剛剛宣告的 target directory
# trg_dir = 'Apple'
target_cfg = f"cfg/yolov4-tiny-{trg_dir}.cfg"
classes_nums = 1
target_filter = int((classes_nums+5)*3)
max_batches = classes_nums*2000 if classes_nums>=3 else 6000
steps_1, steps_2 = int(max_batches*0.8), int(max_batches*0.9)
info = '\
classes_num : {}\n\
target_filter : {}\n\
max_batches : {}\n\
steps : {},{}\n\
'.format(classes_nums, target_filter, max_batches, steps_1, steps_2)
print(info)
!cp cfg/yolov4-tiny-custom.cfg {target_cfg}
!sed -i '7c subdivisions=16' {target_cfg}
!sed -i '8c width=416' {target_cfg}
!sed -i '9c height=416' {target_cfg}
# classes*2000 = max_batches
!sed -i '20c max_batches={max_batches}' {target_cfg}
# step = max_batches*80% and 90%
!sed -i '22c steps={steps_1},{steps_2}' {target_cfg}
# last layer's filter = ( classes + 5 ) * 3
# classes代表有幾個類別 而這邊5代表的是 class,x1,x2,y1,y2 3代表rgb通道數
!sed -i '212c filters={target_filter}' {target_cfg}
!sed -i '220c classes={classes_nums}' {target_cfg}
!sed -i '263c filters={target_filter}' {target_cfg}
!sed -i '269c classes={classes_nums}' {target_cfg}
print(f'Created {target_cfg}.')

3. 建立訓練用的檔案 (obj.data, obj.names)

注意obj是代名詞,這邊會替換成trg_dir的名稱,其中obj.data存放訓練所需要的資訊,像是類別有幾類、訓練的檔案名稱、驗證的檔案名稱、類別的名稱、權重儲存的位置,obj.names會存放所有類別的名稱,直接看程式可能會比較清楚,最後這兩個檔案會被存放在data資料夾裡:

import os
# 剛剛宣告的 target directory
# trg_dir = 'Apple'
obj_names_cnt=[
 'Apple' 
]
classes_nums = len(obj_names_cnt)
obj_data = f'data/{trg_dir}.data'
obj_names = f'data/{trg_dir}.names'
obj_data_cnt = [
f'classes = {classes_nums}',
f'train = data/{trg_dir}_train.txt',
f'valid = data/{trg_dir}_test.txt',
f'names = data/{trg_dir}.names',
'backup = ../backup-tiny/'
]
# 寫入data檔案
with open(obj_data, 'w') as f:
 [ f.write(i+'\n') for i in obj_data_cnt]
# 寫入name檔案
with open(obj_names, 'w') as f:
 [ f.write(i+'\n') for i in obj_names_cnt]
# 確保儲存權重的資料夾要存在
try:
 import os
 os.mkdir('../backup-tiny/')
except FileExistsError:
 print("Direction is exist !!!")

4. 下載預訓練模型開始訓練

上述檔案都準備完成之後我們就可以下載預訓練模型來進行transfer learning了,這邊yolov4有幫我們準備好可供預訓練的模型 (yolov4-tiny.conv.29):

!wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.conv.29

接著就可以運行下列指令進行訓練,這邊的trg_dir是之前所宣告的:

%cd '/content/gdrive/MyDrive/yolov4/darknet'
!./darknet detector train data/{trg_dir}.data cfg/yolov4-tiny-{trg_dir}.cfg ./yolov4-tiny.conv.29 -dont_show -map

開始訓練之後,每1000次訓練都會備份一次權重,所以會在backup-tiny的資料夾當中就可以看到如同我畫面中的內容,每一個檔案都有紀錄多少批次的時候儲存的,除此之外還有best、last等權重。

image0126_77601e97e88407c7517f6caa61ca183900942ebb.png

補充1. 當訓練中斷了

我們可以使用backup-tiny當中的last.weights來接續訓練:

!./darknet detector train data/{trg_dir}.data cfg/yolov4-tiny-{trg_dir}.cfg ../backup-tiny/yolov4-tiny-{trg_dir}_last.weights -dont_show -map

補充2. 模擬使用者還在線上

這部分是我在網路上找到的方式,我們可以透過下列程式不斷開啟關閉特定視窗來模擬使用者還在使用中,首先我們必須先按Ctrl+Shift+i,點擊Console於最底部貼上程式碼並且按下Enter執行:

function ClickConnect(){
console.log("Working"); 
document
.querySelector('#top-toolbar > colab-connect-button')
.shadowRoot.querySelector('#connect')
.click() 
}
setInterval(ClickConnect,60000)

於Colab上進行測試

這個部分與上篇的程式相同,有些不同的地方是在宣告完darknet_helper之後就直接導入圖片 (image) 並且進行辨識,取得辨識完的結果用for迴圈將所有辨識到的物件畫上邊界窗以及標籤資訊,最後用matplot顯示出來。

import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# import darknet functions to perform object detections
from darknet import *
# load in our YOLOv4 architecture network
# 執行剛剛訓練好的模型
#trg_dir = 'Apple'
network, class_names, class_colors = load_network(f"cfg/yolov4-tiny-{trg_dir}.cfg", f"data/{trg_dir}.data", f"../backup-tiny/yolov4-tiny-{trg_dir}_best.weights")
width = network_width(network)
height = network_height(network)
# darknet helper function to run detection on image
def darknet_helper(img, width, height):
 darknet_image = make_image(width, height, 3)
 img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
 img_resized = cv2.resize(img_rgb, (width, height),
 interpolation=cv2.INTER_LINEAR)
 # get image ratios to convert bounding boxes to proper size
 img_height, img_width, _ = img.shape
 width_ratio = img_width/width
 height_ratio = img_height/height
 # run model on darknet style image to get detections
 copy_image_from_bytes(darknet_image, img_resized.tobytes())
 detections = detect_image(network, class_names, darknet_image)
 free_image(darknet_image)
 return detections, width_ratio, height_ratio
# run test on person.jpg image that comes with repository
image = cv2.imread('./data/apple_test.jpg')
detections, width_ratio, height_ratio = darknet_helper(image, width, height)
for label, confidence, bbox in detections:
 left, top, right, bottom = bbox2points(bbox)
 left, top, right, bottom = int(left * width_ratio), int(top * height_ratio), int(right * width_ratio), int(bottom * height_ratio)
 cv2.rectangle(image, (left, top), (right, bottom), class_colors[label], 2)
 cv2.putText(image, "{} [{:.2f}]".format(label, float(confidence)),
 (left, top - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
 class_colors[label], 2)
fig = plt.gcf()
fig.set_size_inches(18, 10)
plt.axis('off')
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.show()

結果如下:

image0139_fbf84449e6ece90601eb67f826da7b63a557c8ef.png

image0144_2ff90b150b60a6926b1e9f8f1497ab01bbee68a9.png

下載至Jetson Nano並進行測試

由於程式細節與Colab上相同只差在darknet建構,所以這邊我提供流程:

  1. 參考此篇文章在Jetson Nano上建構darknet (https://www.rs-online.com/designspark/nvidia-jetson-nanotensor-rtyolov4-cn )。
  2. 從Google Drive中下載訓練好的 {obj}.data、{obj}.names、yolov4-tiny-{}.cfg、yolov4-tiny-{}.weights,並且放在Jetson Nano的darkent中。
  3. 執行程式或參考此篇文章自行撰寫程式 (https://www.rs-online.com/designspark/nvidai-jetson-nano-opencvyolov4-cn ) 。

結語

這篇的資訊量算是蠻大的,大家可以多多善用Colab進行訓練,訓練完之後在放置到Jetson Nano中去做Inference等應用,最後鼓勵大家多去翻看darknet的github它不僅僅有更詳細的細節也在issue當中可以找到很多常遇到的問題,除此之外還有提供幫助訓練的技巧。

CAVEDU Education is devoted into robotics education and maker movement since 2008, and is intensively active in teaching fundamental knowledge and skills. We had published many books for readers in all ages, topics including Deep Learning, edge computing, App Inventor, IoT and robotics. Please check CAVEDU's website for more information: http://www.cavedu.com, http://www.appinventor.tw
DesignSpark Electrical Logolinkedin