はじめに
本記事は Qiita AdventCalendar2022 Elixir vol10 18日目の記事です
Evisionシリーズの7つ目の記事になります
このシリーズは技術評論社の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とDNN.ClassificationModelを組み合わせて
YOLOv4の結果をEfficientnetで更に分類する、簡易アノテーションツールを作ります。
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分野の多くの学習済みモデルを使用できる
アノテーションツールについて
AIアノテーションツールとは、AIの精度を高めるために必要な教師データ(訓練データ)の作成作業、すなわちアノテーションを自動化もしくは効率化できるツールのことを指します。
https://www.aspicjapan.org/asu/article/16061
アノテーションツールに種類が色々あるみたいですが、今回はいろんな画像からクラス分類に使えそうな訓練データを作るツールとしましょう
流れとしては以下になります
- YOLOv4で物体検知してBBox(対象が写っている領域)を取得
- 1の領域を切り取って個別の画像にする
- 2の画像を1つずつEfficientnetで分類する
- 分類結果のクラス名をファイル名として切り取った画像を書き出す
使用するモデル
YOLOv4とEfficientnet-b7を使います
以下から7.3と7.4をダウンロードしてください
直リンク
https://gihyo.jp/assets/files/book/2022/978-4-297-12775-6/download/7.3.zip
https://gihyo.jp/assets/files/book/2022/978-4-297-12775-6/download/7.4.zip
setup
livebookは公式サイトを参考にインストールしてください
livebookを起動してノートブックを作成したらsetupセルに以下を追加して実行してください
Mix.install([
{:evision, "~> 0.1.21"},
{:kino, "~> 0.7.0"},
])
物体検知部分
alias Evision, as: Ev
base = "your 7.3 donwloaded folder abs path"
image = base <> "7.3/yolov4/dog.jpg"
weights = base <> "7.3/yolov4/yolov4.weights"
config = base <> "7.3/yolov4/yolov4.cfg"
dog = Ev.imread(image) |> Ev.resize({416, 416})
model =
Ev.DNN.DetectionModel.detectionModel(weights, config: config)
|> Ev.DNN.DetectionModel.setInputParams(
scale: 1.0 / 255.0,
size: {416, 416},
mean: {0, 0, 0},
swapRB: true,
crop: false
)
{_class_ids, _confidences, boxes} =
Ev.DNN.DetectionModel.detect(model, dog, confThreshold: 0.5, nmsThreshold: 0.4)
images =
Enum.map(boxes, fn {x, y, w, h} ->
Ev.Mat.roi(dog, {x - 10, y - 10, w + 10, h + 10})
end)
基本的にDNN.DetectionModelと同じで、Enum.zip_reduceで画像に書き込む部分がなくて
Ev.Mat.roiで指定した座標内のみのRGB値の画像にします
どんな感じになったか見てみましょう
to_img = fn img -> Ev.imencode(".png", img) |> Kino.Image.new(:png) end
Enum.map(images, &to_img.(&1)) |> Kino.Layout.grid(columns: 3)
ちゃんと切り取られてますね
次はこれを使ってクラス分類を行います
クラス分類部分
データは物体検知と分けるためにそれぞれ
c_weigths,c_modelで定義します
tap関数は第1引数を受け取り、何かしら処理を行い、第1引数の値をそのまま帰す関数です
今回は切り取った画像データを書き出すのに使っています
本来は 画像を書き出すEvision.imwrite
だけでいいのですが、推論結果を確認するためにtap
を挟んで、推論結果を画像に書き込んで表示するようにしています
c_weights = base <> "7.4/efficientnet/efficientnet-b7.onnx"
classes =
(base <> "7.4/efficientnet/imagenet.names")
|> File.read!()
|> String.split("\n")
c_model =
Ev.DNN.ClassificationModel.classificationModel(c_weights)
|> Ev.DNN.ClassificationModel.setInputParams(
scale: 1.0 / 255.0,
size: {412, 412},
mean: {124.675, 116.28, 103.53},
swapRB: true,
crop: true
)
images =
Enum.map(images, fn image ->
{class_id, confidence} =
Ev.DNN.ClassificationModel.classify(c_model, image)
name = Enum.at(classes, class_id) |> String.split(",") |> List.first()
Ev.resize(image, {412, 412}, interpolation: Ev.cv_INTER_NEAREST())
|> tap(&Ev.imwrite("images/#{name}.png", &1))
|> Ev.putText(
"#{name}(#{Float.round(confidence, 3)})",
{50, 50},
Ev.cv_FONT_HERSHEY_DUPLEX(),
0.8,
{0, 0, 255}
)
end)
|> Enum.map(&to_img.(&1))
|> Kino.Layout.grid(columns: 3)
自転車の画像で犬に分類されたり、犬の画像で別の犬種が出たりと微妙な結果ですが、
もうちょっといいモデルとかにすれば改善できるといいな・・・
最後に
DNN.DetectModelとDNN.ClassificationModelを組み合わせて簡易的なアノテーションツールを作ってみました
Elixirはパイプ演算子で処理を繋げれるので、複数のDNNの処理をつなげるのが見通しよくかけるのがとても気に入っています
またLivebookはデスクトップアプリもあるので、livemdをそのまま渡したり、ElixirDesktopにEvisionを組み込んでもうちょっとちゃんとしたアノテーションツールにしたりなど色々できそうですね
本記事は以上になりますありがとうございました
全コード
alias Evision, as: Ev
base = "/Users/shou/livebook_samples/"
weights = base <> "7.3/yolov4/yolov4.weights"
config = base <> "7.3/yolov4/yolov4.cfg"
c_weights = base <> "7.4/efficientnet/efficientnet-b7.onnx"
c_classes =
(base <> "7.4/efficientnet/imagenet.names")
|> File.read!()
|> String.split("\n")
model =
Ev.DNN.DetectionModel.detectionModel(weights, config: config)
|> Ev.DNN.DetectionModel.setInputParams(
scale: 1.0 / 255.0,
size: {416, 416},
mean: {0, 0, 0},
swapRB: true,
crop: false
)
c_model =
Ev.DNN.ClassificationModel.classificationModel(c_weights)
|> Ev.DNN.ClassificationModel.setInputParams(
scale: 1.0 / 255.0,
size: {412, 412},
mean: {124.675, 116.28, 103.53},
swapRB: true,
crop: true
)
{_class_ids, _confidences, boxes} =
Ev.DNN.DetectionModel.detect(model, dog, confThreshold: 0.5, nmsThreshold: 0.4)
boxes
|> Enum.map(fn {x, y, w, h} ->
Ev.Mat.roi(dog, {x - 10, y - 10, w + 10, h + 10})
end)
|> Enum.map(fn image ->
{class_id, _confidence} =
Ev.DNN.ClassificationModel.classify(c_model, image)
name = Enum.at(classes, class_id) |> String.split(",") |> List.first()
Ev.resize(image, {412, 412}, interpolation: Ev.cv_INTER_NEAREST())
|> tap(&Ev.imwrite("images/#{name}.png", &1))
end)