画像の２値化　OpenCVのadaptiveThreshold(適応的閾値処理)の応用

## はじめに
初投稿で慣れてないので、お見苦しかったら申し訳ございません。

## 背景
現在、プライベートな時間で、ある検索アプリを作成しています。
まず、検索データの登録にあたり、カメラからスキャンした画像に含まれている文字を抽出したくて、pythonと幾つかのライブラリで２値化処理を作りました。
もともと、opencvにはthreshold、大津手法、adaptiveThreshold(適応的閾値処理)などが提供されていることは知っていました。
各実行結果の画像を下記に掲載します。

:::note
入力画像(カラー)
![test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/0e587eea-ec49-4ab1-87a5-b899f835dc6a.png)
:::

:::note
入力画像(グレースケール)
![debug1_in.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/70c0036b-d587-ee8a-7dad-c13b276c85e5.png)
:::

:::note
threshold(パラメータ閾値=127)の場合
![debug10_out_threshold_127.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/78c37a0e-8183-c95f-586b-eea10f444325.png)
:::

:::note
threshold(大津手法)の場合
![debug10_out_threshold_otsu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/c3dbbc94-e198-461b-907f-9a8aa6ae7696.png)
:::

:::note
adaptiveThreshold(パラメータBLOCK_SIZE=11、C=2)の場合
![debug10_out_athreshold_11_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/c7539cf3-34c0-c12b-9b04-bb77ecea1b23.png)
:::

## 課題
画像が荒くて一部に影も映りこんでいる文字を含む画像を２値化するのにadaptiveThresholdは有効と考えられるが、同じ物体を撮影した画像でも照明などの撮影環境が異なる画像ではadaptiveThresholdの２つのパラメータの最適値は異なるのでは？異なる物体を撮影した画像でもパラメータの最適値も異なるのでは？という疑問がありました。
そこで、２つのパラメータの組み合わせが数百パターンある場合、その最適値を自動的に算出し２値化が可能かをpythonでアプリを作って検証しました。

## 入力データの特徴
1.画像が荒い
2.文字と余白のピクセルカラーの差が少ない。
3.影の部分と影でない部分の文字と余白のピクセル値に誤差がある。

## 課題を解決する為の方針
1.影を除去し、影と影でない部分の誤差を減らす。
2.図形(文字など)のエッジを検出し、検出したエッジを含む矩形範囲を検出し、
　各矩形範囲についてadaptiveThresholdによる２値化を実行する。
3.各範囲における２値化画像は、各範囲の特徴に最も近い画像を採用する。

## ロジック
1.パラメータなどのチェック

2.コントラストの強調
　グレースケール画像をパラメータにopencvのequalizeHistメソッドを実行する。

![debug3_equalize_v3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/50d1fd78-9165-4438-2d90-f379ff181a80.png)

3.影の除去
　2の戻り値画像をパラメータ(ガンマは0.2)にopencvのLUTメソッドを実行する。

![debug4_rs_v3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/b42e735c-a160-7ae2-fd44-60a38d5f7ba4.png)

4.細かいノイズの除去
　2の戻り値画像をパラメータ3でopencvのmedianBlurメソッドを実行する。

![debug5_denoize_v3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/ada3d736-b998-421b-7d11-ec17933df228.png)

5.矩形範囲の検出
　5.1.エッジの検出
 　 opencvのmorphologyExメソッドで輪郭を検出する。
　  (パラメータop=MORPH_GRADIENT、kernel=(3,3))

　5.2.仮の閾値処理の実行
 　 KMeansで、5.1.の戻り値画像を明るい色(白に近い)の集合と暗い色(黒に近い)の集合に
　 分けて、その境界値を取得し、5.1の戻り値画像と境界値をパラメータにopencvの
　 閾値処理(threshold)を実行し、仮の画像を取得する。

　5.3.エッジに外接する矩形範囲を取得
 　 ・5.2.の戻り値画像からopencvのfindContoursメソッドで輪郭を検出する。
   　 ・輪郭に外接する範囲をopencvのboundingRectで検出する。

:::note
赤が検出した範囲
![debug7_rect_v3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/dedd0afe-409d-d6a4-f88e-37f37b6415f0.png)
:::

6.２値化処理実行
5で検出した複数の矩形範囲について、下記の処理を実行する。
なお、この処理は時短の為、ThreadPoolExecutorで並行に処理する。

　6.1.適応的閾値処理での特徴の算出
　 矩形範囲のピクセル座標内でピクセルごとに、隣接するピクセルとの差分を
　 元に白か黒かを決め、黒いピクセル数と四隅のピクセルの値（白か黒）を取得する。
 　 ※ライブラリは使わなかった。

　6.2.適応的閾値処理
 　 矩形範囲について、パラメータblock_sizeを3～51、Cを0～10の全ての組み合わせで
　 opencvのadaptiveThresholdを実行し、6.1で取得した矩形範囲の特徴に最も近い画像を
   　 採用する。

7.集計・補正
　6で実行した結果画像の一部のピクセルは複数の矩形範囲に含まれている可能性も
 　考慮し、各ピクセルについて、黒として判定された回数が6の閾値処理実行回数の
　半分以上の場合、そのピクセルを黒とし、その他を白に補正する。

:::note
最終結果
![debug10_out_v3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/9e75d46e-5852-abd1-3b0d-2cdf8a2994f6.png)
:::

## 結論
最終結果としては、影と影でない部分の境界のピクセルが欠落し、
ランダムなパラメータ(block_size=11,C=2)で試行したadaptiveThresholdの２値化の精度を超えることはできなかった。
また、新たな仮説が思い浮かんだら挑戦します。

以上