7
5

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 Image (Vix, libvips)で簡単画像処理

Last updated at Posted at 2023-01-31

はじめに

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 を使ってください

https://rancherdesktop.io/

実行環境の注意点

Image (Vix) を使うためには libvpc が必要です

Ubuntu の場合は apt-get install libvpc-dev を実行してください

事前準備

適当な画像を以下のパスに用意したものとします

  • /tmp/puppies.jpg (子犬画像、なんでも良い)
  • /tmp/noize.png (黒背景に白で単純な模様を描いた画像、点々のノイズがある)

私が用意したのはこちらの画像

puppies.jpg

noize.png

また、クロマキー用に背景が一色の画像も任意の場所に用意しておいてください

私が用意したのはこちらの画像

pen.png

セットアップ

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

Mix.install([
  {:image, "~> 0.35"},
  {:req, "~> 0.3"},
  {:kino, "~> 0.9"}
])

画像の読込

ファイルから

Image.open! にパスを渡すと画像を開きます

puppies_img = Image.open!("/tmp/puppies.jpg")

結果は以下のように、属性情報と画像のタブ表示になります

image_tabs.gif

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)

pen_r.png

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)

lenna_400.png

画像処理

色々な画像処理を実行してみましょう

色変換

Image.to_colorspace! で色を変換します

以下のコードはカラー画像を白黒にします

puppies_img
|> Image.to_colorspace!(:bw)
|> Image.Kino.show()

puppies_bw.png

ぼかし

Image.blur! で画像をぼかします

puppies_img
|> Image.blur!()
|> Image.Kino.show()

blur.png

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)

blur_sigma.png

モザイク

Image.pixelate! でモザイクを掛けます

puppies_img
|> Image.pixelate!()
|> Image.Kino.show()

pixelated.png

第2引数にモザイクの大きさ(画像全体を 1.0 としたときの相対値)を指定してモザイクの強さを調整します

1..10
|> Enum.map(fn scale ->
  puppies_img
  |> Image.pixelate!(scale / 100)
  |> Image.Kino.show()
end)
|> Kino.Layout.grid(columns: 5)

pixelate_scale.png

リサイズ

Image.resize! で元の大きさを 1.0 としたときの比を指定することでリサイズできます

puppies_img
|> Image.resize!(0.2)
|> Image.Kino.show()

small.png

反転

Image.flip! で反転します

第2引数で方向を指定します

:horizontal で左右反転

puppies_img
|> Image.flip!(:horizontal)
|> Image.Kino.show()

flipped_h.png

:vertical で上下反転

puppies_img
|> Image.flip!(:vertical)
|> Image.Kino.show()

flipped_v.png

回転

Image.rotate! に角度を指定して回転します

puppies_img
|> Image.rotate!(45)
|> Image.Kino.show()

rotated.png

回転したとき余白を追加しています

せっかくなのでアニメーションで見てみましょう

Stream.interval(1)
|> Stream.take(361)
|> Kino.animate(fn angle ->
  puppies_img
  |> Image.rotate!(angle)
  |> Image.Kino.show()
end)

rotate.gif

切り抜き

画像の特定の範囲を切り抜きます

引数は画像全体の幅、高さを 1.0 としたときの比で与えます

  • 左上の X 座標
  • 左上の Y 座標
  • 高さ
puppies_img
|> Image.crop!(0.3, 0.2, 0.5, 0.7)
|> Image.Kino.show()

croped.png

合成

Image.compose! で複数の画像を合成します

blend_mode で合成方法を指定します

:over は単純に上書きします

puppies_img
|> Image.compose!(lenna_img, blend_mode: :over)
|> Image.Kino.show()

over.png

:lighten は明るい方の値にします

puppies_img
|> Image.compose!(lenna_img, blend_mode: :lighten)
|> Image.Kino.show()

lighten.png

:add は画素値を足します

puppies_img
|> Image.compose!(lenna_img, blend_mode: :add)
|> Image.Kino.show()

add.png

:difference は画素値を引きます(差分抽出)

puppies_img
|> Image.compose!(lenna_img, blend_mode: :difference)
|> Image.Kino.show()

difference.png

膨張、収縮(ノイズ除去)

ノイズが乗っている画像を読み込みます

noize_img = Image.open!("/tmp/noize.png")

Image.Kino.show(noize_img)

noize.png

画像内の白い部分を 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)

dilate_erode.png

収縮してから膨張させることで、ノイズ(白い点々)を除去することができます

[
  noize_img,
  noize_img |> Image.erode!(3) |> Image.dilate!(3)
]
|> Enum.map(&Image.Kino.show(&1, max_height: 400))
|> Kino.Layout.grid(columns: 2)

remove_noize.png

クロマキー

画像から背景を消します

Image.chroma_color で背景色を取得します

chroma_key_img
|> Image.chroma_color()
|> then(&Image.new!(200, 200, color: &1, bands: 3))
|> Image.Kino.show()

bg.png

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)

chroma_mask.png

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)

chroma_key.png

まとめ

Image を使うことで簡単に画像処理を実装できました

まだまだ様々な機能があるので、色々試してみたいと思います

7
5
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
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?