Python(OpenCV) 顔が上を向くように写真から切り出す方法

さてさて、この間このブログでElectron + Python で選んだ顔にモザイクをかけるアプリ(ダウンロード可)という記事を公開しました。

最近では機械学習の向上によって画像認識の精度がとてもよくなってきた結果、機械学習自体に精通していない我々でも簡単にこういったアプリがつくることができるようになってきました。

そのため、今回はこの顔認識を使って、もう少し工夫したプログラムをPythonでつくってみます。

テーマはずはり、「顔が上を向くように写真から切り出す」です。

ぜひ学習の参考にしてくださいね。
(記事の最後でソースコードをダウンロードできます)

※ 開発環境: Python 2.7

やりたいこと

例えば次の写真には顔が5つあります。

もちろん、冒頭で紹介した方法でこの5つの顔を検出することができるのですが、これらの顔はすこしずつ傾いていて上を向いていません。特に左下の女の子は、左30度ぐらい傾いています。

そこで、今回は顔が上を向くように回転させ、次のように画像を切り出してみます。

※ 分かりやすいので、一番顔の傾きが大きい左下の女の子に注目してコードを説明していきます。

準備

顔の検出には次のライブラリが必要になります。
もしまだインストールしていない場合は、別途インストールしておいてください。

  • OpenCV
  • dlib
  • numpy

※ インストールは、Electron + Python で選んだ顔にモザイクをかけるアプリ(ダウンロード可)を参照して下さい。

そして、今回は顔の傾きを計算するために、両目の座標を取得する必要があります。そのため、顔のポイント68ヶ所を取得できるモデル “shape_predictor_68_face_landmarks.dat”dlibのファイルページからダウンロードして展開しておいてください。

http://dlib.net/files/

※ ファイル名は、shape_predictor_68_face_landmarks.dat.bz2です。
※ 展開後100MB近くになるのでダウンロードに少し時間がかかるかもしれません。

では次から実際に開発をしていきましょう!

上向きの画像を切り出す

ライブラリを読み込む

まずは必要なライブラリを読み込みます。
(日本語を使いたいのでutf-8が使えるようにしています)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import cv2
import dlib
import numpy as np
import math

顔検出とポイント検出のインスタンスを取得する

それぞれ、dlibdetectorpredictorをつくっておきます

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('***/shape_predictor_68_face_landmarks.dat')

ちなみに、役割は次のとおりです。

  • detector ・・・ 顔がある位置を取得する
  • predictor ・・・ 目や鼻など68ヶ所の座標を取得する

画像をOpenCVで読み込む

切り出したい顔が写っている画像をOpenCVで読み込みます。

im = cv2.imread('***/family.jpg', cv2.IMREAD_COLOR)

if im is None:
    print('画像が見つかりません')
    exit()

※ もし画像が見つからない場合は終了します。

detectorで顔を検出する

続いて、detectorで顔の位置を検出します。

rects = detector(im, 1)

if len(rects) == 0:
    print('顔が抽出されませんでした')
    exit()

なお、取得された顔はdlibrectanglesとして取得できます。

※ もし検出された顔が見つからない場合は終了します。

顔を切り出す

少し長くなりますが、全てループ処理の中にあるのでまずはコードです。

for index,rect in enumerate(rects):

    # 顔だけ切り出す
    rectWidth = rect.width()
    rectHeight = rect.height()
    rectCenter = rect.center()
    x_start = rectCenter.x - rectWidth
    x_end = x_start + rectWidth*2
    y_start = rectCenter.y - rectHeight
    y_end = y_start + rectHeight*2
    face_im = im[y_start:y_end, x_start:x_end]

    # 顔の角度を修正
    points = []
    for point in predictor(im, rect).parts():
        points.append([int(point.x), int(point.y)])
    x_diff = points[45][0] - points[36][0]
    y_diff = points[45][1] - points[36][1]
    angle = math.degrees(math.atan2(y_diff, x_diff))
    rotated_im = fitting_rotated_image(face_im, angle)

    # 回転後の画像で顔検出して画像保存
    rotated_rects = detector(rotated_im, 1)
    if len(rotated_rects) == 0:
        print('顔が抽出されませんでした')
        continue

    rotated_rect = rotated_rects[0]
    x_start = rotated_rect.left()
    x_end = rotated_rect.right()
    y_start = rotated_rect.top()
    y_end = rotated_rect.bottom()
    cropped_im = rotated_im[y_start:y_end, x_start:x_end]
    cv2.imwrite('face_'+ str(index) +'.jpg', cropped_im)

ではひとつずつ見ていきましょう。

顔だけ切り出す

まずは検出された顔部分だけを切り出します。
これは先に画像全体を回転させてしまうと、顔の座標が移動してしまうからです。

ただし、注意が必要なのは切り出す大きさです。
もし、検出された部分だけ切り出してしまうと後で回転させますので、必要な部分が欠けてしまうかもしれないからでs.

そのため、次のように検出された縦横ともに2倍の長さで切り出します。

顔の角度を修正

続いて、取得された顔の角度を修正します。
手順は次のとおりです。

  1. predictorで左右の目の座標を取得する
  2. その2つの座標から傾きを計算
  3. 傾きが0になるように顔画像を回転させる

目の位置は、points[45][36]に入っています。

なお、コードでは割愛しましたが、画像の回転も元のサイズと同じにしてしまうと必要な部分が欠けてしまうので、opencv の基本的な画像変形: 全12実例! の「回転する」で紹介した「ぴったり収まる回転」を応用しています。

def fitting_rotated_image(img, angle):
    height,width = img.shape[:2]
    center = (int(width/2), int(height/2))
    radians = np.deg2rad(angle)
    M = cv2.getRotationMatrix2D(center, angle, 1.0)

    new_width = int(abs(np.sin(radians)*height) + abs(np.cos(radians)*width))
    new_height = int(abs(np.sin(radians)*width) + abs(np.cos(radians)*height))

    M[0,2] += int((new_width-width)/2)
    M[1,2] += int((new_height-height)/2)

    return cv2.warpAffine(img, M, (new_width, new_height))

これで画像はこうなります。

回転後の画像で顔検出して画像保存

そして、最後にもう一度detectorを使って顔検出をし、取得された部分を切り出して保存しています。(こちらのほうが回転前のデータを修正して切り出すより精度が高いです)

こちらが実際の画像です。

    

ちなみに

detectorは顔を検出するため頭が少し欠けてしまうことが多いです。
そのため、そんな場合は次のように切り出す部分を大きくするといいでしょう。

例えば、こちらは1.5倍のサイズにする例です。

rotated_rect_center = rotated_rect.center()
x_start = int(rotated_rect_center.x - rotated_rect.width()*0.75)
x_end = int(x_start + rotated_rect.width()*1.5)
y_start = int(rotated_rect_center.y - rotated_rect.height()*0.75)
y_end = int(y_start + rotated_rect.height()*1.5)
cropped_im = rotated_im[y_start:y_end, x_start:x_end]

結果はこうなります。

お疲れ様でした!

ソースコードをダウンロードする

今回実際に使ったソースコードを以下からダウンロードすることができます。

※ ただし、shape_predictor_68_face_landmarks.datと画像はご自身で準備してください。

Python(OpenCV) 顔が上を向くように写真から切り出す

おわりに

ということで、今回はPythonOpenCVdlibなどを使って顔が上を向くように画像を切り出す方法をお届けしました。

このように、画像の世界では機械学習の成果が大きくなってきていますが、ここまで簡単に実行できるようになったことにはホントに驚くばかりです。

また、今回は人の顔を検出しましたが、dlibのダウンロードページには犬を検出するモデルや車を検出するものまであるようです。まだ試してみたことはないですが、そのうち時間を見つけてやってみたいと思います。

ぜひご期待下さいね。

ではでは〜!