火曜日, 6月 3, 2025
- Advertisment -
ホームニューステックニュースLivebook で Elixir の Evision 、 Image の画像と Python の OpenCV 、...

Livebook で Elixir の Evision 、 Image の画像と Python の OpenCV 、 Pillow の画像を相互変換する #画像処理



Livebook で Elixir の Evision 、 Image の画像と Python の OpenCV 、 Pillow の画像を相互変換する #画像処理

はじめに

Livebook で Python が実行できるようになりました(試験導入ですが)

それだけでなく、 Elixir で定義した変数を Python で使ったり、 Python で定義した変数を Elixir で使ったり、外部ファイルや通信を介したりすることなく、シームレスにデータのやり取りをすることが可能です

前回の記事では数値や文字列、マップ、リストなどの基本的な変数について、 Elixir と Python 間で連携しました

今回の記事はその発展形として、画像データを Elixir 、 Python 間で連携します

Elixir は Evision 、 Image で画像を扱います

Python は OpenCV 、 Pillow で画像を扱います

Evision 、 Image 、 OpenCV 、 Pillow の画像データを相互変換することで、Elixir で分散処理した先のノードで Python が画像処理するなど、様々な応用が考えられます

実装したノートブックはこちら

セットアップ

Livebook のセットアップセルをクリックすると、左下に “+ Python” ボタンが表示されます

スクリーンショット 2025-05-29 9.52.04.png

“+ Python” ボタンをクリックすると、セットアップセルに Python セル用の設定が自動的に追加されます

スクリーンショット 2025-05-29 9.56.16.png

上部、 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 と同じように画像が表示されます

スクリーンショット 2025-06-01 1.44.03.png

では、 Numpy 配列を Elixir に持っていきましょう

そのままでは Elixir で扱えないため、先に配列に変換します

# Python
l_img = img.tolist()

配列をデコードして Nx のテンソル、 Evision の行列に順次変換することで、画像を表示できます

# Elixir
l_img
|> Pythonx.decode()
|> Nx.tensor(type: :u8)
|> Evision.Mat.from_nx_2d()

実行結果

スクリーンショット 2025-06-01 1.52.21.png

しかし、この方法では 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)

実行結果

スクリーンショット 2025-06-01 1.52.21.png

この方法では処理に数ミリ秒しかかかりません

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()

実行結果

スクリーンショット 2025-06-01 1.57.11.png

先ほどと同じように、バイト配列として連携します

# 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))

実行結果

スクリーンショット 2025-06-01 2.01.02.png

Pillow to Evision

次は Pillow の画像を Evision の画像に変換します

# Python
pil_img = Image.new("RGB", (300, 200), (0, 255, 0))
pil_img

実行結果

スクリーンショット 2025-06-01 2.02.18.png

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())

実行結果

スクリーンショット 2025-06-01 2.05.46.png

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))

実行結果

スクリーンショット 2025-06-01 2.02.18.png

Pillow to Image

Elixir の Image パッケージの画像データにも変換してみましょう

# Python
pil_yellow_img = Image.new("RGB", (300, 200), (255, 255, 0))
pil_yellow_img

実行結果

スクリーンショット 2025-06-01 2.08.58.png

今までと同じようにバイナリに変換します

# 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!()

実行結果

スクリーンショット 2025-06-01 2.11.44.png

Image to Pillow

逆も同様です

# Elixir
ex_yb_img = Image.write!(ex_yellow_img, :memory, suffix: ".png")

スクリーンショット 2025-06-01 2.13.11.png

# Python
Image.open(io.BytesIO(ex_yb_img))

実行結果

スクリーンショット 2025-06-01 2.14.09.png

まとめ

バイナリを経由することで、膨大な行列である画像データの連携も高速に行うことができました

Elixir の強みと Python の強みを組み合わせることで、様々な画像処理ができそうです





Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -