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

この記事は、画像処理エンジニア検定を受験するにあたり、OpenCVを実際に利用して画像処理について学ぶことを目的としています。勉強した内容をメモします。
[画僧処理初心者がOpenCVを使ってみた(1)](https://qiita.com/naccchan/items/b06a20e271feacf97e39) の続きです。
※画像処理でできることをメインに書いていくので、Pythonや使用する各関数の説明などは省略しています。

目次

* エッジの検出
* 直線の検出
* モルフォロジー演算
* 特徴抽出
* 顔検出

## エッジの検出
エッジとは何かについては、以下のサイトで勉強させていただきました。

https://jp.mathworks.com/discovery/edge-detection.html


### ソーベル法(Sobel)でエッジ検出
ここでは画像のX軸とY軸、それぞれについて画像のエッジ検出結果を見てみたいと思います。

```python
import cv2

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

img_sobelx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize = 3)
img_sobely = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize = 3)

img_sobelx = cv2.convertScaleAbs(img_sobelx)
img_sobely = cv2.convertScaleAbs(img_sobely)

cv2.imshow("x",img_sobelx)
cv2.imshow("y",img_sobely)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記のコード実行結果は以下の画像のようになりました。
X軸とY軸でエッジの検出結果が違うことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/768ffebf-d365-2c46-2aec-6f2d07a542ed.png)

### ラプラシアン法でエッジ検出

```python
import cv2

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

img_lap = cv2.Laplacian(img, cv2.CV_32F)

img_lap = cv2.convertScaleAbs(img_lap)

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

この方法では、画像の軸に関係なくエッジを検出します。
以下の画像が出力結果です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/2887bd08-c8fa-452f-9ed6-4b0742076e53.png)

さらにエッジ検出をわかりやすくするために、`GaussianBlur`を用いてラプラシアン法を適用してみました。

```python
import cv2

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

img_blur = cv2.GaussianBlur(img, (3,3), 2)
img_lap = cv2.Laplacian(img_blur, cv2.CV_32F)
img_lap = cv2.convertScaleAbs(img_lap)

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

上記のコードの実行結果は以下のようになりました。
上のラプラシアン法よりもノイズが少ないことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/66d46d03-2c7b-2f4f-bc5f-a14eff0f9066.png)


### キャニー法でエッジ検出


```python
import cv2

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

img_canny = cv2.Canny(img, 10, 200)

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

上記のコードを実行した結果が以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/f38cd98a-4280-451f-5674-0fc7d2fc8f29.png)

`cv2.Canny`の関数の閾値を変更してエッジの検出がどうなるか確認しました。

```python
import cv2

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

#引数でエッジの閾値を変更する
img_canny = cv2.Canny(img, 100, 200)

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

上記のコードを実行した結果が以下です。上の例よりノイズが少ないことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/df7f11fa-70e6-0b88-eb00-06b5e0c504d3.png)

## 直線の検出
画像から直線を検出する方法を試しました。

```python
import cv2
import numpy as np

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

img_canny = cv2.Canny(img_gray, 300, 400)
lines = cv2.HoughLines(img_canny, 1, np.pi/180, 100)

for  i in lines[:]:
    rho = i[0][0]
    theta = i[0][1]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = rho * a
    y0 = rho * b
    x1 = int(x0 + 1000 * (-b))
    y1 = int(y0 + 1000 * a)
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * a)
    cv2.line(img, (x1,y1), (x2, y2), (255,0,0), 1)

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

上記のコードを実行した結果、以下の画像が出力されました。直線部分が検出されていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/be610f40-7c0a-4fc0-04fe-42cc1147ecd5.png)

##モルフォロジー演算
画像ピクセルを収縮させたり、膨張させて画像内の図形を分離するとき、ノイズを消したりするときに使用します。
詳しくは以下のページを参考に勉強させていただきました。

https://pystyle.info/opencv-morpology-operation/

以下のコードは、元画像(img)に対して、膨張と縮小を行った結果です。

```python
import cv2
import numpy as np

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

#画像の二値化
ret, img_th = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)

kernel = np.ones((3, 3), dtype=np.uint8)

#膨張
imf_d = cv2.dilate(img_th, kernel)
#縮小
img_e = cv2.erode(img_th, kernel)

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

実行した結果が以下の画像です。膨張させた際は画像の白い部分を拾ってきていますが、縮小させた際は黒い部分を拾ってきていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/4772998b-4512-8ab3-462e-af8762ea03f5.png)

次にクロージングをしてみます。
画像を膨張させた後に縮小を行ってみました。
以下が実行コードです。

```python
#上のコードの続きです
img_c = cv2.morphologyEx(img_th, cv2.MORPH_CLOSE, kernel)
cv2.imshow("img",img)
cv2.imshow("img_c",img_c)
cv2.imshow("img_e",img_e)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記のコードを実行した結果が以下の画像です。左のimg_c画像を膨張させたのち、収縮させました。元の画像に近い状態を保っていることが分かります。中央のimg_d画像は、膨張させただけの画像です。比較するために表示させてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/1db110f4-075f-326f-5840-3074939c5010.png)

## 特徴抽出
特徴とはパターン認識に役立つ情報量の多い部分のことを指します。

詳しくまとまっている記事がありましたので、こちらで勉強させていただきました。

https://qiita.com/icoxfog417/items/adbbf445d357c924b8fc

###Harris
ハリスのコーナー検出は、画像からコーナーを検出する手法のことです。
以下のコードでハリスの手法を用いてコーナーの特徴量を抽出しました。

```python
import cv2
import numpy as np
import copy

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

#元の画像が上書きされないようにコピーを作成しておきます
img_harris = copy.deepcopy(img)

img_dst = cv2.cornerHarris(img_gray, 2, 3, 0.05)

#特徴量（コーナー）に赤色を付けます
img_harris[img_dst > 0.05 * img_dst.max()] = [0, 0, 255]

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

実行結果は以下の画像です。
コーナーの部分が赤色で表示されていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/3c1a740d-2a05-a3e0-889d-aaf507247232.png)

###kaze
こちらも特徴量を抽出するアルゴリズムです。
上と同様にコーナーの特徴量の抽出を行いました。

```python
import cv2
import numpy as np
import copy

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

#元の画像が上書きされないようにコピーを作成しておきます
img_kaze = copy.deepcopy(img)
kaze = cv2.KAZE_create()
kp1 = kaze.detect(img, None)
img_kaze = cv2.drawKeypoints(img_kaze, kp1, None)
cv2.imshow("img",img)
cv2.imshow("img_kaze",img_kaze)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

上記のコードの実行結果は以下の画像です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/de6ce194-0f7a-6579-b6ed-b601e55cefce.png)

###ORB
こちらも特徴量を抽出するアルゴリズムです。
上と同様にコーナーの特徴量の抽出を行いました。

```python
import cv2
import numpy as np
import copy

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

#元の画像が上書きされないようにコピーを作成しておきます
img_orb = copy.deepcopy(img)
orb = cv2.ORB_create()
kp2 = orb.detect(img_orb)
img_orb = cv2.drawKeypoints(img_orb, kp2, None)

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

上記のコードを実行した結果が以下の画像です。kazeを使用したときより特徴量の検出が少ないのが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/ee5d3cee-8bee-557e-fcfc-e36429db5b78.png)

## 顔検出
OpenCVの中にすでに顔を検出するための検出器が用意されているので、それを用いて顔検出を行いました。
以下、顔検出するためコードです。

```python
import cv2

#検出器のパスです
HAAR_FILE = "C:/Users/natsumin/anaconda3/pkgs/libopencv-3.4.2-h20b85fd_0/Library/etc/haarcascades/haarcascade_frontalface_default.xml"
cascade = cv2.CascadeClassifier(HAAR_FILE)

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

face = cascade.detectMultiScale(img_gray)

#検出した顔部分に緑の枠を付けます
for x, y, w, h in face:
    cv2.rectangle(img, (x, y),(x + w, y + h),(0, 255,0), 1)

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

上記の実行結果は以下の画像です。顔検出できた部分は緑の枠で囲まれています。RMさんだけ検出されませんでした。
[BTSの新しいシングルのイメージ画像](https://news.kstyle.com/article.ksn?articleNo=2168122)を使わせていただきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186902/00b8eb54-c14f-688a-f69a-6848f37399fe.png)

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