Pythonを用いた画像処理(openCV,skimage)

#はじめに
今年、独学でPythonと機械学習について勉強を始めました。
機械学習をする際に、学習させる画像をいろいろな方法で画像処理や特徴量の抽出を行ったので簡単にまとめたいとおもいます。

余談になりますが、私周辺の知り合いに
「何故Pythonを勉強してるのか？、JavaやC++を勉強しないのか？」
と聞かれることが多いです。
たしかにPythonは他の言語より実行が遅く、実行速度が重要視されるようなシステム開発には向いてないと思います。しかし、私みたいなプログラミング初心者から見ると、他の言語よりコードの書き方の統一感があって他人のソースコードを理解しやすい、パッケージ(モジュール)が豊富で情報工学などの知識が疎遠な私でも作りたいものを簡単に作ることができる利点があると思います。
なので、本記事はPythonを用いた画像処理についてまとめたいと思います。


## OpenCV
###OpenCVのインストール
OpenCV(Open Source Computer Vision Library)とは、画像や動画を処理するのに必要な様々な機能を提供するライブラリであり、画像の変換やフィルタ処理や、物体判定、物体認識、カメラの入出力などの機能が備わっています。
今回自分のPCでは、バージョン管理の環境構築のためにpyenvを導入して、OS Xでpythonを利用しています。
Windowsの場合はコマンドライン(Anaconda Prompt)から、pipコマンドを利用してOpenCVをインストールできます。

```
pip install opencv-python==3.4.2.16
```

```
[user@baya]# pip install opencv-python                                                                                                                                              [/Users/taka.baya]
Collecting opencv-python
  Downloading https://files.pythonhosted.org/packages/b3/0b/055f90609d6b22885ba178565a439563b00fe58b81a9acd3bbf46212190f/opencv_python-3.4.4.19-cp36-cp36m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl (48.7MB)
    100% |████████████████████████████████| 48.7MB 520kB/s
Requirement already satisfied: numpy>=1.11.3 in ./.pyenv/versions/3.6.0/lib/python3.6/site-packages (from opencv-python) (1.14.5)
Installing collected packages: opencv-python
Successfully installed opencv-python-3.4.4.19
```

次に拡張モジュール群である contrib を導入します。
商用利用が制限されているアルゴリズムが含まれているため、導入には注意が必要です（非自由/特許アルゴリズムが含まれているため）

```
pip install opencv-contrib-python==3.4.2.16
```

```
[user@baya]# pip install opencv-contrib-python==3.4.2.16                                                                                                                            [/Users/taka.baya]
Collecting opencv-contrib-python==3.4.2.16
  Using cached https://files.pythonhosted.org/packages/b8/26/9c631442c828ec88d4b0017ec39bb6ed655d81b3e99010ad1c841d104d54/opencv_contrib_python-3.4.2.16-cp36-cp36m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl
Requirement already satisfied: numpy>=1.11.3 in ./.pyenv/versions/3.6.0/lib/python3.6/site-packages (from opencv-contrib-python==3.4.2.16) (1.14.5)
Installing collected packages: opencv-contrib-python
Successfully installed opencv-contrib-python-3.4.2.16
```


OpenCVがインストールされているかどうかは、コマンドラインからPythonの対話型実行環境を実行して、OpenCVのライブラリcv2が利用できるかどうかで確認できます。

```
[user@baya]# python                                                                                                                                                                 [/Users/user]
Python 3.6.0 (default, Jun  3 2018, 12:06:09)
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>>
```

以下二つのモジュールも使う場面が多いのでインストールしとくといいと思います。

```
pip install numpy
pip install matplotlib
```

### 画像処理
lenaの画像をベースに何点か画像処理について紹介する
![lena.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/57cf9457-5221-1f1b-50e8-def7e14a830a.jpeg)


#### 画像を二値化(白黒画像)
```.py
import cv2

filename = "lena.jpg"
gry = cv2.imread(filename, 0)
cv2.imwrite('gray.jpg', gry)
```
imread(filename, flags)の第２引数を以下から選択することによって、任意の画像読み込み方法を選択することが可能です。

| 値 | 機能 |
|:-:|:-:|
| -1  | 無変換  |
| 0  | グレー  |
| 1  | カラー  |
| 2  | 任意の深度  |
| 3  | 任意のカラー  |



![gray.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/bb0f63d4-2064-5074-0cd2-11049bd00d60.jpeg)

#### 指定範囲の色のみ抽出

ソースコードにあるlower_colorとupper_colorを調節すれば任意の色を調節することが可能です。

```.py
import cv2
import numpy as np

filename = "lena.jpg"
img = cv2.imread(filename,1)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 取得する色の範囲を指定する
lower_color = np.array([20, 50, 50])
upper_color = np.array([255, 255, 255])

# 指定した色に基づいたマスク画像の生成
img_mask = cv2.inRange(hsv, lower_color, upper_color)

# フレーム画像とマスク画像の共通の領域を抽出する。
img_color = cv2.bitwise_and(img, img, mask=img_mask)

cv2.imwrite("hsv.jpg", img_color)
```

![hsv.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/ea8cd342-ae84-4551-c897-ad3b5187ade2.jpeg)

#### 画像のエッジ部分を抽出

```.py
import cv2
import numpy as np

filename = "lena.jpg"
# 白黒画像で画像を読み込み
img = cv2.imread(filename,0)
#エッジ検出
canny_img = cv2.Canny(img, 50, 110)

cv2.imwrite("canny.jpg", canny_img)

```

![canny.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/e3b863b0-b6c8-dfee-546b-fff777193b1a.jpeg)

#### 画像をリサイズ
個人的に機械学習を行うにあたって、学習データ量を小さくするために画像のリサイズをかなりの頻度で行いました。

```.py
import cv2
import numpy as np

filename = "lena.jpg"
# 白黒画像で画像を読み込み
img = cv2.imread(filename,1)
# 画像の高さ幅を指定
width,height=60,60
# 画像をリサイズ
img = cv2.resize(img,(width,height))

cv2.imwrite("resize_lena.jpg", img)
```
![resize_lena.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/0f130f2c-3fb9-c658-ef1b-3e519fd27dcd.jpeg)

####画像のヒストグラムを可視化
ヒストグラムとはその画像がどのような濃度値(緑・赤・青)を持った画素から成り立っているかの情報をまとめたものです。
二値化(白黒画像化)することによって、濃度値(白黒)も得ることができます。

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

img = cv2.imread('lena.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
    histr = cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlim([0,256])
plt.show()
```
![Figure_1.png](https://qiita-image-store.s3.amazonaws.com/0/154154/1365e315-70ba-156d-0210-e944b1cf00d2.png)
### 特徴点抽出

#### ORB特徴点抽出

```.py
import cv2
import numpy as np

filename = "lena.jpg"
# 白黒画像で画像を読み込み
img = cv2.imread(filename,1)

# ORB
detector = cv2.ORB_create()

# 特徴検出
keypoints = detector.detect(img)

# 画像への特徴点の書き込み
img_orb = cv2.drawKeypoints(img, keypoints, None)

cv2.imwrite("orb.jpg", img_orb)
```
![orb.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/1af0e354-1973-0425-90c9-2c5ba006c9a6.jpeg)

OpenCVでは、ORB以外のアルゴリズムでも特徴点を抽出することができます。
プログラムの```detector = (任意)```を切り替えるだけでアルゴリズムを変更することができます。

| アルゴリズム | 書き方 |
|:-:|:-:|
| ORB | cv2.ORB_create() |
| FAST | cv2.FastFeatureDetector_create() |
| AKAZE | cv2.AKAZE_create() |
| BRISK | cv2.BRISK_create() |
| KAZE | cv2.KAZE_create() |
| SIFT |  cv2.ORB_create() |
| MSER | cv2.MSER_create() |
| AgastFeatureDetector | cv2.AgastFeatureDetector_create() |

####特徴点のマッチング

```.py
import cv2
import numpy as np

filename = "lena.jpg"
# 白黒画像で画像を読み込み
img = cv2.imread(filename,0)

# 画像サイズの取得(横, 縦)
size = tuple([img.shape[1], img.shape[0]])

# 画像の中心位置(x, y)
center = tuple([int(size[0]/2), int(size[1]/2)])

# 回転させたい角度（正の値は反時計回り）
angle = 180.0

# 拡大比率
scale = 1.0

# 回転変換行列の算出
rot_matrix = cv2.getRotationMatrix2D(center, angle, scale)

# アフィン変換
rot_img = cv2.warpAffine(img, rot_matrix, size, flags=cv2.INTER_CUBIC)

# ORB (Oriented FAST and Rotated BRIEF)
detector = cv2.ORB_create()

# 特徴検出
kp1, des1 = detector.detectAndCompute(img, None)
kp2, des2 = detector.detectAndCompute(rot_img, None)

# マッチング処理
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key = lambda x:x.distance)

# マッチング上位20個の特徴点を線でリンクして画像に書き込む
match_img = cv2.drawMatches(img, kp1, rot_img, kp2, matches[:20], None, flags=2)

cv2.imwrite("matcher.jpg", match_img)
```

![matcher.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/9f0b0ff6-b93b-e466-a754-907c031ac4c1.jpeg)

####マッチング距離の計算
```.py
import cv2
import numpy as np

filename1 = "lena.jpg"
filename2 = "lena2.jpg"
# 白黒画像で画像を読み込み
img1 = cv2.imread(filename1,1)
img2 = cv2.imread(filename2,1)

# ORB (Oriented FAST and Rotated BRIEF)
detector = cv2.ORB_create()

# 特徴検出
kp1, des1 = detector.detectAndCompute(img1, None)
kp2, des2 = detector.detectAndCompute(img2, None)

# マッチング処理
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# 特徴記述子のマッチング
matches = bf.match(des1, des2)
# 距離でソートする
matches = sorted(matches, key = lambda x:x.distance)

distance = 0
for m in matches:
		#print(m.distance)
		distance = distance + m.distance

print("マッチング距離：",distance)

# マッチング上位20個の特徴点を線でリンクして画像に書き込む
match_img = cv2.drawMatches(img1, kp1, img2, kp2, matches[:20], None, flags=2)

cv2.imwrite("matcher.jpg", match_img)
```

```.console
マッチング距離： 13162.0
```

![matcher.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/6f3f14ad-07b3-6b11-9fd7-8e572dfe7c82.jpeg)



## skimage

skimageとは画像処理に関するアルゴリズムを集めたライブラリです。
無料で扱うことができるみたいです。
OpenCVと同様にターミナルまたはコマンドラインからskimageをインストールすることができる。
[skimageのサイト](https://scikit-image.org/)にてサンプルソースがゴロゴロ転がっているので、もっと知りたい人はそちらを見てください。

```
pip install -U scikit-image
```

### 画像処理

####Region Adjacency Graph(RAG)
画像の色の違いの部分を切り分けたりします。ここで説明するとかなり長くなるので、気になる人は調べてみてください。

```.py
import matplotlib.pyplot as plt
import cv2
from skimage import data, segmentation, color
from skimage.future import graph

img = cv2.imread('lena.jpg',1)

labels1 = segmentation.slic(img, compactness=30, n_segments=400)
out1 = color.label2rgb(labels1, img, kind='avg')

g = graph.rag_mean_color(img, labels1, mode='similarity')
labels2 = graph.cut_normalized(labels1, g)
out2 = color.label2rgb(labels2, img, kind='avg')

fig, ax = plt.subplots(nrows=2, sharex=True, sharey=True, figsize=(6, 8))

ax[0].imshow(out1)
ax[1].imshow(out2)

plt.show()

cv2.waitKey(0)
```

<img width="442" alt="スクリーンショット 2018-12-19 20.57.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/154154/bd9ce997-f256-c8d7-0cdf-c7f38201dd8e.png">


### 特徴量抽出
####Dense DAISY特徴量の抽出
DAISYとは、画像の各画素における特徴量を記述する手法の一つです。DAISYを用いることで，カメラ間隔が長く撮影方向が大きく異なる多視点画像に対して比較的強い特徴量らしいです。

```.py
import matplotlib.pyplot as plt
import cv2
from skimage.feature import daisy
from skimage import data, exposure

img = cv2.imread('lena.jpg',0)

descs, descs_img = daisy(img, step=180, radius=58, rings=2, histograms=6,
                         orientations=8, visualize=True)

fig, ax = plt.subplots()
ax.axis('off')
ax.imshow(descs_img)
descs_num = descs.shape[0] * descs.shape[1]
ax.set_title('%i DAISY descriptors extracted:' % descs_num)
plt.show()
```

![Figure_4.png](https://qiita-image-store.s3.amazonaws.com/0/154154/b5f02401-7ef2-2467-9100-06172779a3f0.png)

####HoG特徴量の抽出と可視化
HOG(Histograms of Oriented Gradients)とは局所領域 (セル) の輝度の勾配方向をヒストグラム化したものです。
勾配情報をもとにしているので、異なるサイズの画像を比較対象としても同じサイズにリサイズすることで比較可能になります。
実行した際にコンソール画面にて警告が出た場合は以下記事を参考にして対処してください。
[【備忘録】pythonのパッケージ 「hog」でWarningが出る](https://qiita.com/taka_baya/items/7a05fa1df1ce6765b26f)

```.py
import matplotlib.pyplot as plt
import cv2
from skimage.feature import hog
from skimage import data, exposure

image = cv2.imread('lena.jpg')

fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
                    cells_per_block=(1, 1), visualize=True, multichannel=True)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True)

ax1.axis('off')
ax1.imshow(image, cmap=plt.cm.gray)
ax1.set_title('Input image')

hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))

ax2.axis('off')
ax2.imshow(hog_image_rescaled, cmap=plt.cm.gray)
ax2.set_title('Histogram of Oriented Gradients')
plt.show()
```

![Figure_3.png](https://qiita-image-store.s3.amazonaws.com/0/154154/861b941c-a126-cfe9-1646-eae4232fa0ab.png)


#まとめ
特徴点・特徴量についてどのようなアルゴリズムや手法なのかを説明せずにサンプルソースと実行結果の画像をただ淡々とあげてしましました。
処理した画像を可視化するだけでも楽しいので、少しでも画像処理について興味持って頂けたらと思っています。
~~CTFのforensics問を解く際にも役に立ちます。~~