EvisionのDNN.TextDetectionModelDBでテキスト検出

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

Evisionシリーズの4つ目の記事になります
このシリーズは技術評論社の[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)

Livebook上で画像処理ライブラリOpenCVのElixirラッパーのEvisionのDNN.TextDetectionModelDBを使ってクラス分類を行う方法を紹介します

# 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分野の多くの学習済みモデルを使用できる


# テキスト検出について
> テキスト検出（TextDetection）とは、画像に映っている文字の領域を検出するタスクです。
OpenCVではじめよう ディープラーニングによる画像認識 (p.430)

使用するモジュールはDNN.TextDetectionMoelになります

# 使用するモデル
DB_IC15_resnet18.onnx　を使用します
学習済みデータはこちらの7.6をダウンロードしてください

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

直リンク
https://gihyo.jp/assets/files/book/2022/978-4-297-12775-6/download/7.6.zip

# setup
livebookは公式サイトを参考にインストールしてください
livebookを起動してノートブックを作成したらsetupセルに以下を追加して実行してください


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

# データの準備
`alias`でモジュール名を短くしています `import numpy as np`と同じですね
`weigths`で学習済みデータのファイルパスを定義します
`base`でダウンロードしたフォルダパスを貼っておくと便利です

```elixir
alias Evision, as: Ev
base = "７．6をダウンロードしたフォルダの絶対パスを貼り付けてください"
weights = base <> "7.6/db/DB_IC15_resnet18.onnx"
```

# モデルの読み込み
学習済みデータを読み込んで、モデルを構築します
`textDetectionModelDB/1`で先程の学習済みデータのパスを渡します

モデルの次は`setInputParams/2`でハイパーパラメータを設定します
パラメーターはそれぞれ以下を設定しています
- scale -> 画像のRGB値を0~1.0の範囲に変換
- size -> 入力画像サイズを736x1280にする
- mean -> 各RGB値から指定した平均値を引いて照度変化に強くする
- swapRB -> BGR形式をRGBに入れ替えない
- crop -> アスペクト比をせずにリサイズ

最後はテキスト検出のパラメーターをセットします
- setBinaryThreshold(0.3) -> ２値化のしきい値
- setPolygonThreshold(0.5) -> テキスト輪郭スコアのしきい値
- setMaxCandidates(200) -> テキスト候補領域の上限値
- setUnclipRatio(2.0) -> アンクリップ率

```elixir
model = 
  Ev.DNN.TextDetectionModelDB.textDetectionModelDB(weights)
  |> Ev.DNN.TextDetectionModelDB.setInputParams(
    scale: 1.0 / 255.0,
    size: {736, 1280},
    mean: {122.67891434, 116.66876762, 104.00698793},
    swapRB: false,
    crop: false
  )
  |> Ev.DNN.TextDetectionModelDB.setBinaryThreshold(0.3)
  |> Ev.DNN.TextDetectionModelDB.setPolygonThreshold(0.5)
  |> Ev.DNN.TextDetectionModelDB.setMaxCandidates(200)
  |> Ev.DNN.TextDetectionModelDB.setUnclipRatio(2.0)
```

# 推論の実行

画像を読み込みます
書籍でも使用していたOpenCVと書かれている画像を使います
```elixir
image = base <> "7.6/text.jpg" |> Ev.imread
```
![スクリーンショット 2022-12-03 0.36.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/0dfec555-d964-0a95-ed66-661f5ea7fdaa.png)

`detectTextRectangles/2`で推論を実行します

```elixir
{vertices, confidences} = Ev.DNN.TextDetectionModelDB.detectTextRectangles(model, image)
```

実行すると検出したテキスト領域の回転矩形（中心座標、領域サイズと回転角度）と信頼度のリストを返します。

```elixir
{[
   {{1452.3603515625, 1141.4761962890625}, {331.3262634277344,204.6001434326172},-1.3639297485351562},
   {{676.5, 1155.5}, {721.5119018554688, 317.935302734375}, -2.6274032592773438},
   {{1073.000244140625, 762.9999389648438}, {1441.3055419921875, 416.9986877441406},-3.8075942993164062},
   {{957.3272705078125, 484.9888916015625}, {863.0692749023438, 231.91671752929688}, -1.6870346069335938},
   {{926.2108154296875, 331.9464111328125}, {647.4521484375, 109.35784149169922},-1.7930221557617188}
 ],
 [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, ...]}
```

# 推論結果を画像に描画
Enum.reduceで推論結果を描画していきます

`boxPoints/1`を使って回転矩形から４隅の座標を取得します
書籍だと`detect/2`が無加工で使えるので良いとありますが、返ってくるのが４隅の座標ですがタプルのリストなので使いにくいため`detectTextRectangles/2`のデータをboxPointsで変換して使用します

polylinesですが Matがs32でないとエラーになるので s8からs32に変換します
引数は以下になります

1. 画像
2. 座標データMat(s32)のリスト
3. closeフラグ
4. 色

```elixir
Enum.reduce(vertices, image, fn vertex, img -> 
  points = Ev.boxPoints(vertex) |> Ev.Mat.as_type(:s32)
  Ev.polylines(img,[points], true, {0,255,0})
end)
```

![スクリーンショット 2022-12-03 1.25.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/04e5d6fe-7440-4f96-e164-5db23233df05.png)


# 最後に
TextDetectionModelDBを使用して簡単にテキスト検出ができました
本記事は以上になりますありがとうございました


# 全コード

```elixir
alias Evision, as: Ev
base = "/Users/shou/livebook_samples/"

image = base <> "7.6/text.jpg" |> Ev.imread
weights = base <> "7.6/db/DB_IC15_resnet18.onnx"
model = 
  Ev.DNN.TextDetectionModelDB.textDetectionModelDB(weights)
  |> Ev.DNN.TextDetectionModelDB.setInputParams(
    scale: 1.0 / 255.0,
    size: {736, 1280},
    mean: {122.67891434, 116.66876762, 104.00698793},
    swapRB: false,
    crop: false
  )
  |> Ev.DNN.TextDetectionModelDB.setBinaryThreshold(0.3)
  |> Ev.DNN.TextDetectionModelDB.setPolygonThreshold(0.5)
  |> Ev.DNN.TextDetectionModelDB.setMaxCandidates(200)
  |> Ev.DNN.TextDetectionModelDB.setUnclipRatio(2.0)

#{vertices, confidences} = Ev.DNN.TextDetectionModelDB.detect(model, image)
{vertices, confidences} = Ev.DNN.TextDetectionModelDB.detectTextRectangles(model, image)
Enum.reduce(vertices, image, fn vertex, img -> 
  points = Ev.boxPoints(vertex) |> Ev.Mat.as_type(:s32)
  Ev.polylines(img,[points], true, {0,255,0})
end)
```