Elixir OpenCV で hello world
Tags: elixir, opencv, helloworld
Published: 2023-05-04 02:16:02

何事でも基本が大切だと思います。プログラミングではよく ["hello, world\n"] を印字したりします。[OpenCV] でもそれ相当のことをやってみたいと思います。

[OpenCV]: https://ja.wikipedia.org/wiki/OpenCV
[elixir-image/image]: https://github.com/elixir-image/image
[elixir-nx/stb_image]: https://github.com/elixir-nx/stb_image
[cocoa-xu/evision]: https://github.com/cocoa-xu/evision

["hello, world\n"]: https://ja.wikipedia.org/wiki/Hello_world

## セットアップ

```elixir
Mix.install([
  {:nx, "~> 0.5.0"},

  # OpenCV をつかうため
  {:evision, "0.1.31"},

  # 画像をテンソルに変換するため
  {:stb_image, "~> 0.6.0"},

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

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

## Elixir で OpenCV

[cocoa-xu/evision] を使うと OpenCV のほとんどの関数にElixirからアクセスできます。

[cocoa-xu/evision] は [パッチバージョンでも破壊的な変更](https://github.com/cocoa-xu/evision/blob/main/CHANGELOG.md)があるようのでバージョンをロックしておいた方が無難そうです。

https://qiita.com/RyoWakabayashi/items/2846133bc7014319d172

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

https://github.com/cocoa-xu/evision/tree/main/examples

```elixir
alias Evision, as: Cv
```

## HelloWorld モジュールをつくる

画像を Web からダウンロードし、Evision.Mat に変換します。

https://qiita.com/mnishiguchi/items/1006b5cea9287a91a2d3

グラフィカルユーザーインターフェース（GUI）で画像を表示するときには `Evision.HighGui` モジュールが便利です。

- `Evision.HighGui.imshow/2`
  - 画像ウインドウを表示 
- `Evision.HighGui.waitKey/1`
  - 画像ウインドウを自動で閉じるまでの時間をミリ秒で指定
  - `0` の場合はなんらかのキーが押下されるまで開いたまま
- `Evision.HighGui.destroyAllWindows/0`
  - 全ての画像ウインドウを閉じる

https://hexdocs.pm/evision/Evision.HighGui.html

以下のモジュールはターミナルでもLivebookでもイゴきます。

```elixir
defmodule HelloWorld do
  alias Evision, as: Cv

  @wait_time_ms 30_000

  def download_image(url) do
    Req.get!(url)
    |> then(& &1.body)
    |> StbImage.read_binary!()
    |> StbImage.to_nx()
    |> Cv.Mat.from_nx_2d()
    |> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())
  end

  def show_image_gui(img_list) when is_list(img_list) do
    for {label, img_mat} <- img_list do
      Cv.HighGui.imshow(label, img_mat)
    end

    Cv.HighGui.waitKey(@wait_time_ms)

    Cv.HighGui.destroyAllWindows()
  end

  def show_image_gui(label, img_mat) do
    show_image_gui([{label, img_mat}])
  end
end
```

最初は[原典](https://upload.wikimedia.org/wikipedia/commons/2/21/Hello_World_Brian_Kernighan_1978.jpg)に忠実に実施しようと思います。

## hello world 1

ウィキペディアの画像をダウンロードして、それをGUIに表示します。

```elixir
hello_img_mat1 =
  HelloWorld.download_image(
    "https://upload.wikimedia.org/wikipedia/commons/2/21/Hello_World_Brian_Kernighan_1978.jpg"
  )

HelloWorld.show_image_gui("hello OpenCV", hello_img_mat1)
```

画像ウインドウがフォーカスされている状態でなんらかのキーを押すと閉じることができます。

![CleanShot 2023-05-03 at 21.50.15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/92eca96f-68e3-5ea5-ef07-22c9b65f4286.gif)

## hello world 2

せっかく [OpenCV] を使っているので色チャネル（BGR）を分離する技を使ってみよう思います。それぞれの色の強さを視覚的に確認できます。

```elixir
hello_img_mat2 =
  HelloWorld.download_image(
    "https://upload.wikimedia.org/wikipedia/commons/8/8c/PSP-Homebrew.jpeg"
  )

HelloWorld.show_image_gui([
  {"Red", hello_img_mat2[[.., .., 2]]},
  {"Green", hello_img_mat2[[.., .., 1]]},
  {"Blue", hello_img_mat2[[.., .., 0]]},
  {"BGR", hello_img_mat2}
])
```

![CleanShot 2023-05-03 at 22.10.36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/d90b0abb-660b-3c0c-1d42-6900f5a273ca.gif)