LoginSignup
17
3

More than 1 year has passed since last update.

Livebook + Evision基本編 Evision(OpenCV)でお手軽画像処理

Last updated at Posted at 2022-12-06

はじめに

本記事は Qiita AdventCalendar2022 Elixir vol2 7日目の記事です

Evisionシリーズの1つ目の記事になります
このシリーズは技術評論社のOpenCVではじめよう ディープラーニングによる画像認識の3章の内容を参考に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を使うときの基本的な関数の使い方を紹介します

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分野の多くの学習済みモデルを使用できる

setup

livebookは公式サイトを参考にインストールしてください
livebookを起動してノートブックを作成したらsetupセルに以下を追加して実行してください

Mix.install([
  {:evision, "~> 0.1.21"},
  {:kino, "~> 0.7.0"},
  {:exla, "~> 0.4.0"}
])

黒色画像の作成 -- Mat.zeros

Mat.zerosは第一引数で指定したサイズで0で埋めた行列を作成します
ついでにモジュール名をaliasで短くします

alias Evision, as: Ev

Ev.Mat.zeros({100, 100}, :u8)

スクリーンショット 2022-11-25 23.32.59.png
RGBの値で作ってMatで表示したい場合は Mat.from_nx_2dを経由する必要があるようです

Ev.Mat.zeros({100, 100, 3}, :u8) 

スクリーンショット 2022-11-25 23.40.02.png

Ev.Mat.zeros({100, 100, 3}, :u8) 
|> Ev.Mat.to_nx 
|> Ev.Mat.from_nx_2d()

スクリーンショット 2022-11-25 23.42.01.png

グレー画像の作成 -- Mat.full

Mat.fullは第2引数で指定した値で埋めた第1引数のサイズの行列を作ります

Ev.Mat.full({100, 100}, 200, :u8)

Nxから色画像の作成 -- Mat.from_nx_2d

Mat.fullだと単色画像はできないので、Nx.broadcastを使って作成していきます
Nx.broadcastの例を見ても分かりづらいですが
Nx.tensorで作った行列の要素数が合うようにshapeを指定すると
単色のRGBの行列を作ることができます

Nx.broadcast(200,{100,100})
#Nx.Tensor<
  s64[100][100]
  [
    [200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, ...],
    ...
  ]
>

shapeが一致しないとエラー

Nx.tensor([0,0,255])
|> Nx.broadcast({100,100,5})
** (ArgumentError) cannot broadcast tensor of dimensions {3} to {100, 100, 1} with axes [2]

NxからEv.Mat.from_nxするときの注意点ですが
Nxのデフォルトのタイプは :f16でそれだと Evisionでは対応していないので
:u8か:f32にタイプを指定してください
今回はRGB値なので:u8を指定します

shape = {250, 250, 3}

red =
  Nx.tensor([0, 0, 255], type: :u8)
  |> Nx.broadcast(shape)
  |> Ev.Mat.from_nx_2d()

green =
  Nx.tensor([0, 255, 0], type: :u8)
  |> Nx.broadcast(shape)
  |> Ev.Mat.from_nx_2d()

blue =
  Nx.tensor([255, 0, 0], type: :u8)
  |> Nx.broadcast(shape)
  |> Ev.Mat.from_nx_2d()

to_img = fn img -> Ev.imencode(".png", img) |> Kino.Image.new(:png) end

Kino.Layout.grid(
  [to_img.(red), to_img.(green), to_img.(blue)],
  columns: 3
)

複数の画像を表示する際の小技ですが、
Ev.encodeでMatのデータをpng形式に変換し
Kino.Image.new()で png形式のKino.Imageを作成
Kino.Layout.gridの第1引数にリストで渡して表示する
デフォルトだと縦方向なので:columnsオプションを指定すると以下のように表示できます

スクリーンショット 2022-11-26 0.12.22.png

画像の読み込み -- imread

git cloneしてきたlivebookで起動しているので、cwdはlivebook直下になります
static/imagesにロゴ画像があるのでそちらを読み込みます

img = "static/images/logo.png" |> Ev.imread()

スクリーンショット 2022-11-28 14.22.52.png

ファイル書き込み -- imwrite

ファイルを書き込む時は適切な拡張子をつけていれば、それに沿って画像ファイルを生成します

Ev.imwrite("images/logo.png", img)
Ev.imwrite("images/logo.jpg", img)

スクリーンショット 2022-11-30 11.37.06.png

拡張子を指定しなければエラーになります

Ev.imwrite("images/logo", img)
{:error, "OpenCV(4.6.0) /Users/runner/work/evision/evision/3rd_party/opencv/opencv-4.6.0/modules/imgcodecs/src/loadsave.cpp:730: error: (-2:Unspecified error) could not find a writer for the specified extension in function 'imwrite_'\n"}

リサイズ -- resize

第2引数に縦横のタプルを渡します

img = Ev.resize(img, {200, 200})

スクリーンショット 2022-11-30 11.38.50.png

回転 -- rotate

時計回りに90度

Ev.rotate(img, Ev.cv_ROTATE_90_CLOCKWISE())

反時計回りに90度

Ev.rotate(img, Ev.cv_ROTATE_90_COUNTERCLOCKWISE())

180度回転

Ev.rotate(img, Ev.cv_ROTATE_180())

スクリーンショット 2022-11-30 11.41.32.png

反転 -- flip

# 縦方向に反転
Ev.flip(img, 0)
# 横方向に反転
Ev.flip(img, 1)

スクリーンショット 2022-11-30 11.46.29.png

連結 -- hconcat, vconcat

横連結

Ev.hconcat([img, img])

スクリーンショット 2022-11-30 11.51.21.png

縦連結

Ev.vconcat([img, img])

スクリーンショット 2022-11-30 13.23.39.png

結合箇所のサイズ(横連結の場合は高さ)が違うとエラーになります

Ev.hconcat([img, Ev.resize(img, {100, 100})])
{:error,"OpenCV(4.6.0) /Users/runner/work/evision/evision/3rd_party/opencv/opencv-4.6.0/modules/core/src/matrix_operations.cpp:67: error: (-215:Assertion failed) src[i].dims <= 2 && src[i].rows == src[0].rows && src[i].type() == src[0].type() in function 'hconcat'\n"}

結合箇所以外はサイズが違っても問題なく結合できます

Ev.hconcat([img, Ev.resize(img, {100, 200})])

スクリーンショット 2022-11-30 13.27.23.png

短形図形を描く -- rectangle

短形(四角)を画像内に描画します、物体検知(YOLOとか)でよく使うあれです
引数はそれぞれ

  • 1 image
  • 2 始点(x, y)
  • 3 終点(x+w, y+h)
  • 4 色
  • 5 オプションいろいろ、 今回は線の太さ
{x, y, w, h} = {25, 25, 50, 50}
color = {255, 255, 255}
Ev.rectangle(img, {x, y}, {x + w, y + h}, color, thickness: 3)

スクリーンショット 2022-11-30 13.41.48.png

thicknessを-1にすると塗りつぶしになります

Ev.rectangle(img, {x, y}, {x + w, y + h}, color, thickness: -1)

スクリーンショット 2022-11-30 13.44.02.png

円を描く -- circle, ellipse

  • 1 画像
  • 2 中心座標
  • 3 半径
  • 4 色
  • 5 その他オプション
Ev.circle(img, {100, 100}, 40, color, thickness: 3)

スクリーンショット 2022-11-30 13.48.21.png

ellipseは楕円を描くときに使います

  • 1 画像
  • 2 中心座標
  • 3 {縦幅、横幅}
  • 4 回転
  • 5 描画位置始点
  • 6 描画位置終点
  • 7 色
  • 8 その他オプション
Ev.ellipse(img, {100, 100}, {50, 100}, 60, 0, 360, color)

スクリーンショット 2022-11-30 13.51.07.png

幅を揃えれば円になりますし
90度回転させて、描画位置を0~180に指定すると半円になります

Ev.ellipse(img, {100, 100}, {50, 50}, 90, 0, 180, color, thickness: -1)

スクリーンショット 2022-11-30 13.52.35.png

文字を入れる -- putText

画像の指定座標に文字を描画します

  • 1 画像
  • 2 入れる文字
  • 3 {x座標,y座標}
  • 4 フォント
  • 5 文字の大きさ
  • 6 文字の色
Ev.putText(img, "LiveViewJP", {30, 50}, Ev.cv_FONT_ITALIC(), 1.0, color)

スクリーンショット 2022-11-30 14.09.22.png

切り取り -- Mat.roi

roiは関心領域(Region of Interest)の略称だそうです
指定した領域を切り抜きます

  • 1 画像
  • 2 {x, y, x + w, y + h}
Ev.Mat.roi(img, {50, 50, 100, 100})

スクリーンショット 2022-11-30 13.58.47.png

アフィン変換 -- warpAffine

最後は名前だけはよく聞くアフィン変換です
使いこなすには行列計算の知識とかいるのでちょっとハードル高そうですね

画像の拡大縮小、回転、平行移動などを行列を使って座標を変換する事をアフィン変換と呼びます。 by https://imagingsolution.net/imaging/affine-transformation/

src_img = img
{width, height} = {200, 200}

# 並行移動
x = 50
y = -10
m_shift = Nx.tensor([[1, 0, x], [0, 1, y]], type: :f32) |> Ev.Mat.from_nx()
sheer_img = Ev.warpAffine(src_img, m_shift, {width, height})

# 回転
angle = 45
m_rotate = Ev.getRotationMatrix2D({width / 2, height / 2}, angle, 1.0)
rotation_img = Ev.warpAffine(src_img, m_rotate, {width, height})

# スキュー(平行四辺形に変形する処理)
a = 0.2
b = 0.0
m_shear = Nx.tensor([[1, a, 0], [b, 1, 0]], type: :f32) |> Ev.Mat.from_nx()
shear_img = Ev.warpAffine(src_img, m_shear, {width, height})

スクリーンショット 2022-11-30 14.05.57.png

17
3
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
17
3