pythonで一から画像処理 (1)閾値処理

基本となる画像処理を一から勉強していくシリーズ (1)。

OpenCV-Pythonチュートリアルを参考に、
画像認識本　https://www.amazon.co.jp/dp/4061529129/
でやっている処理の理解を進める方針です。

#目次
1. 環境
2. 閾値処理
3. 大津の2値化

# 1. 環境
Python 3.7.0
OpenCV 4.1.0
Jupyter Notebook

# 2. 閾値処理

まずはじめに画像の読み込みと、画像とgrayscaleのヒストグラムの表示。

```python:Histgram.py
import cv2
import numpy as np
from matplotlib import pyplot as plt
from pylab import rcParams #画像表示の大きさを変える
%matplotlib inline
rcParams['figure.figsize'] = 25, 20  #画像表示の大きさ

#画像読み込み
image = cv2.imread('brabra/1.jpg',0) #grayscaleで読み込み
img = cv2.cvtColor(image,cv2.COLOR_BGR2RGB) #RGB形式に変換する

#画像表示
plt.subplot(2,1,1),plt.imshow(img,'gray')
#graysscaleのヒストグラム表示
plt.subplot(2,1,2),plt.hist(img.ravel(),256)
plt.show()
```
![img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480516/57017586-6df2-8d6a-64e8-f3e0fc77489b.png)

次に、基本となる閾値処理を試す。

```python:Threshold.py
image = cv2.imread('brabra/1.jpg',0)

#閾値は100
#retには閾値、thresh1~3には処理画像が入る

#閾値以下を黒にする
ret,thresh1 = cv2.threshold(img,100,255,cv2.THRESH_BINARY)
#閾値以上を黒にする
ret,thresh2 = cv2.threshold(img,100,255,cv2.THRESH_BINARY_INV)
#閾値以上を閾値に、以下は変更なし
ret,thresh3 = cv2.threshold(img,100,255,cv2.THRESH_TRUNC)
#閾値以上は変更なし、それ以下は黒
ret,thresh4 = cv2.threshold(img,100,255,cv2.THRESH_TOZERO)
#閾値以上は黒、それ以下は変更なし
ret,thresh5 = cv2.threshold(img,100,255,cv2.THRESH_TOZERO_INV)

titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

plt.show()
```
![thres.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480516/b5f56c1a-b207-bb8f-1303-310be10477e6.png)

閾値を変化させてみる。

```python:Threshold2.py
image = cv2.imread('brabra/1.jpg',0)

#50~300まで閾値を変化させる
ret1,thresh1 = cv2.threshold(img,50,255,cv2.THRESH_BINARY)
ret2,thresh2 = cv2.threshold(img,100,255,cv2.THRESH_BINARY)
ret3,thresh3 = cv2.threshold(img,150,255,cv2.THRESH_BINARY)
ret4,thresh4 = cv2.threshold(img,200,255,cv2.THRESH_BINARY)
ret5,thresh5 = cv2.threshold(img,250,255,cv2.THRESH_BINARY)
ret6,thresh6 = cv2.threshold(img,300,255,cv2.THRESH_BINARY)

titles = [ret1,ret2,ret3,ret4,ret5,ret6]
images = [thresh1, thresh2, thresh3, thresh4, thresh5, thresh6]

for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

plt.show()

```
![thres2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480516/cc13bb75-174b-233b-33aa-63dafefa6bd7.png)

次に、Adaptive Thresholdingをしてみる。
これは光源によって依存しているような画像の場合に基本の閾値処理ではうまくいかない場合があるが、小領域毎に閾値処理することで解決するもののようです。

```python:Adaptive_Thresholding.py
image = cv2.imread('brabra/1.jpg',0)

#基本の閾値処理
ret,th1 = cv2.threshold(img,100,255,cv2.THRESH_BINARY)

#adaptive mean thresholding
#画像の小領域毎に閾値処理。その領域の平均値を閾値とする。
#後ろの引数は近傍のサイズ、閾値から引く定数
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,51,0)

#adaptive Gaussian thresholding
#画像の小領域毎に閾値処理。その領域の正規分布で重み付けした平均値を閾値とする
#後ろの引数は近傍のサイズ、閾値から引く定数
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,51,0)

titles = ['Original Image', 'Global Thresholding (v = 100)',
            'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in range(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()
```
![adap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480516/cf00d219-042e-ffbb-bb92-e211adc08b0a.png)

領域毎に閾値処理されたため、背景も出力されている。ガウシアン分布を使用した方はよりシャープな印象。

#大津の2値化

次に、大津の2値化を試す。
これは適切な閾値を出すために、grayscaleのヒストグラムが2山分布であると仮定して、その閾値で山間の分散/各山内の分散という比率を最大にするように、閾値を自動で算出する方法になる。
画像は分かりやすいようにパンダにした。

```python:Ootu.py
image = cv2.imread('brabra/1.jpg',0)

#基本の閾値処理 閾値は50
ret1,th1 = cv2.threshold(img,50,255,cv2.THRESH_BINARY)

#大津の二値化
ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

#ガウシアン平均化+大津の二値化
blur = cv2.GaussianBlur(img,(11,11),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=50)',
          'Original Noisy Image','Histogram',"Otsu's Thresholding_Thr="+str(ret2),
          'Gaussian filtered Image','Histogram',"Otsu's Thresholding_Thr="+str(ret3)]

for i in range(3):
    plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
    plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
    plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
    plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()
```
![ootu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480516/9fb03040-27c8-5a81-9f66-8b4f3a1d8146.png)


大津の2値化によって人形の輪郭を捉えている。このときの決められた閾値は60となった(山を分けている)。さらに前処理で平均化を入れた場合はよりノイズを削減できている。

#まとめ
今回は閾値処理について理解を深めた。
次回は平均化処理の予定。

#参考文献

- OpenCVを使った画像処理（OpenCV-Pythonチュートリアル）

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_table_of_contents_imgproc/py_table_of_contents_imgproc.html#py-table-of-content-imgproc

- 画像認識（機械学習プロフェッショナルシリーズ）

https://www.amazon.co.jp/dp/4061529129/