🚗

labelme+YOLOXで自作データセットの学習!

2022/01/10に公開3

前回記事の終わりで「YOLOXの記事書きたいなぁ・・・」と言っていましたが、今回はそのYOLOXを実際に動かしてみたいと思います!

ただ、「COCOデータセットを使ってYOLOXを学習」だとみんなやってる味気ないので、自作データセットを用いてYOLOXの学習を行ってみたいと思います!

環境

前回の記事で作成したDocker環境内で学習していきたいと思います!
https://zenn.dev/opamp/articles/7880812d78226f

画像データにラベル付けを行う

YOLOXを学習させるためにはデータセットが必要となります。今回はデータセットを自前で作成するところから始めていきます。
以降の作業は、画像データがすでに存在する(すでに撮影済み)であると仮定して進めていきます。

今回はアノテーションによく利用されるlabelmeというツールを用いてアノテーションします。

labelmeのインストール

labelmeはローカルの環境にインストールします。

pip install labelme

Successfully installedと表示されればインストールは完了です!

labelmeの起動

labelmeを利用する際は、ターミナルにて

labelme

と入力することで、GUIが立ち上がります。こんな感じですね。

アノテーションを開始するには、左のメニューバーからOpenDirを選択し、画像が保存されているフォルダを選択します。
フォルダを選択すると、保存されている画像が表示されると思います。
筆者環境では、仕事猫の検出を行ってみたいと思います。

アノテーション

今回は物体検出なので長方形のアノテーションになります。
画像上で右クリックするとメニューが表示されますが、その中からCreate Rectangleを選択します。
カーソルが十字に変化したら、検出したい物体の左上をクリックします。すると長方形が描画されると思うので、検出したい物体をすべて囲めるようにマウスを動かし、もう一度クリックします。

すると新たなウィンドウが表示されます。このウィンドウは今囲った対象のラベルを選択する画面です。ラベルをまだ登録していない場合は、Enter object labelに対象のラベル名を入力、すでに存在する場合は下から選択しokをクリックします。

これでアノテーションは完了です!ほかの画像に対しても同様に行いましょう!(遠い目)

アノテーションが終われば、学習に利用するデータと検証に利用するデータを別々のフォルダに格納して保存しておきます。

また、利用したクラスを記載したtextデータを作成しておきましょう。

classes.txt
__ignore__
class1
class2
class3

アノテーションデータの変換

続いてデータセットの変換を行います。まずは変換の際に必要となるpycocotoolsのインストールから始めましょう。
こちらはwindowsにインストールします。

pip install pycocotools

もしインストールの際に以下のようなエラーが発生した場合は、

error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/

https://visualstudio.microsoft.com/visual-cpp-build-tools/
にアクセスし、インストールラーをダウンロードします。

続いて
https://self-development.info/「microsoft-visual-c-14-0-or-greater-is-required-」が出た場合の対処方法/
に従いながら準備を進めてください!再起動してくださいと出ると思うので、必ず再起動をして下さい。(セットアップが終わりません・・・)

インストールが終われば、続いて
https://github.com/wkentaro/labelme
をクローンします。

git clone https://github.com/wkentaro/labelme

クローンが終わると、いよいよ変換に移ります。
変換にはlabelme2coco.pyというものを使います。labelme2coco.py/labelme/examples/instance_segmentationに入っていますので、そこまで移動しましょう!

移動先に作成したデータ類を移動させます。こんな感じですね。

では変換していきましょう!変換は

python labelme2coco.py [データセットのフォルダ] [保存名] --labels classes.txt

のように書くことができます。
筆者の環境ですと、

python labelme2coco.py 01.train train_dataset --labels classes.txt
python labelme2coco.py 02.val val_dataset --labels classes.txt

のようになります。

次に生成されたデータセットをYOLOX用に変更します。
まずはじめにフォルダを作成します。

mkdir -p dataset/train2017/
mkdir -p dataset/val2017/
mkdir -p dataset/annotations

続いて生成された学習データセットの中にある画像(JPEGImagesの中の画像)をdataset/train2017/に移動します。
同様に検証データセットの中にある画像をdataset/val2017/へ移動します。
最後に各データセットフォルダの中にあるannotations.jsontrain_annotations.jsonval_annotations.jsonに変更し、dataset/annotationsに格納します。
ここまででフォルダ構成は次のようになっています。

dataset
├── train2017
│   └── 画像.jpg ... (学習用画像群)
└── val2017
│   └── 画像.jpg ... (評価用画像軍)
└── annotations
  ├── train_annotations.json
  └── val_annotations.json

最後にアノテーションデータの修正を行います。
datasetフォルダ内にfix_annot.pyファイルを作成し、以下を記述します。

fix_annot.py
import os 
import glob

annot_list = glob.glob(os.path.join('./annotations/','*.json'))

for path in annot_list:
    with open(path , 'r' ,encoding = 'utf-8') as f:
        jf = json.load(f)
    for data in jf['images']:
        data['file_name'] = data['file_name'].split('\\')[-1]
    with open(path , 'w' ,encoding = 'utf-8') as f:
        json.dump(jf , f)

これでデータセットは完成です!

YOLOXのインストール

続いてYOLOXのインストールを行います。ここからはDockerで作業していきます。
YOLOXのGithubサイト
https://github.com/Megvii-BaseDetection/YOLOX
からインストール方法を確認。ちょっと変更・・・。

git clone https://github.com/Megvii-BaseDetection/YOLOX
cd YOLOX
pip3 install -U pip && pip3 install -r requirements.txt
pip3 install -v -e .  # or  python3 setup.py develop
pip3 install cython; pip3 install 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'

このコマンドで一通り入ります!

学習定義ファイルの作成

今回は小さなモデル、YOLOX-Sモデルで実装していきます!
YOLOXは学習の際に学習内容を定義したファイルを必要とします。
YOLOXの学習定義ファイルはYOLOX/yolox/exp/yolox_base.pyを親クラスとした構築されます。
まずはyolox_base.pyを確認してみましょう。

yolox_base.py
        self.num_classes = 80
        self.depth = 1.00
        self.width = 1.00
        self.act = 'silu'

        # ---------------- dataloader config ---------------- #
        # set worker to 4 for shorter dataloader init time
        self.data_num_workers = 4
        self.input_size = (640, 640)  # (height, width)
        # Actual multiscale ranges: [640-5*32, 640+5*32].
        # To disable multiscale training, set the
        # self.multiscale_range to 0.
        self.multiscale_range = 5
        # You can uncomment this line to specify a multiscale range
        # self.random_size = (14, 26)
        self.data_dir = None
        self.train_ann = "instances_train2017.json"
        self.val_ann = "instances_val2017.json"

        # --------------- transform config ----------------- #
        self.mosaic_prob = 1.0
        self.mixup_prob = 1.0
        self.hsv_prob = 1.0
        self.flip_prob = 0.5
        self.degrees = 10.0
        self.translate = 0.1
        self.mosaic_scale = (0.1, 2)
        self.mixup_scale = (0.5, 1.5)
        self.shear = 2.0
        self.enable_mixup = True

        # --------------  training config --------------------- #
        self.warmup_epochs = 5
        self.max_epoch = 300
        self.warmup_lr = 0
        self.basic_lr_per_img = 0.01 / 64.0
        self.scheduler = "yoloxwarmcos"
        self.no_aug_epochs = 15
        self.min_lr_ratio = 0.05
        self.ema = True

        self.weight_decay = 5e-4
        self.momentum = 0.9
        self.print_interval = 10
        self.eval_interval = 10
        self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]

        # -----------------  testing config ------------------ #
        self.test_size = (640, 640)
        self.test_conf = 0.01
        self.nmsthre = 0.65

dataloader configには学習データの配置場所、使用するCPU数や画像サイズなどが定義されています。
transform configには学習時のデータ拡張の方法が定義されています。
training configには学習時の戦略が書かれています。(最大Epoch数や学習率など)
testing configにはテスト時の設定が記載されています。

今回はdataloader configのみ変更していきます。

独自のモデル設定を作る前に、ひな形の設定をコピーしましょう。
ひな形の設定はYOLOX/exp/defaultに保存されています。

この中から今回はyolox_s.pyYOLOX直下にコピーします。
ではコピーしたファイルを編集していきます。
以下のコード内のコメントを参考にしながら設定を変更してください!

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Copyright (c) Megvii, Inc. and its affiliates.


import os

from yolox.exp import Exp as MyExp


class Exp(MyExp):
    def __init__(self):
        super(Exp, self).__init__()
        self.depth = 0.33
        self.width = 0.50
        self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]

	## データセットの場所を定義(./datasets/フォルダに配置した場合は以下のようになる)
	self.data_dir = "./datasets/dataset"
        self.train_ann = "train_annotations.json"
        self.val_ann = "val_annotations.json"
	
	## クラス数の変更
	self.num_classes = 3
	
	## 評価間隔を変更(初期では10epochごとにしか評価が回らない)
	self.eval_interval = 1

これが完成すれば、いよいよ学習です!

YOLOXの学習

では学習していきましょう!
学習にはYOLOX/tools/train.pyが必要なので、YOLOXの直下にコピーしておきましょう。

cp ./tools/train.py ./

また学習済みモデルを利用する際は、先にダウンロードしておきます。
GithubのBenchmarkのところからダウンロードできます。
YOLOX-sを使う場合は以下のコマンドでも大丈夫です!

wget https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s.pth

学習は以下のコマンドで行います!

python train.py -f yolox_s.py -d 1 -b 16 --fp16 -o -c ./yolox_s.pth --cache

引数の説明は

  • -f:設定ファイルのパス
  • -d:利用するGPUの数
  • -b:バッチサイズ
  • -o:学習前に利用するGPUメモリを予約する
  • -c:学習済みモデルのパス
  • --fp16:畳み込み層などの計算をfloat16で行う。精度は若干落ちるが、学習が高速になる
  • --cache:学習済みデータをYOLOXが高速に読み出せる形に変形する。学習データを変更する際はデータセットフォルダ内に生成されたキャッシュデータを削除しないと、データが変更されないので注意が必要。

もしOpenCVのエラーが出力された場合は

apt-get install -y libgl1-mesa-dev
apt-get install -y libglib2.0-0

を実行してみてください。

無事学習が開始され

2022-01-09 14:46:09 | INFO     | yolox.core.trainer:126 - args: Namespace(batch_size=16, cache=True, ckpt='./yolox_s.pth', devices=1, dist_backend='nccl', dist_url=None, exp_file='yolox_s.py', experiment_name='yolox_s', fp16=True, machine_rank=0, name=None, num_machines=1, occupy=True, opts=[], resume=False, start_epoch=None)
2022-01-09 14:46:09 | INFO     | yolox.core.trainer:127 - exp value:
╒══════════════════╤═════════════════════════════════════════════════════════╕
│ keys             │ values                                                  │
╞══════════════════╪═════════════════════════════════════════════════════════╡
│ seed             │ None                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ output_dir       │ './YOLOX_outputs'                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ print_interval   │ 10                                                      │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ eval_interval    │ 1                                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ num_classes      │ 3                                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ depth            │ 0.33                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ width            │ 0.5                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ act              │ 'silu'                                                  │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ data_num_workers │ 4                                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ input_size       │ (640, 640)                                              │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ multiscale_range │ 5                                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ data_dir         │ './datasets/dataset/images'                             │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ train_ann        │ './datasets/dataset/annotations/train_annotations.json' │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ val_ann          │ './dataset/dataset/annotations/val_annotations.json'    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ mosaic_prob      │ 1.0                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ mixup_prob       │ 1.0                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ hsv_prob         │ 1.0                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ flip_prob        │ 0.5                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ degrees          │ 10.0                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ translate        │ 0.1                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ mosaic_scale     │ (0.1, 2)                                                │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ mixup_scale      │ (0.5, 1.5)                                              │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ shear            │ 2.0                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ enable_mixup     │ True                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ warmup_epochs    │ 5                                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ max_epoch        │ 300                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ warmup_lr        │ 0                                                       │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ basic_lr_per_img │ 0.00015625                                              │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ scheduler        │ 'yoloxwarmcos'                                          │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ no_aug_epochs    │ 15                                                      │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ min_lr_ratio     │ 0.05                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ ema              │ True                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ weight_decay     │ 0.0005                                                  │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ momentum         │ 0.9                                                     │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ exp_name         │ 'yolox_s'                                               │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ test_size        │ (640, 640)                                              │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ test_conf        │ 0.01                                                    │
├──────────────────┼─────────────────────────────────────────────────────────┤
│ nmsthre          │ 0.65                                                    │
╘══════════════════╧═════════════════════════════════════════════════════════╛

のような画面が出れば成功です!あとは気長に待ちましょう・・・。

推論する!

ではお待ちかねの推論タイムです!
YOLOXフォルダ内にYOLOX_outputs/yolox_sが作成されていると思います。(yolox_s部分は設定ファイルの名前によって変化します。例:設定ファイルをylx_s.pyにしていると、YOLOX_outputs/ylx_sになります。)

その中からbest_ckpt.pthYOLOX直下にコピーします。

cp ./YOLOX_outputs/yolox_s/best_ckpt.pth/ ./

では推論してみましょう!

cp tools/demo.py ./
python demo.py image -f [設定ファイル].py --device gpu --fp16 --path [検査画像までのパス] -c ./best_ckpt.pth --save_result

・・・。精度はよくないですね・・・。学習の感じを見ていると早々に過学習している傾向にあったので、22枚の画像ではだめでした・・・。
多分YOLOX-Sはこのデータセットに対して大きすぎるモデルだったのでしょう。Nanoとか使えばよかったのかな?

あと、この画像を見てもらうとわかる通り、表示されるラベルがおかしいことになってます。(carって出てますね)

これはCOCOデータセットのラベルが利用されていることが原因です。
では最後にこれを修正しましょう。

demo.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Copyright (c) Megvii, Inc. and its affiliates.

import argparse
import os
import time
from loguru import logger

import cv2

import torch

from yolox.data.data_augment import ValTransform
#from yolox.data.datasets import COCO_CLASSES #ここをコメントアウト
from yolox.exp import get_exp
from yolox.utils import fuse_model, get_model_info, postprocess, vis

COCO_CLASSES = ('class1','class2','class3') #ここに追記

変化しましたね!(見切れててすみません・・・。お手元の画像で確認してもらえればと思います!)

まとめ

今回はlabelmeとYOLOXで自作データセットの学習にチャレンジしました!
YOLOv5は使いやすいためいろいろな人が利用していますが、YOLOXはYOLOv5と比べると使いにくいのでまだまだ日本語記事が少ないイメージです。

この記事が皆様の開発の一助になれば幸いです!

ではまた!次はCenterNetの実装記事を書きたいな・・・

Discussion

K.MK.M

初めまして、OPampさん、
私もyoloxで実装しているのですが、自分はWindowsのVoTTでデータセットを自作して、学習させました。
私この分野はあまり経験が浅いものでして、少し相談といいますか、アドバイスが欲しいんです。学習後の
推論で、自分が設定したアノテーション名前が全く違って(ほとんどのデータが)出るんです。アノテーションのタグの名前だけはすべて表示に成功しているのですが、例えば自作データセットをリンゴの画像が"apple"、オレンジの画像を"orange"とVoTTで各20枚作成し、yoloxで学習させたとします。すると私の場合リンゴが"orange"、オレンジが"apple"と出るんです。私の場合

python tools/train.py -f yolox_〇〇〇.py -d 1 -b 16 --fp16 -o -c model/yolox_tiny.pth

で走らせました。実際の学習はクラス数=4 各20枚ずつです。
一応yolox_○○○.pyの追加文は以下です、あと、YOLOX\yolox\data\datasets\coco_classes.pyの名前はオリジナルに変えました。

self.num_classes = 4 #今回はクラス=4の検出のため4
self.max_epoch = 50
self.data_num_workers = 4
self.eval_interval = 1

特にエラーで止まるということはないです。学習が足りないのでしょうか?しかし、全く逆に表示されたり
するのは何か不具合なのか、、というところなんですが、一度枚数を減らしてわかりやすいテストにしてみるべきでしょうか? (添付なのですが、このwarningが気になります。)
長文で本当にごめんなさい。お返事お待ちしております。

OPampOPamp

コメントありがとうございます。
ラベルが変化するのは常に一定でそのようになる感じですか?

例で挙げていただいたリンゴにOrange、オレンジにAppleのように常にその割り当てになっている感じでしょうか?
もしこのような場合でしたら、COCO_CLASSES = ('class1','class2','class3')の順番が違う可能性があります。
学習時に割り振った番号と合うように記載("Apple", "Orange")にしていただくと治るかと思います(たぶんですが・・・)

一方で、ランダムにラベルが変わる場合ではそのほかの原因がありそうです!

K.MK.M

お返事大変ありがとうございます。
左様です。一応クラス2まで減らして各400枚程度VoTTで作って学習させたんですが、添付のように全くアベコベにラベリングされ一体なぜ・・・と悩んでおります。 
それと、coco_classesですが、これによってもしスートが変わってしまうなら、もっとクラスを増やしてしまうと、すべて異なった表示になる・・・これが影響しているということは考えにくいと思うのですが、、、今回は試してみます。
ありがとうございます。