画像をマスキングするためのnumpyのTips

OpenCVでカラー画像を読み込み、グレースケール、二値化処理、というながれでマスク画像を作ります。

# 1. 画像読み込み

OpenCVで画像を読み込みます。
OpenCVはBGR(Blue/Green/Red)カラーモデル(3色のチャネルの順番の話です)でデータを読み込みますが、
一般的にはRGBカラーモデルであり、また`matplotlib.pyplot`の表示はRGBカラーモデルのため、オリジナルの画像と色合いを合わせるためにRGBカラーモデルへの変換を行います。

```python
>>> import cv2  # 読み込みOpenCVライブラリ
>>> import matplotlib.pyplot as plt # 出力

>>> img = cv2.imread('sample.JPG')
>>> img.shape
(240, 400, 3)

>>> rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
>>> plt.imshow(rgb_img)
>>> plt.show()
```
`rgb_img`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/993f281b-d7c2-759a-2597-b5df8b621c24.png)

# 2. グレースケール処理

(RGB)カラー画像は1ピクセルごとに3層のパラメータにより色合いが決まっています。
グレー画像に変換すると1ピクセルごとに1つのパラメータにより色合いが決まります。
ではカラー画像からグレー画像へ変換してみます。

```python
>>> gray_img = cv2.cvtColor(rgb_img,cv2.COLOR_RGB2GRAY)
>>> plt.imshow(gray_img)
>>> plt.show()
```
`gray_img`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/d226e11c-5d6d-ae95-720f-da63682a1b1c.png)

# 3. 二値化処理

グレー画像のパラメータについて、基準値を設定して、1ピクセルごとに0か1に変換します。
では適当に基準値を設定してグレー画像からバイナリー画像へ変換してみます。

```python
>>> bin_threshold = 175
>>> ret, bin_img = cv2.threshold(gray_img ,bin_threshold, 1, cv2.THRESH_BINARY_INV)
>>> bin_img
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [1, 1, 1, ..., 1, 1, 1],
       [1, 1, 1, ..., 1, 1, 1],
       [1, 1, 1, ..., 1, 1, 1]], dtype=uint8)
>>> plt.imshow(bin_img)
>>> plt.show()
```
`bin_img`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/e7420bed-9591-f01d-e4ec-f25ae48a28df.png)



# booleanな配列・リストの0と1の反転

`boolean`な`numpy.array`や`list`の0と1の反転は、方法はいろいろありますが、次の方法がシンプルです。

「**1 - np.array(*対象の配列やリスト*)**」

for文でぐるぐるまわしたり、内包表記を使うに及びません。
numpyでスッキリかけます。

リストにしたい場合はこの結果に`tolist()`という関数で変換すればよいです。

## `boolean`な配列・リストの反転
例として簡単な配列、リストに対して上記方法により0と1を反転してみます。

```python
>>> import numpy as np

>>> array1 = np.array([1,0,0])
>>> array1_not = 1 - array1
>>> array1_not
array([0, 1, 1])

>>> array2 = np.array([[1,0,0],[0,1,0]])
>>> array2_not = 1 - array2
>>> array2_not
array([[0, 1, 1],
       [1, 0, 1]])

>>> list1 = [1,0,0]
>>> list1_not = (1 - np.array(list1)).tolist()
>>> list1_not
[0, 1, 1]

>>> list2 = [[1,0,0],[0,1,0]]
>>> list2_not = (1 - np.array(list2)).tolist()
>>> list2_not
[[0, 1, 1], [1, 0, 1]]
```



### バイナリー画像の反転

binary画像の反転も配列・リストと同じです。
サイズが変わろうが、行う方法は同じでシンプルです。

「**1 - np.array(*対象の配列やリスト*)**」

それでは、作成したバイナリー画像の0と1を反転してみます。

```python
>>> bin_img_not = 1 - bin_img
```

`bin_img_not`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/59ef15fb-b0db-b366-ee7f-674bf3bcfd8d.png)


# 画像のマスキングの方法

バイナリー画像のマスクイメージを作って、イメージに沿って情報をマスキングします。
必要な個所のみを切り取るCropではなく、同じサイズの画像から必要な情報のみを残す方法です。

## グレー画像のマスキング
グレー画像もバイナリー画像も同じ次元であり、バイナリー画像は0と1で構成されるため、
対応するピクセル同士の数値を掛け算してあげれば、簡単にマスキングできます。
`numpy.array` で構成されるグレー画像、バイナリー画像であれば、その掛け算も簡単にできます。

作成したバイナリー画像をマスクイメージとしてグレー画像からマスキングを実施してみます。

```python
>>> gray_img_masked = gray_img * bin_img
>>> gray_img_masked_not = gray_img * (1 - bin_img)
```

`gray_img_masked`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/d1d070dd-4d3b-ede6-e26e-0d5579bea6d7.png)

`gray_img_masked_not`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/c79ed002-7bb4-3b3c-8f64-2e98f1439db8.png)


## カラー画像のマスキング

同じピクセル数でもRGBカラーモデルでは、1ピクセルごとに3層分のデータを保有するため、バイナリー画像で単純に掛け算するにはひと工夫必要です。
`numpy.stack` を使って、層の数を合わせることができます。

これにより同じ値のマスクイメージ(バイナリー画像)を3層もつ状態が表現できます。
カラー画像とこの3層に対応したマスクイメージを掛け合わせるとカラー画像のマスキングができます。

それではカラー画像のマスキングを実施してみます。

```python
>>> rgb_img_masked = rgb_img * np.stack([bin_img]*3, axis=2)
>>> rgb_img_masked_not = rgb_img * np.stack([1 - bin_img]*3, axis=2)
```

`rgb_img_masked`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/e444b618-2adb-3c9c-a2c2-9fee5990222d.png)|

`rgb_img_masked_not`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188272/95ea1796-d212-7a58-0848-9da4e81e6969.png)


(参考)
カラー画像のマスキングについて
OpenCVで `cv2.bitwise_and` という関数を使っても同じ結果が得られます。
`cv2.bitwise_and(img, img, mask=bin_img)`


# まとめ
- `numpy`で`boolean`な配列やリストの0と1の反転が簡単にできる！
 「**1 - np.array(*対象の配列やリスト*)**」

- `numpy`で画像のマスキングが簡単にできる！
 「**画像×マスクイメージ**」
　ただし、マスクイメージは、バイナリー画像で画像のチャネルと合わせる工夫が必要