evision で検出した顔に Elixir Image でモザイクをかける

## はじめに

前回の記事で evision と Image モジュールを連携させました

https://qiita.com/RyoWakabayashi/items/34de2207bcf8d745a01a

今回はそれを利用して、 evision で顔を検出して Image でモザイク加工します

いつものように Livebook を使います

https://livebook.dev/

実装したノートブックはコチラ

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/vix/pixelate.livemd

## 実行環境

- Elixir: 1.14.2 OTP 24
- Livebook: 0.8.1

以下のリポジトリーの Docker コンテナ上で起動しています

https://github.com/RyoWakabayashi/elixir-learning

:::note info

Docker が使える環境であれば簡単に実行できます

https://docs.docker.com/engine/install/

Docker Desktop を無償利用できない場合は Rancher Desktop を使ってください

https://rancherdesktop.io/

:::

## セットアップ

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

```elixir
Mix.install([
  {:image, "~> 0.24"},
  {:evision, "~> 0.1"},
  {:req, "~> 0.3"},
  {:kino, "~> 0.8"}
])
```

Req は画像やモデルファイルを Web からダウンロードするのに使っています

https://github.com/wojtekmach/req

## 画像の読込

毎度の如くレナさんの画像をダウンロードしてきます

まず最初は `Image.from_binary` で読み込み、 `Vix.Vips.Image` の形にしておきます

```elixir
lenna_img =
  "https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
  |> Req.get!()
  |> then(& &1.body)
  |> Image.from_binary()
  |> elem(1)
```

![image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c74742e4-fae5-65a2-539b-71a0c72e739b.gif)

`Vix.Vips.Image` の場合、画像自体と属性情報のタブが表示されます

## 顔検出モデルの準備

evision (OpenCV) で使うカスケード分類器のモデルを用意します

詳しくは以前の記事を参照してください

https://qiita.com/RyoWakabayashi/items/746c52aaa23a6dbb7e6f

```elixir
face_model_path = "face_model.xml"

"https://github.com/opencv/opencv/raw/master/data/haarcascades/haarcascade_frontalface_default.xml"
|> Req.get!(connect_options: [timeout: 300_000], output: face_model_path)

face_model = Evision.CascadeClassifier.cascadeClassifier(face_model_path)
```

## 顔を検出する

顔検出は evision で行うので、画像を `Evision.Mat` に変換してからカスケード分類器にかけます

```elixir
face_rect =
  {left, top, width, height} =
  lenna_img
  |> Image.to_evision()
  |> elem(1)
  |> then(&Evision.CascadeClassifier.detectMultiScale(face_model, &1))
  |> List.first()
```

今回は顔が一つなのでリストの先頭を取り出し、顔の左上XY座標、幅、高さを取得します

実行結果は以下の値になります

```elixir
{217, 201, 173, 173}
```

## 顔にモザイクをかける

Image モジュールの `crop` 関数で簡単に画像を切り取れます

```elixir
face_img = Image.crop!(lenna_img, left, top, width, height)
```

![face.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/977ca51b-34f3-264b-5544-1f717ab7a2ab.png)

また、 `pixelate` 関数で簡単にモザイク処理できます（evision でもモザイクは可能ですが、少し手間が多いです）

```elixir
pixelated_face_img = Image.pixelate!(face_img)
```

![pixelated_face.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/808cd13e-cf20-8066-ff64-0be391518766.png)

モザイク加工した顔画像を `compose` 関数で元の位置に貼り付ければ完成です

```elixir
pixelated_lenna_img = Image.compose!(lenna_img, pixelated_face_img, x: left, y: top)
```

![lenna_face_pixelated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/def12e8b-a1c3-a7c2-66e4-72f29cbd68c0.png)

モザイク処理を全てパイプで繋ぐと以下のようになります

```elixir
lenna_img
|> Image.crop!(left, top, width, height)
|> Image.pixelate!()
|> then(&Image.compose!(lenna_img, &1, x: left, y: top))
```

![lenna_face_pixelated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/def12e8b-a1c3-a7c2-66e4-72f29cbd68c0.png)

ね、簡単でしょ？

## まとめ

OpenCV と libvips のいいとこ取りができるので、かなり書きやすいです

https://github.com/opencv/opencv

https://github.com/libvips/libvips