7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

OpenCVとPythonでビデオチャットをもっと楽しくしよう

Posted at

はじめに

久しぶりにPythonの記事を書いてみました。おそらく最近は仕事、プライベートでビデオチャットを使うことが多くなったかと思います。今回はPythonとOpenCVを使ってビデオチャットをもっと楽しくするようなハックをしてみようかと思います。

つくるもの

せっかくのビデオチャットなので普段の自分とは違う姿になりたいなと誰もが思うはずです。今回は以下のような感じで「手をかざすと魔法陣が出てくる」ようなSnap Cameraもどきを作ってみます。

魔法使い.jpg

手の検出

手の検出にはOpenCVでサポートされているカスケード分類器を使用します。詳しい内容はこちらの記事を参照してみてください。簡単に説明すると物体検出をしてくれるライブラリです。OpenCVの導入方法については、OSによって異なるのと既に多くの記事が出回っているのでここでは省略いたします。
OpenCVでのカスケード分類器は「顔の検出」がメジャーかと思います。このカスケード分類器のモデルを自分で組み立てるのはものすごく大変な作業になるのでこちらの学習済みモデルを公式より拝借いたしました。

魔法陣の表示について

魔法陣の表示は単純に検出した手に魔法陣を被せるだけなのですが、それだと透過してくれないのでこちらを参考にしながらマスクを使用した画像合成を実装しました。

実装

コードは以下のような感じです。普段Pythonはあまり書かないので多少の汚さはご容赦ください。

import cv2
import numpy as np
import random

def masking(background, foreground, size):
    gray = cv2.cvtColor(foreground, cv2.COLOR_BGR2GRAY)
    _, binary = cv2.threshold(gray, 90, 255, cv2.THRESH_BINARY_INV)
    _, contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    mask = np.zeros_like(foreground)
    cv2.drawContours(mask, contours, -1, color=(255, 255, 255), thickness=-1)
    roi = background[size[0]:size[2], size[1]:size[3], :]
    result = np.where(mask==255, foreground, roi)
    return result

def HandDetector(img):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_rgb = clahe.apply(img_rgb)
    hand_rects = hand_cascade.detectMultiScale(img_rgb, 1.1, 5)
    if len(hand_rects) > 0:
        for i, (x,y,w,h) in enumerate(hand_rects):
            size = [y-h//2, x-w//2, y+h//2*3, x+w//2*3]
            if i == 0 and size[0] > 0 and size[1] > 0 and size[2] < img.shape[0] and size[3] < img.shape[1]:
                magicsquare = cv2.imread(magic)
                hand = img[size[0]:size[2], size[1]:size[3]]
                magicsquare = cv2.resize(magicsquare, (hand.shape[0], hand.shape[1]))
                magicsquare = masking(img, magicsquare, size)
                img[size[0]:size[2], size[1]:size[3]] = magicsquare
                center = np.array([x+w//2, y+h//2])
    return img

cascade_path = "./data/aGest.xml" # 手の検出用モデルの読み込み
hand_cascade = cv2.CascadeClassifier(cascade_path)
clahe = cv2.createCLAHE(clipLimit=1.5, tileGridSize=(8,8))
cap = cv2.VideoCapture(1)

magic = './魔法陣の画像のPath.jpg'

while True:
    ret, frame = cap.read()
    frame = cv2.flip(frame, 1)
    height, width, channels = frame.shape
    frame = HandDetector(frame)
    cv2.namedWindow('img', cv2.WINDOW_NORMAL)
    cv2.imshow('img', frame)
    c = cv2.waitKey(1)
    if c == 27:#ESC
        break
cap.release()
cv2.destroyAllWindows()

HandDetectorで手の検出、maskingで魔法陣を透過させるように合成させるといった感じです。後はimgshowで実際に合成させた映像を表示させます。

image.png

こんな感じでできました!魔法陣はどこかのフリーのものを拾ってきました。

ビデオチャットで使ってみる

せっかく作ったのでこれを実際のビデオチャットでも使ってみたいですよね。今回は簡単のためにSplitCamを使って、imgshowで出力した映像を仮想デバイスとして出力させました。あとはそれをビデオチャットツールに入力させました(今回は話題のZoomを用います)。以下で簡単にSplitCamでの手順を説明します。
Media Layersと呼ばれる操作エリアに「+」のボタンがあるのでそれをクリックすると以下のようなプルダウンメニューが表示されます。そのままScreenshareを選択します。するとプルダウンメニューがまた出てくるので、imgAddします(上記のコードをそのまま実行した場合)。

image.png

するとこんな感じにPythonで出力した映像がキャプチャされました。

image.png

このままZoomを開くとカメラの選択メニューからSplitCam Video Driverが選択できるようになったのでそれを選びます。するとZoomにPythonからの映像を出すことができました!

image.png

さいごに

今回のコードを一応gistにも公開しておきます。画像や学習済みモデルは拾いものなので各自で探してください。学習済みモデルのリンクをここに再掲します。
ただこの手の検出なのですが、グーの形しか検出できないのでもう少しバリエーションのある手の画像を検出させたいですね。そうなるとTensorFlowに久しぶりに手を出すことになりそう。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?