EvisionのCascadeClassifierで顔認識をやってみた

# はじめに
本記事は [Qiita AdventCalendar2022 Elixir](https://qiita.com/advent-calendar/2022/elixir) vol9 7日目の記事です

Evisionシリーズの3つ目の記事になります
このシリーズは技術評論社の[OpenCVではじめよう ディープラーニングによる画像認識](https://gihyo.jp/book/2022/978-4-297-12775-6)の7章の内容を参考にElixirとEvisionで書き換えて行っています

https://gihyo.jp/book/2022/978-4-297-12775-6

1. [Livebook + Evision基本編](https://qiita.com/the_haigo/items/baf47624567cb992d934)
4. [EvisionのCascadeClassifierで顔認識](https://qiita.com/the_haigo/items/f1ffbdcb875eb83e52f4)
2. [EvisionのDNN.ClassificationModelを使ってクラス分類](https://qiita.com/items/8fdbb80bdff8ff6fd2fc/)
3. [EvisionのDNN.TextDetectionModelDBでテキスト検出](https://qiita.com/items/6b177d2c85625ace131a)
5. [EvisionのDNN.DetectionModelでYOLOv4を使って物体検知](https://qiita.com/the_haigo/items/3f8b4d1801cdbba3bfe8) 12/14公開
6. [EvisionのDNN.SegmentationModelでセグメンテーション](https://qiita.com/the_haigo/items/c99a6ca3b51d18e3f127) 12/14公開
7. [YOLOv4の結果を切り取ってEfficientnetで更に分類するシステムをEvisionで書く](https://qiita.com/the_haigo/items/7e426f2176f7a6535320) 12/18公開

この記事ではLivebook上で画像処理ライブラリOpenCVのElixirラッパーのEvisionのCascadeClassifierを使って顔認識を行う方法を紹介します

# Livebookについて
https://livebook.dev/

https://github.com/livebook-dev/livebook

> Livebook is a web application for writing interactive and collaborative code notebooks.

LivebookはコラボレーションもできるElixir対話的実行環境を提供するWebアプリケーションです

# Evisionについて
https://github.com/cocoa-xu/evision


* OpenCVのElixirラッパー
* Port等を使わずに直にElixirから使うことができる
* Nxのバックエンドとして使用でき、行列演算の高速化(CPU,GPU)ができる
* Nxデータに相互に変換できる
* 膨大な画像処理の関数を使用できる
* DNNモジュールでCV分野の多くの学習済みモデルを使用できる

# setup
実行環境は以下で行いました
OS macOS monterey
Hard apple m1 max
elixir 1.14.1-otp-25
erlang 25.0.4

livebook 0.7.2
evision 0.1.16

Livebookの環境は本家のgithubからクローンしたものを使っています

https://github.com/livebook-dev/livebook#mix


# Evision(OpenCV)ができること
画像処理 Evision Module
キャプチャ Evision.VideoCapture
行列処理 Evision.Mat　(Evision独自実装)
AI/ML
- ML(機械学習) module
- DNN(深層学習) module 
- CascadeClassifier
- FaceDetector
....

# 今回やること
色々できますが、今回は以下に絞って紹介します
- Livebook上でeivison環境のセットアップ
- 画像の読み込み
- 画像のグレースケール変換
- CascadeClassifierで顔認識

# Livebook上でeivison環境のセットアップ

READMEに沿って最新版のlivebookを起動させます
```
git clone https://github.com/livebook-dev/livebook.git
cd livebook
mix deps.get --only prod
MIX_ENV=prod mix phx.server
```
起動すると認証トークン付きのurlが表示されるのでそれにアクセスします

```
[Livebook] Application running at http://localhost:8080/?token=[your token]
```
アクセスしたら
setupのところに以下のMix.installを追加します

```elixir
Mix.install([
  {:evision, "~> 0.1.21"},
  {:kino, "~> 0.7.0"}
])
```

次にEvisionが頻繁に打つには長いので aliasと
現在実行しているlivebookのフォルダが作業ディレクトリなので、画像等を置いてあるフォルダのパスを指定します
２つ目はlivebookのフォルダに直接置くのであれば不要です
```elixir
alias Evision, as: Ev
base = "/Users/shou/livebook_samples/"
```

# 画像の読み込み

画像の読み込みは Evision Moduleにimreadがあるのでそちらを使います

https://hexdocs.pm/evision/Evision.html#imread/1

```elixir
Ev.imread(base <> "images/dog.jpg")
```

これを実行すると３つのタブがついたセルが表示されます
こちらは最近実装された機能で、本来はimencode で画像形式に変換した後に　　Kino.Image.new しなければ画像を出力できなかったのですが、
Evision.Mat構造体が最後に出力される場合に以下のようなセルが表示されるようになりました

![スクリーンショット 2022-11-10 16.50.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/25fbbd33-e455-83ab-43d3-485d6291be92.png)

Imageはそのまま画像で、Rawはデータ構造、NumericalはNxのテンソルがそれぞれ表示されます

imread/2はオプションを指定でき、画像処理でよく使われるのはグレースケールでの読み込みになります

```elixir
Ev.imread(base <> "images/dog.jpg", flags: Ev.cv_IMREAD_GRAYSCALE())
```

![スクリーンショット 2022-11-10 16.54.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/7465cac5-7b1c-cf40-52fc-7a682ae3b532.png)

# 画像のグレースケール変換
先のほどの imreadのオプションでグレースケールで読み込めましたが、今度はもとはカラーのものをグレースケール変換していきます

関数は Evision.cvtColor/2を使います

https://hexdocs.pm/evision/Evision.html#cvtColor/2

```elixir
Ev.imread(base <> "images/dog.jpg")
|> Ev.cvtColor(Ev.cv_COLOR_BGR2GRAY())
```

第２引数は `Evision.cv_COLOR_` でいくつか定数が定義されているので、気になる方は　livebookの補完かドキュメントで調べてみてください

# CascadeClassifierで顔認識

> カスケードは複数の分類器を繋げて行うアンサンブル学習の一種です。 画像が与えられたとき、まず特徴抽出器で画像から特徴量を抽出します。 その特徴量がカスケード分類器に与えられ、分類結果 (positive または negative) が出力されます。 

from https://pystyle.info/opencv-cascade-classifier/

今回は顔か顔でないかを判別するデータを使います
データはこちら

https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml

使う関数は分類器を作成する cascadeClassifierと検知する DetectMultiScale2 です

https://hexdocs.pm/evision/Evision.CascadeClassifier.html#cascadeClassifier/1
https://hexdocs.pm/evision/Evision.CascadeClassifier.html#detectMultiScale2/3


```elixir
image = base <> "images/face.jpg"
path = base <> "opencv_file/hararcascade_frontalface_default.xml"

mat = Ev.imread(image)
gray_image = Ev.cvtColor(mat, Ev.cv_COLOR_BGR2GRAY())
cascade = Ev.CascadeClassifier.cascadeClassifier(path)
{boxes, _numDetections} = Ev.CascadeClassifier.detectMultiScale2(cascade, gray_image)
```

実行すると２つ座標が返ってきます
タプルの２要素目は numDetectionsとなっていますがよくわかりませんが、今回は使わないので無視します

```elixir
{[{260, 36, 47, 47}, {296, 129, 81, 81}], [5, 5]}
```

座標データをもとに Evision.rectangleで短形を描画します
引数はそれぞれ
- Matrix
- x,y座標
- width,height
- 線の色(今回は赤)
- 先の太さ
となっています

```elixir
Enum.reduce(boxes, mat, fn {x, y, w, h}, mat ->
  Ev.rectangle(
    mat,
    {x, y},
    {x + w, y + h},
    {0, 0, 255},
    thickness: 2
  )
end)
```
実行すると元画像に検知した範囲を囲んだ以下のような画像が出力されます

![スクリーンショット 2022-11-10 20.09.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/882b0f5c-fd34-52e6-e27f-c696658195ca.png)

# 最後に

書籍の内容に沿って画像の読み込み、グレースケール変換、顔認識といったことをやっていきました
書籍が手元にあれば各メソッドや処理の解説を読むこともできますので、興味が出た方は以下でElixirで書き直したコードを載せていますので試してみてください

https://github.com/thehaigo/livebook_samples/tree/main/OpenCV%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AB%E3%82%88%E3%82%8B%E7%94%BB%E5%83%8F%E5%87%A6%E7%90%86