Skip to content

Instantly share code, notes, and snippets.

@shoz-f
Last active January 11, 2022 11:54
Show Gist options
  • Select an option

  • Save shoz-f/3ebf1d09dc2a1a29997c81ea3d56c495 to your computer and use it in GitHub Desktop.

Select an option

Save shoz-f/3ebf1d09dc2a1a29997c81ea3d56c495 to your computer and use it in GitHub Desktop.
「...サクッと画像加工するノリ」の猿まねをやってみた💦
# 「...サクッと画像加工するノリ」の猿まねをやってみた💦
## 1.はじめに
@piacerexさんのQuiika記事【「JupyterNotebook + NumPyでサクッと画像加工するノリ」をElixirでやってみた】
に触発されて、拙作の cimg_exでもやってみようと思い立った。要は猿まねである🐵
<!-- livebook:{"break_markdown":true} -->
まず最初に、Livebookのインストールならびに起動etc.に関しては、@piacerexさんの
[記事](https://qiita.com/piacerex/items/533d26c81bada4741422)を参照して頂きたい。そう、手抜きである。
Livebookが起動できて、無事ブラウザを接続出来たものとして話を進める。
本記事を通して必要となるモジュールは下記の4つである。
`Elixir cell`の左上の"Evaluate"ボタンを押すか、cell内で
ctrl+Enterをキーインしてインストールを実行する。
```elixir
Mix.install([
{:cimg, "~> 0.1.6"},
{:nx, "~> 0.1.0"},
{:kino, "~> 0.3.1"},
{:download, "~> 0.0.4"}
])
```
次に、@piacerexさんの記事に倣って、旧世代の画像処理屋にとっては超有名人のLennaさんの画像をダウンロードしておく。
ここまでが前準備だ。
```elixir
# 再実行時、Download.from()でeexistエラーになるのを防止
File.rm("Lenna_%28test_image%29.png")
lenna =
Download.from("https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png")
|> elem(1)
```
## 2.CImgでサクッと猿まねする
猿まねなので、やってみる画像加工の課題は元記事と同じく次の3つだ。
1. Lennaさん画像をロードして、KinoでLiveBookに表示
2. グレイ画像に変換して表示
3. 反転画像に変換して表示
課題1のコードは次の通り。CImg.load/1で画像を読み込み、その画像をjpegフォーマットに変換し Kino.Image.new/2に渡しておしまい。
```elixir
img = CImg.load(lenna)
img
# CImg画像をjpegバイナリに変換する
|> CImg.to_jpeg()
|> Kino.Image.new(:jpeg)
```
課題2のコードは次の通り。
```elixir
img
# グレイ画像に変換する
|> CImg.gray()
|> CImg.to_jpeg()
|> Kino.Image.new(:jpeg)
```
課題3のコードは次の通り。
```elixir
img
# カラー反転画像に変換する
|> CImg.invert()
|> CImg.to_jpeg()
|> Kino.Image.new(:jpeg)
```
……記事になる内容が無かった orz
## 3.Nxを使ってごにょごにょやってみる
このままでは薄っぺらな記事にしかならないので、Nxを使って次の2つの課題をやってみる。要は記事の水増しである。
1. Lennaさんの画像をR/G/Bそれぞれのカラー・プレーンに分割して表示
2. Rプレーンを差し替えた画像を合成して表示
これらの課題は、cimg_exが目的とする用途ではたぶん発生しないので、機能として実装していない。ゆえに、Nxを使ってごにょごにょとやることになる。
数行のコードで終わるなんてことはない…きっと
<!-- livebook:{"break_markdown":true} -->
課題4に取り掛かろう。<br>
まず CImg画像を Nx.tensorに変換する。CImg.to_flat/2を用いると CImg画像をバイナリにシリアライズすることができる
(正しくはNpy形式)。そのバイナリを Nx.from_binary/3に食わして、Nx.reshape/3で整形し Nx.tensor `rgb`を得る。
この時点では、tensorに格納されている画像データは所謂 (N)HWC形式になっている。
```elixir
# CImg画像をバイナリにシリアライズする(NHWCオーダー)
rgb =
CImg.to_flat(img, [{:dtype, "<u1"}]).data
|> Nx.from_binary({:u, 8})
|> Nx.reshape({512, 512, :auto})
```
画像データをR/G/Bの各カラー・プレーンに分割するには、(N)HWC形式よりも(N)CHW形式の画像データの方が都合が良いので、
Nx.transpose/2で`rgb`の軸を入れ替えて(N)CHW形式に変換する。これで tensorの一軸目のインデックスが色を表すことになったので、
あとは red=tensor[0], green=tensor[1], blue=tensor[2]の様に分割すればよい。
```elixir
# NHWCからNCHWに変換する
[red, green, blue] =
Nx.transpose(rgb, axes: [2, 0, 1])
# R/G/Bにスプリットする
|> (&[&1[0], &1[1], &1[2]]).()
```
さて、無事に画像データをR/G/Bの tensor - red, green, blue - に分割出来たわけだが、
今やそれらのtensorは 24bitカラー情報を持っておらず、カラー・プレーン毎の 8bitの輝度情報しか持っていない。
つまり、red, green, blueをそのままCImg画像に戻すと、赤,緑,青の画像にはならないのだ。
それぞれで足らない色情報をゼロで補い、24bitカラー情報に加工する必要がある。
加工で必要となる zero tensorを用意しよう。
```elixir
zero = Nx.broadcast(0, {512, 512}) |> Nx.as_type({:u, 8})
```
課題4の総仕上げだ。<br>
tensorの組、赤{red,zero,zero}, 緑{zero,green,zero}, 青{zero,zero,blue}を、
それぞれ Nx.concatenate/2(4行目)で合成し、それら3つの合成 tensorを縦に繋げて一つにする(6行目)。
これを Nx.to_binary/2でシリアライズし、CImg.create_from_bin/6で CImg画像に戻す。あとは例の如く。(完)
```elixir
res =
[red, zero, zero, zero, green, zero, zero, zero, blue]
# 4行目のconcatenate/2の為に軸を増やす
|> Enum.map(&Nx.reshape(&1, {512, 512, 1}))
|> Enum.chunk_every(3)
# R/G/B [512][512][3] のそれぞれの画像データを合成する
|> Enum.map(&Nx.concatenate(&1, axis: 2))
|> IO.inspect()
# R/G/BをH方向に繋げて一つの画像データにする
|> Nx.concatenate()
|> Nx.to_binary()
# CImg画像に変換する
|> CImg.create_from_bin(512, 512 * 3, 1, 3, "<u1")
|> CImg.to_jpeg()
|> Kino.Image.new(:jpeg)
```
課題5は、課題4の応用だ。<br>
Rプレーンのデータを Gプレーンで置き換えてみた。ブルーが映えるモノクロームな画像になった。予想外だ。<br>
コードの説明は省略。
```elixir
# RのデータをGのデータで置き換える
res =
[green, green, blue]
|> Enum.map(&Nx.reshape(&1, {512, 512, 1}))
|> Nx.concatenate(axis: 2)
|> IO.inspect()
|> Nx.to_binary()
|> CImg.create_from_bin(512, 512, 1, 3, "<u1")
|> CImg.to_jpeg()
|> Kino.Image.new(:jpeg)
```
## 4.まとめ
以上、猿まねとNxでごにょごにょをやってみた。結局中身が無い記事になったよーな…
[注釈]<br>
拙作の cimg_exは、組み込みDeep Learningエンジンと組み合すことを狙いとした、ライト・ウエイトな画像処理モジュールを目指している。
そのため、SHIFT局所特徴量検出やCannyエッジ検出、はたまた表示機能やカメラ制御など、現場ではほぼ不要と思われる機能は搭載していない。
そう、実はこの記事の様な汎用的な用途には向かないのだ。ぱっと見は機能が少なく貧相に見えるが、同じく拙作のTensorflow lite拡張 TflInterp
と組み合わせていくつか Deep Learningのデモを書いてみたところ、これで十分だったりするのだが…
<!-- livebook:{"break_markdown":true} -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment