はじめに
Livebook で Python が実行できるようになりました(試験導入ですが)
それだけでなく、 Elixir で定義した変数を Python で使ったり、 Python で定義した変数を Elixir で使ったり、外部ファイルや通信を介したりすることなく、シームレスにデータのやり取りをすることが可能です
前回の記事では数値や文字列、マップ、リストなどの基本的な変数について、 Elixir と Python 間で連携しました
今回の記事はその発展形として、画像データを Elixir 、 Python 間で連携します
Elixir は Evision 、 Image で画像を扱います
Python は OpenCV 、 Pillow で画像を扱います
Evision 、 Image 、 OpenCV 、 Pillow の画像データを相互変換することで、Elixir で分散処理した先のノードで Python が画像処理するなど、様々な応用が考えられます
実装したノートブックはこちら
セットアップ
Livebook のセットアップセルをクリックすると、左下に “+ Python” ボタンが表示されます
“+ Python” ボタンをクリックすると、セットアップセルに Python セル用の設定が自動的に追加されます
上部、 Elixir 側のセットアップセルに以下のコードを入力します
Mix.install([
{:pythonx, "~> 0.4.2"},
{:kino_pythonx, "~> 0.1.0"},
{:evision, "~> 0.2.13"},
{:image, "~> 0.59.3"}
])
下部、 Python 側のセットアップせるに以下のコードを入力します
[project]
name = "project"
version = "0.0.0"
requires-python = "==3.13.*"
dependencies = [
"opencv-python == 4.11.*",
"pillow == 11.2.*"
]
これにより、 Elixir 、 Python でそれぞれ利用する画像処理パッケージがインストールされました
OpenCV to Evision
まずは OpenCV の画像データを Evision の画像データに変換しましょう
Python セルで Python のパッケージを読み込みます
# Python
import io
import cv2
import numpy as np
from PIL import Image
Python セルで Numpy を使い、 200 x 300 の青い画像を生成します
# Python
img = np.zeros((200, 300, 3), np.uint8)
img[:,:,0] = 255
img
実行結果
array([[[255, 0, 0],
[255, 0, 0],
[255, 0, 0],
...,
[255, 0, 0],
[255, 0, 0],
[255, 0, 0]]], shape=(200, 300, 3), dtype=uint8)
このままだとただの Numpy 配列としてしか表示されないので、 Pillow の画像に変換します
# Python
Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
すると、 Jupyter と同じように画像が表示されます
では、 Numpy 配列を Elixir に持っていきましょう
そのままでは Elixir で扱えないため、先に配列に変換します
# Python
l_img = img.tolist()
配列をデコードして Nx のテンソル、 Evision の行列に順次変換することで、画像を表示できます
# Elixir
l_img
|> Pythonx.decode()
|> Nx.tensor(type: :u8)
|> Evision.Mat.from_nx_2d()
実行結果
しかし、この方法では 200 x 300 x 3 の配列をループしてデコードしている関係上、処理に 10 秒ほどかかってしまいます
もっと高速に Python から Elixir にデータを持っていくため、バイト配列を利用します
# Python
b_img = img.tobytes()
b_img
実行結果
b'\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff...'
バイト配列をデコードし、バイナリから Evision 行列に変換します
# Elixir
ex_img =
b_img
|> Pythonx.decode()
|> Evision.Mat.from_binary(:u8, 200, 300, 3)
実行結果
この方法では処理に数ミリ秒しかかかりません
Evision to OpenCV
今度は Evision の画像(行列)を OpenCV の画像(Numpy 配列)に変換します
# Elixir
sky_img =
[255, 255, 0]
|> Nx.tensor(type: :u8)
|> Nx.broadcast({200, 300, 3})
|> Evision.Mat.from_nx_2d()
実行結果
先ほどと同じように、バイト配列として連携します
# Elixir
py_b_img = Evision.Mat.to_binary(sky_img)
実行結果
255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255,
255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0, 255, 255, 0,
255, 255, 0, 255, 255, ...>>
# Python
py_img = np.frombuffer(py_b_img, dtype=np.uint8)
py_img = np.reshape(py_img, [200, 300, 3])
py_img
実行結果
array([[[255, 255, 0],
[255, 255, 0],
[255, 255, 0],
...,
[255, 255, 0],
[255, 255, 0],
[255, 255, 0]]], shape=(200, 300, 3), dtype=uint8)
変換した結果を画像として表示します
# Python
Image.fromarray(cv2.cvtColor(py_img, cv2.COLOR_BGR2RGB))
実行結果
Pillow to Evision
次は Pillow の画像を Evision の画像に変換します
# Python
pil_img = Image.new("RGB", (300, 200), (0, 255, 0))
pil_img
実行結果
PNG 形式のバイナリに変換します
# Python
pil_b_img = io.BytesIO()
pil_img.save(pil_b_img, format='PNG')
pil_b_img = pil_b_img.getvalue()
pil_b_img
実行結果
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01,\x00\x00\x00\xc8\x08\x02\x00\x00\x00\xdd\xbdK\x02\x00\x00\x02BIDATx\x9c\xed\xd31\x01\x00 ...'
バイナリを Pythonx.decode
でデコードした後、 Evision.imdecode
で画像データとして読み込みます
# Elixir
ex_pil_img =
pil_b_img
|> Pythonx.decode()
|> Evision.imdecode(Evision.Constant.cv_IMREAD_COLOR())
実行結果
Evision to Pillow
逆方向も同様にバイナリを介して連携します
# Elixir
png_img = Evision.imencode(".png", ex_pil_img)
実行結果
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01,\x00\x00\x00\xc8\x08\x02\x00\x00\x00\xdd\xbdK\x02\x00\x00\x02BIDATx\x9c\xed\xd31\x01\x00 ...'
# Python
Image.open(io.BytesIO(png_img))
実行結果
Pillow to Image
Elixir の Image パッケージの画像データにも変換してみましょう
# Python
pil_yellow_img = Image.new("RGB", (300, 200), (255, 255, 0))
pil_yellow_img
実行結果
今までと同じようにバイナリに変換します
# Python
pil_yb_img = io.BytesIO()
pil_yellow_img.save(pil_yb_img, format='PNG')
pil_yb_img = pil_yb_img.getvalue()
pil_yb_img
実行結果
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01,\x00\x00\x00\xc8\x08\x02\x00\x00\x00\xdd\xbdK\x02\x00\x00\x02BIDATx\x9c\xed\xd31\x01\x00 ...'
バイナリからデコードし、 Image に変換します
# Elixir
ex_yellow_img =
pil_yb_img
|> Pythonx.decode()
|> Image.from_binary!()
実行結果
Image to Pillow
逆も同様です
# Elixir
ex_yb_img = Image.write!(ex_yellow_img, :memory, suffix: ".png")
# Python
Image.open(io.BytesIO(ex_yb_img))
実行結果
まとめ
バイナリを経由することで、膨大な行列である画像データの連携も高速に行うことができました
Elixir の強みと Python の強みを組み合わせることで、様々な画像処理ができそうです
Views: 0