はじめに
本記事は Qiita AdventCalendar2022 Elixir vol9 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.SegmentationModelを使ってセグメンテーションを行う方法を紹介します
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分野の多くの学習済みモデルを使用できる
セグメンテーションについて
セグメンテーション(Segmentation)とは、画像に映っているオブジェクトのクラスごとに領域を分割するタスクです。
OpenCVではじめよう ディープラーニングによる画像認識 (p.410)
使用するモジュールはDNN.DetectionMoelになります
使用するモデル
deeplab-v3
学習済みデータはこちらの7.5をダウンロードしてください
直リンク
https://gihyo.jp/assets/files/book/2022/978-4-297-12775-6/download/7.5.zip
setup
livebookは公式サイトを参考にインストールしてください
livebookを起動してノートブックを作成したらsetupセルに以下を追加して実行してください
Mix.install([
{:evision, "~> 0.1.21"},
{:kino, "~> 0.7.0"},
])
データの準備
alias
でモジュール名を短くしています import numpy as np
と同じですね
weigths
で学習済みデータのファイルパスを指定します
base
でダウンロードしたフォルダパスを貼っておくと便利です
alias Evision, as: Ev
base = "7.3をダウンロードしたフォルダの絶対パスを貼り付けてください"
weights = base <> "7.5/deeplab-v3/optimized_graph_voc.pb"
classes
で推論結果のラベル名を取得するためにクラス名のリストを作ります
colors
で推論結果を囲うためにラベル毎にランダムな色のリストを作ります
色リストの一番最初はラベル外なので黒を入れておきます
またあとでNx.takeで使うので Nx.tensorでテンソル化しておきます
classes =
(base <> "7.5/deeplab-v3/voc.names")
|> File.read!()
|> String.split("\n")
colors =
Enum.with_index(classes, fn _class, index ->
case index do
0 -> [0, 0, 0]
_ -> [Enum.random(0..255), Enum.random(0..255), Enum.random(0..255)]
end
end)
|> Nx.tensor()
モデルの読み込み
学習済みデータを読み込んで、モデルを構築します
segmentationModel/1
で先程の学習済みデータとオプションでネットワークの設定ファイルのパスを渡します
モデルの次はsetInputParams/2
でハイパーパラメータを設定します
パラメーターはそれぞれ以下を設定しています
- scale -> meanを引いた後に画像のRGB値を0~1.0の範囲に変換
- size -> 入力画像サイズを513x513にする
- mean -> 入力画像の各RGB値から127.5を引く
- swapRB -> BGR形式をRGBに入れ替える
- crop -> アスペクト比をせずにリサイズ
model =
Ev.DNN.SegmentationModel.segmentationModel(weights)
|> Ev.DNN.SegmentationModel.setInputParams(
scale: 1.0 / 127.5,
size: {513, 513},
mean: {127.5, 127.5, 127.5},
swapRB: true,
crop: false
)
推論の実行
画像を読み込みます
書籍のサンプルにあった自転車の画像を使います
image = Ev.imread(base <> "7.5/deeplab-v3/bicycle.jpg")
segment/3
でセグメンテーションを実行します
mask =
Ev.DNN.SegmentationModel.segment(model, image)
各画素のクラスIDが返ってきますが、画像としてはRGBではなく、濃淡になってしまいます
こちらを上の方で定義したランダムの色リストに変換します
流れは以下のようになっています
- Evision.MatからNx.tensorに変換
- Evision.Backendだとtakeが使えないのでNx.BinaryBackendに変換する
- Nx.takeでクラスの数作成した色から該当するインデックスの値だけでテンソルを作る
- Evisionで扱えるようにf16からu8にデータタイプを変換
- Evision.Mat形式に変換
mask =
mask
|> Ev.Mat.to_nx(Nx.BinaryBackend)
|> then(&Nx.take(colors, &1))
|> Nx.as_type(:u8)
|> Ev.Mat.from_nx_2d()
色がついてだいぶわかりやすくなりました
セグメンテーション結果と画像をアルファブレンド
セグメンテーション結果から作ったマスクを半透明にして元画像に重ねます(アルファブレンド)
処理はそれぞれ以下のようになっています
- 元画像のサイズを取得
- マスクが513x513なので元画像とおなじになるようにリサイズ
- オプションに補間方法(interpolation)をINTER_NEAREST(最近傍補間)で指定
- addWeighted でアルファブレンド
{h, w, _} = Ev.Mat.shape(image)
color_mask = Ev.resize(mask, {w, h}, interpolation: Ev.cv_INTER_NEAREST())
alpha = 0.4
beta = 1.0 - alpha
blend = Ev.addWeighted(image, 0, color_mask, beta, 0.0)
元画像とマスクも並べてみましょう
Kino.Layout.grid(
[
Ev.imencode(".png", image) |> Kino.Image.new(:png),
Ev.imencode(".png", color_mask) |> Kino.Image.new(:png),
Ev.imencode(".png", blend) |> Kino.Image.new(:png)
],
columns: 3
)
最後に
SegmentationModelを使用して簡単にセグメンテーションができました
clipで切り抜きや、各種フィルターで人をぼかす、人以外をぼかす等色々できそうですね
本記事は以上になりますありがとうございました
全コード
alias Evision, as: Ev
base = "7.3をダウンロードしたフォルダの絶対パスを貼り付けてください"
weights = base <> "7.5/deeplab-v3/optimized_graph_voc.pb"
classes =
(base <> "7.5/deeplab-v3/voc.names")
|> File.read!()
|> String.split("\n")
colors =
Enum.with_index(classes, fn _class, index ->
case index do
0 -> [0, 0, 0]
_ -> [Enum.random(0..255), Enum.random(0..255), Enum.random(0..255)]
end
end)
|> Nx.tensor()
model =
Ev.DNN.SegmentationModel.segmentationModel(weights)
|> Ev.DNN.SegmentationModel.setInputParams(
scale: 1.0 / 127.5,
size: {513, 513},
mean: {127.5, 127.5, 127.5},
swapRB: true,
crop: false
)
image = Ev.imread(base <> "7.5/deeplab-v3/bicycle.jpg")
mask =
Ev.DNN.SegmentationModel.segment(model, image)
|> Ev.Mat.to_nx(Nx.BinaryBackend)
|> then(&Nx.take(colors, &1))
|> Nx.as_type(:u8)
|> Ev.Mat.from_nx_2d()
{h, w, _} = Ev.Mat.shape(image)
color_mask = Ev.resize(mask, {w, h}, interpolation: Ev.cv_INTER_NEAREST())
alpha = 0.4
beta = 1.0 - alpha
blend = Ev.addWeighted(image, alpha, color_mask, beta, 0.0)
Kino.Layout.grid(
[
Ev.imencode(".png", image) |> Kino.Image.new(:png),
Ev.imencode(".png", color_mask) |> Kino.Image.new(:png),
Ev.imencode(".png", blend) |> Kino.Image.new(:png)
],
columns: 3
)