39
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2022

Day 5

Elixir で OpenCV (Evision) を使った画像処理(移動、回転、フィルターなど)

Last updated at Posted at 2022-05-09

はじめに

これまで Python での画像処理や AI の学習・推論は実務でも扱ってきました

また、 Elixir は Phoenix を使った REST API に数年使っています

しかし、 Elixir で画像処理、 AI というのは未経験です

というわけで、 Nx と evision で Elixir での画像処理を実装してみました

実装したもの(Docker 上に環境構築):

2022/12/13 更新

最新のモジュールを使うように更新しました

参考記事:

Nx とは

Elixir で多次元配列(テンソル)を使うためのライブラリ

Python の numpy のような感覚で使えるため、画像処理に向いています

[
  [1, 2],
  [3, 4]
]
|> Nx.tensor()
|> Nx.divide(3)

上記のコードを Livebook で実行するとこんな感じ

スクリーンショット 2022-05-06 17.00.20.png

テンソルの各要素が 3 で割られているのが分かりますね

evision とは

Elixir 用の OpenCV ラッパー

Python の OpenCV と同じことが Elixir 上で実行できます

Nx とも連携できるため、 Python と同じ感覚で画像処理できます

実行環境

  • MacBook Pro 13 inchi
    • 2.4 GHz クアッドコアIntel Core i5
    • 16 GB 2133 MHz LPDDR3
  • macOS Ventura 13.0.1
  • Rancher Desktop 1.6.2
    • メモリ割り当て 12 GB
    • CPU 割り当て 6 コア

Livebook 0.8.0 の Docker イメージを元にしたコンテナで動かしました

コンテナ定義はこちらを参照

実行方法

リポジトリーをクローンして docker-compose up するだけです

git clone https://github.com/RyoWakabayashi/elixir-learning.git
cd elixir-learning
docker-compose up

ビルドが終わると以下のように localhost の URL が表示されるので、ブラウザで開いてください

...
Attaching to elixir-learning-livebook-1
elixir-learning-livebook-1  | [Livebook] Application running at http://localhost:8080/?token=xxxxx

こんな感じで Livebook が開きます

スクリーンショット 2022-05-06 17.18.21.png

Elixir 学習用のリポジトリーなので他のファイルもありますが、
evision/image_processing.livemd が本記事で紹介しているコードです

evision の導入

evision インストールのための Dockerfile

Docker 上では evision インストールに必要なもの + 他で使うので Phoenix を入れています

RUN mix local.hex --force \
  && mix archive.install hex phx_new --force \
  && mix local.rebar --force

RUN apt-get update \
  && apt-get upgrade -y \
  && apt-get install --no-install-recommends -y \
    libopencv-dev \
    build-essential \
    erlang-dev

しかし、 Livebook 上から evision をインストールすると10分以上ビルドに時間がかかってしまったため、
以下のようにフラグを立て、ビルド済のものを使うようにしました

ENV EVISION_PREFER_PRECOMPILED=true

evision のインストール実行

他に使うものも併せてインストールします

Mix.install([
  {:httpoison, "~> 1.8"},
  {:evision, "~> 0.1"},
  {:kino, "~> 0.8"},
  {:nx, "~> 0.4"}
])

画像生成

Nx を使ってテンソルから画像生成できます

[
  [
    [255, 0, 0],
    [255, 128, 0],
    [255, 255, 0]
  ],
  [
    [0, 255, 0],
    [0, 255, 128],
    [0, 255, 255]
  ],
  [
    [0, 0, 255],
    [128, 0, 255],
    [255, 0, 255]
  ]
]
|> Nx.tensor(type: {:u, 8})
# Evision のマトリックスに変換
|> Evision.Mat.from_nx_2d()
# 見やすいように拡大
|> Evision.resize({300, 300}, interpolation: Evision.cv_INTER_AREA())

スクリーンショット 2022-12-13 12.00.35.png

画像のダウンロード

HTTPoison を使って画像のバイナリデータを取得します

img_binary =
  "https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
  |> HTTPoison.get!()
  |> then(&(&1.body))

スクリーンショット 2022-12-13 12.01.20.png

バイナリデータをデコードします

img =
  img_binary
  |> Evision.imdecode(Evision.cv_IMREAD_COLOR())

スクリーンショット 2022-12-13 12.02.03.png

画像の書込

Evision.imwrite で画像をファイルに保存します

lenna_path = "Lenna.png"

Evision.imwrite(lenna_path, img)

画像の読込

Python の OpenCV と同じように画像ファイルを読み込みます

img = Evision.imread(lenna_path)

スクリーンショット 2022-12-13 12.03.38.png

画像サイズも以下のようにして取得できます

size = Evision.Mat.shape(img)

Evision.Mat.to_nx でテンソル化することもできます

Evision.Mat.to_nx(img)

リサイズ

リサイズも Python と同じように記述可能です

Evision.resize(img, {256, 128})

スクリーンショット 2022-12-13 12.05.01.png

グレースケール

グレースケールでの読込もシンプルに記述できます

Evision.imread(lenna_path, flags: Evision.cv_IMREAD_GRAYSCALE())

スクリーンショット 2022-12-13 12.06.41.png

読込済のマトリックスをグレースケールに変換する場合は Evision.cvtColor を使います

定数は cv_ で evision に定義されています

Evision.cvtColor(img, Evision.cv_COLOR_BGR2GRAY())

二値化

閾値を指定しての二値化も同様です

{threshold, mono_img} =
  lenna_path
  |> Evision.imread(flags: Evision.cv_IMREAD_GRAYSCALE())
  |> Evision.threshold(127, 255, Evision.cv_THRESH_BINARY())

IO.inspect(threshold)

mono_img

スクリーンショット 2022-12-13 12.08.25.png

平行移動

平行移動にはアフィン変換を利用します

アフィン変換用の変換行列は以下のように定義します

まず2次元リストで定義した後、
それを Nx で tensor に変換し、
evision で更に matrix に変換します

affine =
  [
    [1, 0, 100],
    [0, 1, 50]
  ]
  |> Nx.tensor(type: {:f, 32})
  |> Evision.Mat.from_nx()

あとは Python と同じように wrapAffine に変換行列と出力サイズを与えるだけです

Evision.warpAffine(img, affine, {512, 512})

スクリーンショット 2022-12-13 12.09.14.png

回転

回転の場合は getRotationMatrix2D に回転の中心座標、角度、スケールを指定して変換行列を取得します

affine = Evision.getRotationMatrix2D({512 / 2, 512 / 2}, 70, 1)

Evision.warpAffine(img, affine, {512, 512})

スクリーンショット 2022-12-13 12.09.49.png

ぼかし

その他、各種フィルター系も Python と同じように記述できます

通常のブラー

Evision.blur(img, {9, 9})

スクリーンショット 2022-12-13 12.10.17.png

中央値ブラー

Evision.medianBlur(img, 9)

スクリーンショット 2022-12-13 12.10.47.png

ガウシアンブラー

Evision.gaussianBlur(img, {9, 9}, 5)

スクリーンショット 2022-12-13 12.11.14.png

図形描画

四角形や楕円などの図形も描画できます

img
# 直線
|> Evision.line(
  # 始点{x, y}
  {200, 400},
  # 終点{x, y}
  {300, 450},
  # 色{R, G, B}
  {0, 255, 255},
  # 線の太さ
  thickness: 5
)
# 矢印
|> Evision.arrowedLine(
  # 始点{x, y}
  {300, 200},
  # 終点{x, y}
  {400, 150},
  # 色{R, G, B}
  {255, 255, 0},
  # 線の太さ
  thickness: 3,
  # 頭の大きさ
  tipLength: 0.3
)

スクリーンショット 2022-12-13 12.12.02.png

四角形

img
# 四角形
|> Evision.rectangle(
  # 左上{x, y}
  {150, 120},
  # 右下{x, y}
  {225, 320},
  # 色{R, G, B}
  {0, 0, 255},
  # 線の太さ
  thickness: 12,
  # 線の引き方(角がギザギザになる)
  lineType: Evision.cv_LINE_4()
)
|> Evision.rectangle(
  # 左上{x, y}
  {50, 120},
  # 右下{x, y}
  {125, 320},
  # 色{R, G, B}
  {0, 0, 255},
  # 線の太さ
  thickness: 12,
  # 線の引き方(角が滑らかになる)
  lineType: Evision.cv_LINE_AA()
)
|> Evision.rectangle(
  # 左上{x, y}
  {250, 60},
  # 右下{x, y}
  {325, 110},
  # 色{R, G, B}
  {0, 255, 0},
  # 塗りつぶし
  thickness: -1
)

スクリーンショット 2022-12-13 12.12.39.png

楕円、扇形

img
# 円
|> Evision.circle(
  # 中心{x, y}
  {100, 100},
  # 半径
  50,
  # 色{R, G, B}
  {255, 0, 0},
  # 塗りつぶし
  thickness: -1
)
# 楕円
|> Evision.ellipse(
  # 中心{x, y}
  {300, 300},
  # {長径, 短径}
  {100, 200},
  # 回転角度
  30,
  # 弧の開始角度
  0,
  # 弧の終了角度
  360,
  # 色{R, G, B}
  {255, 255, 0},
  # 線の太さ
  thickness: 3
)
# 扇形
|> Evision.ellipse(
  # 中心{x, y}
  {400, 200},
  # {長径, 短径}
  {100, 100},
  # 回転角度
  0,
  # 弧の開始角度
  100,
  # 弧の終了角度
  200,
  # 色{R, G, B}
  {0, 255, 0},
  # 塗りつぶし
  thickness: -1
)

スクリーンショット 2022-12-13 12.13.40.png

文字描画

文字も書けます

img
|> Evision.putText(
  # 文字列
  "Lenna",
  # 左下{x, y}
  {150, 200},
  # フォント種類
  Evision.cv_FONT_HERSHEY_SIMPLEX(),
  # フォントサイズ
  2.5,
  # 文字色
  {0, 0, 255},
  # 文字太さ
  thickness: 5
)

スクリーンショット 2022-12-13 12.14.16.png

Nx との連携

OpenCV と numpy のように、 Evision と Nx も連携できます

Evision.Mat.to_nxEvision.Mat.from_nx_2d で Nx テンソルと相互変換できます

img
|> Evision.Mat.to_nx(Nx.BinaryBackend)
|> Nx.transpose(axes: [1, 0, 2])
|> Evision.Mat.from_nx_2d()
|> dbg

スクリーンショット 2022-12-13 12.15.29.png

img
|> Evision.Mat.to_nx(Nx.BinaryBackend)
|> Nx.mean(axes: [2])
|> Nx.broadcast({512, 512, 3}, axes: [0, 1])
|> Evision.Mat.from_nx_2d()
|> dbg

スクリーンショット 2022-12-13 12.16.07.png

おわりに

Evision を使うことで、 Python と同じことが同じように Elixir で実装できました

Elixir で画像処理が簡単に実装できるため、バックエンドの実装の幅が広がりますね

39
17
6

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?