プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

動画の顔を置き換えるやつやってみた

Python & OpenCVでやってみました

f:id:s51517765:20210822220254p:plain
OpenCVでカメラ画像を取り込み、顔検出してその場所と大きさに調整して他の画像を載せた動画を作ります。
基本的なところは下の参考サイト組み合わせです。

まず、動画画像から顔を抽出するには、OpenCVで行います。
GitHubから好きなモデルを選択しますが、ここでは正面を向いた顔の抽出を行いました。
https://github.com/opencv/opencv/tree/master/data/haarcascades

cascade_path = "haarcascade_frontalface_default.xml"
# カスケード分類器の特徴量を取得する
cascade = cv2.CascadeClassifier(cascade_path)

カメラで動画を取得します。
引数は複数のカメラ(USBカメラなど)があるときは1とか2にします。
ここではPCの内蔵カメラ(Web会議などに使うインカメラ)を使うので0を指定します。

cap = cv2.VideoCapture(0)

上書きする画像を取得します。
ここでは、画像背景を透過にしたいのでPNGで取得します。
イコン画像を加工して使用しました。

faceImg = cv2.imread('s51517765.png', cv2.IMREAD_UNCHANGED)

動画から顔を認識します。
認識出来たら、少し大きめ(130%)に画像を調整します。
顔認識と同じサイズでは頭や顎が出てしまうためです。
また、拡大した分座標を中心が一致するようにoffsetします。

facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))
if len(facerect) > 0:
        faceImg = cv2.resize(faceImg, ((int)(rect[2]*1.3), (int)(rect[3]*1.3)), cv2.IMREAD_UNCHANGED)
rect[0] -= rect[2]*0.15  # x_offset
rect[1] -= rect[3]*0.15  # y_offset

透過処理をして画像を重ねます。

  frame[rect[1]:rect[1]+faceImg.shape[0],rect[0]:rect[0]+faceImg.shape[1]] = frame[rect[1]:rect[1]+faceImg.shape[0], rect[0]:rect[0]+faceImg.shape[1]] * (1 - faceImg[:, :, 3:] / 255) + faceImg[:, :, :3] * (faceImg[:, :, 3:] / 255)

動画を表示します。

cv2.imshow('cv2', frame)

1st tryがこんな感じですが、首元や背景に余計な顔誤認識があります。

そこで、少し工夫をしました。
顔の認識サイズを移動平均で取得し、これに対して大きく外れている場合は誤認識とみなしてスキップします。
顔認識の縦横サイズを両方採用しましたが、ほぼ同じサイズなのでどちらかでも大丈夫かもしれません。
ここでは、移動平均の95%以下をスキップするようにしました。

aveSize = aveSize*0.8+rect[2]*0.1+rect[3]*0.1
thresh = aveSize*0.95  # 移動平均の95%以上を閾値
if rect[2] < thresh or rect[3] < thresh:
       break

安定するようになりました。

まとめ

別件でPythonでカメラを扱う案件があってカメラの使い方を調べたので、テレビでたまに見る似顔絵で顔を隠すやつをやってみました。
Zoomミーティングとかで使ってみようかな。

ソースコード

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

import cv2
import os

# https://github.com/opencv/opencv/tree/master/data/haarcascades
cascade_path = "haarcascade_frontalface_default.xml"
# カスケード分類器の特徴量を取得する
cascade = cv2.CascadeClassifier(cascade_path)


def VideoCapt():
    cap = cv2.VideoCapture(0)  # カメラが複数あるときは 0を1や2にする
    aveSize = 0
    count = 0
    while cap.isOpened():
        try:
            # マネしたい人はイラストやのぶたさんでもつかってね
            # https://www.irasutoya.com/2017/09/blog-post_966.html
            faceImg = cv2.imread('s51517765.png', cv2.IMREAD_UNCHANGED)
            _, frame = cap.read()
            # グレースケール変換
            image_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            facerect = cascade.detectMultiScale(
                image_gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))

            # print(facerect)
            color = (255, 255, 255)  # 白

            # 検出した場合
            if len(facerect) > 0:
                for rect in facerect:
                    count += 1
                    if count < 4:
                        aveSize += (rect[2]+rect[3])/2
                        break
                    elif count == 4:
                        aveSize += (rect[2]+rect[3])/2
                        aveSize /= 5
                    else:
                        aveSize = aveSize*0.8+rect[2]*0.1+rect[3]*0.1
                    thresh = aveSize*0.95  # 移動平均の95%以上を閾値
                    if rect[2] < thresh or rect[3] < thresh:
                        break
                    # 検出した顔を囲む矩形の作成
                    #cv2.rectangle(frame, tuple(rect[0:2]), tuple( rect[0:2]+rect[2:4]), color, thickness=2)
                    faceImg = cv2.resize(
                        faceImg, ((int)(rect[2]*1.3), (int)(rect[3]*1.3)), cv2.IMREAD_UNCHANGED)

                    rect[0] -= rect[2]*0.15  # x_offset
                    rect[1] -= rect[3]*0.15  # y_offset
                    frame[rect[1]:rect[1]+faceImg.shape[0],
                          rect[0]:rect[0]+faceImg.shape[1]] = frame[rect[1]:rect[1]+faceImg.shape[0],
                                                                    rect[0]:rect[0]+faceImg.shape[1]] * (1 - faceImg[:, :, 3:] / 255) + \
                        faceImg[:, :, :3] * (faceImg[:, :, 3:] / 255)
                    cv2.imshow('cv2', frame)

        except:
            pass
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cap.release()
    print("Camera is closed.")


print('Python Active:      ', os.getcwd())
path = os.path.dirname(__file__)
print('Script location:     ', path)
os.chdir(path)

if __name__ == '__main__':
    VideoCapt()