画像処理初心者がOpenCVを使ってみた(1)

この記事は、画像処理エンジニア検定を受験するにあたり、OpenCVを実際に利用して画像処理について学ぶことを目的としています。勉強した内容をメモします。
※画像処理でできることをメインに書いていくので、Pythonや使用する各関数の説明などは省略しています。

使用環境

* windows10
* Anaconda: 1.7.2
* python: 3.7.9
* Jupyter Notebook

# 前提
### 画像処理とは

* 画像はパターンを表す数値（画素値）の集まり

* 0から255で表現する
RGBの3色から成る。

例
(10, 20, 30)
※順番がR(Red)G(Green)B(Blue)ではないことに注意

| B | G | R | 
|:-----|:-----|:-----|
| 10 | 20 | 30 |

* 画像を解析しやすくする、情報を抽出するための処理

### [OpenCV](http://opencv.jp/)とは

* オープンソースの画像処理ライブラリ

* マルチプラットフォーム：windows, mac, linux

* 多言語対応：C++, Pyhton, Java...

* 画像処理以外にも機械学習などのコンテンツも含む

## 目次
* 環境構築

* 画像の表示・出力

* ウィンドウの調整

* 色変換

* γ（ガンマ）変換で画像を明るくする、暗くする

* 表示した画像に図形を描写する

* 画像の二値化

* 透視変換

* 畳み込み

## 環境構築
### AnacondaのインストールとOpenCVのインストール

https://itport.cloud/?p=6615

Anacondaはすでにインストール済みだったので、上記のブログを参考にOpenCVをインストールしました。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/3df39767-1278-d00a-4899-2a78ec16955a.png)

## 画像の表示・出力

```python
import cv2
#画像を保存するためにimport
import os

img = cv2.imread("data/src/score.jpg")

#画像の表示
cv2.imshow("img",img)

#何かしらのキーが入力されるとウィンドウが閉じられる
cv2.waitKey(0)
cv2.destroyAllWindows()

#outputディレクトリを作成
os.mkdir("./output")
#読み込んだimgをtest.jpgとして保存
cv2.imwrite("output/test.jpg", img)
```
出力された画像は以下。左上のtitleが「img」となっている。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/3229c45e-6bd6-7abd-6b74-7d0471ed2190.png)

## ウィンドウの調整
前項の画像の表示で表示させたウィンドウは固定サイズでした。
以下のようにコードを書くと、表示されるウィンドウのサイズを調整することができます。

```python
import cv2
img = cv2.imread("data/src/favorite.jpg")
#第二引数にcv2.WINDOW_NORMALを指定するとウィンドウの調整ができるようになる
cv2.namedWindow("window", cv2.WINDOW_NORMAL)
cv2.imshow("window", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
表示される画像は以下のような感じです。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/7b9fb7f7-fac0-2a47-b040-dd8280039ad4.png)

ウィンドウを縮小させるとこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/d8b9ddec-b385-5221-e7da-a19642dbf1bb.png)


また、表示させる画像のリサイズを行う方法もあります。

```python
import cv2
img = cv2.imread("data/src/score.jpg")
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
img.shape #出力(3672, 4896, 3)
```
```python
#高さ、幅
size = (300, 200)
img_resize = cv2.resize(img,size)
img_resize.shape #出力(200, 300, 3)
```
```python
cv2.imshow("resize",img_resize)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

表示される画像は、以下のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/af125e0a-5cfd-f100-fbff-f3313557565b.png)

上記とは別の方法で画像をリサイズする方法もあります。

```python
img_area = cv2.resize(img, size, interpolation = cv2.INTER_AREA)
img_linear = cv2.resize(img, size, interpolation = cv2.INTER_LINEAR)
cv2.imshow("area", img_area)
cv2.imshow("linear", img_linear)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

出力結果は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/84d6512b-8c49-bdc1-5e60-59bb0f02dfd8.png)

2つの画像を比べると、titleが「linear」となっている画像の方がギザギザ？になっていることが分かります。

## 色変換
カラー画像を白黒画像にする方法を以下のコードで書きます。

```python
import cv2
img = cv2.imread("data/src/favorite.jpg")
#用意した画像が大きいためリサイズして縮小させてます
size = (300, 200)
img = cv2.resize(img,size)

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gray.shape #出力は解像度の情報だけ残る -> (200, 300)
```

```python
cv2.imshow("img", img)
cv2.imshow("gray", img_gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記コードを実行した結果、以下のように画像が表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/43d49f62-d367-3866-c4e0-b972b93fbf58.png)

また、以下の方法で画像を白黒にすることもできます。

```python
import cv2

# imread()の第二引数に0を設定することで白黒画像として読み込まれる。
img_gray2 = cv2.imread("data/src/favorite.jpg", 0)

#用意した画像が大きいためリサイズして縮小させてます
size = (300, 200)
img_gray2 = cv2.resize(img_gray2,size)

#白黒画像になっているか確認するコード
cv2.imshow("gray", img_gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
```


## γ（ガンマ）変換で画像を明るくする、暗くする
こちらの記事にγ変換についてまとまっているので、勉強させていただきました。

https://algorithm.joho.info/image-processing/gamma-correction/

実際にγ変換を用いて画像の明るさを変換するコードを実行しました。

```python
import cv2
import numpy as np

#元画像より明るい画像を表示させます
gamma = 2.0

img = cv2.imread("data/src/favorite.jpg")

#元画像が大きいためリサイズしています
size = (300, 200)
img = cv2.resize(img,size)

gamma_cvt = np.zeros((256,1), dtype = np.uint8)

for i in range(256):
    gamma_cvt[i][0] = 255 * (float(i) / 255) ** (1.0 / gamma)

img_gamma = cv2.LUT(img, gamma_cvt)

cv2.imshow("img",img)
cv2.imshow("gamma",img_gamma)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

実行結果は以下のようになりました。画像が明るくなっているのが分かると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/17db48d8-1c7b-e6ec-e806-b3268b0e3608.png)


また、変数gammaを1より小さくすることで、元画像より暗い画像へ変換することもできます。

```python
import cv2
import numpy as np

#元画像より暗い画像を表示させます
gamma = 0.5

#元画像が大きいためリサイズしています
img = cv2.imread("data/src/favorite.jpg")
size = (300, 200)
img = cv2.resize(img,size)

gamma_cvt = np.zeros((256,1), dtype = np.uint8)

for i in range(256):
    gamma_cvt[i][0] = 255 * (float(i) / 255) ** (1.0 / gamma)

img_gamma = cv2.LUT(img, gamma_cvt)

cv2.imshow("img",img)
cv2.imshow("gamma",img_gamma)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記のコードを実行した結果がこちらです。元画像よりも暗い画像に変換されていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/968c56f9-f831-5e30-b294-1e0ce541a4d3.png)


## 表示した画像に図形を描写する
読み込んだ画像に対して、図形などを表示させることができます。
以下にサンプルを作成しました。座標軸などは任意の値を設定しています。

```python
import cv2
import numpy as np

#白い画像を生成
img = np.ones((1000, 1000, 3)) * 255

#青い直線を描写
cv2.line(img, (0, 0), (150,300),(255, 0, 0,), 2)
#緑四角形を描写
cv2.rectangle(img, (100, 100), (200, 200),(0, 255, 0), 4)
#赤い円を描写
cv2.circle(img, (250,100), 50, (0, 0, 255), -1)
#青い楕円を描写
cv2.ellipse(img,(250, 250), (100, 50), 20, 0, 360, (255, 0, 0), 1)
#水色の多角形（今回は三角形）を描写
pts = np.array([[100, 30], [200, 30], [200, 80], [100, 30]])
cv2.polylines(img, [pts], False,(100, 255, 0), 3)
#テキストを描写
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, "text!!!", (400, 400), font, 1, (0, 255, 0),3, cv2.LINE_AA)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記コードを実行した結果がこちらです。白い画像にそれぞれ図形が描写されていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/f356b84d-8488-fd17-0526-b643940e13cd.png)

## 画像の二値化
画像の二値化とは、画像を白と黒の2色に変換する処理のことで、ある値（閾値）以上の画素値を白、ある値未満の画素値を黒に変換することを指します。

以下のコードで画像の二値化をしてみます。

```python
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.imread("data/src/favorite.jpg", 0)

#画像サイズが大きいためリサイズしています
size = (300, 200)
img = cv2.resize(img,size)

#閾値を50に設定
threshold = 50

#retは閾値が返ってくる
ret, img_th = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)

cv2.imshow("img_th", img_th)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記のコードを実行した結果が以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/47ebcc2f-2af5-3eb0-cd9c-dbe5ec7119be.png)


また、以下のように適応に閾値を設定して、二値化することも可能です。

```python
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.imread("data/src/favorite.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (300, 200)
img = cv2.resize(img,size)

img_ada = cv2.adaptiveThreshold(img, 255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, 1)
cv2.imshow("img_ada",img_ada)
cv2.waitKey(0)
cv2.destroyAllWindows()
```


上記のコードを実行した結果が以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/22f93d4f-8668-7357-d4fe-3f07164b63df.png)


また、トラックバーを使用して、閾値を変化させることも可能です。

```python
import cv2

img = cv2.imread("data/src/favorite.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (300, 200)
img = cv2.resize(img,size)

#トラックバーを用いて、二値化の閾値を変化させる
def onTrackbar(position):
    global threshold
    threshold = position
cv2.namedWindow("img")
threshold = 100
cv2.createTrackbar("track","img",threshold, 255, onTrackbar)

while True:
    ret, img_th = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
    cv2.imshow("img",img_th)
    #Escapeキーでウィンドウを閉じます
    if cv2.waitKey(10) == 27:
        break
cv2.destroyAllWindows()
```

上記コードでは、閾値を0～255まで変化させることができます。
以下の画像は閾値を0, 77, 255に変化させたときの画像です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/f49e684a-59cc-315b-84dd-b2c7c84735c1.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/79e7b23d-025f-924c-48d2-b25bf0826de2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/6b438859-b8d2-93ce-a912-c419bae8f340.png)


## 透視変換
透視変換とは、任意の視点から3次元空間を眺めて遠近感を出す変換です。
以下のサンプルで透視変換がどういう変換か確認します。

```python
import cv2
import numpy as np

img = cv2.imread("data/src/score.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (500, 500)
img = cv2.resize(img,size)

h, w = img.shape[:2]

p1 = np.float32([[100, 500], [300, 500], [300, 100],[100,100]])
p2 = np.float32([[100, 500], [300, 500], [200, 200],[100,200]])

p_matrix = cv2.getPerspectiveTransform(p1, p2)
img_p = cv2.warpPerspective(img, p_matrix, (w, h))
cv2.imshow("img",img_p)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

実行結果は以下のような画像です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/f65be5c7-1d1f-6b69-8517-02466bada39d.png)

## 畳み込み

以下のページに畳み込みについてまとまっているので、こちらで勉強させていただきました。

https://techacademy.jp/magazine/24017

フィルターを用いて、画像の平滑化（画素値の変化を滑らかにすること）を行いました。
以下がそのコードです。

```python
import cv2
import numpy as np

kernel = np.ones((5,5)) / 25.0

img = cv2.imread("data/src/score.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (500, 500)
img = cv2.resize(img,size)

img_2 = cv2.filter2D(img, -1, kernel)

cv2.imshow("img_2",img_2)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記を実行すると次のような画像が表示されます。「img」が元画像です。
楽譜の周りや音符などがぼやけて見えると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/7c90b439-7e66-438a-8413-9793b041dd70.png)

また、画像の平滑化には`GaussianBlur`を用いた方法もあります。

```python
import cv2

img = cv2.imread("data/src/score.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (500, 500)
img = cv2.resize(img,size)

img_gaussian = cv2.GaussianBlur(img,(9,9),2)

cv2.imshow("img_gaussian",img_gaussian)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

```

上記コードの実行結果は以下です。
こちらの画像も平滑化されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/2065803f-8ee6-0624-109d-bd384c379e4c.png)

`medianBlur`を用いた場合のコードと実行結果も以下に書きます。

```python
import cv2

img = cv2.imread("data/src/score.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (500, 500)
img = cv2.resize(img,size)

#第二引数の値が大きいほど画像がぼやけます
img_median = cv2.medianBlur(img, 5)

cv2.imshow("img_median",img_median)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記の実行結果は以下のようになります。他の方法よりもぼやけた結果となりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/23631a75-5e0a-7572-671a-b9021376c9f0.png)


`bilateralFilter`を用いた方法も書きます。

```python
import cv2

img = cv2.imread("data/src/score.jpg", 0)
#画像サイズが大きいためリサイズしています
size = (500, 500)
img = cv2.resize(img,size)

img_bi = cv2.bilateralFilter(img, 20, 30, 30)

cv2.imshow("img_bi",img_bi)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

実行結果は以下のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/d6c12b87-58b8-bd11-65e4-80ad19f930b7.png)


### 最後に
まだまだ画像処理を理解するには道のりが長そうです。
引き続き勉強します。
間違いなどありましたら、ご指摘いただけると幸いです。