OpenCVのdnnによるメギドキャラの顔検出

メギド72は利用規約により商用利用以外での公式画像の加工・転載が可能なため使用させて頂いております

メギド所持率チェッカーにキャラ画像が欲しいとご要望をいただいたものの、200体以上のキャラの顔を切り抜くのは骨が折れる…ということでOpenCVによる顔検出を試してみました。

顔画像をいくつか学習させる部分から試したみたいと思っていましたが、少ない画像からカスケード分類器を作成する為のツール(opencv_createsamples)などがOpenCV4では終了しており、既存の学習済みの顔検出カスケードモデルを使用して検出を行った所…

ウァサゴの顔検出を試した画像。顔以外の部分が検出されている
こら そこは顔じゃない

続きを読む

検出精度がかなり低く、またカスケード分類器の性質上、正面顔のモデルからは正面顔のみにしか使用できない…という性質も有り断念

OpenCV4ではdnn(deep neural network)による顔検出を利用する方向のようで、こちらのサイトを参考にOpenCV Face Detectorを使用してみました。

その結果…

バエルの顔検出を試した画像。きちんと顔が認識されている
顔認識!

これはなかなかの精度では?

画像を切り抜いて検出ボックスごとに保存するプログラムを組んで実行、二次元絵のため信頼度の数値を下げながら検出していきました

顔画像一覧
信頼度が下がっているため顔でないものもちらほら

キャラ画像により顔のサイズが違うため、検出ボックスのサイズから拡大縮小する記述も入れ、認識しなかったキャラについては手動で加工しました。

ちなみにAIが最後まで顔認識してくれなかったのはCフォルネウス/バラキエル/ベルおじさん/イヌーン/BCバールゼフォン でした。 親友… 親友はむしろメギド体の頭の方が顔認識されました わあい

せっかくなので機械学習をさせるところからやってみようと思った割にはカスケード分類器は理論的にも単純な部分もあり、正解モデル作成の面倒さも併せると顔検出に関しては学習済みモデルを借りた方が便利…という結論に ちょっとさみしいね

参考元からちょっと書き換えた程度なのであまり参考になりませんが

import cv2
import numpy as np
import glob
import re

# 元画像ファイル一覧を取得
files = glob.glob("./mg3/*.png")
myfile = []
for file in files:
    myfile.append(file.replace("./mg3\\", ""))
myfile.sort()

# 顔検出準備
prototxt = 'deploy.prototxt'
model = 'res10_300x300_ssd_iter_140000_fp16.caffemodel'
# 信頼度
confidence_limit = 0.08
net = cv2.dnn.readNetFromCaffe(prototxt, model)


# 検出した顔を切り抜いて保存する関数
def face_m(myimage):
    image = cv2.imread("./mg3/" + myimage)
    (h, w) = image.shape[:2]
    blob = cv2.dnn.blobFromImage(cv2.resize(image, (600, 600)), 1.0,
                                 (600, 600), (104.0, 177.0, 123.0))
    net.setInput(blob)
    detections = net.forward()
    for i in range(0, detections.shape[2]):
        confidence = detections[0, 0, i, 2]
        if confidence < confidence_limit:
            continue
        box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
        (startX, startY, endX, endY) = box.amtype('int')
        # 検出したボックスの中心を取得
        # 顔の大きさにばらつきがあるためある程度近くなるようにリサイズ
        if (endX - startX) > 30 and (endY - startY) > 30:
            ax = startX + int((endX - startX) / 2)
            ay = startY + int((endY - startY) / 2)
            if (endY - startY) > (endX - startX):
                am = int((endY - startY) / 2) + 30
            else:
                am = int((endX - startX) / 2) + 30
            # 画面端にかかるものは無視
            if (ax - am) > 0 and (ay - am) > 0 and (ax + am) < 600 and (ay + am) < 600:
                img1 = image[(ay - am):(ay + am), (ax - am):(ax + am)]
                img1 = cv2.resize(img1, (100, 100))
                img1name = "./mg4/" + myimage.replace(".png", "") + "_" + str(i) + ".jpg"
                print(img1name)
                cv2.imwrite(img1name, img1)

# 生成先保存フォルダにある画像一覧を取得
to_files = glob.glob("./mg/*")
myfiles = []

for file in to_files:
    m = re.findall(r'\d+', file)
    myfiles += m

# 生成先フォルダに生成画像がないファイルにのみ実行
for myfile in myfile:
    r = myfile.replace(".png", "")
    if r in myfiles:
        print("")
    else:
        print(r)
        face_m(myfile)

最初は信頼度を高めに設定→実行後生成後のフォルダにて正しくない画像を削除→信頼度を下げて再度実行…を繰り返した感じです