顔モーフィング(3.三角形をワープしてブレンド)

こんにちは。顔モーフィングの記事3本目で今回が最後です。今日は「三角形をワープしてブレンド」について書いていきます。もしご興味のある方は前回の記事もご覧になってください。

顔モーフィング手順

  1. 顔の特徴点の検出
  2. ドロネーの三角形分割でメッシュ生成
  3. 三角形をワープしてブレンド

顔モーフィング(2.メッシュ生成(ドロネーの三角形分割))

2020.07.01

顔モーフィング(1.顔の特徴点の検出)

2020.06.30

三角形をワープしてブレンド

前回の記事で画像をドロネーの三角形分割する方法をご紹介しました。それを使用して、オードリーヘップバーンとマリリンモンローの特徴点を一定の割合にしたワープさせたい特徴点を取得し、三角形分割します。オードリーとマリリンのオリジナルも三角形分割します。そして三角形を1つずつ画像1,2をワープさせたい位置に変換します。この時アフィン変換を使用します。アフィン変換後、画像1画像2をブレンドします。

openCVでアフィン変換はwarpAffineを使用します。ただ、warpAffineは三角形ではなく画像を取り込むので、三角形のままでは使用できないので、以下手順で行います。

手順

  1. 三角形の座標を含む最小の矩形領域 (バウンディングボックス)を取得
  2. 三角形のマスクを生成 三角形の領域のピクセル値は1で、残りの領域のピクセル値は0になる
  3. アフィン変換の実行
  4. 2つの画像に重みを付けて、三角形の最終的なピクセル値を見つける
  5. マスクと投影結果を使用して論理AND演算を実行し、三角形領域の投影されたピクセル値を取得しOutput用画像にコピー

アフィン変換「cv2.warpAffine」の使い方

dst = cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]] )

パラメータ

src入力画像
M2 × 3 変換行列
dsize出力画像のサイズ
dst出力イメージ
flags変換方法
borderModeborderMode境界の対処方法
borderValue一定の境界線に使用される値

ソースコード

def Face_morph(img1, img2, img, tri1, tri2, tri, alpha) :
    """モーフィング画像作成

    Args:
        img1 : 画像1
        img2 : 画像2
        img  : 画像1,2のモーフィング画像(Output用画像)
        tri1 : 画像1の三角形
        tri2 : 画像2の三角形
        tri  : 画像1,2の間の三角形
        alpha: 重み
    """
    
    # 各三角形の座標を含む最小の矩形領域 (バウンディングボックス)を取得
    # (左上のx座標, 左上のy座標, 幅, 高さ)
    r1 = cv2.boundingRect(np.float32([tri1]))
    r2 = cv2.boundingRect(np.float32([tri2]))
    r = cv2.boundingRect(np.float32([tri]))


    # バウンディングボックスを左上を原点(0, 0)とした座標に変換
    t1Rect = []
    t2Rect = []
    tRect = []
    for i in range(0, 3):
        tRect.append(((tri[i][0] - r[0]),(tri[i][1] - r[1])))
        t1Rect.append(((tri1[i][0] - r1[0]),(tri1[i][1] - r1[1])))
        t2Rect.append(((tri2[i][0] - r2[0]),(tri2[i][1] - r2[1])))

    # 三角形のマスクを生成
    # 三角形の領域のピクセル値は1で、残りの領域のピクセル値は0になる
    mask = np.zeros((r[3], r[2], 3), dtype = np.float32)
    cv2.fillConvexPoly(mask, np.int32(tRect), (1.0, 1.0, 1.0), 16, 0)

    # アフィン変換の入力画像を用意
    img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]
    img2Rect = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]]

    # アフィン変換の変換行列を生成
    warpMat1 = cv2.getAffineTransform( np.float32(t1Rect), np.float32(tRect) )
    warpMat2 = cv2.getAffineTransform( np.float32(t2Rect), np.float32(tRect) )

    size = (r[2], r[3])
    # アフィン変換の実行
    # 1.src:入力画像、2.M:変換行列、3.dsize:出力画像のサイズ、4.flags:変換方法、5.borderMode:境界の対処方法
    warpImage1 = cv2.warpAffine( img1Rect, warpMat1, (size[0], size[1]), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )
    warpImage2 = cv2.warpAffine( img2Rect, warpMat2, (size[0], size[1]), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )

    # 2つの画像に重みを付けて、三角形の最終的なピクセル値を見つける
    imgRect = (1.0 - alpha) * warpImage1 + alpha * warpImage2

    # マスクと投影結果を使用して論理AND演算を実行し、
    # 三角形領域の投影されたピクセル値を取得しOutput用画像にコピー
    img[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] = img[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] * ( 1 - mask ) + imgRect * mask

ソース呼び出し方

「1.顔の特徴点の検出」「2.ドロネーの三角形分割でメッシュ生成」「3.三角形をワープしてブレンド」のソースを使用して顔モーフィング画像を作成します。最終的には写真を繋げてgifを作成しています。わかりやすいように、三角形を描いた状態で作ってみました。


def betweenPoints(point1, point2, alpha) :
    points = []
    for i in range(0, len(points1)):
        x = ( 1 - alpha ) * points1[i][0] + alpha * points2[i][0]
        y = ( 1 - alpha ) * points1[i][1] + alpha * points2[i][1]
        points.append((x,y))
    return points

if __name__ == '__main__' :

    # モーフィングする画像取得
    filename1 = 'image/AudreyHepburn.jpg'
    filename2 = 'image/MarilynMonroe.jpg'
    img1 = cv2.imread(filename1)
    img2 = cv2.imread(filename2)
    
    # 画像をfloat型に変換
    img1 = np.float32(img1)
    img2 = np.float32(img2)

    # 長方形を取得
    size = img1.shape
    rect = (0, 0, size[1], size[0])

    # 顔の特徴点を取得
    points1 = face_landmarks.Face_landmarks(filename1)
    points2 = face_landmarks.Face_landmarks(filename2)

    # 1~99%割合を変えてモーフィング
    for cnt in range(1, 100):
        alpha = cnt * 0.01
        
        # 画像1,2の特徴点の間を取得
        points = betweenPoints(points1,points2,alpha)

        # ドロネーの三角形(座標配列とpoints要素番号)を取得
        triangles, delaunay = face_delaunay.Face_delaunay(rect,points)

        # モーフィング画像初期化
        imgMorph = np.zeros(img1.shape, dtype = img1.dtype)

        # ドロネー三角形の配列要素番号を読込
        for (i, (x, y, z)) in enumerate(delaunay):

            # ドロネー三角形のピクセル位置を取得
            tri1 = [points1[x], points1[y], points1[z]]
            tri2 = [points2[x], points2[y], points2[z]]
            tri = [points[x], points[y], points[z]]

            # モーフィング画像を作成
            Face_morph(img1, img2, imgMorph, tri1, tri2, tri, alpha)

        # モーフィング画像をint型に変換し出力
        imgMorph = np.uint8(imgMorph)
        cv2.imwrite('output/picture-%s.jpg' % str(cnt).zfill(3),imgMorph)

これで顔モーフィング作成記事は終わります。

3 件のコメント

  • プログラム拝見させていただきました。
    大変参考になりました。

    一つ質問なのですが、25,26行の
    #画像1、画像2の特徴点の間を取得
    points = betweenPoints(points1,points2,alpha)

    この2行のbetweenPointsはどのライブラリの関数になるのでしょうか?

    • 画像処理初心者さま
      コメントありがとうございます。お返事が遅くなりました。
      betweenPointsのソースコードが抜けておりましたので追記しました。ご指摘ありがとうございました!

  • コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

    日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)