LoginSignup
14
0

More than 1 year has passed since last update.

YOLOv4の結果を切り取ってEfficientnetで更に分類するシステムをEvisionで書く

Last updated at Posted at 2022-12-17

はじめに

本記事は Qiita AdventCalendar2022 Elixir vol10 18日目の記事です

Evisionシリーズの7つ目の記事になります
このシリーズは技術評論社のOpenCVではじめよう ディープラーニングによる画像認識の7章の内容を参考にElixirとEvisionで書き換えて行っています

  1. Livebook + Evision基本編
  2. EvisionのCascadeClassifierで顔認識
  3. EvisionのDNN.ClassificationModelを使ってクラス分類
  4. EvisionのDNN.TextDetectionModelDBでテキスト検出
  5. EvisionのDNN.DetectionModelでYOLOv4を使って物体検知 12/14公開
  6. EvisionのDNN.SegmentationModelでセグメンテーション 12/14公開
  7. 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

アノテーションツールに種類が色々あるみたいですが、今回はいろんな画像からクラス分類に使えそうな訓練データを作るツールとしましょう

流れとしては以下になります

  1. YOLOv4で物体検知してBBox(対象が写っている領域)を取得
  2. 1の領域を切り取って個別の画像にする
  3. 2の画像を1つずつEfficientnetで分類する
  4. 分類結果のクラス名をファイル名として切り取った画像を書き出す

使用するモデル

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)

スクリーンショット 2022-12-05 22.29.11.png

ちゃんと切り取られてますね
次はこれを使ってクラス分類を行います

クラス分類部分

データは物体検知と分けるためにそれぞれ
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)

推論結果はこんな感じ
スクリーンショット 2022-12-05 23.31.46.png

画像も問題なく書き出されてます
スクリーンショット 2022-12-05 23.32.33.png

自転車の画像で犬に分類されたり、犬の画像で別の犬種が出たりと微妙な結果ですが、
もうちょっといいモデルとかにすれば改善できるといいな・・・

最後に

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)
14
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
0