【Python & OpenCV】スカウターっぽい表示をするプログラムを作ってみた

OpenCV

ドラゴンボールのスカウターっぽい表示をするプログラムをPython+OpenCV+OpenGLで作ってみました。

結果

プログラムを実行すると、カメラ映像をスカウター表示っぽく緑色にして、顔バレ防止用に顔領域をモザイク処理と戦闘力(乱数)と対象を示す三角形マークをリアルタイムで表示します。

キーボードの「Q」キーを押すと終了します。

なお、そそたたの戦闘力は48万超えなのでギニュー隊長も楽勝で倒せます。

(≧▽≦)/

ソースコード

作成したプログラムのソースコードを載せておきます。

開発環境は、MacBook Pro(2016年モデル)+Python3.5.5+OpenCV3.4.2+OpenGL3.1.1a1です。

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image
import random

cap = cv2.VideoCapture(0)

#顔検出のカスケード分類器を生成 ※環境によりパスが変わります
cascadeFile = '/???/haarcascade_frontalface_alt_tree.xml'
cascade = cv2.CascadeClassifier(cascadeFile)

#スカウターっぽく背景を緑色にするためのマスク画像
mh = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
mw = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
mask = np.full((mh, mw, 3), (70, 110, 0), dtype=np.uint8)

#戦闘力(乱数)
fight = random.randint(5, 530000)

#顔のモザイク処理
def mosaic(src, ratio=0.025):
    small = cv2.resize(src, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    return cv2.resize(small, src.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
def mosaic_area(src, x, y, width, height, ratio=0.025):
    dst = src.copy()
    dst[y:y + height, x:x + width] = mosaic(dst[y:y + height, x:x + width], ratio)
    return dst
    
#フレーム描画
def draw():
    _, curFrame = cap.read()

    #顔検出
    faceRect = cascade.detectMultiScale(curFrame)

    #全体を緑色っぽく
    curFrame = cv2.addWeighted(curFrame, 0.8, mask , 0.9, 0)

    if len(faceRect) > 0:
        for fx, fy, fw, fh in faceRect:
            #顔にモザイク
            curFrame = mosaic_area(curFrame, fx, fy, fw, fh)

            #戦闘力表示
            textColor = (0, 244, 243)
            fontSize = 64
            font = ImageFont.truetype('NikkyouSans-B6aV.ttf', fontSize)
            img_pil = Image.fromarray(curFrame)
            draw = ImageDraw.Draw(img_pil)
            x = fx + (fw / 2) - (font.getsize('FIGHT')[0] / 2)
            y = fy - (fontSize * 1.5) + 5
            draw.text((x, y), 'FIGHT', font = font, fill = textColor)
            x = fx + (fw / 2) - (font.getsize(str(fight))[0] / 2)
            y = fy - (fontSize * 0.5)
            draw.text((x, y), str(fight), font = font, fill = textColor)
            curFrame = np.array(img_pil)

            #真ん中三角
            p1 = (int(fx + (fw / 2)), int(fy + (fh * 1.0)))
            p2 = (int(fx + (fw / 2) - 40), int(fy + (fh * 1.0) + 60))
            p3 = (int(fx + (fw / 2) + 40), int(fy + (fh * 1.0) + 60))
            triangle = np.array([p1, p2, p3])
            cv2.drawContours(curFrame, [triangle], 0, textColor, -1)

            #左三角
            p1 = (int(fx - 35)), int(fy + (fh * 0.5) + 30)
            p2 = (int(fx - 95), int(fy + (fh * 0.5)))
            p3 = (int(fx - 95), int(fy + (fh * 0.5) + 60))
            triangle = np.array([p1, p2, p3])
            cv2.drawContours(curFrame, [triangle], 0, textColor, -1)

            #右三角
            p1 = (int(fx + fw + 35)), int(fy + (fh * 0.5) + 30)
            p2 = (int(fx + fw + 95), int(fy + (fh * 0.5)))
            p3 = (int(fx + fw + 95), int(fy + (fh * 0.5) + 60))
            triangle = np.array([p1, p2, p3])
            cv2.drawContours(curFrame, [triangle], 0, textColor, -1)

    #フレーム描画
    curFrame = cv2.cvtColor(curFrame, cv2.COLOR_BGR2RGB)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, curFrame.shape[1], curFrame.shape[0], 0, GL_RGB, GL_UNSIGNED_BYTE, curFrame)

    glEnable(GL_TEXTURE_2D)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

    glBegin(GL_QUADS) 
    glTexCoord2d(0.0, 1.0)
    glVertex3d(-1.0, -1.0, 0.0)
    glTexCoord2d(1.0, 1.0)
    glVertex3d(1.0, -1.0, 0.0)
    glTexCoord2d(1.0, 0.0)
    glVertex3d(1.0,  1.0, 0.0)
    glTexCoord2d(0.0, 0.0)
    glVertex3d(-1.0, 1.0, 0.0)
    glEnd()

    glFlush()
    glutSwapBuffers()

def idle():
    glutPostRedisplay()

def keyboard(key, x, y):
    key = key.decode('utf-8')
    if key == 'q':
        exit()

if __name__ == "__main__":
    glutInitWindowPosition(0, 0)
    glutInitWindowSize(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE)
    glutCreateWindow("Camera")
    glutDisplayFunc(draw)
    glutKeyboardFunc(keyboard)
    glutIdleFunc(idle)
    glutMainLoop()

    cap.release()

解説

プログラムを解説します。

カメラ映像のフレーム描画に関しては、異物検知プログラムで説明していますのでそちらを参照してください。

全体を緑色っぽくする

キャプチャと同じサイズで単色のマスク画像を作成します。

単色の「70, 100, 0」は、適当にそれっぽいのを選んでいます。

#スカウターっぽく背景を緑色にするためのマスク画像
mh = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
mw = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
mask = np.full((mh, mw, 3), (70, 110, 0), dtype=np.uint8)

キャプチャしたフレーム画像とマスク画像をアルファブレンドで重ねることで全体を緑色っぽくします。

    #全体を緑色っぽく
    curFrame = cv2.addWeighted(curFrame, 0.8, mask , 0.9, 0)

顔検出

OpenCV標準で用意されている顔検出用の検出器を使用してカスケード分類器を生成します。

#顔検出のカスケード分類器を生成 ※環境によりパスが変わります
cascadeFile = '/???/haarcascade_frontalface_alt_tree.xml'
cascade = cv2.CascadeClassifier(cascadeFile)
そそたた
そそたた

OpenCV標準の検出器は複数用意されていて、そそたたの環境で一番検出結果がよかったものを使用しています。

他のものは、Google先生に聞いてください。

あとは、顔検出する画像を食わせると顔の領域を取得することができてしまいます。

    #顔検出
    faceRect = cascade.detectMultiScale(curFrame)

モザイク処理

モザイク処理は、下記サイトのものをそのまま使用させていただきました。

m(__)m

Python, OpenCVで画像にモザイク処理(全面、一部、顔など) | note.nkmk.me
Python, OpenCVを使って画像にモザイク処理を行う。画像全体にモザイク処理 画像の一部をモザイク処理 顔検出して顔部分にモザイク処理 徐々にモザイクがかかるGIFアニメ作成 についてサンプルコードとともに説明する。モザイク処理といっても複雑なアルゴリズムは必要なく、画像を一旦縮小してから拡大して元のサイズに戻...

モザイク処理の対象箇所はここになります。

#顔のモザイク処理
def mosaic(src, ratio=0.025):
    small = cv2.resize(src, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    return cv2.resize(small, src.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
def mosaic_area(src, x, y, width, height, ratio=0.025):
    dst = src.copy()
    dst[y:y + height, x:x + width] = mosaic(dst[y:y + height, x:x + width], ratio)
    return dst

顔検出した領域を上記関数に食わせるとモザイク処理した画像を取得できます。

    if len(faceRect) > 0:
        for fx, fy, fw, fh in faceRect:
            #顔にモザイク
            curFrame = mosaic_area(curFrame, fx, fy, fw, fh)

戦闘力表示

一行目に「FIGHT」、二行目に「戦闘力」を顔検出した領域をもとに位置決めして表示します。

戦闘力の乱数範囲は、銃を持った地球人(戦闘力5)からフリーザ第一形態(戦闘力53万)です。

#戦闘力(乱数)
fight = random.randint(5, 530000)

上記の戦闘力をフレーム画像に描画するのですが、cv2.putTextで文字描画するとしょぼい感じになってしまったので、フリーフォントの「Nikkyou Sans Font」を使用して文字描画するようにしました。

Nikkyou Sans Font | daredemotypo | FontSpace
Free download of Nikkyou Sans Font. Released in 2017 by daredemotypo and licensed for personal and commercial-use. Click now to create a custom image with your ...

戦闘力描画用のフォントカラーとフォントサイズを適当に決めて、顔領域の上部に中央寄せで表示するように座標計算して文字描画します。

            #戦闘力表示
            textColor = (0, 244, 243)
            fontSize = 64
            font = ImageFont.truetype('NikkyouSans-B6aV.ttf', fontSize)
            img_pil = Image.fromarray(curFrame)
            draw = ImageDraw.Draw(img_pil)
            x = fx + (fw / 2) - (font.getsize('FIGHT')[0] / 2)
            y = fy - (fontSize * 1.5) + 5
            draw.text((x, y), 'FIGHT', font = font, fill = textColor)
            x = fx + (fw / 2) - (font.getsize(str(fight))[0] / 2)
            y = fy - (fontSize * 0.5)
            draw.text((x, y), str(fight), font = font, fill = textColor)
            curFrame = np.array(img_pil)

三角形マーク表示

最後に、対象を表す三角形マークを表示します。

顔検出の領域から三角形の頂点を決めて、その三角形領域をdrawContours関数で塗り潰します。

            #真ん中三角
            p1 = (int(fx + (fw / 2)), int(fy + (fh * 1.0)))
            p2 = (int(fx + (fw / 2) - 40), int(fy + (fh * 1.0) + 60))
            p3 = (int(fx + (fw / 2) + 40), int(fy + (fh * 1.0) + 60))
            triangle = np.array([p1, p2, p3])
            cv2.drawContours(curFrame, [triangle], 0, textColor, -1)

            #左三角
            p1 = (int(fx - 35)), int(fy + (fh * 0.5) + 30)
            p2 = (int(fx - 95), int(fy + (fh * 0.5)))
            p3 = (int(fx - 95), int(fy + (fh * 0.5) + 60))
            triangle = np.array([p1, p2, p3])
            cv2.drawContours(curFrame, [triangle], 0, textColor, -1)

            #右三角
            p1 = (int(fx + fw + 35)), int(fy + (fh * 0.5) + 30)
            p2 = (int(fx + fw + 95), int(fy + (fh * 0.5)))
            p3 = (int(fx + fw + 95), int(fy + (fh * 0.5) + 60))
            triangle = np.array([p1, p2, p3])
            cv2.drawContours(curFrame, [triangle], 0, textColor, -1)

考察

OpenCVで何か作りたいと思ったのでネタとしてドラゴンボールのスカウターっぽい表示をするプログラムを作ってみましたが、次の2つを断念してしまいました。

1つ目は、戦闘力を乱数でなく対象の人単位で変わらない数値にしたかったのですが、いい案が浮かばず断念しました。

目の部分などの画像を何らかの方法で数値化してゴニョゴニョしたらいいのやろか?

(-_-)ウーム

2つ目は、対象の人の輪郭を縁取りたかったのですが、背景がごちゃごちゃしていると人だけを縁取るのが難しく断念しました。

(-_-)ウーム

もう少し画像処理の基礎を勉強して色んなものを作ってみないと仕事として使えるまでに経験値が上がらないと思いました。

OpenCVは画像処理の手段(顔検出など)が色々用意されているので、それをそのまま実行して極めた気になってしまう感じ。。

コメント

タイトルとURLをコピーしました