はじめに
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 の強みを組み合わせることで、様々な画像処理ができそうです











