Last active
January 11, 2022 11:54
-
-
Save shoz-f/3ebf1d09dc2a1a29997c81ea3d56c495 to your computer and use it in GitHub Desktop.
「...サクッと画像加工するノリ」の猿まねをやってみた💦
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 「...サクッと画像加工するノリ」の猿まねをやってみた💦 | |
| ## 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