Pythonを使って顔ランドマークで遊んでみよう

今回はPythonを使ったプログラミングをやってみます。ただの数値計算では面白くないので

  1. WebCAMを使って自分の顔をキャプチャ
  2. 顔検出
  3. 顔ランドマーク検出
  4. ランドマークを使って何かやる

という流れです。

使うパッケージ

この例では

  • OpenCV: 画像処理ライブラリ(cv2)
  • dlib: 機械学習ライブラリ

を使います。

1. WebCAMを使って自分の顔をキャプチャ

まず,OpenCV(cv2)とdlibを使う宣言をします。C言語の#includeみたいなもんです。 セルが緑色の状態(青だったらEnterを押す)でSHIFT+Enterを押して下さい。そうするとIn[?]となっているセル内のPython文が実行されます。 その際, Errorのようなメッセージが出なければ成功です。メッセージは英語ですが少し気合を入れれば読めます。


In [1]:
import dlib
import cv2

これでdlibとcv2が使えるようになりました。dlib.あるいはcv2.の後に関数名を付けることでそれぞれの機能を呼び出せます。早速WebCAMを使えるようにしましょう。


In [25]:
cap = cv2.VideoCapture(0)

カメラのタリーが光りましたか? 光らない場合は括弧の中の数字を1や2に変えてみて下さい。

次に画像をキャプチャします。カメラに目線を送りながら次のセルを実行しましょう。


In [29]:
ret, img = cap.read()

capはWebCAMを使うための操縦桿(ハンドル)と思って下さい。それにread(読め)と命令した訳です。では,成功したか確認しましょう。readという関数(機能)は成功したか否かの結果と,画像を返してくれます。


In [30]:
print(ret)


True

Trueと出ましたか? 出ていれば成功です。画像を見てみましょう。


In [43]:
cv2.imshow('image', img)
cv2.waitKey(2000)


Out[43]:
-1

自分の顔が出てきましたか? waitKey(2000)は2000ms待って終了する意味です。この2000を0にすると特別な意味になり,入力待ちになります。(ウィンドウを選択してアクティブな状態にしてから何かキーを押して下さい。Outに何か数字が出るでしょう。この数字はキーの認識番号とでも思って下さい。)

カメラが暗い場合はもう一度cap.read()のところを実行してみてください。


In [32]:
cap.release()

2. 顔検出

さて,顔検出をやってみます。OpenCVにも機能がありますがdlibの機能を使います。


In [33]:
detector = dlib.get_frontal_face_detector()

detectorはdlibのget(よこせ) frontal(正面の) face(顔) detector(検出器)の結果。という意味です。要するに今度は顔検出の操縦桿がdetectorということです。では早速使ってみましょう。


In [34]:
dets = detector(img, 1)
len(dets)


Out[34]:
1

"1"以上の数字が出てきたら成功です。これは検出した顔の数です。1行目で画像imgから, upsamplingを1回だけして(色々な大きさの顔に対応する処理),その結果をdetsに入れてます。

ではdetsの中身を見てみましょう。


In [35]:
dets[0]


Out[35]:
rectangle(262,139,485,361)

rectangle(xxx, xxx, xxx, xxx)と出てきましたね。これはdlibのrectangleというモノです。訳がわからないのでdlib.rectangle?と実行してみましょう。


In [36]:
dlib.rectangle?

恐らく

Docstring:      This object represents a rectangular area of an image.
Init docstring:
__init__( (object)arg1) -> None

__init__( (object)arg1, (int)left, (int)top, (int)right, (int)bottom) -> None
File:           
Type:           class

のような表示が出てきたと思います。詳しく説明しませんが,rectangle(四角形)にleft, top, right, bottomとくれば何となく想像できるでしょう。


In [37]:
print(dets[0].left())
print(dets[0].top())
print(dets[0].right())
print(dets[0].bottom())


262
139
485
361

答えは四角形の左上,右下の座標です。では画像に四角形を重ねてみましょう。ここではcv2の機能を使います。使い方を見て実行してみましょう。


In [38]:
cv2.rectangle?

In [39]:
img = cv2.rectangle(img, (dets[0].left(), dets[0].top()), (dets[0].right(), dets[0].bottom()), (255, 0, 0))
cv2.imshow('image', img)
cv2.waitKey(2000)


Out[39]:
-1

顔に四角形が重なりましたか?失敗した場合には顔が正面を向いていないか,rectangleに渡す座標が間違えています。ちなみにこれを連続的に実行すると以下のようになります。(ウィンドウをアクティブにしてESCキーを押すと止まります)


In [40]:
import cv2
import dlib
cap = cv2.VideoCapture(0)
while not cap.isOpened():
    pass
detector = dlib.get_frontal_face_detector()
key = 0
while key != 27:
    ret, img = cap.read()
    dets = detector(img, 1)
    if len(dets) > 0:
        img = cv2.rectangle(img, (dets[0].left(), dets[0].top()), (dets[0].right(), dets[0].bottom()), (255, 0, 0))
        cv2.imshow('image', img)
    else:
        cv2.imshow('image', img)
    key = cv2.waitKey(10)
cap.release()

3. 顔ランドマーク検出

いよいよ顔ランドマークです。顔ランドマークは学習済みのデータ,shape_predictor_68_face_landmarks.datを使います。これは顔ランドマーク68点を検出できます。その前に仕切り直しです。また顔をカメラに向けて以下を実行して下さい。


In [41]:
cap = cv2.VideoCapture(0)
while not cap.isOpened():
    pass
ret, img = cap.read()
cv2.imshow('image', img)
cv2.waitKey(2000)

イマイチな場合はもう一度下のセルを実行しましょう。


In [47]:
ret, img = cap.read()
cv2.imshow('image', img)
cv2.waitKey(2000)


Out[47]:
-1

では顔ランドマークの検出器の操縦桿を作りましょう。


In [48]:
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

もし,エラーが出てしまったらshape_predictor_68_face_landmarks.datファイルがこのノートブックファイルと同じ場所にないせいです。ネットからダウンロードするなり先生に聞くなりして解決しましょう。

手順としてはdetectorで顔検出し,predictorで検出した顔領域内の顔ランドマークを検出,という流れです。


In [49]:
dets = detector(img, 1)
shape = predictor(img, dets[0])

In [50]:
shape


Out[50]:
<dlib.full_object_detection at 0x2aa200b29d0>

結果を入れたshapeを見てみようと思ったらdlib.full_object_detection at ....と出てきました。?を使って調べてみましょう。


In [51]:
dlib.full_object_detection?
Docstring:      This object represents the location of an object in an image along with the     positions of each of its constituent parts.
Init docstring:
__init__( (object)arg1) -> None

__init__( (object)arg1, (object)arg2, (object)arg3) -> object :
    requires 
        - rect: dlib rectangle 
        - parts: list of dlib points
File:          
Type:           class

どうもrectとpartsがあるようです。rectは恐らくdetsと同じものでしょう。ではpartsはどうでしょう。


In [52]:
shape.parts?
Docstring:
parts( (full_object_detection)arg1) -> points :
    A vector of dlib points representing all of the parts.
Type:      method

と出てきました。実行すると場所が詰まったベクトルが出てくると言っています。ベクトルの何番目は[]で指定できます。


In [53]:
shape.parts()[0]


Out[53]:
point(236, 147)

出ました。0番です。さて,どこでしょう。これはググってみましょう。ついでにdlib.pointも調べてみましょう。


In [54]:
dlib.point?
Docstring:      This object represents a single point of integer coordinates that maps directly to a dlib::point.
Init docstring:
__init__( (object)arg1) -> None

__init__( (object)arg1, (int)x, (int)y) -> None
File:           
Type:           class

とあるので,x, yで座標を指定できそうです。


In [55]:
print(shape.parts()[0].x)
print(shape.parts()[0].y)


236
147

では取り敢えず右目を囲ってみましょう。左端は36番のx,上端は38番のy,右端は39番のx,下端は41番のyを使ってみます。長くなるのでそれぞれx1, y1, x2, y2に代入してしまいましょう。


In [56]:
x1 = shape.parts()[36].x
y1 = shape.parts()[38].y
x2 = shape.parts()[39].x
y2 = shape.parts()[41].y

そしてimgに四角形を書き込んでみましょう。


In [58]:
img = cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255))
cv2.imshow('image', img)
cv2.waitKey(2000)
cap.release()

先程の連続処理に手を加えてみましょう。


In [59]:
import cv2
import dlib
cap = cv2.VideoCapture(0)
while not cap.isOpened():
    pass
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
key = 0
while key != 27:
    ret, img = cap.read()
    dets = detector(img, 1)
    if len(dets) > 0:
        shape = predictor(img, dets[0])
        x1 = shape.parts()[36].x
        y1 = shape.parts()[38].y
        x2 = shape.parts()[39].x
        y2 = shape.parts()[41].y
        img = cv2.rectangle(img, (dets[0].left(), dets[0].top()), (dets[0].right(), dets[0].bottom()), (255, 0, 0))
        img = cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255))
    cv2.imshow('image', img)
    key = cv2.waitKey(10)
cap.release()

4. 顔ランドマークを使って何かやる

さて,最後です。ランドマークを使って雑コラをしてみます。とりあえず改変OKなものを探してここから拾ってきました。

また仕切り直しですのでカメラを見て下のセルを実行しましょう。


In [3]:
cap = cv2.VideoCapture(0)
while not cap.isOpened():
    pass
ret, img = cap.read()
cv2.imshow('image', img)
cv2.waitKey(2000)


Out[3]:
-1

イマイチな場合はもう一度下のセルを実行しましょう。


In [7]:
ret, img = cap.read()
cv2.imshow('image', img)
cv2.waitKey(2000)


Out[7]:
-1

ではdetectorとpredictorの出番です。


In [8]:
dets = detector(img, 1)
shape = predictor(img, dets[0])

今度は両目を覆いたいので(x1, y1) = (17のx, 19のy), (x2, y2) = (26のx, 29のy)としました。


In [9]:
x1 = shape.parts()[17].x
y1 = shape.parts()[19].y
x2 = shape.parts()[26].x
y2 = shape.parts()[29].y

では囲えてるか確認しましょう。


In [10]:
img = cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255))
cv2.imshow('image', img)
cv2.waitKey(2000)


Out[10]:
-1

では画像の一部置き換えです。Pythonを使うと簡単ですが注意が必要です。

置き換える画像の読み込み(cv2.imread)
置き換える画像をリサイズ(cv2.resize)サイズは(x2 - x1, y2 - y1)
元画像[yの範囲, xの範囲] = リサイズした置き換える画像

となります。Pythonは通常「行,列」で扱っているので3行目はxとyが逆になっています。


In [11]:
img2 = cv2.imread('cartoon-718659_640.png', cv2.IMREAD_ANYCOLOR)
newSize = (x2 - x1, y2 - y1)
img3 = cv2.resize(img2, newSize)
img[y1:y2, x1:x2] = img3

さて,確認してみましょう。


In [12]:
cv2.imshow('image', img)
cv2.waitKey(2000)
cap.release()

では連続処理にしてみましょう。


In [2]:
import cv2
import dlib
cap = cv2.VideoCapture(0)
while not cap.isOpened():
    pass
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
img2 = cv2.imread('cartoon-718659_640.png', cv2.IMREAD_ANYCOLOR)
key = 0
while key != 27:
    ret, img = cap.read()
    dets = detector(img, 1)
    if len(dets) > 0:
        shape = predictor(img, dets[0])
        x1 = shape.parts()[17].x
        y1 = shape.parts()[19].y
        x2 = shape.parts()[26].x
        y2 = shape.parts()[29].y
        newSize = (x2 - x1, y2 - y1)
        img3 = cv2.resize(img2, newSize)
        img[y1:y2, x1:x2] = img3
    cv2.imshow('image', img)
    key = cv2.waitKey(10)
cap.release()

これで今日のゼミは終了です。この顔ランドマークの情報があれば目を大きくしたり,顔だけ美白にしたり,目を少女漫画みたいに置き換えたりもできますね。世に出回っているカメラアプリはこのようにして加工をしています。違和感なく滑らかにする技術がアプリの差とも言えますね。