LoginSignup
5
0

Elixir OpenCV: StbImageとNx.Tensor と Evision.Mat で相互変換

Last updated at Posted at 2023-05-03

Elixir で OpenCV を使うときにはどのような素材をどこから取ってきてどう加工するかによって求められる関数が異なり、場合により複数のパッケージが連携する必要があるかと思います。

@RyoWakabayashi さんのElixir Image と Nx と evision で相互変換 が大変参考になりました。

ここでは elixir-image/image よりシンプルな elixir-nx/stb_imagecocoa-xu/evision で相互変換をしてみようと思います。

セットアップ

Mix.install([
  {:nx, "~> 0.5.0"},
  {:evision, "0.1.31"},
  {:stb_image, "~> 0.6.0"},

  # 画像を Web からダウンロードするため
  {:req, "~> 0.3.0"},

  # Livebook上で画像を表示するため
  # {:kino, "~> 0.9.0"}
])

cocoa-xu/evisionパッチバージョンでも破壊的な変更があるようのでバージョンをロックしておいた方が無難そうです。

cocoa-xu/evision の作者は好んで Cv エイリアスを使用しているのでそれに則ります。

alias Evision, as: Cv

画像加工の一例

一例として画像をこのように加工することができます。ここで StbImageNx.TensorEvision.Mat で相互変換がされています。

img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Antonio_Inoki_IMG_0398-2_20121224.JPG/330px-Antonio_Inoki_IMG_0398-2_20121224.JPG"

{x, y, w, h} = {40, 30, 250, 250}
rect_start_point = {x, y}
rect_end_point = {x + w, y + h}
rect_color = {0, 255, 0}
rect_options = [thickness: 5]

Req.get!(img_url)
|> then(& &1.body)
|> StbImage.read_binary!()
|> StbImage.to_nx()
|> Cv.Mat.from_nx_2d()
|> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())
|> Cv.rectangle(rect_start_point, rect_end_point, rect_color, rect_options)

こういう書き方もできそうです。

img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Antonio_Inoki_IMG_0398-2_20121224.JPG/330px-Antonio_Inoki_IMG_0398-2_20121224.JPG"
save_as = Path.join(System.tmp_dir!(), URI.encode_www_form(img_url))

{x, y, w, h} = {40, 30, 250, 250}
rect_start_point = {x, y}
rect_end_point = {x + w, y + h}
rect_color = {0, 255, 0}
rect_options = [thickness: 5]

Req.get!(img_url, output: save_as)
|> then(fn _ -> Cv.imread(save_as) end)
|> Cv.rectangle(rect_start_point, rect_end_point, rect_color, rect_options)

アントニオ猪木

image creadit: https://ja.wikipedia.org/wiki/アントニオ猪木

画像の読込

画像はバイナリとして読み込む場合もあれば、ファイルを読み込む場合もあると思います。

Req.get!/2 を用いて Web から画像バイナリをダウンロードすることができます。

img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Antonio_Inoki_IMG_0398-2_20121224.JPG/330px-Antonio_Inoki_IMG_0398-2_20121224.JPG"

img_data =  Req.get!(img_url) |> then(& &1.body)

また、画像をファイルとしてローカルに保存したい場合があるかもしれません。

download = fn url ->
  save_as = Path.join(System.tmp_dir!(), URI.encode_www_form(url))
  Req.get!(url, output: save_as)
  save_as
end

img_file = download.(img_url)

Binary -> StbImage

画像バイナリの読み込みには StbImage.read_binary!/1 が手軽で便利です。StbImage 構造体が帰ります。

img_stb = StbImage.read_binary!(img_data)

File -> Evision.Mat

ローカルに保存された画像ファイルを読み込む場合は Cv.imread/1 が使えます。

img_mat = Cv.imread(img_file)

StbImage <-> Nx.Tensor

StbImage は簡単に Nx.Tensor へ変換できます。

img_nx = StbImage.to_nx(img_stb)

逆も簡単です。

img_stb = StbImage.from_nx(img_nx)

Nx.Tensor <-> Evision.Mat

Nx.Tensor から Evision.Mat への変換も基本的には同様にできるのですが、ここでは注意が必要です。2点あります。

RGB vs BGR

  • OpenCVは色チャネルをB(青)・G(緑)・R(赤)の順に保持しています。ですので、適切な場所で B と R 入れ替える必要があります。

Evision.Mat.from_nx vs Evision.Mat.from_nx_2d

  • これは正直いうとよくわかりませんが、2Dの画像を取り扱う場合は from_nx_2d を使わないとうまく処理ができないようです。OpenCV の関数を使って画像加工しても何も起こらない場合はここを見落としている可能性があります。エラーがでないので厄介です。
img_nx =
  img_mat
  |> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())
  |> Cv.Mat.to_nx()

img_mat =
  img_nx
  |> Cv.Mat.from_nx_2d()
  |> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())

Evision.Mat <-> StbImage

Evision.MatStbImage との間では直接の変換はできませんが、Nx.Tensor を介在させることにより実現可能です。

img_mat
|> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())
|> Cv.Mat.to_nx()
|> StbImage.from_nx()

img_stb
|> StbImage.to_nx()
|> Cv.Mat.from_nx_2d()
|> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())

アルファチャネルを取り除く変換

場合によりアルファチャネルを取り除きたい場合があると思いますが、現時点では Evision.Backend はスライス構文に対応していないようです。

Cv.Mat.to_nx(img_mat)[[.., .., 0..2]]

# ** (RuntimeError) operation slice is not yet supported on Evision.Backend.
# Please use another backend like Nx.BinaryBackend or Torchx.Backend.
#   To use Torchx.Backend, :torchx should be added to your app's deps.
#   Please see <https://github.com/elixir-nx/nx/tree/main/torchx> for more information on how to install and use it.
# To convert the tensor to another backend, please use Evision.Mat.to_nx(tensor, Backend.ModuleName)
#   for example, Evision.Mat.to_nx(tensor, Nx.BinaryBackend) or Evision.Mat.to_nx(tensor, Torchx.Backend).

Evison.MatNx.Tensor に変換するときにバックエンドをしていするとうまくいきます。

Cv.Mat.to_nx(img_mat, Nx.BinaryBackend)[[.., .., 0..2]]
5
0
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
5
0