画像処理100本ノック!!(011 - 020)序盤戦

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


# 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.11. 平滑化フィルタ
>平滑化フィルタ(3x3)を実装せよ。
平滑化フィルタはフィルタ内の画素の平均値を出力するフィルタである。

```python:A11
"""
平滑化フィルタ
cv2.filter2D(src, -1, kernel)
src	入力画像
kernel	フィルタのカーネル(※NumPy配列で与える)
"""

# フィルタのカーネル
kernel = np.ones((3,3),np.float32)/9
# 平滑化フィルタ
img11 = cv2.filter2D(img, -1, kernel)

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

![img11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/88dd3502-4018-94e0-03bb-5a5935c7c13b.png)

参考: [【Python/OpenCV】空間フィルタリングで平滑化・輪郭検出](https://algorithm.joho.info/programming/python/opencv-spatial-filter/)
参考: [脱初心者を目指す](http://asdm.hatenablog.com/entry/2016/02/16/165206)

## Q.12. モーションフィルタ
>モーションフィルタ(3x3)を実装せよ。



```python:A12
def motion_filter(img, k_size=3):
    """
    モーションフィルタ(対角方向の平均値を取るフィルタ)

    parameters
    -------------------------
    param1: numpy.ndarray形式のimage
    param2: カーネルサイズ

    returns
    -------------------------
    (130x130)のnumpy.ndarray形式のimage
    """
    # 画像の高さ、幅、色を取得
    H, W, C = img.shape

    # カーネル(numpy.diag()で対角成分を抽出)
    K = np.diag([1] * k_size).astype(np.float)  # array([[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]])
    K /= k_size                                # array([[0.33333333, 0., 0.],[0., 0.33333333, 0.],[0., 0., 0.33333333]])

    # ゼロパディング
    pad = k_size // 2
    out = np.zeros((H + pad * 2, W + pad * 2, C), dtype=np.float)     #out.shape >>> (130, 130, 3)
    out[pad:pad+H, pad:pad+W] = img.copy().astype(np.float)
    tmp = out.copy()

    # フィルタリング
    for y in range(H):
        for x in range(W):
            for c in range(C):
                out[pad + y, pad + x, c] = np.sum(K * tmp[y:y+k_size, x:x+k_size, c])
    out = out[pad: pad + H, pad: pad + W].astype(np.uint8)

    return out

# モーションフィルタ
img12 = motion_filter(img, k_size=3)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans12.jpg', img12)
# 画像を表示する
cv2_imshow(img12)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/055f9cbc-5d3f-b6c5-38f2-3409fb20a308.png)

## Q.13. MAX-MINフィルタ
>MAX-MINフィルタ(3x3)を実装せよ。
MAX-MINフィルタとはフィルタ内の画素の最大値と最小値の差を出力するフィルタであり、エッジ検出のフィルタの一つである。 エッジ検出とは画像内の線を検出るすることであり、このような画像内の情報を抜き出す操作を特徴抽出と呼ぶ。 エッジ検出では多くの場合、グレースケール画像に対してフィルタリングを行う。

```python:A13
def max_min_filter(img, k_size=3):
    """
    Max-Minフィルタ(対角方向の平均値を取るフィルタ)
    グレースケールの処理となるのでカラー画像の場合と場合分けする

    parameters
    -------------------------
    param1: numpy.ndarray形式のimage
    param2: カーネルサイズ

    returns
    -------------------------
    (130x130)のnumpy.ndarray形式のimage
    """
    # 　入力画像がカラーの場合
    if len(img.shape) == 3:
        # H(高さ), W(幅), C(色)
        H, W, C = img.shape

        # ゼロパディング
        pad = k_size // 2
        out = np.zeros((H + pad * 2, W + pad * 2, C), dtype=np.float)     #out.shape >>> (130, 130, 3)
        out[pad:pad+H, pad:pad+W] = img.copy().astype(np.float)
        tmp = out.copy()

        # フィルタリング
        for y in range(H):
            for x in range(W):
                for c in range(C):
                    # 3x3のカーネル内の最大から最小を引く
                    out[pad + y, pad + x, c] = np.max(tmp[y:y+k_size, x:x+k_size, c]) - np.min(tmp[y:y+k_size, x:x+k_size, c])
        out = out[pad: pad + H, pad: pad + W].astype(np.uint8)

    # 　入力画像がグレースケールの場合
    else:
        H, W = img.shape
        # ゼロパディング
        pad = k_size // 2
        out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float)
        out[pad:pad+H, pad:pad+W] = img.copy().astype(np.float)
        tmp = out.copy()

        # フィルタリング
        for y in range(H):
            for x in range(W):
                # 3x3のカーネル内の最大から最小を引く
                out[pad + y, pad + x] = np.max(tmp[y:y+k_size, x:x+k_size]) - np.min(tmp[y:y+k_size, x:x+k_size])

        out = out[pad: pad + H, pad: pad + W].astype(np.uint8)

    return out

# Max-Minフィルタ
img13 = max_min_filter(img, k_size=3)
# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans13.jpg', img13)
# 画像を表示する
cv2_imshow(img13)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/1e5201a7-3b8f-8806-796b-639a9b465db8.png)


参考: [村上研究室 画像処理 - 画像処理・理解研究室](http://ipr20.cs.ehime-u.ac.jp/column/gazo_syori/chapter5.html)

## Q.14. 微分フィルタ
>微分フィルタ(3x3)を実装せよ。
微分フィルタは輝度の急激な変化が起こっている部分のエッジを取り出すフィルタであり、隣り合う画素同士の差を取る。
画像でエxジになるのは輝度変化が激しい部分である。赤部分は殆ど色が変化しないが、青枠は色の変化が激しい。この変化がエッジとなる。

```python:A14
"""
cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
src: 入力画像
ddepth: 出力の色深度
dx: x方向の微分の次数
dy: y方向の微分の次数
ksize: カーネルサイズ、1, 3, 5, 7のどれかを指定
"""
# 横方向
dx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
# 縦方向
dy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans14_v.jpg', dy)
cv2.imwrite(OUT_DIR + 'ans14_h.jpg', dx)

# 画像を表示する
cv2_imshow(dy)
cv2_imshow(dx)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
![img14_h.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/70f5a237-d4f1-258a-0047-8ae1dd56d14e.png)   ![img14_v.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/ee34407c-cc51-ed0d-7177-c2d08d3b4978.png)

模範解答の画像より白部分がくっきりした印象

参考: [python+OpenCVでエッジ抽出(Sobelフィルタ、ラプラシアンフィルタ)](https://qiita.com/shim0mura/items/3ab2a67c78eafd456c32)

## Q.15. Prewittフィルタ
> Prewittフィルタ(3x3)を実装せよ。
Prewitt(プレウィット)フィルタはエッジ抽出フィルタの一種であり、次式で定義される。 これは微分フィルタを3x3に拡大したものである。

```python:A15
"""
Prewitt(プレヴィット)は、画像から輪郭を抽出する空間フィルタの1つ
cv2.filter2D(src, -1, kernel)
src	入力画像
cv2.CV_64F float64
kernel	フィルタのカーネル(※NumPy配列で与える)
"""

# カーネル（水平、垂直方向の輪郭検出用）
kernel_x = np.array([[1, 0, -1],
                     [1, 0, -1],
                     [1, 0, -1]])

kernel_y = np.array([[1, 1, 1],
                     [0, 0, 0],
                     [-1, -1, -1]])

dx = cv2.filter2D(gray, cv2.CV_64F, kernel_x)
dy = cv2.filter2D(gray, cv2.CV_64F, kernel_y)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans15_v.jpg', dy)
cv2.imwrite(OUT_DIR + 'ans15_h.jpg', dx)

# 画像を表示する
cv2_imshow(dy)
cv2_imshow(dx)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img15_h.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/bb022d30-aaa3-b996-e63c-d6169907391c.png) ![img15_v.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/8e10f175-b867-863a-8ab1-d4d32e0ffe0d.png)


参考: [1次微分フィルタ Prewitt(プレヴィット)フィルタ - 画像のエッジ抽出](https://www.mitani-visual.jp/mivlog/imageprocessing/edgefilter001.php)
参考: [【Python/OpenCV】Prewittフィルタで輪郭検出](https://algorithm.joho.info/programming/python/opencv-prewitt-filter-py/)

## Q.16. Sobelフィルタ
>Sobelフィルタ(3x3)を実装せよ。
Sobel(ソーベル)フィルタもエッジを抽出するフィルタであり、次式でそれぞれ定義される。 これはprewittフィルタの中心部分に重みをつけたフィルタである。

```python:A16
"""
ソーベルフィルタは輝度差のが少ないエッジも非常に強調
cv2.filter2D(src, -1, kernel)
src	入力画像
cv2.CV_64F float64
kernel	フィルタのカーネル(※NumPy配列で与える)
"""

# カーネル（水平、垂直方向の輪郭検出用）
kernel_x = np.array([[1, 0, -1],
                     [2, 0, -2],
                     [1, 0, -1]])

kernel_y = np.array([[1, 2, 1],
                     [0, 0, 0],
                     [-1, -2, -1]])

dx = cv2.filter2D(gray, cv2.CV_64F, kernel_x)
dy = cv2.filter2D(gray, cv2.CV_64F, kernel_y)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans16_v.jpg', dy)
cv2.imwrite(OUT_DIR + 'ans16_h.jpg', dx)

# 画像を表示する
cv2_imshow(dy)
cv2_imshow(dx)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img16_h.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/43e06c0e-aab3-68cc-5d3f-0fc6164add47.png) ![img16_v.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/d5bd7a0c-dcc7-98c2-d336-f98303da6744.png)

参考: [1次微分フィルタ Sobel(ソーベル)フィルタ - 画像のエッジ抽出](https://www.mitani-visual.jp/mivlog/imageprocessing/sobel001.php)
参考: [【Python/OpenCV】Prewittフィルタで輪郭検出](https://algorithm.joho.info/programming/python/opencv-prewitt-filter-py/)

## Q.17. Laplacianフィルタ
>Laplacianフィルタを実装せよ。
Laplacian（ラプラシアン）フィルタとは輝度の二次微分をとることでエッジ検出を行うフィルタである。
デジタル画像は離散データであるので、x方向・y方向の一次微分は、それぞれ次式で表される。（微分フィルタと同じ）

```python:A17
"""
ラプラシアンフィルタ(Laplacian Filter)は、二次微分を利用して画像から輪郭を抽出する空間フィルタ
cv2.filter2D(src, -1, kernel)
src	入力画像
cv2.CV_64F float64
kernel	フィルタのカーネル(※NumPy配列で与える)
"""

# カーネル
kernel = np.array([[0, 1, 0],
                     [1, -4, 1],
                     [0, 1, 0]])

# ラプラシアンフィルタ
img17 = cv2.filter2D(gray, cv2.CV_64F, kernel)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans17.jpg', img17)

# 画像を表示する
cv2_imshow(img17)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/54cc8807-331a-a6e0-bd20-5ec57cbb42c5.png)

参考: [【Python/OpenCV】ラプラシアンフィルタで輪郭検出（エッジ抽出）](https://algorithm.joho.info/programming/python/opencv-laplacian-filter-py/)

## Q.18. Embossフィルタ
>Embossフィルタを実装せよ。
Embossフィルタとは輪郭部分を浮き出しにするフィルタで、次式で定義される。

```python:A18
"""
エンボスフィルタ(Emboss Filter)は、輪郭部分を浮き出しする空間フィル
cv2.filter2D(src, -1, kernel)
src	入力画像
cv2.CV_64F float64
kernel	フィルタのカーネル(※NumPy配列で与える)
"""

# カーネル
kernel = np.array([[-2, -1, 0],
                     [-1, 1, 1],
                     [0, 1, 2]])

# ラプラシアンフィルタ
img18 = cv2.filter2D(gray, cv2.CV_64F, kernel)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans18.jpg', img18)

# 画像を表示する
cv2_imshow(img18)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

![img18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/8b458396-43e0-1185-9b23-426204eb9bb8.png)


参考: [【Python/OpenCV】エンボスフィルタで加工](https://algorithm.joho.info/programming/python/opencv-emboss-filter-py/)

## Q.19. LoGフィルタ
>LoGフィルタ(sigma=3、カーネルサイズ=5)を実装し、imori_noise.jpgのエッジを検出せよ。
LoGフィルタとはLaplacian of Gaussianであり、ガウシアンフィルタで画像を平滑化した後にラプラシアンフィルタで輪郭を取り出すフィルタである。
Laplcianフィルタは二次微分をとるのでノイズが強調されるのを防ぐために、予めGaussianフィルタでノイズを抑える。LoGフィルタは次式で定義される。

```python:A19

"""
LoGフィルタ(Laplacian Of Gaussian Filter)とは、ガウシアンフィルタとラプラシアンフィルタを組み合わせたフィルタ
ガウシアンフィルタで画像を平滑化してノイズを低減した後、ラプラシアンフィルタで輪郭を取り出します。

ガウシアンフィルタ
    cv2.GaussianBlur(src, ksize, sigmaX)
    src: 入力画像, ksize: カーネルサイズ, sigmaX: ガウス分布のsigma_x
ラプラシアンフィルタ
    cv2.filter2D(src, -1, kernel)
    src	入力画像
    cv2.CV_64F float64
    kernel	フィルタのカーネル(※NumPy配列で与える)
"""

# ガウシアンフィルタ
gauss_img = cv2.GaussianBlur(gray_noise, ksize=(3, 3), sigmaX=1.3)

# カーネル
kernel = np.array([[0, 0, 1, 0, 0],
                [0, 1, 2, 1, 0],
                [1, 2, -16, 2, 1],
                [0, 1, 2, 1, 0],
                [0, 0, 1, 0, 0]])

# ラプラシアンフィルタ
img19 = cv2.filter2D(gauss_img, cv2.CV_64F, kernel)

# 結果を保存する
cv2.imwrite(OUT_DIR + 'ans19.jpg', img19)

# 画像を表示する
cv2_imshow(img19)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
![img19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/fb6e0f22-3c37-3347-e18d-b706334a96b1.png)

回答と違う画像。でもラプラシアンフィルタにするとこんな感じな気がする。間違っていたら教えてください。

参考: [【画像処理】LoGフィルタの原理・特徴・計算式](https://algorithm.joho.info/image-processing/laplacian-of-gaussian-filter/)

## Q.20. ヒストグラム表示
>matplotlibを用いてimori_dark.jpgのヒストグラムを表示せよ。
ヒストグラムとは画素の出現回数をグラフにしたものである。 matplotlibではhist()という関数がすでにあるので、それを利用する。

```python:A
# ヒストグラム
"""
matplotlib.pyplot.hist(x, bins=10, range=None, normed=False, weights=None,
                       cumulative=False, bottom=None, histtype='bar',
                       align='mid', orientation='vertical', rwidth=None,
                       log=False, color=None, label=None, stacked=False,
                       hold=None, data=None, **kwargs)
x (必須)	ヒストグラムを作成するための生データの配列。
bins	ビン (表示する棒) の数。階級数。(デフォルト値: 10)
range	ビンの最小値と最大値を指定。(デフォルト値: (x.min(), x.max()))
rwidth	各棒の幅を数値または、配列で指定。
"""
# ravel: 多次元のリストを1次元のリスト, ビン数:255, 範囲0~255
plt.hist(img_dark.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("out.png")
plt.show()
```

参考: [Python でデータサイエンス
](https://pythondatascience.plavox.info/matplotlib/%E3%83%92%E3%82%B9%E3%83%88%E3%82%B0%E3%83%A9%E3%83%A0)

# 感想
公式版ではnumpyを用いて、原理通りやっているがOpenCVを可能な限り用いて、簡単に実装できるようにする。