はじめに
本記事は Qiita AdventCalendar2022 Elixir vol2 9日目の記事です
Evisionシリーズの2つ目の記事になります
このシリーズは技術評論社の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.ClassificationModelを使ってクラス分類を行う方法を紹介します
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分野の多くの学習済みモデルを使用できる
クラス分類について
クラス分類(Classfication)とは、画像に何が映っているのかをクラスごとに信頼度を出して分類するタスクです。
by OpenCVではじめよう ディープラーニングによる画像認識 (p.380)
使用するモデル
Efficientnet-b7.onnx を使用します
クラスリストと学習済みデータはこちらの7.4をダウンロードしてください
直リンク
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
でモジュール名を短くしています import numpy as np
と同じですね
weigths
で学習済みデータのファイルパスを定義します
base
でダウンロードしたフォルダパスを貼っておくと便利です
classes
でクラス分類の推論結果は学習時に使用した正解リストのインデックスを返すので、クラス名がわかるように正解リストをリストにします
alias Evision, as: Ev
base = "7.4をダウンロードしたフォルダの絶対パスを貼り付けてください"
weights = base <> "7.4/efficientnet/efficientnet-b7.onnx"
classes =
(base <> "7.4/efficientnet/imagenet.names")
|> File.read!()
|> String.split("\n")
モデルの読み込み
学習済みデータを読み込んで、モデルを構築します
classificationModel/1
で先程の学習済みデータのパスを渡します
次にsetInputParams/2
でハイパーパラメータを設定します
パラメーターはそれぞれ以下を設定しています
- scale -> 画像のRGB値を0~1.0の範囲に変換
- size -> 入力画像サイズを600x600にする
- mean -> 各RGB値から指定した平均値を引いて照度変化に強くする
- swapRB -> BGR形式をRGBに入れ替える
- crop -> アスペクト比を保持してリサイズする
model =
Ev.DNN.ClassificationModel.classificationModel(weights)
|> Ev.DNN.ClassificationModel.setInputParams(
scale: 1.0 / 255.0,
size: {600, 600},
mean: {124.675, 116.28, 103.53},
swapRB: true,
crop: true
)
推論の実行
画像を読み込みます
使うのはYOLOでおなじみの犬と自転車が写っているアレです
image = Ev.imread(base <> "images/dog.jpg")
classify/2
で推論を実行します
{class_id, confidence} = Ev.DNN.ClassificationModel.classify(model, image)
実行するとクラスIDと正当確率が返ってきます
{249, 8.626869201660156}
推論結果を画像に描画
推論結果をクラスIDからクラス名に変換し、確率も小数点以下3まで表示するようにして、putText/6
で元画像に描画します
putTextの引数はそれぞれ以下のようになっています
- 画像
- 書き込むテキスト
- 書き込む座標(左下)
- フォント
- 文字の大きさ
- 文字の色
Ev.putText(
image,
"#{Enum.at(classes, class_id)}(#{Float.round(confidence, 3)})",
{30, 30},
Ev.cv_FONT_HERSHEY_TRIPLEX(),
1.0,
{255, 255, 255}
)
おまけ上位5つを表示
他の候補も見たい場合は predict/2
を使います、
処理は上から
- 推論実行
- 結果がMatのリスト([Mat])で返ってくるので1つ目の要素を取得
- Matだと行列処理がやりにくいのでNxに変換
- shapeが{1, 1000} なので squueeze で {1000}に変換
- 出力されたデータを0~1の範囲の確率の値に変換(Softmax)
となっています
confidences =
Ev.DNN.ClassificationModel.predict(model, image)
|> List.first()
|> Ev.Mat.to_nx(Nx.BinaryBackend)
|> Nx.squeeze()
|> then(&Nx.divide(
Nx.exp(&1),
Nx.sum(Nx.exp(&1))
))
上記のデータはクラス毎の確率になっているので、以下の処理を行います
-
Nx.argsort direction: :desc
で大きい順のインデックスに並び替えます -
Enum.take(5)
で先頭から5つでリストを作ります -
Enum.map
で クラス名と確率のタプルに変換
top5 =
Nx.argsort(confidences, direction: :desc)
|> Nx.to_flat_list()
|> Enum.take(5)
|> Enum.map(fn c ->
{Enum.at(classes, c), Nx.to_number(confidences[c])}
end)
実行すると以下のような結果を得られます
[
{"malamute, malemute, Alaskan malamute", 0.7833796739578247},
{"Tibetan mastiff", 0.017758667469024658},
{"mountain bike, all-terrain bike, off-roader", 0.015341623686254025},
{"Eskimo dog, husky", 0.006293073762208223},
{"Siberian husky", 0.0028612350579351187}
]
最後に
ClassificationModelを使用して簡単にクラス分類を行うことができました
他にも試してみたい方はmodel zooやOpenCV zoo で色々試してみてください
本記事は以上になりますありがとうございました
全コード
alias Evision, as: Ev
base = "/Users/shou/livebook_samples/"
weights = base <> "7.4/efficientnet/efficientnet-b7.onnx"
classes =
(base <> "7.4/efficientnet/imagenet.names")
|> File.read!()
|> String.split("\n")
model =
Ev.DNN.ClassificationModel.classificationModel(weights)
|> Ev.DNN.ClassificationModel.setInputParams(
scale: 1.0 / 255.0,
size: {600, 600},
mean: {124.675, 116.28, 103.53},
swapRB: true,
crop: true
)
image = Ev.imread(base <> "images/dog.jpg")
{class_id, confidence} = Ev.DNN.ClassificationModel.classify(model, image)
Ev.putText(
image,
"#{Enum.at(classes, class_id)}(#{Float.round(confidence, 3)})",
{30, 30},
Ev.cv_FONT_HERSHEY_TRIPLEX(),
0.5,
{255, 255, 255}
)