python + OpenCVで画像を加工④ 画像の顔を検出してモザイクをかける

[前回の記事](https://qiita.com/HIROKOBA/items/92c6b8584b615b7ec401)と[前々回の記事](https://qiita.com/HIROKOBA/items/c28cf419c2214a1a4e2f)でそれぞれ画像の中から顔を検出し、モザイクをかけることができるようになりました。

今回は画像の中から顔を検出し、検出した顔の周囲にのみモザイクをかける処理をやってみようと思います。

最終的な完成コードはこちらになります。

```python
#写真の中の顔を検出して自動でモザイクをかける処理
import cv2
 
cascade_file= "haarcascade_frontalface_default.xml"
clas = cv2.CascadeClassifier(cascade_file)
img = cv2.imread("photo.jpg")

#写真の中の顔をすべて検出して周囲の四角形の座標を取得
#検出できた顔のすべてについて[四角の左上のx座標、四角の左上のy座標、幅、高さ]の数値を取得
face_list = clas.detectMultiScale(img, scaleFactor = 1.1, minSize=(30,30))

#モザイク処理
#[四角の左上のx座標、四角の左上のy座標、幅、高さ]をそれぞれ変数x, y, w, hに代入
for x, y, w, h in face_list:
    face= img[y:y+h, x:x+w]
    small_pic = cv2.resize(face, (8,8))
    mosaic = cv2.resize(small_pic,(w,h))
    img[y:y+h, x:x+w]=mosaic

cv2.imshow("photo_processed.jpg",img)
cv2.waitKey()

cv2.destroyAllWindows()
```
####顔の部分だけモザイク処理
前回の記事までとかぶる内容は割愛するとしまして、今回の肝は下記の部分です。

```python
for x, y, w, h in face_list:
    face= img[y:y+h, x:x+w]
    small_pic = cv2.resize(face, (8,8))
    mosaic = cv2.resize(small_pic ,(w,h))
    img[y:y+h, x:x+w]=mosaic
```

```python
for x, y, w, h in face_list:
```
検出した顔について①四角の左上のx座標、②四角の左上のy座標、③幅、④高さをそれぞれ変数x, y, w, hに代入しています。

```python
    face= img[y:y+h, x:x+w]
```
imread()で取り込んだ画像＝imgの中から、座標[y:y+h, x:x+w]の部分を切り取ってface変数に代入しています。座標というと[x, y]の順番に書いてしまいそうですが、OpenCVの場合yを先に記述することも多いので注意が必要です。ついでに言いますと、y座標は数値が増えるにつれて位置的に下に下がっていくので慣れるまで混乱しました。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634245/cee511a7-d357-820a-00a7-5e50466f4738.png)

座標を図式化すると、四角の左上は単純に[x, y]ですが、右下はそれぞれ幅と高さを足し合わせているので[x+w, y+h]になります。代入するときにはx座標とy座標が入れ替わります。

```python
    small_pic = cv2.resize(face, (8,8))
```
resize()を使って切り取った顔をいったんものすごく小さく加工してsmall_picに代入しています。resize()については[python + OpenCVで画像を加工①](https://qiita.com/HIROKOBA/items/d1b076bef22932be5eea)か[OpenCVのチュートリアル](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_gui/py_image_display/py_image_display.html)を参照していただけたらよいかと思います。

```python
    mosaic = cv2.resize(small_pic ,(w,h))
```
今度は切り取った写真の顔部分を元の大きさに戻しています。画像を小さくして画素数を減らし、画素数の少なくなった画像を無理やり大きいサイズに戻すのでいわゆるモザイクの状態になるということですね。(w, h)で切り取った画像の大きさを指定しているので、これで大きさは元に戻ります。

```python
    img[y:y+h, x:x+w]=mosaic
```
img[y:y+h, x:x+w]、つまり写真を切り取った場所にmosaic変数＝モザイクのかかった顔を当てはめています。

```python
    face= img[y:y+h, x:x+w]
    small_pic = cv2.resize(face, (8,8))
    mosaic = cv2.resize(small_pic ,(w,h))
    img[y:y+h, x:x+w]=mosaic
```
これで１人分の顔を切り取る→顔の周囲だけ縮小→元の大きさに戻す→切り取った場所に戻す、という作業をやっています。whileのループ処理で検出された顔の数だけこれを繰り返すことで、検出した顔のすべてにモザイク処理が施されるという理屈です。

```python
cv2.imshow("photo_processed.jpg",img)
cv2.waitKey()

cv2.destroyAllWindows()
```
加工した写真を表示して完了。ためしに妻と撮影した写真はこんな感じになりました。
![HiroMaki.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634245/5dce5837-93d0-a767-938c-c407345b9d23.jpeg)


```python
#写真の中の顔を検出して自動でモザイクをかける処理
import cv2

cascade_file= "haarcascade_frontalface_default.xml"
clas = cv2.CascadeClassifier(cascade_file)
img = cv2.imread("photo.jpg")

#写真の中の顔をすべて検出して周囲の四角形の座標を取得
#検出できた顔のすべてについて[四角の左上のx座標、四角の左上のy座標、幅、高さ]の数値を取得
face_list = clas.detectMultiScale(img, scaleFactor = 1.1, minSize=(30,30))

#モザイク処理
#[四角の左上のx座標、四角の左上のy座標、幅、高さ]をそれぞれ変数x, y, w, hに代入
for x, y, w, h in face_list:
    face= img[y:y+h, x:x+w]
    small_pic = cv2.resize(face, (8,8))
    mosaic = cv2.resize(small_pic,(w,h))
    img[y:y+h, x:x+w]=mosaic

cv2.imshow("photo_processed.jpg",img)
cv2.waitKey()

cv2.destroyAllWindows()
```
####平滑化(ぼかし)もやってみた
さらに、[前回の記事](https://qiita.com/HIROKOBA/items/92c6b8584b615b7ec401)で触れたblur()関数を使って画像をぼかす効果も使ってみました。モザイクの代わりに顔部分をぼかしてみます。

```python
#写真の中の顔を検出して自動で「ぼかし効果」をかける処理
import cv2
 
cascade_file= "cascade_classification.xml"
clas = cv2.CascadeClassifier(cascade_file)
img = cv2.imread("photo.jpg")
face_list = clas.detectMultiScale(img, scaleFactor = 1.1, minSize=(30,30))

for x, y, w, h in face_list:
    #平均化(ぼかし)処理
    face= img[y:y+h, x:x+w]
    img_blur= cv2.blur(face,(55,55))
    img[y:y+h, x:x+w]=img_blur

cv2.imshow("photo_processed.jpg",img)
cv2.waitKey()
cv2.destroyAllWindows()
```
![hiromaki_processed.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634245/19fb8a5a-574d-beed-a63d-9299ad817f28.jpeg)

ちょっとモザイクっぽい感じもしますが、、、平滑化については別のアルゴリズムで動く関数もあるようなので今後勉強しようと思います。

ちなみに、「モザイクやぼかしのかかった顔部分を切り取った場所に戻す」というのが理屈ではいまいちピンとこなかったのでテストしてみました。
![view.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634245/06230181-b632-5fc6-6b4c-6b85a46a8444.jpeg)
![curtain.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634245/7c43eaca-36c6-0694-7547-e93ea7a40765.jpeg)
下のカーテンの適当な部分を切り取って、上の風景写真の一部にはめ込んでみます。

```python
import cv2

view = cv2.imread("view.jpg")
curtain = cv2.imread("curtain.jpg")

#カーテンの画像を一部切り取る
curtain_rectangle = curtain[100:300, 200:400]

#切り取ったカーテンの画像を風景画像に挿入
view[100:300, 100:300] = curtain_rectangle

cv2.imwrite("view_curtain.jpg",view)
cv2.destroyAllWindows()
```
結果は・・・
![view_curtain.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634245/08e4ccc5-3a94-afb3-e864-20bb9641bbd7.jpeg)

個人的には、この結果を見てようやく理解できました。

今後は、動画の顔を検出してモザイク処理をやってみたいなと思っています。