はじめに
Elixir の Image という、そのまんまのネーミングのモジュールを使って画像処理を行います
Image は Vix という画像処理モジュールをより使いやすくしたモジュールです
そして Vix は libvips という軽量、高速な画像処理ライブラリの Elixir ラッパーです
今回も Livebook を使っていきます
実装したノートブックはこちら
実行環境
- Elixir: 1.14.2 OTP 24
- Livebook: 0.9.3
以下のリポジトリーの Docker コンテナ上で起動しています
Docker が使える環境であれば簡単に実行できます
https://docs.docker.com/engine/install/
Docker Desktop を無償利用できない場合は Rancher Desktop を使ってください
実行環境の注意点
Image (Vix) を使うためには libvpc が必要です
Ubuntu の場合は apt-get install libvpc-dev
を実行してください
事前準備
適当な画像を以下のパスに用意したものとします
- /tmp/puppies.jpg (子犬画像、なんでも良い)
- /tmp/noize.png (黒背景に白で単純な模様を描いた画像、点々のノイズがある)
私が用意したのはこちらの画像
また、クロマキー用に背景が一色の画像も任意の場所に用意しておいてください
私が用意したのはこちらの画像
セットアップ
必要なモジュールをインストールします
Mix.install([
{:image, "~> 0.35"},
{:req, "~> 0.3"},
{:kino, "~> 0.9"}
])
画像の読込
ファイルから
Image.open!
にパスを渡すと画像を開きます
puppies_img = Image.open!("/tmp/puppies.jpg")
結果は以下のように、属性情報と画像のタブ表示になります
Kino.Input から
Kino.Input からの画像を読み込みます
以下を実行して表示される画像アップロードに、事前準備していたクロマキー用の画像を入力してください
chroma_key_img_input = Kino.Input.image("CHROMA KEY IMAGE", format: :png)
Image.from_binary
でバイナリを読み込みます
chroma_key_img =
chroma_key_img_input
|> Kino.Input.read()
|> then(& &1.data)
|> Image.from_binary!()
Image.Kino.show(chroma_key_img, max_height: 400)
Web から
Web からダウンロードする場合も同様です
lenna_img =
"https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
|> Req.get!()
|> then(& &1.body)
|> Image.from_binary!()
Image.Kino.show(lenna_img, max_height: 400)
画像処理
色々な画像処理を実行してみましょう
色変換
Image.to_colorspace!
で色を変換します
以下のコードはカラー画像を白黒にします
puppies_img
|> Image.to_colorspace!(:bw)
|> Image.Kino.show()
ぼかし
Image.blur!
で画像をぼかします
puppies_img
|> Image.blur!()
|> Image.Kino.show()
sigma に 1.0 から 10.0 の値を与えることでぼかし具合を調整します
1..10
|> Enum.map(fn sigma ->
puppies_img
|> Image.blur!(sigma: sigma)
|> Image.Kino.show()
end)
|> Kino.Layout.grid(columns: 5)
モザイク
Image.pixelate!
でモザイクを掛けます
puppies_img
|> Image.pixelate!()
|> Image.Kino.show()
第2引数にモザイクの大きさ(画像全体を 1.0 としたときの相対値)を指定してモザイクの強さを調整します
1..10
|> Enum.map(fn scale ->
puppies_img
|> Image.pixelate!(scale / 100)
|> Image.Kino.show()
end)
|> Kino.Layout.grid(columns: 5)
リサイズ
Image.resize!
で元の大きさを 1.0 としたときの比を指定することでリサイズできます
puppies_img
|> Image.resize!(0.2)
|> Image.Kino.show()
反転
Image.flip!
で反転します
第2引数で方向を指定します
:horizontal
で左右反転
puppies_img
|> Image.flip!(:horizontal)
|> Image.Kino.show()
:vertical
で上下反転
puppies_img
|> Image.flip!(:vertical)
|> Image.Kino.show()
回転
Image.rotate!
に角度を指定して回転します
puppies_img
|> Image.rotate!(45)
|> Image.Kino.show()
回転したとき余白を追加しています
せっかくなのでアニメーションで見てみましょう
Stream.interval(1)
|> Stream.take(361)
|> Kino.animate(fn angle ->
puppies_img
|> Image.rotate!(angle)
|> Image.Kino.show()
end)
切り抜き
画像の特定の範囲を切り抜きます
引数は画像全体の幅、高さを 1.0 としたときの比で与えます
- 左上の X 座標
- 左上の Y 座標
- 幅
- 高さ
puppies_img
|> Image.crop!(0.3, 0.2, 0.5, 0.7)
|> Image.Kino.show()
合成
Image.compose!
で複数の画像を合成します
blend_mode
で合成方法を指定します
:over
は単純に上書きします
puppies_img
|> Image.compose!(lenna_img, blend_mode: :over)
|> Image.Kino.show()
:lighten
は明るい方の値にします
puppies_img
|> Image.compose!(lenna_img, blend_mode: :lighten)
|> Image.Kino.show()
:add
は画素値を足します
puppies_img
|> Image.compose!(lenna_img, blend_mode: :add)
|> Image.Kino.show()
:difference
は画素値を引きます(差分抽出)
puppies_img
|> Image.compose!(lenna_img, blend_mode: :difference)
|> Image.Kino.show()
膨張、収縮(ノイズ除去)
ノイズが乗っている画像を読み込みます
noize_img = Image.open!("/tmp/noize.png")
Image.Kino.show(noize_img)
画像内の白い部分を Image.dilate!
で膨張させ、 Image.erode!
で収縮させます
[
noize_img |> Image.dilate!(3),
noize_img |> Image.erode!(3)
]
|> Enum.map(&Image.Kino.show(&1, max_height: 400))
|> Kino.Layout.grid(columns: 2)
収縮してから膨張させることで、ノイズ(白い点々)を除去することができます
[
noize_img,
noize_img |> Image.erode!(3) |> Image.dilate!(3)
]
|> Enum.map(&Image.Kino.show(&1, max_height: 400))
|> Kino.Layout.grid(columns: 2)
クロマキー
画像から背景を消します
Image.chroma_color
で背景色を取得します
chroma_key_img
|> Image.chroma_color()
|> then(&Image.new!(200, 200, color: &1, bands: 3))
|> Image.Kino.show()
Image.split_alpha
で画像を RGB と A に分割します
{rgb, alpha} = Image.split_alpha(chroma_key_img)
RGB 画像を Image.chroma_mask!
に渡すと背景が黒、前景が白の画像(マスク画像)を取得できます
[
chroma_key_img,
Image.chroma_mask!(rgb)
]
|> Enum.map(&Image.Kino.show(&1, max_height: 400))
|> Kino.Layout.grid(columns: 2)
RGB 画像を Image.chroma_key!
に渡すと背景を消した画像を取得できます
[
chroma_key_img,
Image.chroma_key!(rgb)
]
|> Enum.map(&Image.Kino.show(&1, max_height: 400))
|> Kino.Layout.grid(columns: 2)
まとめ
Image を使うことで簡単に画像処理を実装できました
まだまだ様々な機能があるので、色々試してみたいと思います