データクレンジング3　OpenCVの利用と画像データの前処理

[Aidemy](https://aidemy.net/)　2020/9/30
#はじめに
　こんにちは、んがょぺです！文系大学生ですが、AI分野に興味が湧いたのでAI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます！
　今回は、データクレンジングの３つ目の投稿になります。どうぞよろしくお願いします。

＊本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。

今回学ぶこと
・画像データについて
・OpenCV（画像を扱うライブラリ）でできること

#1 画像データについて
##コンピュータ上の「色」
・データ上、色は__RGBデータ__として、赤、緑、青で表現される（255,0,255 など）。
・画像は点__（ピクセル）__の集まりでできている。
・1つのピクセルを表すための色要素の数を__チャンネル数__という（RGBなら三色なので「3」）。

##画像データの種類
・BMP　圧縮不可でサイズ大
・JPG　高圧縮可だが解凍不可
・PNG　圧縮解凍可　透過処理可
・GIF　アニメをサポート　透過処理可

#2 OpenCV
##OpenCVで画像の読み込み、表示
・OpenCVは画像を扱う際に使われるライブラリ。cv2をインポートして使う。
・__cv2.imread("ファイル名")__で読み込み
__cv2.imshow("ウィンドウ名",読込済みの画像データ)__で表示（ウィンドウ名は自由に付けてOK）

```python
import cv2
#sample.jpgというファイル名の画像を読み込む
img = cv2.imread("./4050_data_cleansing_data/sample.jpg")
#ウィンドウ名をwindowにしてimgを出力
cv2.imshow("window",img)
```

##画像の作成と保存（単色）
・画像を作成するには、__np.array()関数__（NumPyの行列を作る関数）を使う。また、range()を変数なしで多重ループさせて、縦横のピクセル情報（順番は青緑赤）を設定する。
__・np.array([[[B,G,Rの値]for _ in range(横のサイズ)] for_ in range(縦のサイズ)],dtype="uint8")__
*uint8型とは、int型の中でも０〜255の値しか取らないもの。
・画像の保存は__cv2.imwrite("ファイル名",画像)__で行う。

```python
#(512*512)の一面緑(0,255,0)の画像を作成
img = np.array([[[0,255,0]for _ in range(512)]for _ in range(512)],dtype="uint8")
#imgを保存(ファイル名は「green.img」)
cv2.imwrite("green.img",img)
```

##トリミング（切り取り）、リサイズ（拡大・縮小）
・トリミングは__画像データ[y軸始点:y軸終点,x軸始点:x軸終点]__　0は左上
・リサイズは__cv2.resize(画像データ,(幅,高さ))__で変更。

```python
#トリミング用に画像サイズを取得(高さ,幅,色数)
size = img.shape #(1000,667,3)
#高さが1/2、幅が1/3になるようトリミング（始点を指定せず、余りが出ないように割る）
new_img=img[:size[0]//2,:size[1]//3]
#高さ2倍、幅3倍にリサイズ
new_img=cv2.resize(new_img,(new_img.shape[0]*2,new_img.shape[1]*3))
```

##回転・反転
・画像の回転にはアフィン変換という変換が必要であり、変換行列を取得しなければならない。
・__cv2.getRotationMatrix2D(画像の中心座標,回転角度,拡大縮小の倍率)__で変換行列を取得する。
・そして__cv2.warpAffine(画像,変換行列,出力サイズ)__で実際に回転させる。

・反転は__cv2.flip(画像,flipCode)__で行う。第二引数には、「0」と指定すれば上下反転、正の数を指定すれば左右反転、負の数を指定すれば上下左右反転となる。

```python
#変換行列の取得(倍率2倍で90度回転)
mat=cv2.getRotationMatrix(tuple(np.array([img.shape[1],img.shape[0]])/2),90,2.0)
#実際に回転
cv2.warpAffine(img,mat,img.shape[::-1][1:3])
```

##色調変換・色反転
・今までは「BGR色空間」しか使っていなかったが、OpenCV上には他の色空間も存在する。
・__cv2.cvtColor(画像,変換コード)__で色空間を変更できる。変換コードは、例えばBGR色空間をLabという色空間に変換したい時は「cv2.COLOR_BGR2LAB」となる。

・色反転は、各ピクセルを順番に取り出して、値xについて「255-x」とすることで行うこともできるが、
__cv2.bitwise_not(画像)__で簡単に反転できる。

```python
#imgの色空間をLabに変更
c_img=cv2.cvtColor(img,cv2.COLOR_BGR2LAB)
#imgの色を反転
r_img=cv2.bitwise_not(img)
```

#OpenCVの応用
##閾値処理
・画像の容量を小さくするために、色のデータを「白」「黒」のみにする。
・__cv2.threshold(画像,閾値,最大値（濃度）,閾値処理の種類)__
＊閾値より大きいか小さいかでデータを分ける。その分け方を第４引数に指定する。

```python
#閾値127、最大値255、種類はcv2.THRESH_TOZERO(閾値以下は0、閾値以上は変更なし)
new_img=cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
```

##マスキング
・白黒のマスク画像を別途準備し、cv2.bitwise_and()関数で元の画像と合わせると、元の画像のうち、マスク画像の白の部分しか出力されなくなる。これを「マスキング」という。
・__cv2.bitwise_and(元の画像1,元の画像2(マスク時に使用),mask=マスク用の白黒画像)__

```python
#マスク用画像を読み込み（チャンネル数1の白黒画像）、リサイズして大きさを揃える。
mask=cv2.imread("./4050_cleansing_data/mask.png", 0)
mask=cv2.resize(mask,(img.shape[1],img.shape[0]))
#マスキングする
cv2.bitwise_and(img,img,mask=mask)
```

##画像をぼかす
・画像をぼかすには「ガウシアンフィルタ」を使う。1ピクセルの周りn*n(nは奇数)を平均化してぼかす。
・__cv2.GaussianBlur(画像,(n*nの値),標準偏差)__
＊標準偏差は通常0。n*nの値と標準偏差が大きくなるほどぼかしが強くなる。

```python
#(51*51)のぼかしを行う
new_img=cv2.GaussianBlur(img,(51,51),0)
```

##ノイズ（荒）の除去
・__cv2.fastNlMeansDenoisingColored(画像)__で行う。カラー画像でなければ関数名の「Colored」は抜いて良い。(NlMeansとは、Non-local Means Filterというノイズ除去のフィルターを指す、Denoisingはノイズを除去するという意味)

```python
new_img=cv2.fastNlMeansDenoisingColored(img)
```

##収縮・膨張
・ノイズ処理の別の方法として、画像を一度収縮してから再度膨張させる方法がある。この方法は「閾値処理」のノイズ除去に使われることが多い。
・__cv2.dilate(画像,フィルタ)__　で膨張、__cv2.erode(画像,フィルタ)__　で収縮。

```python
#imgを閾値処理
new_img=cv2.threshold(img,127,255,cv2.THRESH_BYNARY)
#フィルタの定義
filter=np.array([[0,1,0],[1,0,1],[0,1,0]],np.uint8)
#収縮して膨張
new_img=cv2.erode(new_img,filter)
new_img=cv2.dilate(new_img,filter)
```

#まとめ
・画像データはOpenCVで処理する。
・OpenCVでは、__imread()__で画像読み込み、__imshow（）__で画像出力、__resize（）__でリサイズ、__flip（）__で反転、**bitwise_not**で色反転ができる。その他、画像作成やトリミング、回転などもできる。
・応用として、__threshold()__で閾値処理、**bitwise_and()**でマスキング、__GaussianBlur()__でぼかしを行う。その他、ノイズを除去することもできる。