OpenCVで画像を扱うときの主な処理まとめ

# はじめに
Pythonで画像を扱いたい！となった時のライブラリと言えばOpenCVですよね。

ここでは[公式チュートリアル](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/index.html)の中から、自分がよく使う処理をコードと共にまとめたいと思います。

基本的にはモデルの学習用に画像を前処理するのが目的なのでガッツリ画像を重ねたり、変形してみたいなのはやりません。

これからOpenCV使って画像データ扱いたい！という人の参考になれば幸いです。

# 環境・バージョン
AnacondaのJupyterLabを使います。

OpenCVのバージョンは4.5.1です。

# 1. OpenCVのインストール


macなら

```
brew install opencv
```
の後に

```
$ pip install opencv-python
```
でうまくいくはず。Windowsの方やエラーが出たら...各自ググって頂きたく存じます(投げやり)

# 2. 画像を読み込む
まずは画像を読み込みましょう。使う画像は僕が櫻坂46の田村保乃ちゃんが好きなので、推しの尊い画像を使わさせて頂きます。
![20210904_212621.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/4fe3eff4-acd8-9b34-617a-c28002fd8149.jpeg)

とその前にopen-cvをimportしておきましょう。

```python
import cv2
```
インポートはこれでok
じゃ画像を読み込みましょう

```python:
# 画像パスの定義
image_path = 'sample.jpg'

# cv2.imread(画像のパス)でcv2に画像を読み込ませられる
img = cv2.imread(image_path)
```

簡単ですね。特に難しいところはないかと思います。
一応引数でグレースケール、カラーを変更できます。

```python
# imreadの第二引数を0にすることでグレースケール
img = cv2.imread(image_path, 0)

# 1にするとカラー画像：デフォルト
img = cv2.imread(image_path, 1)
```

# 2. 画像を表示する
推しの画像がちゃんと読み込めたか表示してみましょう。
ちなみに表示するなら`matplotlib`を使った方が楽なので`matplotlib`で表示します。

```python
import matplotlib.pyplot as plt

# plt.imshowで表示できる
plt.imshow(img)
```
表示結果⇩
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/db69f7ec-3c73-1ab2-94aa-5e4f95d29304.png)

ありゃ、表示はされたけど、なんか色がおかしいですね。
`matplotlib`での表示は楽なんですけど1点が注意が必要です。

というのもcv2.imreadで画像を読み込むとBGRモードで読み込まれます。ただ`matplotlib`はRGBモードで表示するので、こうした色成分がおかしくなっちゃうんですね。

そのため画像をRGBモードに変える必要があります。

```python
image_path = 'sample.jpg'
img = cv2.imread(image_path)

# cv2.cvtColorでカラー変更できる
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
```

第一引数に変えたい画像、第二引数にどう変えたいか書きます。`COLOR_BGR2RGB`はCOLOR(色)をBGR TO RGB(BGRからRGBに）ということですね。

それじゃ変えてない画像と表示し比べてみましょう。

```python
image_path = 'sample.jpg'
img = cv2.imread(image_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 描画領域の設定
fig = plt.figure(figsize=(8,8))

# BGR画像の表示
ax = plt.subplot(121)
ax.axis('off')
ax.imshow(img)
ax.set_title('BGR_mode')

# RGB画像の表示
ax2 = plt.subplot(122)
ax2.axis('off')
ax2.imshow(img_rgb)
ax2.set_title('RGB_mode')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/492ab2e9-6dab-4cb6-7194-d8454b6de7eb.png)

正しく表示されていますね。この1点だけ注意しておけば怖くないです。

### ※おまけ：BGRからRGBへの変換の別のやり方

```python
image_path = 'sample.jpg'
img = cv2.imread(image_path)

# cv2.splitでチャネルごとに分けてアンパック代入できる
b,g,r = cv2.split(img)

# cv2.mergeでrgbの順番で結合
img_rgb = cv2.merge([r,g,b])
```

お好きな方でいいと思います。

### ※さらにおまけ:グレースケール表示に関して

先ほど`cv2.imread(image_path,0)`でグレースケールで読み込めると言ったので、グレー画像も表示してみようと思います。

```python
image_path = 'sample.jpg'
img_gray = cv2.imread(image_path,0)

plt.imshow(img_gray)
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/f00d06cf-6089-459b-8ff2-de9d9051d822.png)

なんか変ですね。これもさっきと一緒でグレースケールの画像を`matplotlib`が勝手にRGBモードで表示してるので緑っぽい感じになっちゃうと。

こういう時はmatplotlib側をいじりましょう。

```python
# cmapを'gray'にすることでグレー画像として表示できる
plt.imshow(img_gray, cmap='gray')
```
すると
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/6f0a3ab3-d078-0f22-dc7b-80e6d878713f.png)

ちゃんとグレーで表示されてますね。~~白黒でもかわいい保乃ちゃん~~

# 3. 画像を拡大・縮小する
モデル学習用に画像データを用意するときには画像サイズを揃えることは大事です。

今回は256 × 256にリサイズします。

```python
image_path = 'sample.jpg'
img = cv2.imread(image_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# リサイズ前のサイズ（高さ、幅、チャネル数）
img_rgb.shape
# -> (1102, 1478, 3)

# cv2.resizeでリサイズ
resize_img = cv2.resize(img_rgb, (256, 256))

# リサイズ後のサイズ
resize.shape
# -> (256, 256, 3)
```
リサイズ後の画像
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/2cf2e0ef-c1e3-1f80-e624-edead52620d0.png)

`cv2.resize(リサイズしたい画像, (高さ、幅))`で指定サイズにリサイズできます。

# 4. （応用）顔部分を検出する
まぁ人の画像を扱うなら顔の部分だけを切り抜くことは多いと思います。ここでは他の記事でもたくさん使われているカスケード分類器を使って顔部分を検出、切り抜きまでやってみようと思います。


（宣伝）自分がカスケード分類器を使って櫻坂メンバーの顔分類をやった記事もあるのでよければ
→[櫻坂46メンバーの顔を分類するアプリケーションをStreamlitで作りました。](https://qiita.com/okateru/items/6f9daf1094ef8c2d6d68)

## 4.1 カスケード分類の用意
[コチラ](https://github.com/opencv/opencv/tree/master/data/haarcascades)から分類器をDLしましょう。

今回は`haarcascade_frontalface_alt2.xml`を使います。

## 4.2 カスケード分類器を適用
```python
# カスケード分類器がグレー画像を要求するのでグレースケールで読み込む
image_path = 'sample.jpg'
img = cv2.imread(image_path,0)

# カスケード分類器を表すパスを定義
cascade_path = 'haarcascade_frontalface_alt2.xml'

# カスケード分類器を読み込む
cascade = cv2.CascadeClassifier(cascade_path)

# 画像にカスケード分類器を適用させる
# scaleFactor: 顔検出する検索窓の拡大率
# minNeighbors: 近くにこの値以上の検出された矩形がないとNG。大きくすると精度は上がるが、検出されない可能性も高まる
# minSize: 検出する矩形の最小サイズ
face_list = cascade.detectMultiScale(img, scaleFactor=1.1,
                                             minNeighbors=2, minSize=(64, 64))

print(face_list)
# -> array([[581, 369, 308, 308]], dtype=int32)
```
どのカスケード分類器を使うかや、cascade.detectMultiScaleの各種パラメータに関しては[コチラ](http://workpiles.com/2015/04/opencv-detectmultiscale-scalefactor/)が参考になります。


無事検出できているとface_listの中に顔の部分の座標情報が格納されています。

それぞれ[四角形の左上の角の座標のx座標, 四角形の左上の角の座標のx座標, 四角形の横の幅, 四角形の縦の幅 ]
を表します。

雑なイメージ図です⇩

![無題のスプレッドシート.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/d08c1227-269f-3f62-5a8b-e192def75d46.jpeg)

それでは顔部分を表す四角形の部分だけ切り抜いてみましょう。

```python
image_path = 'sample.jpg'
img = cv2.imread(image_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 顔部分の左上の座標、幅、高さをアンパック代入
x,y,w,h = face_list[0]

# 縦は左上の座標から高さを足した値まで
# 横は左上の座標から幅を足した値までをスライスで指定すればok
face_img = img_rgb[y:y+h, x:x+h]

plt.imshow(face_img)
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/2aa7cf23-419e-cde7-3b96-8bb386e39105.png)

ちゃんと顔部分だけ表示できていますね。~~かわいすぎるな...~~

### おまけカラーでもやってみる
基本はグレースケール画像を要求するのですが、別にカラーでもエラーは出ないので検出される座標が違うのかやってみます。

```python
image_path = 'sample.jpg'
img = cv2.imread(image_path)
img_gray = cv2.imread(image_path, 0)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

cascade_path = 'haarcascade_frontalface_alt2.xml'
cascade = cv2.CascadeClassifier(cascade_path)

plt.figure(figsize=(8,8))
index = 1
for image in [img_gray, img_rgb]:
    face_list = cascade.detectMultiScale(image, scaleFactor=1.1,
                                             minNeighbors=2, minSize=(64, 64))
    x,y,w,h = face_list[0]
    face_img = image[y:y+h, x:x+h]
    plt.subplot(1,2,index)
    if index == 1:
        plt.imshow(face_img, cmap='gray')
    else:
        plt.imshow(face_img)
    plt.title(face_list[0])
    index += 1
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/6a67a81f-eda6-2dcf-ad08-b69f59ff2f09.png)

検出はできているけど、検出座標は違うようですね。

# 5. 画像の保存
せっかく顔部分だけ切り抜いたので画像の保存もしておきましょう。

```python
# cv2.imwriteで保存
cv2.imwrite('hono_face.jpg', face_img)
```
第一引数がファイル名、第二引数が保存したい画像。

さてここでも注意が必要です。
cv2.imwriteも画像をBGRモードで保存しちゃいます。

face_imgはRGBモードの画像です。これがBGRモードになるので色が変なまま保存されます。

なので`img = cv2.imread(image_path)`のBGRモードの画像に対して顔検出して、その画像を保存すればokです。

何度も言いますが、BGRモードなのかRGBモードなのかは要注意ポイントです。

# まとめ

一通り、画像データを扱うときの主な処理をまとめてみました。参考になれば幸いです。