この記事は、Elixir Advent Calendar 2021の14日目です
昨日は @Yoosuke さんの「LiveViewを使って簡単にステートフルなタイピングゲームアプリを作ろう!前編」でした
ElixirImp/fukuoka.ex/kokura.exとLiveView JPの piacere です、ご覧いただいてありがとございます
今回は、PythonのJupyterNotebook + NumPyで画像加工するノリで、「JupyterNotebook」のElixir版である「Livebook」と、「NumPy」のElixir版である「Nx」による画像加工(ネガポジ反転、グレースケール)をやってみました
Livebookを使うと、最も気軽にElixirを始められるので、Elixirが初めての方にも今回コラムはオススメです
内容が、面白かったり、役に立ったら、「LGTM」よろしくお願いします
本コラムの検証環境
本コラムは、以下環境で検証しています(Windows WSL2で実施していますが、Windowsネイティブや他Linuxディストリビューション、macでも動作する想定です)
検証環境①:Windows+WSL2 Ubuntu 18.04
- Windows 11+WSL2 Ubuntu 18.04
- Elixir 1.13.0 on WSL2 Ubuntu ※最新版のインストール手順はコチラ
- git 2.17.1
検証環境②:Ubuntu 18.04
- Ubuntu 18.04
- Elixir 1.13.0 ※最新版のインストール手順はコチラ
- sudo apt installで下記インストール
- git
- erlang-dev
- erlang-os-mon
- erlang-xmerl
- erlang-parsetools
事前準備:Livebook + Nx + 画像表示
LivebookをローカルPCで起動
下記コマンドでLivebook最新版をダウンロードし、起動します
git clone https://github.com/elixir-nx/livebook.git
cd livebook
mix deps.get --only prod
MIX_ENV=prod mix phx.server
下記のように待ち受けログが出れば、起動成功です
[Livebook] Application running at http://localhost:8080/?token=a55u2laaty53acnvyokw27ucjx7yrjfu
上記ログに書かれたURLをブラウザでアクセスすると、下記のような画面が表示されます
「New notebook」を開くと、下記画面が表示され、ここでElixirコードを実行することが出来ます
NxをLivebookで使えるようにする
Pythonで言うところのNumPyと同様の行列計算ライブラリ「Nx」をLivebookで使えるようにします
通常のElixir/Phoenixプロジェクトであれば、mix.exsのdepsにライブラリを追加することで対応しますが、Livebookの場合は、ライブラリ追加もノートブックにて行えるので、下記コードをLivebook上のコードブロック(Elixirコードを入力する枠)に入力し、「Evaluate」ボタンをクリックするか、「Ctrl」+「Enter」キーで実行してください
Mix.install(
[
{:nx, "~> 0.1.0-dev", github: "elixir-nx/nx", branch: "main", sparse: "nx"}
]
)
Nxが使えるか試してみましょう
Livebook上で、コードブロックの実行結果の下にカーソルを持っていくと、下記にように、ブロック追加のためのボタンが表示されるので、「+Elixir」ボタンをクリックしてください
ここに、下記コードを書いて、実行してみましょう
Nx.tensor([[1, 2], [3, 4]])
|> Nx.divide(3)
このコードは、下記のような行列をNx.tensor()
で作り、各値を3で除算するというNxのコードです
\begin{bmatrix}
1 & 2 \\
3 & 4
\end{bmatrix}
行列のヒートマップ表示も、このように簡単です
Nx.tensor([[9, 8, 7, 6, 5], [8, 7, 6, 5, 4], [7, 6, 5, 4, 3], [6, 5, 4, 3, 2], [5, 4, 3, 2, 1]])
|> Nx.to_heatmap
他にも、NumPyの配列であるndarrayのバイナリ保存されたものをロードするfrom_numpy()や、行列をフラットなリストに変換するto_flat_list()等、便利な関数ありますが、NxはHexDocsがアップされていないため、関数をご自身で調べたい方は、下記ソースコードのdefを直接見てください
もしくは、Nxをgit cloneし、nxフォルダ配下でmix docsコマンドを叩き、出力されるdocsをご覧ください
画像をLivebookで表示する
画像表示のためにLivebook用レンダーライブラリ「Kino」とダウンロードライブラリ「Download」を追加します
ただしLivebookでは、ノートブック内で何度もライブラリ追加を行うことは出来ず、下図のようにエラーとなります
そのため、先頭のライブラリ追加を下記に書き換え、再実行します(その際、一度エラーを出した後は、「Restart runtime」ボタンをクリックしないとエラーが解消されません)
Mix.install(
[
{:nx, "~> 0.1.0-dev", github: "elixir-nx/nx", branch: "main", sparse: "nx"},
{:kino, "~> 0.3.1"},
{:download, "~> 0.0.4"}
]
)
まず、Downloadで画像をダウンロードしましょう
File.rm("Lenna_%28test_image%29.png") # 再実行時、Download.from()でeexistエラーになるのを防止
lenna = Download.from("https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png")
|> elem(1)
次に、Kinoでダウンロードした画像を表示してみます
lenna
|> File.read!
|> Kino.Image.new(:jpeg)
ちなみに下記コードだと、lennaさんをバージョンアップできます(が、jpgファイルなので、この後、扱いません)
File.rm("D2_x0z7UkAAI4gJ.jpg")
Download.from("https://pbs.twimg.com/media/D2_x0z7UkAAI4gJ.jpg")
|> elem(1)
|> File.read!
|> Kino.Image.new(:jpeg)
ここまでで、Livebookの基礎的な道具は揃いました
行列による画像加工の土台を作る
画像加工を行う前に、元画像を無変換で表示できるようにすることで、画像加工検証に使える土台を作ります
画像をRGBA形式の行列に変換
バージョンアップ前のlennaさんはpngファイルなので、この後、画像処理しやすいよう、RGBA形式に変換します
まず、先頭のライブラリにpng操作ライブラリ「Pixels」「Pngex」を追加します
Mix.install(
[
{:nx, "~> 0.1.0-dev", github: "elixir-nx/nx", branch: "main", sparse: "nx"},
{:kino, "~> 0.3.1"},
{:download, "~> 0.0.4"},
{:pixels, "~> 0.2.1"},
{:pngex, "~> 0.1.1"}
]
)
それから、lennaさんの画像を行列に変換する前に、画像サイズと画像ピクセルデータを確認します
lenna
|> Pixels.read_file
512 x 512の画像であることが確認でき、画像ピクセルデータは「R/G/B/A」の4バイトで1ピクセルを構成しています
つまり、4データ x 262,144ピクセル(≒512 x 512)の行列に割り当てれば、1ピクセル毎の処理がしやすくなるので、下記コードで、画像ピクセルデータをNx.from_binary()
とNx.reshape()
でこの形式の行列に変換します
rgba = lenna
|> Pixels.read_file
|> elem(1)
|> Map.get(:data)
|> Nx.from_binary({:u, 8})
|> Nx.reshape({262144, 4})
画像加工のための枠を設ける
画像加工処理を後で入れるための枠を設け、加工後の行列を画像ピクセルデータに戻す処理を作ります
多次元行列であっても、Nx.to_flat_list()
を使えば、1次元リストに変換できます
なお今回未使用ですが、後続処理でバイナリデータに変換したい場合は、Enum.into()
を使います
# ここから画像加工処理
new_rgba = rgba
# ここまで画像加工処理
changed = new_rgba
|> Nx.to_flat_list
# |> Enum.into(<<>>, & <<&1::8>>) # リストでは無くバイナリデータにしたいときの書き方
現時点では画像加工を行っていないため、元の画像ピクセルデータと同一のデータが返ってきます
png画像として保存し、画像表示する
Pngex.new()
とPngex.generate()
を使うと、png画像が生成できるので、File.write()
でファイルとして保存できます
Pngex.generate()
に渡すデータは、RGBAのタプルのリストなので、Enum.chunk_every()
で4つ1組のリストへと変換し、Enum.map()
でリストからタプルに変換します
rgba_tuple_list = changed
|> Enum.chunk_every(4)
|> Enum.map(fn [r, g, b, a] -> {r, g, b, a} end)
image = Pngex.new(type: :rgba, depth: :depth8, width: 512, height: 512)
|> Pngex.generate(rgba_tuple_list)
File.write("new.png", image)
保存したファイルを表示します
"new.png"
|> File.read!
|> Kino.Image.new(:png)
これで、画像加工処理の土台ができたので、画像加工処理をやっていきましょう
画像加工を行う
それでは、上記「画像加工のための枠を設ける」で作った枠を、下記の画像加工処理で入れ替えていきます
- ネガポジ反転
- グレースケール
ネガポジ反転
ネガポジ反転は、RGBの各値を、MAXである255から引くと生成できます
そのために、RGBAの「RGB」と「A」を分離し、RGBの各値を255から引いた後、RGBとAをNx.concatenate()
で再度、結合します
下記コードで、該当コードブロックを入れ替えてみましょう
# ここから画像加工処理
#new_rgba = rgba
rgb = rgba
|> Nx.slice_axis(0, 3, -1)
|> Nx.map(& Nx.subtract(255, &1))
a = rgba
|> Nx.slice_axis(4, 1, -1)
new_rgba = Nx.concatenate([rgb, a], axis: -1)
# ここまで画像加工処理
changed = new_rgba
|> Nx.to_flat_list
# |> Enum.into(<<>>, & <<&1::8>>) # リストでは無くバイナリデータにしたいときの書き方
以降のコードブロックを実行した結果は、下図の通り、ネガポジ反転した画像を作成できました
グレースケール
グレースケールは、RGBの各値の平均値をNx.mean()
で取り、その値をRGBの各値に設定すると生成できます
こちらもネガポジ反転と同様、「RGB」と「A」を分離した後、RGBを加工し、結合します
下記コードで、該当コードブロックを入れ替えてみましょう
# ここから画像加工処理
#new_rgba = rgba
rgb = rgba
|> Nx.slice_axis(0, 3, -1)
|> Nx.mean(axes: [-1])
|> Nx.round()
|> Nx.as_type({:u, 8})
|> Nx.to_flat_list
|> Enum.map(& [&1, &1, &1])
|> Nx.tensor
a = rgba
|> Nx.slice_axis(4, 1, -1)
new_rgba = Nx.concatenate([rgb, a], axis: -1)
# ここまで画像加工処理
changed = new_rgba
|> Nx.to_flat_list
# |> Enum.into(<<>>, & <<&1::8>>) # リストでは無くバイナリデータにしたいときの書き方
以降のコードブロックを実行した結果は、下図の通り、グレースケールした画像を作成できました
終わり
fukuoka.ex Elixir/Phoenix Advent Calendar 2021をご覧になっている方は、お気付きになったかも知れませんが、今回のネタは、「LiveViewとNxで画像処理」 という @the_haigo さんのコラムのLivebookバージョンでした
Livebookを使えば、ノートブック形式で、気軽にビジュアルも扱えるElixir開発ができるということが伝われば幸いです
それと、今回は画像処理でしたが、JupyterNotebookやColaboratoryが、AI・ML開発に利用できるのと同様に、Elixir/LivebookでもAI・ML開発ができる…というノウハウを2022年は開放していこうと思っているので、ワクワクしてお待ちください
明日は @kikuyuta さんで「世俗派関数型言語 Elixir を関数型言語風に使ってみたらやっぱり関数型言語みたいだった」です
主催/運営しているElixirコミュニティ紹介
4. LiveView JP : A place to mob-program in LiveView, LiveBook+Nx+Axon, and elixir-desktop
5. Neos.ex : A place to connecting Elixir and NeosVR to create a new world
Elixir生誕10周年を祝い、"Elixirの現在" に追いつける
Elixir界隈に激震をもたらした2021年の大変動を活用するコラム群を日々アップデートしています
本コラムも、第3弾「Elixir/Livebook+NxでPythonっぽくAI・ML」に追加しています