4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Elixir Livebook と Python Jupyter で画像・グラフの並べ方を比較する

Last updated at Posted at 2023-01-10

はじめに

Elixir Livebook は、ブラウザ上でコードを実行して結果を表示するツールです

スクリーンショット 2023-01-04 14.58.28.png

画像もグラフも表も地図も簡単に表示できます

ノートブックで画像表示

ノートブックでグラフ表示

ノートブックで表表示

ノートブックで地図表示

ちなみに別の言語も実行できます

AI・機械学習にも使えます

そして Elixir は API も Web サイトもデスクトップアプリ(Windows、macOS、Linux)もモバイルアプリ(iOS、Android)も実装できるため、 Livebook でやったことは何にでも組み込めます

要するに Elixir の Livebook は Python の Jupyter です

今回は画像・グラフの並べ方を例として、両者を比較してみます

例としてガンマ補正の画像表示、グラフ表示を実装してみます

参考サイト

Python のガンマ補正実装はこちらのサイトを参考にしました

他のサイトでは for 文を使って変換テーブルを生成していることが多いのですが、 for ループは非常に遅いので、せっかく numpy を使うのであれば、ちゃんと numpy.arange を使うこちらの実装が良いと思います

Python Jupyter で画像・グラフを並べる

実行環境

  • Python 3.10.9
  • JupyterLab 3.5.2

セットアップ

必要なモジュールをインポートします

Jupyter の場合、各環境にインストール済のモジュールをそのまま使います

もし Jupyter 上からインストールする場合は ! に続けてシェルコマンドが実行できます

# !pip install opencv-python numpy matplotlib

import cv2
import numpy as np
import math
import requests
from matplotlib import pyplot as plt
  • cv2: OpenCV 画像処理
  • numpy: 行列演算
  • math: 数学系ユーティリティー
  • requests: HTTP リクエスト
  • matplotlib: グラフ、画像出力

画像の取得

画像を Web から取得してきます

ストレージへの無駄なI/Oを行わないため、ファイルとして保存しないまま表示します

画像を表示する場合 pyplot.imshow を使用します

url = "https://github.com/opencv/opencv/blob/master/samples/data/butterfly.jpg?raw=true"

# URL を指定して画像のバイナリデータをダウンロード
response = requests.get(url)

# バイナリデータを Numpy 行列に変換
array = np.frombuffer(response.content, dtype=np.uint8)

# Numpy 行列を OpenCV で画像としてデコード
org_img = cv2.imdecode(array, cv2.IMREAD_UNCHANGED)

# 画像を表示
plt.imshow(org_img)

スクリーンショット 2023-01-08 19.07.20.png

ガンマ補正

後でガンマ補正用テーブルのグラフを表示するため、テーブル生成を別関数にしています

numpy.arange で 0 から 255 の等差数列を生成し、ガンマ補正の計算を実行します

cv2.LUT で変換テーブルに従って 0 から 255 の値を変換します

# ガンマ補正用変換テーブルの生成
def gamma_table(gamma):
    table = (np.arange(256) / 255) ** (1 / gamma) * 255
    # 整数に変換
    table = np.clip(table, 0, 255).astype(np.uint8)
    return table

# ガンマ補正
def gamma_correction(img, gamma):
    table = gamma_table(gamma)
    return cv2.LUT(img, table)
# ガンマ補正を実行
corrected_img = gamma_correction(org_img, 2.0)

# ガンマ補正結果を表示
plt.imshow(corrected_img)

スクリーンショット 2023-01-08 19.10.32.png

元画像と補正後画像を並べて表示

複数の画像やグラフを並べて表示する場合、 pyplot.subplot で位置を指定します

  • 第1引数: 行数(縦に何分割するか)
  • 第2引数: 列数(横に何分割するか)
  • 第3引数: 左上からZ字に進むとき何番目か
# 元画像
plt.subplot(1, 2, 1) # 1行2列の表示域の1番目
plt.imshow(org_img)

# 補正後画像
plt.subplot(1, 2, 2) # 1行2列の表示域の2番目
plt.imshow(corrected_img)

スクリーンショット 2023-01-08 19.13.40.png

ちなみに、省略した書き方も可能です

第1引数 * 100 + 第2引数 * 10 + 第3引数 を第1引数に渡すことで同じ結果になります

# 元画像
plt.subplot(121)
plt.imshow(org_img)

# 補正後画像
plt.subplot(122)
plt.imshow(corrected_img)

簡略に書ける一方、可読性が高いとは言えないでしょう

値による画像の変化を表示

Python では for x in array:  で配列の要素をループできます

# ガンマの値を配列で指定
gamma_array = [0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0]
for index, gamma in enumerate(gamma_array):
    # 行数は配列の長さから計算する
    plt.subplot(math.ceil(len(gamma_array) / 4), 4, index + 1)
    plt.imshow(gamma_correction(org_img, gamma))

スクリーンショット 2023-01-08 19.18.04.png

グラフ表示

グラフを表示する場合、 pyplot.plot に Numpy 配列を渡すだけでいい感じに解釈して線グラフとして表示してくれます

table = gamma_table(2.0)
plt.plot(table)

スクリーンショット 2023-01-08 19.19.15.png

値によるグラフの変化を表示

グラフも画像と同様に subplot を使えば並べてくれます

ただし、 subplots_adjust で間隔を調整しないとグラフ同士が重なってしまうことがあります

# グラフ間の横間隔を調整する
plt.subplots_adjust(wspace=0.5)

for index, gamma in enumerate(gamma_array):
    plt.subplot(math.ceil(len(gamma_array) / 4), 4, index + 1)
    plt.plot(gamma_table(gamma))

スクリーンショット 2023-01-08 21.53.14.png

複数のグラフを重ねて表示

ガンマの値を変えた複数のグラフを重ねて表示します

pyplot.plotlabel 引数に値を指定することで自動的に色を変えて表示できます

pyplot.legend で凡例を表示します

for index, gamma in enumerate(gamma_array):
    plt.plot(gamma_table(gamma), label=str(gamma))

plt.legend() # 凡例を表示
plt.show()

スクリーンショット 2023-01-08 19.22.28.png

Elixir Livebook で画像・グラフを並べる

実行環境

  • Elixir 1.14.2 OTP 24
  • Livebook 0.8.0

セットアップ

必要なモジュールをインストールします

インストールすべきモジュールが必ず明示されるため、再現性は高いです

Mix.install([
  {:evision, "~> 0.1"},
  {:nx, "~> 0.4"},
  {:req, "~> 0.3"},
  {:kino, "~> 0.8"},
  {:kino_vega_lite, "~> 0.1.7"}
])
  • evision: OpenCV 画像処理
  • nx: 行列演算
  • req: HTTP リクエスト
  • kino: 入出力補助
  • kino_vega_lite: グラフ出力

演算子で行列演算を表現できるように Nx.Defn をインポートします

import Nx.Defn

画像の取得

画像を Web から取得してきます

Elixir の特徴であるパイプ演算子 |> によって、一連の処理を繋げて表現できます

|> すると、前の関数の結果が次の関数の第1引数に渡されます

以下のコードでは URL からデータを取得し、取得したデータから body を取り出し、body を Evision のマトリックス(画像として扱える形式)にデコードしています

org_img =
  "https://github.com/opencv/opencv/blob/master/samples/data/butterfly.jpg?raw=true"
  |> Req.get!()  # URL を指定して画像のバイナリデータをダウンロード
  |> then(& &1.body)
  |> Evision.imdecode(Evision.Constant.cv_IMREAD_COLOR())  # バイナリを Evision で画像としてデコード

Livebook では、画像として扱える Evision のマトリックスがセル内の最後の結果になっている場合、自動的に画像を表示してくれます

スクリーンショット 2023-01-08 19.29.51.png

ちなみに画像の上にあるタブで表示を切り替えることが可能です

evision.gif

  • Image: 画像
  • Raw: Evision マトリックス
  • Numerical: Nx テンソル

このようなタブ表示が簡単にできるのも Livebook の特長です

ガンマ補正

defn を使いたいため、モジュールを定義します

Nx.iota で指定した形の等差数列を生成し、 0 から 255 の範囲に切り取って、整数に変換しています

defmodule Image do
  # ガンマ補正用変換テーブルの生成
  defn gamma_table(gamma) do
    (Nx.iota({256, 1}) / 255) ** (1 / gamma) * 255
    |> Nx.clip(0, 255)
    |> Nx.as_type(:u8)  # 整数に変換
  end

  # ガンマ補正
  def gamma_correction(img, gamma) do
    table = gamma_table(gamma)
    Evision.lut(img, table)
  end
end

ガンマ補正後の画像もそのまま表示できます

# ガンマ補正を実行、補正結果を表示
corrected_img = Image.gamma_correction(org_img, 2.0)

スクリーンショット 2023-01-08 19.35.28.png

元画像と補正後画像を並べて表示

ここに関しては Livebook が圧倒的に分かりやすいです

表示したいものを配列として Kino.Layout.grid に渡すだけで並べて表示してくれます

更に columns で列数を指定すればそれだけで横に並べることもできます

配列の長さを気にしなくていいのが素晴らしいです

Kino.Layout.grid([org_img, corrected_img], columns: 2)

スクリーンショット 2023-01-08 19.40.19.png

値による画像の変化を表示

Elixir では Enum.map で配列の各要素をループすることができます

# ガンマの値を配列で指定
gamma_list = [0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0]

gamma_list の各値 gamma でガンマ補正した結果をそのまま配列として Kino.Layout.grid に渡せば4列に並べて表示してくれます

gamma_list
|> Enum.map(fn gamma -> Image.gamma_correction(org_img, gamma) end)
|> Kino.Layout.grid(columns: 4)  # 4列で表示

スクリーンショット 2023-01-08 19.42.46.png

画像をどう表示するかについて、ほとんど考える必要がありません

しかも Layout.grid(columns: 4) という表記は、一目で「ここで4列に並べて表示しているんだな」と分かる最高の可読性だと思います

グラフ表示

Python の matplotlib と比べると、 Elixir の Kino.VegaLite はキチンと指定しないと良しなに解釈はしてくれません

ガンマ補正用変換テーブルをグラフ化する場合、ちゃんと X 軸用に 0 から 255 の配列を作ってあげます

また、 type の指定が重要です

  • quantitative: 数値データ
  • temporal: 日付、時刻データ
  • ordinal: 序数=ランクなど、数字の大小に意味がある値
  • nominal: カテゴリ=IDなど、数字の大小に意味がない値
plot_data =
  %{
    input: Enum.to_list(0..255),
    output: Image.gamma_table(2.0) |> Nx.to_flat_list()
  }

VegaLite.new()
|> VegaLite.data_from_values(plot_data)
|> VegaLite.mark(:line)
|> VegaLite.encode_field(:x, "input", type: :quantitative)
|> VegaLite.encode_field(:y, "output", type: :quantitative)

gamma.png

表示したグラフは SVG や PNG としてそのまま保存できます

save_graph.gif

値によるグラフの変化を表示

グラフも画像と同様に Kino.Layout.grid で並べることができます

ただし、グラフの横幅を指定しないと、全体が見えなくなることがあります

gamma_list
|> Enum.map(fn gamma ->
  %{
    input: Enum.to_list(0..255),
    output: Image.gamma_table(gamma) |> Nx.to_flat_list()
  }

  VegaLite.new(width: 150) # グラフの全体が見えるように横幅を指定
  |> VegaLite.data_from_values(plot_data)
  |> VegaLite.mark(:line)
  |> VegaLite.encode_field(:x, "input", type: :quantitative)
  |> VegaLite.encode_field(:y, "output", type: :quantitative)
end)
|> Kino.Layout.grid(columns: 4)

スクリーンショット 2023-01-08 20.21.14.png

複数のグラフを重ねて表示

Kino.VegaLite では、項目を color として指定することで複数表示することが可能です

この場合、当然 color 用の項目も準備しないといけないので、少しコードが複雑になってしまいます

plot_data =
  gamma_list
  |> Enum.reduce(%{gamma: [], input: [], output: []}, fn gamma, acc ->
    %{
      gamma: acc.gamma ++ List.duplicate(gamma, 256),
      input: acc.input ++ Enum.to_list(0..255),
      output: acc.output ++ (Image.gamma_table(gamma) |> Nx.to_flat_list())
    }
  end)

VegaLite.new()
|> VegaLite.data_from_values(plot_data)
|> VegaLite.mark(:line)
|> VegaLite.encode_field(:x, "input", type: :quantitative)
|> VegaLite.encode_field(:y, "output", type: :quantitative)
|> VegaLite.encode_field(:color, "gamma", type: :nominal)

multi_gamma.png

まとめ

グラフ表示に関しては、 Python の matplotlib がかなり上手く解釈してコード量を少なくしています

既に整形されたデータであれば Elixir でもそこまで手間は必要ありませんが、単に配列をグラフ表示したい場合には Python の方が今のところ簡単です

逆に画像表示や複数結果を並べる場合、 Elixir Livebook の方がかなり簡単で直感的でした

4
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?