【YOLOv8】建築資材の物体検出 (クランプ編)

# はじめに
建築資材の物体検知の2回目となります。
前回はパイプの検知を行いました。

https://qiita.com/yutakam/items/848bb44831d3ee715452

# 検出対象の資材
今回は形状が複雑なクランプの物体検出にトライします。
こんな感じの画像から、クランプ自体を検出して個数を数えるイメージです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351434/458cda66-1c3a-72e0-1705-ca65989fa73b.jpeg" width=70%>

# 学習に使用するデータ
今回も現場で撮影した 35 枚の写真を使用します。
前回同様に以下の手順で学習データを用意しました。
- 元画像を圧縮 (今回は 1/2 にリサイズ) 
- 640px × 640px にランダムに切り取る

1/2 に圧縮した理由は、対象物が細かいためアノテーションが大変かと思ったためです。
学習データは前回同様 640px × 640px の画像を入力にしようと思いますので、元画像を 1/4 圧縮で下げるのと比較して切り取った際に物体が大きめに表示される (インスタンスサイズが大きくなる) ため楽かなと思ったわけです。
ただしこの方法だと推論の性能面でよくないというデメリットもあるという認識です。
以下の記事がとても参考になりました。

https://qiita.com/ground0state/items/01ba52ccc299d9d1e6a0

こちらの文章に該当します。次回からは気を付けようと思います。
> 物体検出AIはバウンディングボックスを回帰問題として予測しているので、学習データに含まれないサイズのバウンディングボックスは予測しにくいです。したがって、　学習データのインスタンスサイズも本番に近いバリエーションを用意することが望ましいです。

# 画像サイズの検討
学習における画像の入力サイズは imgsz パラメーターで指定出来るわけですが、このサイズを大きくすることで理論的には精度向上が期待出来ます。
このサイズの指定はネットワークストライドの倍数 = 32 の倍数ということで、自分の環境で取り得る値の候補としては 1280、960、800 が考えられます。
試しにパイプの学習パラメーターをこれらのパターンに変更して学習が進むか試してみました。(この場合、640px を超える領域にはパディングが追加されます)
VRAM 24GB の RTX 3090 の結果としては
- 1280：out of memory
- 960：ギリギリ回るかなという感じ
- 800：大丈夫そう

という感じでした。
バッチサイズを 16 より下げたくないので 1280 と 960 は断念し、800 でもよいのですが、そこまでして得られるメリットが分かりませんでしたので、やはり既定値の 640px で進めます。
マシンリソースや推論時のスピードの要件等で最適な選択肢を検討する必要があるということで。

また "rect" パラメーターがありますので、これを指定することで学習データを正方形に reshape せず長方形のままトレーニングすることが出来ます。
ただし以下のようなにパフォーマンスが悪かったという事例もあり、ultralytics の創設者・エンジニアの方も "not always the best option" ということで、無難に正方形で学習した方が良さそうな気がしました。
(最後に記載されている通り、各自で様々なパラメーターを総合的に考慮して判断する必要はあります)

https://github.com/ultralytics/ultralytics/issues/4526

画像が横長の長方形の場合、アスペクト比を維持して上下にパディングが入った 640px の正方形に前処理されます。
ただし今回も前述の通り、水増しもかねて元画像から正方形に切り取った画像にアノテーションを施して学習データを用意しました。

その他、imgsz パラメーターで参考になったやり取りを貼り付けます。

https://github.com/ultralytics/ultralytics/issues/3955

https://github.com/ultralytics/ultralytics/issues/2590

# アノテーション
前回同様、Windows 版 labelImg を使用してアノテーションを実施しました。
訓練データと検証データはそれぞれ、282 枚と 122 枚
アノテーションに要した期間は 2 日強くらいです。


# データの水増し
アノテーションした画像のインスタンス数はあまり多くないため、今回はデータの水増し (データオーギュメンテーション：Data Augmentation) を施しました。
使用したライブラリは Albumentations です。
こちらを参考にさせて頂きました。コードもほぼそのまま活用させて頂きました。

https://qiita.com/ToppaD/items/7cc8c54538c8dd5f6af2

変換に使用した関数は、今回は以下としました。
- Rotate (90度 / 180度 / 270度)
- HorizontalFlip
- VerticalFlip

その他様々な関数を以下から確認できます。

https://albumentations.ai/docs/api_reference/full_reference/

具体的に Rotate の 90 度であれば limit のレンジを固定してしまい、p=1 で確実に変換できます。
```py
transform = A.Compose(
    [
        A.Rotate(limit=[90, 90], p=1)
    ],
    bbox_params = A.BboxParams
    (
        format='yolo',
        min_area=1024,
        min_visibility=0.1,
        label_fields=['class_labels']
    )
)
```

実際にクランプは屋外で撮影するため、他の関数として、"RandomRain"、"RandomShadow"、"RandomSunFlare" を学習データに適用してアノテーションしてみようかと思いました。

"RandomShadow" と "RandomSunFlare" はこんな感じです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351434/05549dbe-952b-2d1e-aa97-352178b41e67.jpeg" width=70%>

"RandomShadow" は影の辺の数を指定したり "RandomSunFlare" はフレアサークルの数を指定出来たりします。
今回は、現実的に出来うる現象してはちょっと違うかなと判断し採用はしませんでした。
色々な関数を試して実際にあり得る状況を慎重に検討する必要があります。

とりあえず学習データは 5 倍に増やし、インスタンス数としては訓練データと検証データ合計で約 15,000 となりました。

# 学習
パイプと同じパラメーターで学習してみます。
学習に要した時間とメモリはこのような感じです。

| 学習時間 | VRAM 使用量 |
|:-:|:-:|
|1,792.8 sec | 約 20 GB / 1 epoch|

results.png は以下の通りです。
![results.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351434/88ded6b1-99ac-25d5-3d11-739046a9cfcb.png)

# 学習したモデルを使用して推論

- うまく検出しているパターン
実際の 30 個をちゃんと検出できました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351434/48ed2b3c-58d4-9c21-a2e1-804c3a223357.jpeg" width=70%>

- うまく検出していていないパターン
これはわざと試してみた結果ですが、左下の赤矢印の 2 か所は、縦に重ねてみたりクロスさせたりしてどうか試したものですが、案の定といいますか、やはり 1 個で検知されてしまったようです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351434/a1772ec9-eccf-b1f5-260f-de6e92f8600c.jpeg" width=70%>

# おわりに
今回はクランプで物体検出モデルの作成を検証しました。
以外と検出率は良いと思います。
パイプと違いクランプの場合は形状が様々になりますので何枚か推論すると、学習が足りなそうな形状は conf が低かったりします。
もう少し色んな形状の学習データは必要だと感じました。