OBS Studioを使ってZoomで笑い男になってみる
東大のある講義で、教授が自分の顔が少し微笑んでいるように見えるソフトを使って話していたので私もやりたくなった。
今は友人に薦められて攻殻機動隊をAmazon Primeで見ており、笑い男さんがこのネタにぴったりだと思ったので私も笑い男さんになってみようじゃないか。
ただ、今回作ったものは笑い男さんが作ったものより何倍もレベルが低いので、その技術力に敬意を表し、笑い男「さん」と呼ばせていただく。
ちなみに攻殻機動隊STAND ALONE COMPLEXは初回放送が2002年で私が生まれた年である。
動作環境
MacBook Air (M1, 2020)
この先必要になるもの
都度インストールやセットアップの方法は述べていくが先にまとめておいた方が見やすいだろう。
Windowsでは動作確認していないのでお手数だが調べて欲しい。
OBS Studio (https://obsproject.com/)
Python3 (https://www.python.org/downloads/)
Homebrew (https://brew.sh/index_ja)
Zoomなど、自分の使いたいソフト
OBS Studioとは
OBS(Open Broadcaster Software)とはオープンソースで開発されている無料の画面配信・録画のソフトウェアである。
いろいろ機能があるらしいのだがまだ使いこなせていない。詳しいことはOBSのwiki(https://obsproject.com/wiki/)を見ていただくなどして、今回はこのソフトウェアの「仮想カメラ」を使っていく。
とりあえずはOBS Studioをインストールしていこう。
https://obsproject.com/にアクセスし、自分の使っているOSに適したものをダウンロードする。
Macの場合はdmgファイルがダウンロードされるのでダブルクリックして出てきたウィンドウでOBS.appをApplicationsのエイリアスにぶち込んでおけばOK。
Homebrewを使ってOpenCVをダウンロードする
HomebrewはMacで使えるパッケージマネージャーで、かなり便利。ターミナル.appを開いて(LaunchPadのその他にある)、以下をペースト。
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
homebrewの準備が終わったらOpenCVの準備をしていこう。
OpenCVとは画像処理のためのつよつよライブラリである。こちらも機能がめちゃくちゃ豊富だが今回はほんの一部しか使わない。
以下をターミナルにペースト。
brew install opencv
これでOpenCVの準備は完成。
Python3を準備
Pythonはプログラミング言語である。めちゃくちゃ簡単に書けるので書いたことがない方は少し挑戦してみるといいと思う。機械学習分野でもホットであり、Pythonで大体のことができる。すごいね。
Python3のダウンロードページhttps://www.python.org/downloads/にアクセスしてインストールする。
ターミナルで
python3 -c "print('hello world')"
を実行してhello worldと出力されればOK。
Python3でOpenCVを使うために以下のコマンドを実行。
python3 -m pip install opencv-python opencv-contrib-python
これで全ての準備が揃った。それではプログラムを書いていこう。
Pythonでプログラムを書く
手っ取り早く試したいので今回はOpenCVの分類器を使って顔検出をする。
https://github.com/opencv/opencv/tree/master/data/haarcascadesにアクセスするといくつかファイルがある。
haarcascade_frontalface_default.xmlをクリック。
rawと書かれたボタンを押して開いたページで右クリックすると(control押しながらクリックでもOK)、
別名で保存というオプションが出てくるのでクリック。
face_cascade.xmlなど適当な名前をつけて保存。
smile-man-zoomなどと適当な名前のフォルダを作って先ほどダウンロードしたface_cascade.xmlを入れておく。
今回は笑い男になりたいので笑い男の画像を探してこよう。Googleで検索すれば何個か出てくる。
このままだと背景が白で邪魔なので私はペイントアプリで透明に塗りつぶした。
著作権に配慮してここでは配布しないから自分で頑張って。
smileface.pngなどと名前をつけておいて、smile-man-zoomフォルダにぶち込んでおこう。
次にsmile-man-zoomフォルダ内にsmile.pyという名前のファイルを作成する。
ソフトはなんでもいいんだけど、私はVSCodeを愛用しているので布教しておこう。
VSCode (https://azure.microsoft.com/ja-jp/products/visual-studio-code/)
以下のコードをコピペ
import cv2 face_cascade = cv2.CascadeClassifier("./face_cascade.xml") camera = cv2.VideoCapture(0) smile_face = cv2.imread("./smileface.png", cv2.IMREAD_UNCHANGED) # α値まで取得 MAX_FRAME = 10 # これ以下では取得できなくても前回の位置に画像を貼る。 k = 0 image_detected = False last = [] # 最後に取得された位置 rate = 1.5 # 少し大きめに画像を貼る # ループ中にフレームを1枚ずつ取得(qキーで撮影終了) while True: ret, img = camera.read() img = cv2.flip(img, 1) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray,minSize=(100,100)) image_detected = False #モザイク for x, y, w, h in faces: #image detected image_detected = True k = 0 # 少し大きめに取得したいのでrate倍に調整 if x - w * (rate - 1) / 2 >= 0: x = x - w * (rate - 1) / 2 else: x = 0 if y - h * (rate - 1) / 2 >= 0: y = y - h * (rate - 1) / 2 else: y = 0 if x + rate * w < img.shape[1]: w = w * rate else: w = img.shape[1] - x if y + rate * h < img.shape[0]: h = h * rate else: h = img.shape[0] - y x, y, w, h = int(x), int(y), int(w), int(h) last = (x, y, w, h) # ストレージしておく smile = cv2.resize(smile_face, (w, h)) img[y:y+h, x:x+w] = img[y:y+h, x:x+w] * (1 - smile[:, :, 3:] / 255) + smile[:, :, :3] * (smile[:, :, 3:] / 255) # α値を考慮した足し算 if not image_detected: k += 1 #顔が検出されなくてもMAX_FRAMEまでは同じ場所に画像を出しておく if k < MAX_FRAME and len(last) > 0: x, y, w, h = last smile = cv2.resize(smile_face, (w, h)) img[y:y+h, x:x+w] = img[y:y+h, x:x+w] * (1 - smile[:, :, 3:] / 255) + smile[:, :, :3] * (smile[:, :, 3:] / 255) cv2.imshow('smile face', img) # 画像を表示 if cv2.waitKey(1) & 0xFF == ord('q'): break camera.release() cv2.destroyAllWindows()
これで準備完了。
動作確認
再びターミナルを開き、今作業していたフォルダを開く。
cdと入力した後に一つ空白を開けて、Finderからフォルダをターミナルにドラッグしてくれば開ける。
python3 smile.py
と実行すると自分の顔に笑い男のアイコンが被さった動画が映し出されるはずだ。
qキーを入力すると終了できる。
Zoomで使う
ここまで来れば後もう少し。OBS.appを開いてソースと書かれたセクションの+ボタンを押す。
ウィンドウキャプチャを選択し、ウィンドウを[python3] smile faceにする。
出てきた画面をいい感じに枠に合わせれば設定完了。
普段Zoomの画面を反転させて使用しているならOBS上でも画面を反転させる必要がある。
右クリック→変換→水平反転を押せば良い。
あとは仮想カメラ開始を押す。
Zoomを開いてカメラのソースをOBS Virtual Cameraに変更する(ビデオマークの横の^を押すと出てくる)。
これでZoom上で笑い男さんになれたはずである。
YuNetを使って顔検出を行う
ここまでで笑い男さんにはなれるのだが、精度が低いと不満を持っている方もいると思う。
というわけで新しく追加されたOpenCVのAPIを使ってさらに精度を上げて笑い男さんになってみよう。
https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunetからコードとモデルを拝借して書いてみる。
yunet.pyとface_detection_yunet_2021dec.onnxをsmile-man-zoomフォルダに入れる。
長いのでyunet.onnxとリネームしておいた。
smile2.pyを作成して以下のコードをコピペする。
import cv2 from yunet import YuNet import numpy as np camera = cv2.VideoCapture(0) width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT)) model = YuNet("./yunet.onnx", inputSize=(width, height), confThreshold=.8) smile_face = cv2.imread("./smileface.png", cv2.IMREAD_UNCHANGED) MAX_FRAME = 30 # これ以下では取得できなくても前回の位置に画像を貼る。 k = 0 image_detected = False last = [] # 最後に取得された位置 rate = 1.5 # 撮影=ループ中にフレームを1枚ずつ取得(qキーで撮影終了) while True: ret, img = camera.read() img = cv2.flip(img, 1) results = model.infer(img) image_detected = False for det in (results if results is not None else []): bbox = det[0:4].astype(np.int32) x, y, w, h = bbox[0], bbox[1], bbox[2], bbox[3] w = max([w, h]) h = max([w, h]) # 正方形に変換 #image detected image_detected = True k = 0 # 少し大きめに取得したいので少しrate倍に調整 if x - w * (rate - 1) / 2 >= 0: x = x - w * (rate - 1) / 2 else: x = 0 if y - h * (rate - 1) / 2 >= 0: y = y - h * (rate - 1) / 2 else: y = 0 if x + rate * w < img.shape[1]: w = w * rate else: w = img.shape[1] - x if y + rate * h < img.shape[0]: h = h * rate else: h = img.shape[0] - y x, y, w, h = int(x), int(y), int(w), int(h) last = (x, y, w, h) smile = cv2.resize(smile_face, (w, h)) img[y:y+h, x:x+w] = img[y:y+h, x:x+w] * (1 - smile[:, :, 3:] / 255) + smile[:, :, :3] * (smile[:, :, 3:] / 255) if not image_detected: k += 1 if k < MAX_FRAME and len(last) > 0: x, y, w, h = last smile = cv2.resize(smile_face, (w, h)) img[y:y+h, x:x+w] = img[y:y+h, x:x+w] * (1 - smile[:, :, 3:] / 255) + smile[:, :, :3] * (smile[:, :, 3:] / 255) cv2.imshow('smile face', img) if cv2.waitKey(1) & 0xFF == ord('q'): break camera.release() cv2.destroyAllWindows()
おそらくnumpyがないと怒られるので、ターミナルで
python3 -m pip install numpy
と入力してから、
python3 smile2.py
を実行する。あとは先述した手順でOBSから読み込めば良い。
体感だがface cascadeを使うよりも精度がいい感じがする。お好みということで。