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 Evision と Nx で画像の明るさ調整(ガンマ補正)を実装する

Last updated at Posted at 2022-12-28

はじめに

画像が暗くて見づらい、印象が悪く見えてしまう、そんなとき、ありますよね

dark.png

その問題、解決できます

そう、 Elixir ならね

というわけでガンマ補正で画像の明るさを調整します

もちろん Elixir 、 Nx 、 Evision の組み合わせです

当然のように Livebook で実行します

実装の全量はこちら

実行環境

Livebook 0.8.0 の Docker イメージを元にしたコンテナで動かしました

コンテナ定義はこちらを参照

セットアップ

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

Mix.install([
  {:req, "~> 0.3"},
  {:evision, "~> 0.1"},
  {:kino, "~> 0.8"},
  {:nx, "~> 0.4"},
  {:kino_vega_lite, "~> 0.1.7"}
])
  • Req: 元画像のダウンロード
  • Evision: 画像処理
  • Kino: 結果表示
  • Nx: 行列演算
  • Kino_vega_lite: グラフ表示

実行自体に必要なのは Nx と Evision だけです

行列演算を見やすくしたいので Nx.Defn をインポートしておきます

これで defn 内で演算子による行列演算が記述できます

import Nx.Defn

画像のダウンロード

いつものように Lenna さんを呼びます

img =
  "https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
  |> Req.get!()
  |> then(& &1.body)
  |> Evision.imdecode(Evision.Constant.cv_IMREAD_COLOR())

これが元画像になります

スクリーンショット 2022-12-28 11.23.36.png

ガンマ補正

ガンマ補正モジュールを以下のように定義します

GammaCorrection.correct に画像と gamma パラメータを渡すことで明るさ補正ができます

gamma < 1.0 のとき暗くなり、 gamma > 1.0 のとき明るくなります

中身は後で解説します

defmodule GammaCorrection do
  defn generate_gamma_tensor(gamma) do
    (Nx.iota({256, 1}) / 255) ** (1 / gamma) * 255
  end

  def correct(img, gamma) do
    gamma_tensor = generate_gamma_tensor(gamma)
    Evision.lut(img, gamma_tensor)
  end
end

では実行してみましょう

元画像とガンマ補正結果を並べてみます

[
  img,
  GammaCorrection.correct(img, 5.0)
]
|> Kino.Layout.grid(columns: 2)

スクリーンショット 2022-12-28 11.34.32.png

明るくなりましたね

次は 0.25 から 2.0 の範囲でガンマ補正をかけてみます

1..8
|> Enum.to_list()
|> Enum.map(fn gamma_seed -> GammaCorrection.correct(img, gamma_seed / 4) end)
|> Kino.Layout.grid(columns: 4)

スクリーンショット 2022-12-28 11.27.59.png

左上(gamma = 0.25)から右下(gamma = 2.0)に行くにつれて、どんどん明るくなるのがわかると思います

演算内容の確認

ガンマ補正で使っているテンソルの中身を見てみましょう

gamma_tensor = GammaCorrection.generate_gamma_tensor(0.1)

スクリーンショット 2022-12-28 11.36.51.png

うーん、これだけを見てもなんともですね

gamma を変化させながらグラフにして見ましょう

data =
  [0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0]
  |> Enum.reduce(%{gamma: [], src: [], dst: []}, fn gamma, acc ->
    %{
      gamma: acc.gamma ++ List.duplicate(gamma, 256),
      src: acc.src ++ Enum.to_list(0..255),
      dst: acc.dst ++ (gamma |> GammaCorrection.generate_gamma_tensor() |> Nx.to_flat_list())
    }
  end)
VegaLite.new()
|> VegaLite.data_from_values(data, only: ["src", "dst", "gamma"])
|> VegaLite.mark(:line)
|> VegaLite.encode_field(:x, "src", type: :quantitative)
|> VegaLite.encode_field(:y, "dst", type: :quantitative)
|> VegaLite.encode_field(:color, "gamma", type: :nominal)

visualization.png

このグラフの src が元の明るさ、 dst が変換後の明るさと捉えてください

gamma = 10 のグラフを見ると、 src が小さい値のときから dst の値がかなり大きくなっています
gamma = 1 のときは src = dst です
gamma = 0.1 のとき、 src が 150 を越えないとほとんど dst が大きくなりません

Evision.lut は Look Up Table という関数で、テーブルを元に値を変換します

変換例

  • src(変換元): [0, 1, 2, 0, 1, 2]

  • 変換テーブル: [255, 100, 0, 200, 50, ...](長さ=256)
    0 -> 255, 1-> 100, 2 -> 0, 3 -> 200, 4 -> 500, ... というように変換する

  • dst(変換後): [255, 100, 0, 255, 100, 0]

なので、 gamma = 10 の変換テーブルを使えば小さい値(暗い色)でも大きい値(明るい値)に変換される結果、画像が明るくなります

逆に gamma = 0.1 の変換テーブルを使うと大きい値でも小さい値に変換されるため、画像が暗くなります

まとめ

思った以上にガンマ補正がシンプルに実装できました

Elixir だとやっぱり書くのが楽しいですね

4
3
0

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?