AIが顔認識して自動でスタンプ貼るアプリ作って大儲けする

f:id:phorizon20:20190719010018j:plain

作ってみました。

※アプリ化までは出来てません

動作環境

動作環境・ライブラリ等は以下の通り。

Python 3.6.8

opencv-python==3.4.3.18
dlib==19.17.0

中間生成物も含め、フォルダ構成は大体こんな感じです。

├── data
│   ├── img
│   │   └── Green11_kessai20141123152655_TP_V.jpg
│   ├── json
│   │   └── Green11_kessai20141123152655_TP_V.json
│   └── stamps
│       ├── 01.png
│       └── 02.png
├── prog
│   ├── detector.py
│   ├── predictor
│   │   └── shape_predictor_68_face_landmarks.dat
│   └── stamper.py
└── result
    └── Green11_kessai20141123152655_TP_V._stamped.jpg

インタフェースはくそ雑設計です。data/img/にある1つ目の画像に対して ①顔認識 ②スタンプ合成 を実行します。

画像はぱくたそEmojipediaからお借りしています。

元画像:

Green11_kessai20141123152655_TP_V.jpg f:id:phorizon20:20190719003718j:plain

スタンプ:

01.png f:id:phorizon20:20190719003744p:plain

02.png f:id:phorizon20:20190719003755p:plain

コード

つべこべ言わずにソースコード

まずはdetector.py

# coding:utf-8

import json
import math
import os

import cv2
import dlib


def main():
    ## input path and read image
    img_path = "../data/img/" + os.listdir("../data/img/")[0]
    img_filename = os.path.basename(img_path)
    img_ext = img_filename.split(".")[-1]

    img = cv2.imread(img_path, cv2.IMREAD_COLOR)
    if img is None:
        print("Could not read input image")
        exit()


    ## instance detector/predictor
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor("predictor/shape_predictor_68_face_landmarks.dat")


    ## detect faces
    faces = detector(img, 1)
    if len(faces) == 0:
        print("Could not detect any faces")
        exit()


    ## compute face properties
    face_properties = {}
    for i, f in enumerate(faces):
        ## get 68 points
        points = predictor(img, f).parts()

        ## Left/Right temples
        t_L = points[0]
        t_R = points[16]

        ## calc properties
        face_width = math.sqrt((t_L.x - t_R.x)**2 + (t_L.y - t_R.y)**2)
        face_center = [(t_L.x + t_R.x)/2, (t_L.y + t_L.y)/2]
        degree_acw = -1 * math.degrees(math.atan2((t_R.y - t_L.y), (t_R.x - t_L.x)))

        ## add result
        face_properties[i] = {
            "stamp": "01.png",
            "center": face_center,
            "width": face_width,
            "angle": degree_acw
        }

    ## save
    with open("../data/json/" + img_filename.replace(img_ext, "json"), "w") as f:
        json.dump(face_properties, f, ensure_ascii=False, indent=4, separators=(',', ': '))


if __name__ == "__main__":
    main()

data/img/にある画像を読み込み、AIが顔認識して、顔の ①座標 ②幅 ③傾き をjson形式で出力します。

今回の画像に対してはこんな感じの結果を出力します。

{
    "0": {
        "stamp": "01.png",
        "center": [
            158.0,
            334.0
        ],
        "width": 113.03096920755833,
        "angle": 13.29857033049428
    },
    "1": {
        "stamp": "01.png",
        "center": [
            374.0,
            106.0
        ],
        "width": 82.46211251235322,
        "angle": -14.036243467926479
    },
    "2": {
        "stamp": "01.png",
        "center": [
            1172.0,
            134.0
        ],
        "width": 92.04890004774636,
        "angle": -17.056965576617063
    },
    "3": {
        "stamp": "01.png",
        "center": [
            1314.0,
            333.0
        ],
        "width": 109.65856099730654,
        "angle": 9.977712620150575
    }
}

この結果を元に次のstamper.pyが、data/stamps/にあるスタンプ画像を使って合成します。jsonを直いじりすれば、誰の顔にどのスタンプを使うかを変更することも出来る親切設計です。

次にstamper.py

# coding:utf-8

import json
import os

import cv2


def paste(img_back, img_front, center_xy):
    ## itiou
    img_ret = img_back.copy()


    ## set params
    c_x, c_y = center_xy
    f_h, f_w = img_front.shape[:2]


    ## get roi/mask
    roi = img_back[int(c_y-(f_h/2)):int(c_y+(f_h/2)), int(c_x-(f_w/2)):int(c_x+(f_w/2))]
    _, mask = cv2.threshold(cv2.cvtColor(img_front, cv2.COLOR_BGR2GRAY), 10, 255, cv2.THRESH_BINARY)
    mask_inv = cv2.bitwise_not(mask)

    img_back_bwa  = cv2.bitwise_and(roi, roi, mask=mask_inv)
    img_front_bwa = cv2.bitwise_and(img_front, img_front, mask=mask)


    ## paste
    dst = cv2.add(img_back_bwa, img_front_bwa)
    img_ret[int(c_y-(f_h/2)):int(c_y+(f_h/2)), int(c_x-(f_w/2)):int(c_x+(f_w/2))] = dst

    return img_ret


def main():
    ## read image
    img_path = "../data/img/" + os.listdir("../data/img/")[0]
    img_filename = os.path.basename(img_path)
    img_ext = img_filename.split(".")[-1]

    img = cv2.imread(img_path, cv2.IMREAD_COLOR)
    if img is None:
        print("Could not read input image")
        exit()


    ## read properties(json)
    with open("../data/json/" + img_filename.replace(img_ext, "json")) as f:
        face_properties = json.load(f)


    ## stamp
    for f_id, f_prop in face_properties.items():
        ## read stamp image
        img_stamp = cv2.imread("../data/stamps/" + f_prop["stamp"])

        ## rotate
        s_h, s_w = img_stamp.shape[:2]
        s_center = (int(s_w / 2), int(s_h / 2))
        M = cv2.getRotationMatrix2D(s_center, f_prop["angle"], 1.0)
        img_stamp = cv2.warpAffine(img_stamp, M, (s_w, s_h))

        ## paste
        img = paste(img, img_stamp, center_xy=f_prop["center"])


    ## save
    cv2.imwrite("../result/" + img_filename.replace(img_ext, "_stamped."+img_ext), img)


if __name__ == "__main__":
    main()

処理は至ってシンプルで、元画像とdetector.pyが出力したjsonを読み込んで、顔それぞれに対してスタンプ画像を顔の傾きに合わせて回転させて合成するだけです。

結果

大体こんな感じ。

Green11_kessai20141123152655_TP_V._stamped.jpg f:id:phorizon20:20190719005543j:plain

以上!