はじめに
本記事は Qiita AdventCalendar2022 Elixir vol2 14日目の記事です
Evisionシリーズの5つ目の記事になります
このシリーズは技術評論社のOpenCVではじめよう ディープラーニングによる画像認識の7章の内容を参考にElixirとEvisionで書き換えて行っています
- Livebook + Evision基本編
- EvisionのCascadeClassifierで顔認識
- EvisionのDNN.ClassificationModelを使ってクラス分類
- EvisionのDNN.TextDetectionModelDBでテキスト検出
- EvisionのDNN.DetectionModelでYOLOv4を使って物体検知 12/14公開
- EvisionのDNN.SegmentationModelでセグメンテーション 12/14公開
- YOLOv4の結果を切り取ってEfficientnetで更に分類するシステムをEvisionで書く 12/18公開
Livebook上で画像処理ライブラリOpenCVのElixirラッパーのEvisionのDNN.DetectionModelを使って物体検知を行う方法を紹介します
Livebookについて
Livebook is a web application for writing interactive and collaborative code notebooks.
LivebookはコラボレーションもできるElixir対話的実行環境を提供するWebアプリケーションです
Evisionについて
- OpenCVのElixirラッパー
- Port等を使わずに直にElixirから使うことができる
- Nxのバックエンドとして使用でき、行列演算の高速化(CPU,GPU)ができる
- Nxデータに相互に変換できる
- 膨大な画像処理の関数を使用できる
- DNNモジュールでCV分野の多くの学習済みモデルを使用できる
物体検知について
物体検出(ObjectDetection)とは、画像の中から物体らしき領域を検出し、その物体がカップなのかペンなのかといったクラスに分類するタスクです。
OpenCVではじめよう ディープラーニングによる画像認識 p.350
使用するモジュールはDNN.DetectionMoelになります
使用するモデル
YOLOv4
学習済みデータはこちらの7.3をダウンロードしてください
直リンク
https://gihyo.jp/assets/files/book/2022/978-4-297-12775-6/download/7.3.zip
setup
livebookは公式サイトを参考にインストールしてください
livebookを起動してノートブックを作成したらsetupセルに以下を追加して実行してください
Mix.install([
{:evision, "~> 0.1.21"},
{:kino, "~> 0.7.0"},
])
データの準備
alias
でモジュール名を短くしています import numpy as np
と同じですね
weigths
で学習済みデータのファイルパスを指定します
config
でモデル設定のファイルパスを指定します
base
でダウンロードしたフォルダパスを貼っておくと便利です
alias Evision, as: Ev
base = "7.3をダウンロードしたフォルダの絶対パスを貼り付けてください"
weights = base <> "7.3/yolov4/yolov4.weights"
config = base <> "7.3/yolov4/yolov4.cfg"
classes
で推論結果のラベル名を取得するためにクラス名のリストを作ります
colors
で推論結果を囲うためにラベル毎にランダムな色のリストを作ります
classes = File.read!(base <> "7.3/yolov4/coco.names") |> String.split("\n")
colors =
Enum.map(classes, fn _ ->
{Enum.random(0..255), Enum.random(0..255), Enum.random(0..255)}
end)
モデルの読み込み
学習済みデータを読み込んで、モデルを構築します
DetectionModel/2
で先程の学習済みデータとオプションでネットワークの設定ファイルのパスを渡します
モデルの次はsetInputParams/2
でハイパーパラメータを設定します
パラメーターはそれぞれ以下を設定しています
- scale -> 画像のRGB値を0~1.0の範囲に変換
- size -> 入力画像サイズを416x416にする
- swapRB -> BGR形式をRGBに入れ替えない
- crop -> アスペクト比をせずにリサイズ
model =
Ev.DNN.DetectionModel.detectionModel(weights, config: config)
|> Ev.DNN.DetectionModel.setInputParams(
scale: 1.0 / 255.0,
size: {416, 416},
swapRB: true,
crop: false
)
推論の実行
画像を読み込みます
YOLOでよく使われる犬の画像を使います
image = Ev.imread(base <> "7.3/yolov4/dog.jpg")
detect/3
で推論を実行します
オプションはそれぞれ
- confThreshold -> 信頼度0.5以上のみ対象とする
- nmsTreshhold -> NMS(Non-Maximum Suppression)の信頼度0.4以上のみを対象とする
としています
{class_ids, confidences, boxes} =
Ev.DNN.DetectionModel.detect(model, image, confThreshold: 0.5, nmsThreshold: 0.4)
実行すると検出したオブジェクトのクラスID、信頼度、座標のリストがそれぞれ返ってきます
{[1, 7, 16], [0.9896954298019409, 0.912635087966919, 0.9911838173866272],
[{129, 131, 439, 293}, {465, 75, 227, 96}, {133, 234, 177, 302}]}
推論結果を画像に描画
Enum.zip_reduceで推論結果を描画していきます
zip_reduce
は複数のリストを受け取って、各リストの1番目からをまとめて1つのリストにしてループを回してアキュムレータのimageに対して推論結果を描画します
boxはそれぞれ{x,y,w,h}
に展開します
短形を描画する関数rectangleの引数はそれぞれ
- 画像
- 始点座標 {x,y}
- 終点座標 {x + w, y + h}
- 色(クラス毎にランダム作成した色)
- オプション、今回は線の太さ
クラス名のテキストを描画するputTextの引数はそれぞれ
- 画像
- テキスト(クラス名と信頼度小数点3桁)
- 座標(始点ちょっと上)
- フォント
- 文字の大きさ
- 色
- オプション、今回は線の太さ
となっています
Enum.zip_reduce([class_ids, confidences, boxes], image, fn [id, conf, {x, y, w, h}], mat ->
Ev.rectangle(mat, {x, y}, {x + w, y + h}, Enum.at(colors, id), thickness: 2)
|> Ev.putText(
"#{Enum.at(classes, id)}:(#{Float.round(conf, 3)})",
{x, y - 5},
Ev.cv_FONT_HERSHEY_SIMPLEX(),
0.5,
{0, 0, 0},
thickness: 2
)
end)
最後に
DetectionModelを使用して簡単に物体検出ができました
他のバージョンのYOLOも動くと思うので色々試してみてください
本記事は以上になりますありがとうございました
全コード
alias Evision, as: Ev
base = "/Users/shou/livebook_samples/"
weights = base <> "7.3/yolov4/yolov4.weights"
config = base <> "7.3/yolov4/yolov4.cfg"
classes = File.read!(base <> "7.3/yolov4/coco.names") |> String.split("\n")
colors =
Enum.map(classes, fn _ ->
{Enum.random(0..255), Enum.random(0..255), Enum.random(0..255)}
end)
model =
Ev.DNN.DetectionModel.detectionModel(weights, config: config)
|> Ev.DNN.DetectionModel.setInputParams(
scale: 1.0 / 255.0,
size: {416, 416},
swapRB: true,
crop: false
)
image = Ev.imread(base <> "7.3/yolov4/dog.jpg")
{class_ids, confidences, boxes} =
Ev.DNN.DetectionModel.detect(model, image, confThreshold: 0.5, nmsThreshold: 0.4)
Enum.zip_reduce([class_ids, confidences, boxes], image, fn [id, conf, {x, y, w, h}], mat ->
Ev.rectangle(mat, {x, y}, {x + w, y + h}, Enum.at(colors, id), thickness: 2)
|> Ev.putText(
"#{Enum.at(classes, id)}:(#{Float.round(conf, 3)})",
{x, y - 5},
Ev.cv_FONT_HERSHEY_SIMPLEX(),
0.5,
{0, 0, 0},
thickness: 2
)
end)