OpenCVとPythonを使ったさまざまな画像処理まとめ

#背景
OpenCVとPythonを使ってさまざまな画像処理を行ってみました。流用しやすい形として残しておきたいので、よく使うグレースケール化、画像のリサイズや回転、エッジ処理、特徴量検出などをOpenCVで実装したコードと結果を残しておきます。

>OpenCV（オープンシーヴィ、英語: Open Source Computer Vision Library）とはインテルが開発・公開したオープンソースのコンピュータビジョン向けライブラリ。画像処理・画像解析および機械学習等の機能を持つC/C++、Java、Python、MATLAB用ライブラリ。プラットフォームとしてmacOSやFreeBSD等全てのPOSIXに準拠したUnix系OS、Linux、Windows、Android、iOS等をサポートしている。（参考：https://ja.wikipedia.org/wiki/OpenCV）

#目次
[1. グレースケール化：cv2.COLOR_BGR2GRAY](#1-グレースケール化)
[2. 画像サイズの変更：cv2.resize](#2-画像サイズの変更)
[3. 画像の回転：cv2.warpAffine](#3-画像の回転)
[4. エッジ抽出：cv2.Canny](#4-エッジ抽出)
[5. 特徴量検出：cv2.AKAZE_create](#5-特徴量検出)
[6. 色情報のヒストグラム化：cv2.calcHist](#6-色情報のヒストグラム化)

#開発環境
macOS Catalina 10.15.2
Python 3.6.8
opencv 4.0.0
※OpenCVのインストールに関しては今回は割愛します。

今回は有名なlenaの画像（大きさ：512×512）を用います。
![lena.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/f6bae2e4-84cb-27f7-74dc-02cc766450d6.jpeg)


#1. グレースケール化
カラー画像のグレースケール化は下記の通りです。

```ruby:gray.py
import cv2

img = cv2.imread("./lena.jpg") #画像の読み込み
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #画像のグレースケール化
cv2.imwrite("lena_gray.jpg",img_gray) #グレースケール画像の保存
```

保存したlena_gray.jpgは下記の通りで、グレースケール化できていることがわかります。
さまざまな画処理を行う際に、グレースケール化することで処理を高速化することができるので、とてもよく使う機能です。
![lena_gray.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/bec2da1a-c156-987f-150d-976a641f71b5.jpeg)

#2. 画像サイズの変更
画像サイズの変更は下記の通りです。

```ruby:resize.py
import cv2
import numpy as np

img = cv2.imread("./lena.jpg") #画像の読み込み

mag=0.1 #大きさを何倍にするかを指定
orgHeight, orgWidth = img.shape[:2] #読み込んだ画像サイズの高さと幅を定義
img_size = tuple(np.array([img.shape[1], img.shape[0]])) #画像サイズの取得(横, 縦)
img_resize = cv2.resize(img, img_size,interpolation = cv2.INTER_AREA) #画像のリサイズ

cv2.imwrite("lena_resize.jpg",img_resize) #グレースケール画像の保存
```

リサイズした画像lena_resize.jpgはこちら。
![lena_resize.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/61a378b5-a063-18ac-060a-d41961030f3c.jpeg)
元の画像から小さくなっていることがわかります。今回はmag=0.1としているので、大きさは1/10の(51, 51)になっています。

#3. 画像の回転
画像の回転は下記の通りです。

```ruby:rot.py
import cv2
import numpy as np

img = cv2.imread("./lena.jpg") #画像の読み込み

center = tuple(np.array([img.shape[1] * 0.5, img.shape[0] * 0.5])) #画像サイズの中心を計算
img_size = tuple(np.array([img.shape[1], img.shape[0]])) #画像サイズの取得(横, 縦)
rotation_matrix = cv2.getRotationMatrix2D(center, 90, 1) #回転変換行列の算出(cv2.getRotationMatrix2D(center, 角度, 倍率)
img_rot = cv2.warpAffine(img, rotation_matrix, img_size, flags=cv2.INTER_CUBIC) #アフィン変換

cv2.imwrite("lena_rot.jpg",img_rot) #回転画像の保存
```
回転した画像lena_resize.jpgはこちら。回転角度を90にしているので、90°回転した画像が出力されます。
![lena_rot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/6db72036-fed8-5591-f15f-353429d4c0d5.jpeg)
ちなみに、60°にすると下記の通りとなります。この画像は後ほど利用します。
![lena_rot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/3eeee634-ee50-4697-526b-25878507693f.jpeg)

#4. エッジ抽出
エッジ抽出を行うコードは下記の通りです。

```ruby:edge.py
import cv2

img = cv2.imread("./lena.jpg") #画像の読み込み
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #画像のグレースケール化
img_canny = cv2.Canny(img_gray, 100, 200) #エッジ抽出

cv2.imwrite("lena_canny.jpg",img_canny) #エッジ抽出画像の保存
```
エッジ処理を行って出力された画像は以下のようなものになります。Cannyの引数(100と200)を変更することで、抽出されるエッジの結果が変わります。
![lena_canny.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/682c315b-6399-a771-36fe-485a2cf4bf02.jpeg)

#5. 特徴量検出
Akaze特徴量を使用して画像の特徴量検出を行ってみます。
lena.jpgと、上述の60°回転した画像の一致度を特徴量検出を行いて検証します。

```ruby:akaze.py
import cv2
import numpy as np

img_mst = cv2.imread("./lena.jpg") #画像の読み込み
detector = cv2.AKAZE_create() #特徴抽出機の生成
kp1, des1 = detector.detectAndCompute(img_mst, None) #kpは特徴的な点の位置 destは特徴を現すベクトル

img = cv2.imread("./lena_rot.jpg") #画像の読み込み
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #グレースケール化
kp2, des2 = detector.detectAndCompute(img_gray, None) #kpは特徴的な点の位置 destは特徴を現すベクトル

#特徴点の比較機
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1,des2, k=2)

#割合試験を適用
good = [] #適合した点を格納するためのリスト
match_param = 0.6
for m,n in matches: #
    if m.distance < match_param*n.distance:
        good.append([m])

print(len(good)) #参考：一致した点数を表示する

img_akaze = cv2.drawMatchesKnn(img_mst,kp1,img_gray,kp2,good, None,flags=2) #cv2.drawMatchesKnnは適合している点を結ぶ画像を生成する
cv2.imwrite("lena_akaza.jpg",img_akaze) #特徴量検出結果を示した画像の保存
```
出力した画像は下図になります。二つの画像で一致している点動詞が結ばれています。回転している画像であっても、このように多くの一致点が現れます。この条件では、一致点数は371点になります。例えば、この一致点数を判断基準として、二つの画像が同じであるかどうかを判別することも可能となります。
![lena_akaza.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/71479d57-e916-ccd5-7487-826e06a3512c.jpeg)

#6. 色情報のヒストグラム化
これまでとちょっと違いますが、画像に含まれるRGB情報のヒストグラムも作成できます。

```ruby:histgram.py
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread("./lena.jpg") #入力画像を読み込み

b, g, r = img[:,:,0], img[:,:,1], img[:,:,2] #RBG成分を抽出

hist_r = cv2.calcHist([r],[0],None,[256],[0,256]) # R成分のヒストグラムの算出
hist_g = cv2.calcHist([g],[0],None,[256],[0,256]) # G成分のヒストグラムの算出
hist_b = cv2.calcHist([b],[0],None,[256],[0,256]) # B成分のヒストグラムの算出

# matplotlibを使ってグラフの作成・表示
plt.xlim(0, 255)
plt.plot(hist_r, "-r", label="Red")
plt.plot(hist_g, "-g", label="Green")
plt.plot(hist_b, "-b", label="Blue")
plt.xlabel("Pixel value", fontsize=12)
plt.ylabel("Number of pixels", fontsize=12)
plt.legend()
plt.grid()
plt.show()
```
lena.jpgの画像に含まれるRGBそれぞれのヒストグラム（8bit）は下図のようになります。
このように色情報を分析することもできるんですね。
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/ec6c40d7-7ba6-478c-f400-55e18e138b89.png)

#まとめ
今回はOpenCVとPythonを使って画像処理の基本から、特徴量検出まで行ってみました。これらは割と多くのケースで使えると思います。OpenCVでできることはまだまだあるので、また投稿したいと思います。