はじめに
この記事はcvlibで使うように独自データセットでyolov4を学習した手順をまとめたものです
ファイル周りの処理やファイルリストの作成にElixirを使用しています
環境
OS ubuntu 20.04.2 LTS
CPU intel core i7-9700k
GPU Geforce RTX 3060
メモリ 32GB
CUDA 11030
cuDNN 8.2.1
OpenCV 4.2.0
datasetの準備
データはGoogle Open Images Datasetから
pythonのopenimagesを使用してダウンロードします
darknet形式のannotationファイルを出力してくれるのでOIDv4_Toolkitより楽です
インストールはpipで行いダウンロード先を作っておきます
pip install openimages
mkdir ~/openimages
ダウンロードはコマンドかスクリプトで行います
コマンドはdirを指定してlabelsに取得したいクラス
クラス名はサークルかcsvに一覧があるのでそこから選んでください
クラス名の注意点としてv4とv5で以下のように微妙に違ってダウンロード時にエラーになるので気をつけましょう
Bat -> Bat (Animal)
Lady bug -> Ladybug
Jugger -> Jaguar (Animal)
oi_download_dataset --csv_dir ~/openimages --base_dir ~/openimages --labels Dog Cat --format darknet
from openimages.download import download_dataset
labels = ["Woodpecker","Blue jay","Ostrich","Penguin","Raven","Chicken","Eagle","Owl","Duck","Canary","Goose","Swan","Falcon","Parrot","Sparrow","Turkey","Tick","Centipede","Starfish","Isopod","Squid","Jellyfish","Shrimp","Bee","Beetle","Ladybug","Ant","Caterpillar","Butterfly","Dragonfly","Scorpion","Worm","Spider","Snail","Bat (Animal)","Brown bear","Panda","Polar bear","Teddy bear","Cat","Fox","Jaguar (Animal)","Lynx","Red panda","Tiger","Lion","Dog","Leopard","Cheetah","Otter","Raccoon","Camel","Cattle","Giraffe","Rhinoceros","Goat","Horse","Hamster","Kangaroo","Koala","Mouse","Pig","Rabbit","Squirrel","Sheep","Zebra","Monkey","Hippopotamus","Deer","Elephant","Porcupine","Hedgehog","Bull","Antelope","Mule","Dolphin","Whale","Sea lion","Harbor seal","Skunk","Alpaca","Armadillo","Dinosaur","Lizard","Snake","Turtle","Tortoise","Sea turtle","Crocodile","Frog","Goldfish","Shark","Rays and skates","Seahorse","Shellfish","Oyster","Lobster","Shrimp","Crab","Person"]
labels.sort()
download_dataset("openimages",labels, annotation_format="darknet")
darknet学習用に準備
darknetの学習には以下が必要になりますのでそちらも準備していきましょう
- クラス名一覧
- 画像とアノテーションデータ
- train,testの画像リスト
クラス名一覧
openimagesでダウンロードしたときに darknet_obj_names.txtというファイルが作成されていますのでそちらをそのまま使用します
画像とアノテーションデータ
darknetの学習用データセットは1つのフォルダに画像ファイルと同名アノテーションデータのファイルを以下のようにいれます
└── datasets
├── image1.jpg
├── image1.txt
...
├── imagex.jpg
└── imagex.txt
画像ファイルとアノテーションデータはopenimagesでダウンロードされていますがクラスごとに別れていますのでそれをまとめていきましょう
アノテーションデータの中身はこんな感じで先頭はIDでそれ以降が対象が描画されている位置になります
IDはopenimageのIDではなく、darknet_obj_names.txtの行番号になります
これは犬だけをダウンロードしたときなので dog => 0を示しています
0 0.77625 0.597092 0.21999999999999997 0.20356400000000005
ダウンロード先のopenimagesに移動して iexを起動させて以下を実行します
mkdirでまとめる用のdatasetフォルダを作成しておきましょう
File.ls!
|> Enum.each(fn d ->
System.cmd("/bin/sh",["-c","mv #{d}/images/* ./dataset/"])
System.cmd("/bin/sh",["-c","mv #{d}/darknet/* ./dataset/"])
end)
File.ls!でクラスのフォルダ一覧を取得し
Enum.eachでフォルダ以下の画像とアノテーションデータを移動させるコマンドをbashで移動させます
cpでもいいですが容量がでかいのでmvにしています、ストレージ容量と相談でお好きな方を
System.cmd("mv",opt)だと*(wildcard)が使えないのでsh -cで実行しています
Train, Test 画像のリスト
こちらも, train-annotations-bbox.csvとtest-annotations-bbox.csvがありますが
全open imagesのファイルのリストなので不要なものを除いていきます
中身はこんな感じ
ImageID,Source,LabelName,Confidence,XMin,XMax,YMin,YMax,IsOccluded,IsTruncated,IsGroupOf,IsDepiction,IsInside
000002b66c9c498e,xclick,/m/01g317,1,0.012500,0.195312,0.148438,0.587500,0,1,0,0,0
000002b66c9c498e,xclick,/m/01g317,1,0.025000,0.276563,0.714063,0.948438,0,1,0,0,0
000002b66c9c498e,xclick,/m/01g317,1,0.151562,0.310937,0.198437,0.590625,1,0,0,0,0
class = File.read!("path/to/train-annotations-bbox.csv")
|> String.split("\n") # 一塊の文字列で来るので改行コードで分割
|> Enum.map(fn s -> String.split(s, ",") end) # ListになったのでEnum.mapでコンマで分割
|> Enum.drop(1) # header部分が邪魔なので先頭をdrop
|> Enum.filter(fn s -> File.exists?("dataset/#{Enum.at(s, 0)}.jpg") end)
|> Enum.map(fn s -> Enum.at(s, 0) end)
File.open("train.txt",[:write], fn file ->
Enum.each(class, fn str -> IO.write(file, "/home/user_name/openimages/dataset/#{str}.jpg\n") end)
end)
前半はFile.read!だと結果が文字列で返ってくるのでsplitでcsvの形にして
imageIDと同じ画像が存在するか?を条件にフィルタリングして、imageIDだけのListを作成しています
後半はimageIDのListの内容を絶対パスで書き込んでいます
darknetの仕様なので相対パスではなく絶対パスにしてください
darknetの準備
データセット準備が整ったのでdarknetの準備をしていきましょう
こちらを参考にインストール
git clone https://github.com/AlexeyAB/darknet.git
cd darknet
vim Makefile
変更箇所
GPUがリストになかったけどアーキテクチャ的に同じだろうから ARCHは86
nvccのパスがエラーになったのでフルパスでセット
GPU=1
CUDNN=1
CUDNN_HALF=0
OPENCV=1
ARCH= -gencode arch=compute_86,code=[sm_86,compute_86]
NVCC=/usr/local/cuda/bin/nvcc
変更したらmakeでコンパイル
.dataファイルの準備
train,testファイルの一覧や学習途中のweigthファイルの保存先を設定します
ファイル名はお好みで今回はdog.dataとしています
エラーになるのでそれぞれ絶対パスで設定してください
class= 1 # 今回はdogだけなので1
train = /home/user_name/openimages/train.txt
valid = /home/user_name/openimages/test.txt
names = /home/user_name/openimages/darknet_obj_names.txt
backup = /home/user_name/darknet/backup/
configの編集
custom-objectのときはこれ使ってとここにあるのでyolov4-custom.cfgを使用します
git cloneしたdarknetのcfg以下あるのでコピーしておく
cp darknet/cfg/yolov4-custom.cfg ~/openimages/
こちらを参考に編集
subdivisions=64
max_batches = 46000
4000以上で、クラス数×2000
steps=36800,41400
max_batchesの80%,90%
classes=1
クラス数を指定します
下の方のyoloが3つあるのでそこを編集
filters=18
(クラス数+5)×3
yoloの1つ前のconv層にあるので編集
学習
以下のコマンドで学習を開始します
重みファイルのyolov4.conv.137はこちらからダウンロード
darknet/darknet detector train ~/openimages/dog.data ~/openimages/yolov4-custom.cfg ~/openimages/yolov4.conv.137
あとは気長にお待ち下さい
cvlibで使う
学習中でも学習済みパラメーターがbackupに保存されてると思うのでそちらを使用して
wights, .cfg, darknet_obj_names.txtを渡せば物体検知を行って検知した箇所を囲ってくれます
https://github.com/arunponnusamy/cvlib/blob/master/examples/yolo_custom_weights_inference.py
import cvlib as cv
from cvlib.object_detection import YOLO
import sys
import cv2
weights = sys.argv[1]
config = sys.argv[2]
labels = sys.argv[3]
# read input image
image = cv2.imread(sys.argv[4])
yolo = YOLO(weights, config, labels)
# apply object detection
bbox, label, conf = yolo.detect_objects(image)
print(bbox, label, conf)
# draw bounding box over detected objects
yolo.draw_bbox(image, bbox, label, conf, write_conf=True)
# display output
# press any key to close window
cv2.imshow("object_detection", image)
cv2.waitKey()
# save output
cv2.imwrite("yolo_object_detection.jpg", image)
# release resources
cv2.destroyAllWindows()
python yolo_custom_weights_inference.py yolov4-custom_2000.weights yolov4-custom.cfg darknet_obj_names.txt dog.jpeg
無事犬だけを検知するyolov4が完成しました!
本記事は以上になりますありがとうございました
参考ページ
https://github.com/arunponnusamy/cvlib
https://qiita.com/Ihmon/items/a331d63a7a5d3fa51918
https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects
https://pjreddie.com/darknet/yolo/
https://qiita.com/kitata9/items/936c97f0ac33dae13ab5
https://pypi.org/project/openimages/
https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.conv.137
https://storage.googleapis.com/openimages/2018_04/bbox_labels_600_hierarchy_visualizer/circle.html
https://github.com/EscVM/OIDv4_ToolKit
https://stackoverflow.com/questions/49952153/elixir-shell-command-with-wildcard-failing-when-using-system-cmd