顔のパーツ検出を利用し、顔写真をちいかわにする

この記事は [OpenCV Advent Calender](https://qiita.com/advent-calendar/2022/opencv) 20日目の記事です。

こんにちは。Webバックエンドエンジニアと、フォントクリエイターを名乗っております @irimo と申します。
今回は OpenCV アドカレに初参加させていただき、緊張しております。

昨日は @ki_230 さんの[記事](https://blog.kimizuka.org/entry/2022/12/19/183707)でした。
OpenCV.jsを利用したリアルタイムビデオ処理！
なんでもできそうですが、ネタが浮かばない... でも、いろいろアイデア出ししてみたいところです。


# TL;DR

今回は「顔検出」と「ちいかわ」がテーマです。
顔写真をちいかわちゃんにするのがゴールになります。

このマツコ・デラックスさん（[参照元](https://naturaleight.co.jp/matsuko/)）の写真を顔検出させて加工すると、
![001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/deb206ee-fa83-8aea-7990-b9c5eb94aa52.jpeg)
こうなりました。
![ck.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/b7bc87be-e4c0-7014-01c6-6124aeb73bbd.png)

最近はOpenCV以外の顔検出のライブラリが充実しており、OpenCV縛りだとこれが限界になります。
顔検出ではあまり OpenCV はメジャーじゃないのかと思います。
商用ツールの顔認識は、自前で実装（修正）してるのだとわかりました。

口の検出は残念ながらありません。そのため、今回は適当に貼りました。
（別のライブラリでリベンジしたいところ）


## やった

- 輪郭、目　の検出
- 画像の回転・合成
- 回転率を合わせる
- 貼り付け位置が不自然にならないように調節

## 宿題

- 輪郭画像の透明部分が変になっている
- 口の検出がないとはいえ、場所が適当になっている


# 顔検出について

OpenCVには「顔検出」の機能があります。
人間を読み込ませると、顔のパーツを四角形で返してくれるものです。

今回は一人が映っている写真の「顔の輪郭」「目」の検出をしました。

目の検出の教師データは「右目」「左目」と分かれているバージョンもありますが、厳密でないみたいなので「目」の教師データを使用しました。
この「目」の検出は、一人の写真なのになぜか目が3つ以上検知されます。判定のつきやすいものから検出していますので、前2つの四角を取得すれば、二つの目が検出できます。
ちなみに、「右目」は2つ、「左目」も2つ検出されます。こちらも一番前の四角を適用すると良いです。

```python
class square_reco:
    img_gray = ""
    xml_path = ""
    xml_base_dir = "/usr/local/lib/python3.7/site-packages/cv2/data/"
    def __init__(self, img_gray, xml_filename):
        self.img_gray = img_gray
        self.xml_path = self.xml_base_dir + xml_filename
    def reco(self):
        classifier = cv2.CascadeClassifier(self.xml_path)
        targets = classifier.detectMultiScale(self.img_gray)
        return targets

class face_reco(square_reco):
    xml_face = "haarcascade_frontalface_default.xml"
    def __init__(self, img_gray):
        print("face_reco init")
        super().__init__(img_gray, self.xml_face)

class eyes_reco(square_reco):
    filename = "haarcascade_eye.xml"
    def __init__(self, img_gray):
        print("eyes_reco init")
        super().__init__(img_gray, self.filename)


law_path = "./images/idol/001.jpg"
img_origin = cv2.imread(law_path)
img_gray = cv2.cvtColor(img_origin, cv2.COLOR_BGR2GRAY)

face = face_reco(img_gray)
# targets は、二次元の配列（4要素 x, y, width, height）* 検出された分
targets = face.reco()
```

# 回転について

[こちら](https://qiita.com/mo256man/items/9f4b93dcff0902cb2047)を参考に、透明部分のある画像を回転させました。

# パーツの拡大率について

ちいかわの画像を模写、自前で製作し、パーツ分けをしました。
![ちいかわ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/e7cfcfd9-5604-e36f-9d38-86d851fbe086.png)


元パーツの比率度外視で、顔検出で検出された四角にそのままパーツをあてはめると、パーツの形が変わり、縮小率も違って不自然な画像になります。
ですので、一番縮小率の低い「輪郭パーツ」の縮小率に全てのパーツの縮小率を合わせました。

縮小率を考えていない画像と、考えた画像を比べました。不自然さが少しとれたと思います。回転させていない状態です。
![ck顔と目の比率が’違ってろんぱってる.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/efe3dc46-801a-e943-22ff-8b708d0e7864.png)
顔と目の比率が違う

![ck目の縮小率を合わせたがろんぱってる.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33738/8d6fe518-a3a3-1002-8d35-aca83109642b.png)
顔と目の縮小率を合わせた



# おわりに

もっと「あなたに似たちいかわ」になるようにしたかったですが、顔のこだわりが少ないという性格ですので、あまり凝れませんでした。申し訳ありません。
改めて顔の仕事してる方々に敬意を示します。ですが、軽く挑戦できて面白かったです。

今回のリポジトリはこちらです。

https://github.com/irimo/chiikawaface


明日の担当は @Kazuhito さんです！