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 3 years have passed since last update.

Elixir Evision と Nx で画像のコントラスト強調(ヒストグラム平坦化)を実装する

4
Last updated at Posted at 2023-01-01

はじめに

バッチリメイクをしたはずなのに写真ではイマイチ印象に残らない、そんなとき、ありますよね

スクリーンショット 2022-12-06 17.36.24.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 だけです

画像のダウンロード

いつものように 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-06 17.36.24.png

これが元画像になります

グレースケールの場合

まずは単純なグレースケールの場合について実装します

画像をグレースケールに変換します

gray_img = Evision.cvtColor(img, Evision.Constant.cv_COLOR_BGR2GRAY())

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

このグレーススケールの各ピクセルの値についてヒストグラムを作ってみます

ヒストグラムとは、値毎の件数をグラフ化したものです

画像の場合、 0 (真っ白)から 255 (真っ黒)の値がそれぞれ画像内に何回出現したか、を表します

例えば 128 (灰色)近辺の件数が高く、 0 や 255 近辺の件数が少ない = ヒストグラムが偏っている場合、画像全体が同じような灰色になっている、ということなので、コントラストが弱いと言えます

逆に 0 から 255 まで満遍なく件数が同じくらいある = ヒストグラムが平坦になっている場合、真っ白な部分も真っ黒な部分も灰色の部分も同じくらいある、ということになるのでコントラストが高いと言えます

pixel =
  gray_img
  |> Evision.Mat.to_nx()
  |> Nx.to_flat_list()

VegaLite.new()
|> VegaLite.data_from_values(x: pixel)
|> VegaLite.mark(:bar)
|> VegaLite.encode_field(:x, "x", type: :quantitative)
|> VegaLite.encode_field(:y, "x", type: :quantitative, aggregate: :count)

gray_hist.png

ヒストグラムを見ると、 50 あたりと 150 あたりの値が多いことが分かります

そして 0 や 255 の近辺は明らかに少ないです

では、ヒストグラムを平坦にしてみましょう

Evision.equalizeHist に画像を渡すだけです

equalized_img = Evision.equalizeHist(gray_img)

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

画像がくっきりしたように見えます

元画像と並べてみましょう

Kino.Layout.grid([gray_img, equalized_img], columns: 2)

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

明らかにヒストグラム平坦化した方がくっきり、コントラストが強くなっていますね

平坦化した画像のヒストグラムを確認してみましょう

equalized_pixel =
  equalized_img
  |> Evision.Mat.to_nx()
  |> Nx.to_flat_list()

VegaLite.new()
|> VegaLite.data_from_values(x: equalized_pixel)
|> VegaLite.mark(:bar)
|> VegaLite.encode_field(:x, "x", type: :quantitative)
|> VegaLite.encode_field(:y, "x", type: :quantitative, aggregate: :count)

 equalized_gray_hist.png

完全な平坦ではないですが、元と比較すると凹凸が小さくなっています

カラーの場合

カラーの場合は少し難しくなります

Evision.equalizeHist はグレースケール(チャネル数=色数1)にしか使えません

かといって R G B それぞれについてヒストグラムを平坦化すると、色味が変わってしまいます

なので、画像を BGR 色空間から HSV 色空間に変換します

Evison、OpenCV では RGB ではなく BGR を使っています

たまたま最近そのことについて記事を書いている方がいました

OpenCV のピクセル形式が BGR である理由

BGR は青(Blue)、緑(Green)、赤(Red)の頭文字で、それぞれの原色がどれくらい強いかで色を表現します

HSV は色相(Hue)、彩度(Saturation)、明度(Value)の頭文字で、色の種類、鮮やかさ、明るさの組み合わせで色を表現します

色の種類や鮮やかさは変えずに明るさだけ変えたいので、 HSV から V だけ取り出してヒストグラム平坦化してから、 HS とくっ付ければいいわけです

hsv_img = Evision.cvtColor(img, Evision.Constant.cv_COLOR_BGR2HSV())

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

HSV に変換しても画像を表示するときには BGR として表示しようとするため、なんだかミステリアスな画像になっています

HSV から V を取り出すので、 Nx の行列にしてから 2 次元目の 2 列目を抜き出します

この値は明るさだけを取り出しているので、グレースケール画像のように表示できます

Evision のマトリックスを Evision.Mat.to_nx で Nx のテンソルに変換しています

Evision.Backend だと対応していない関数があるため、 Nx.BinaryBackend を指定しています

value =
  hsv_img
  |> Evision.Mat.to_nx(Nx.BinaryBackend)
  |> Nx.slice_along_axis(2, 1, axis: 2)

Kino.Image.new(value)

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

この V に対して Evision.equalizeHist を実行します

少し気をつけないといけないのは、次元数を下げてから実行し、その後に戻すことです

equalized_value =
  value
  # 次元数を下げる {縦, 横, 1} -> {縦, 横} 
  |> Nx.squeeze()
  |> Evision.equalizeHist()
  |> Evision.Mat.to_nx(Nx.BinaryBackend)
  # 次元数を上げる {縦, 横} -> {縦, 横, 1} 
  |> Nx.new_axis(2)

Kino.Image.new(equalized_value)

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

この時点でコントラストが強調されたのが分かります

最後に HS と V を結合します

hs =
  hsv_img
  |> Evision.Mat.to_nx(Nx.BinaryBackend)
  |> Nx.slice_along_axis(0, 2, axis: 2)
equalized_img =
  [hs, equalized_value]
  |> Nx.concatenate(axis: 2)
  |> Evision.Mat.from_nx_2d()
  |> Evision.cvtColor(Evision.Constant.cv_COLOR_HSV2BGR())

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

カラーでもくっきりしています

元画像と並べて見ましょう

Kino.Layout.grid([img, equalized_img], columns: 2)

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

明確にコントラストが強調されていますね

まとめ

VegaLite だとヒストグラムも簡単に作れるので、平坦化の概念が理解しやすかったです

参考サイト

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?