はじめに
Elixir の Image モジュールは軽量、高速な画像処理モジュールです
Elixir には他にも画像を扱うことのできるモジュールとして、 Nx と evision があります
Image は libvips というライブラリを利用していて、 Vix.Vips.Image
という構造体で画像を扱います
Nx は画像を横×縦×チャネル(RGBなどの色空間)のテンソル Nx.Tensor
として扱います
evision は OpenCV のラッパーになっていて、画像は行列(マトリックス) Evision.Mat
として扱います
Image モジュールにはこれらの相互変換用関数が用意されているため、これら全ては連携可能です
いつものように Livebook を使います
実装したノートブックはコチラ
実行環境
- Elixir: 1.14.2 OTP 24
- Livebook: 0.9.3
以下のリポジトリーの Docker コンテナ上で起動しています
Docker が使える環境であれば簡単に実行できます
https://docs.docker.com/engine/install/
Docker Desktop を無償利用できない場合は Rancher Desktop を使ってください
セットアップ
必要なモジュールをインストールします
Mix.install([
{:image, "~> 0.35"},
{:nx, "~> 0.5"},
{:evision, "~> 0.1"},
{:req, "~> 0.3"},
{:kino, "~> 0.9"}
])
Req は画像を Web からダウンロードするのに使っています
画像の読込
毎度の如くレナさんの画像をダウンロードしてきます
まず最初は Image.from_binary
で読み込み、 Vix.Vips.Image
の形にしておきます
lenna_img =
"https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
|> Req.get!()
|> then(& &1.body)
|> Image.from_binary!()
Vix.Vips.Image
の場合、画像自体と属性情報のタブが表示されます
Image から Nx
Image.to_nx
で Vix.Vips.Image
から Nx.Tensor
に変換します
{:ok, nx_lenna_img} = Image.to_nx(lenna_img)
nx_lenna_img
実行結果は以下のようにテンソルになっています
#Nx.Tensor<
u8[width: 512][height: 512][bands: 3]
[
[
[226, 137, 125],
[226, 137, 125],
...
],
...
]
>
一応、テンソルを画像として表示します(画像上は何も変わりません)
lenna_img
|> Image.to_nx!()
|> Kino.Image.new()
Nx から Image
Image.from_nx
で Nx.Tensor
から Vix.Vips.Image
に変換します
{:ok, image_lenna_img} = Image.from_nx(nx_lenna_img)
image_lenna_img
Image から evision
Image.to_evision
で Vix.Vips.Image
から Evision.Mat
に変換します
{:ok, evision_lenna_img} = Image.to_evision(lenna_img)
evision_lenna_img
Evision.Mat
の場合は画像自体、行列としての情報、テンソルとしての情報をタブ表示します
evision から Image
Image.from_evision
で Evision.Mat
から Vix.Vips.Image
に変換します
{:ok, image_lenna_img} = Image.from_evision(evision_lenna_img)
image_lenna_img
組み合わせ処理
単に変換するだけだと画像としては何も変わらないので、 evision と Image の組み合わせ処理を実装してみます
Image で四角形を描いた後、 evision で台形変換をしてみます
input =
Nx.tensor(
[
[0, 0],
[0, 512],
[512, 0],
[512, 512]
],
type: :f32
)
output =
Nx.tensor(
[
[192, 256],
[0, 512],
[320, 256],
[512, 512]
],
type: :f32
)
perspective_mat = Evision.getPerspectiveTransform(input, output)
lenna_img
|> Image.Draw.rect!(190, 200, 180, 200, fill: false, color: :red, stroke_width: 24)
|> Image.to_evision()
|> elem(1)
|> Evision.warpPerspective(perspective_mat, {512, 512})
|> Image.from_evision()
|> elem(1)
レナさんが何だか強そうになりました
まとめ
Image も evision もそれぞれ多様な関数を持っているので、組み合わせれば大概のことは実装できますね