画像処理100本ノック!!(001 - 010)丁寧にじっくりと

# 1. はじめに
　画像の前処理の技術力向上のためにこちらを実践　[画像処理100本ノック!!](https://github.com/yoyoyo-yo/Gasyori100knock)
とっかかりやすいようにColaboratoryでやります。
目標は２週間で完了できるようにやっていきます。丁寧に解説します。質問バシバシください！


# 2. 前準備
ライブラリ等々を以下のように導入。

```python:
# ライブラリをインポート
from google.colab import drive
import numpy as np
import matplotlib.pyplot as plt
import cv2
from google.colab.patches import cv2_imshow

# 画像の読み込み
img = cv2.imread('画像のパス/imori.jpg')
img_noise = cv2.imread('画像のパス/imori_noise.jpg')
# 画像保存用
OUT_DIR = '出力先のパス/OUTPUT/'
```

# 3.解説

## Q.1. チャネル入れ替え
>画像を読み込み、RGBをBGRの順に入れ替えよ。
画像の赤成分を取り出すには、以下のコードで可能。 cv2.imread()関数ではチャネルがBGRの順になることに注意！ これで変数redにimori.jpgの赤成分のみが入る。

```python:A1
# OpenCVの関数cvtColor()でBGRとRGBを変換
img1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans1_a.jpg', img1)
# 画像を表示する
cv2_imshow(img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/7efc40e3-be19-d732-3571-d4519b0f593a.png)



参考: [Python, OpenCVでBGRとRGBを変換するcvtColor](https://note.nkmk.me/python-opencv-bgr-rgb-cvtcolor/)

## Q.2. グレースケール化
>画像をグレースケールにせよ。 グレースケールとは、画像の輝度表現方法の一種であり下式で計算される。
Y = 0.2126 R + 0.7152 G + 0.0722 B

```python:A2
# OpenCVの関数cv2.cvtColor(), cv2.COLOR_BGR2GRAYで変換
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans2_.jpg', img2)
# 画像を表示する
cv2_imshow(img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/cf072cce-ddeb-92ae-b976-2b46d94c9367.png)


参考: [Python, OpenCV, NumPyでカラー画像を白黒（グレースケール）に変換](https://note.nkmk.me/python-opencv-numpy-color-to-gray/)

## Q.3. 二値化
>画像を二値化せよ。 二値化とは、画像を黒と白の二値で表現する方法である。 ここでは、グレースケールにおいて閾値を128に設定し、下式で二値化する。

```python:A3
# 画像(グレースケール)のコピー
img3 = img2.copy()

# 閾値の設定
threshold = 128

# 二値化(閾値128を超えた画素を255にする。)
ret, img3 = cv2.threshold(img3, threshold, 255, cv2.THRESH_BINARY)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans3_.jpg', img3)
# 画像を表示する
cv2_imshow(img3)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/cef2a00e-6810-7f1e-d0fd-5e904701aa10.png)

参考: [OpenCV 画像の二値化](https://qiita.com/tokkuri/items/ad5e858cbff8159829e9)
参考: [画像のしきい値処理](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html)

## Q.4. 大津の二値化
>大津の二値化を実装せよ。 大津の二値化とは判別分析法と呼ばれ、二値化における分離の閾値を自動決定する手法である。 これはクラス内分散とクラス間分散の比から計算される。

```python:A4
# OpenCVで実装
# 画像(グレースケール)のコピー
gray = img2.copy()
# 大津の２値化処理
ret, image4 = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans4.jpg', img4)
# 画像を表示する
cv2_imshow(img4)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
![img4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/bfbb143d-f5a8-c5a5-2b5f-1e72d828e2d2.png)

参考: [【Python/OpenCV】大津の手法で二値化処理](https://algorithm.joho.info/programming/python/opencv-otsu-thresholding-py/)

## Q.5. HSV変換
>HSV変換を実装して、色相Hを反転せよ。
HSV変換とは、Hue(色相)、Saturation(彩度)、Value(明度) で色を表現する手法である。
Hue ... 色合いを0~360度で表現し、赤や青など色の種類を示す。 ( 0 <= H < 360) 色相は次の色に対応する。
赤 黄色  緑  水色  青  紫   赤
0  60  120  180 240 300 360
Saturation ... 色の鮮やかさ。Saturationが低いと灰色さが顕著になり、くすんだ色となる。 ( 0<= S < 1)
Value ... 色の明るさ。Valueが高いほど白に近く、Valueが低いほど黒に近くなる。 ( 0 <= V < 1)

```python:A5
# 画像をコピーする
img5 = img.copy()
# RGB -> HSVに変換
hsv = cv2.cvtColor(img5, cv2.COLOR_BGR2HSV)
# Hueに１８０を足す
hsv[..., 0] = (hsv[..., 0] + 180) % 360
# HSV -> RGBに変換
img5 = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans5.jpg', img5)
# 画像を表示する
cv2_imshow(img5)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/69ca565d-c977-4c0c-abd8-48fe32333739.png)

なぜか微妙に解答の画像と異なる。間違ってたら教えてください

参考: [Python/OpenCV】RGBからHSVに変換(cv2.cvtColor)](https://algorithm.joho.info/programming/python/opencv-rgb-to-hsv-color-space/)

## Q.6. 減色処理
>ここでは画像の値を256^3から4^3、すなわちR,G,B in {32, 96, 160, 224}の各4値に減色せよ。 これは量子化操作である。 各値に関して、以下の様に定義する。

```python:A6
def decrease_color(img):
    """
    R,G,Bをそれぞれ２５６ある値を４にする
    [0, 64), [64, 128), [128, 192), (192, 256)に４等分する ※[] ：閉区間(以上や以下), ()：開区間(大きいや小さい)
    [0, 64)->32,  [64, 128)->96,  [128, 192)->160,  (192, 256) -> 2224 へ各範囲変換 

    parameters
    -------------------------
    param: numpy.ndarray形式のimage

    returns
    -------------------------
    numpy.ndarray形式 右のように変換[132  80  67] >>> [160  96  96]
    """
    # イメージをコピー
    out = img.copy()
    print(out)
    # 256/4 = 64であるため６４でわり、 +32 することで32, 96, 160,  224へ変換
    out = out // 64 * 64 + 32
    return out

img6 = img.copy()

img6 = decrease_color(img6)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans6.jpg', img6)
# 画像を表示する
cv2_imshow(img6)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/827e9610-0db9-dd8e-08d8-0c0ceda544e6.png)


参考: [なんとなくのブログ](http://beetreehitsuji.hatenablog.com/entry/2015/01/25/104509)

## Q.7. 平均プーリング
>ここでは画像をグリッド分割(ある固定長の領域に分ける)し、かく領域内(セル)の平均値でその領域内の値を埋める。 このようにグリッド分割し、その領域内の代表値を求める操作はPooling(プーリング) と呼ばれる。 これらプーリング操作はCNN(Convolutional Neural Network) において重要な役割を持つ。

```python:A7
def average_pooling(img, G=8):
    """
    平均プーリング

    parameters
    -------------------------
    param1: numpy.ndarray形式のimage
    param2: 8x8にグリッド分割

    returns
    -------------------------
    平均プーリングされたnumpy.ndarray形式のimage
    """
    # 画像をコピー
    out = img.copy()
    # H(画像高さ), W(画像幅), C(色)を取得
    H, W, C = img.shape
    # グリッド分割
    Nh = int(H / G)
    Nw = int(W / G)

    # ８分割された範囲ごとに平均プーリング
    for y in range(Nh):
        for x in range(Nw):
            for c in range(C):
                out[G*y:G*(y+1), G*x:G*(x+1), c] = np.mean(out[G*y:G*(y+1), G*x:G*(x+1), c]).astype(np.int)
    
    return out

# 平均プーリング
img7 = average_pooling(img)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans7.jpg', img7)
# 画像を表示する
cv2_imshow(img7)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/a97520fd-db52-3574-ea39-43e4d131fe7d.png)

## Q.8. Maxプーリング
>ここでは平均値でなく最大値でプーリングせよ。

```python:A8
def max_pooling(img, G=8):
    """
    最大プーリング

    parameters
    -------------------------
    param1: numpy.ndarray形式のimage
    param2: 8x8にグリッド分割

    returns
    -------------------------
    最大プーリングされたnumpy.ndarray形式のimage
    """
    # 画像をコピー
    out = img.copy()
    # H(画像高さ), W(画像幅), C(色)を取得
    H, W, C = img.shape
    # グリッド分割
    Nh = int(H / G)
    Nw = int(W / G)

    # ８分割された範囲ごとに平均プーリング
    for y in range(Nh):
        for x in range(Nw):
            for c in range(C):
                out[G*y:G*(y+1), G*x:G*(x+1), c] = np.max(out[G*y:G*(y+1), G*x:G*(x+1), c]).astype(np.int)
    
    return out

# 最大プーリング
img8 = average_pooling(img)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans8.jpg', img8)
# 画像を表示する
cv2_imshow(img8)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/b4d07ae0-2384-78a3-977c-85557e795f18.png)

## Q.9. ガウシアンフィルタ
>ガウシアンフィルタ(3x3、標準偏差1.3)を実装し、imori_noise.jpgのノイズを除去せよ。
ガウシアンフィルタとは画像の平滑化（滑らかにする）を行うフィルタの一種であり、ノイズ除去にも使われる。
ノイズ除去には他にも、メディアンフィルタ(Q.10)、平滑化フィルタ(Q.11)、LoGフィルタ(Q.19)などがある。
ガウシアンフィルタは注目画素の周辺画素を、ガウス分布による重み付けで平滑化し、次式で定義される。 このような重みはカーネルやフィルタと呼ばれる。
ただし、画像の端はこのままではフィルタリングできないため、画素が足りない部分は0で埋める。これを0パディングと呼ぶ。 かつ、重みは正規化する。(sum g = 1)

```python:A9
# OpenCVで処理
# cv2.GaussianBlur(src, ksize, sigmaX)
# src: 入力画像, ksize: カーネルサイズ, sigmaX: ガウス分布のsigma_x
img9 = cv2.GaussianBlur(img_noise, ksize=(3, 3), sigmaX=1.3)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans9.jpg', img9)
# 画像を表示する
cv2_imshow(img9)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/d076eff8-55ff-fc39-e167-a49c61e8fefd.png)

参考: [【Python/OpenCV】ガウシアンフィルタでぼかし・平滑化](https://algorithm.joho.info/programming/python/opencv-gaussian-filter-py/)


## Q.10 メディアンフィルタ
>メディアンフィルタ(3x3)を実装し、imori_noise.jpgのノイズを除去せよ。
メディアンフィルタとは画像の平滑化を行うフィルタの一種である。
これは注目画素の3x3の領域内の、メディアン値(中央値)を出力するフィルタである。 これもゼロパディングせよ。

```python:A10
# OpenCVで処理
"""
cv2.medianBlur(src, ksize)
src	入力画像
kernel	フィルタのカーネルサイズ（3なら8近傍）
"""
# メディアンフィルタ
img10 = cv2.medianBlur(img_noise, ksize=3)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans10.jpg', img10)
# 画像を表示する
cv2_imshow(img10)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/0541ccf8-c545-b405-5d94-a436ea1714a6.png)

参考: [【Python/OpenCV】メディアンフィルタでぼかし・平滑化・ノイズ除去](https://algorithm.joho.info/programming/python/opencv-median-filter-py/)

# 感想
機械学習において前処理は重要である。手法を知らなければアイデアも出ないので取り組む価値は十分にある。
ノック形式なので取り組みやすい