石油王研究課|CIFLAB

CIFLAB石油王研究課の活動ブログです。

python + opencvで個人認識 その4

GW最終日、明日から海の日までしばらく祝日がないのでテンションが下がっています。テンションが下がりつつ、個人認識の続きを書きました。

今回は前回学習したモデルを用いて、自分の顔を認識させたいと思います。そして何番煎じか分かりませんが、認識させたとき自分の顔を笑い男の画像と置き換えたいと思います。

流れとしては以下を実装しました。

  1. USBカメラから画像を読み込む
  2. カスケード分類器を用いて、顔の候補領域を取り出す
  3. 候補領域を学習した識別器にかけ、本人か識別する
  4. 本人ならば笑い男の画像を合成する

画像の読み込みはその1で書いてあるため飛ばします。

顔の候補領域はopencvにあるカスケード分類器を用います。カスケード分類器に関しては以下のサイトを参考にしました。

http://opencv.jp/opencv-2.2/c/objdetect_cascade_classification.html

カスケード分類器に関するコードはこちらになります。デフォルトで用意されている特徴分類器を用います。
今回は正面顔と横顔用の特徴分類器を用いました。

    def cascade_init(self):
        profilecascade_path = 'haarcascade_profileface.xmlのパス'
        frontalcascade_path = 'haarcascade_frontalface_alt2.xmlのパス'

        self.profile_cascade = cv2.CascadeClassifier(profilecascade_path)
        self.frontal_cascade = cv2.CascadeClassifier(frontalcascade_path)

    def execute_cascade(self, frame):
        profile_facerect = self.profile_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(1, 1))

        frontal_facerect = self.frontal_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(1, 1))

        return profile_facerect, frontal_facerect

次に候補領域から取り出した画像をcnnで自分の顔かどうかを識別させます。
CNNの設定でsimple_predictで画像一枚を入力として、識別結果を返すように設定していました。また今回は自分の顔と認識した中で最も大きい領域を自分の顔の領域としました。これは自分の顔周辺をいくつか候補として出した場合、自分の顔と識別する結果が複数得られるためです。

    def cnn_init(self):
        TRAIN_BATCH = 100
        TEST_BATCH = 90
        self.cnn_net = TensorCNN(TRAIN_BATCH, TEST_BATCH, self.IMG_WIDTH, self.IMG_HEIGHT)
        self.cnn_net.net_init()

    def load_cnnmodel(self, path):
        self.cnn_net.load_model(path)
    
    def rect_cnn(self, imgs, rects):
        get_area = lambda tmprect: (tmprect[2] - tmprect[0]) * (tmprect[3] - tmprect[1])

        myimg_rect = []
        rect_area = []
        for img,rect in zip(imgs,rects):
            res = self.predict_cnn(img)
            print(res)
            if np.argmax(res) == self.MYIMG_LABEL:
                self.exit_prob += 1
                myimg_rect.append(rect)
                rect_area.append(get_area(rect))

        return myimg_rect, rect_area

    def predict_cnn(self, img):
        input_img = cv2.resize(img,(self.IMG_WIDTH, self.IMG_HEIGHT))
        input_img = np.reshape(input_img,(1,self.IMG_HEIGHT,self.IMG_WIDTH,self.CH))
        predict_dict = {self.cnn_net.predict_input: input_img,
        self.cnn_net.keep_prob: 1.0}
        return self.cnn_net.sess.run(self.cnn_net.simple_predict, feed_dict=predict_dict)

最後に自分の顔画像があればそれを笑い男の画像に取り替えます。透過領域があるpng画像からマスクを生成して、合成しました。

    def combine_img(self, frame, img, x,y):
        height_over_check = lambda x: self.HEIGHT if x > self.HEIGHT else x
        width_over_check = lambda x: self.WIDTH if x > self.WIDTH else x

        img = cv2.resize(img, (int(self.WIDTH/1.2), int(self.HEIGHT/1.2)))
        height,width = img.shape[:2]
        
        ex = width_over_check(x+width)
        ey = height_over_check(y+height)

        mask = img[:,:,3]   #これでアルファチャンネルのみの行列が抽出。
        mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
        mask = mask/255.0 

        img = img[:,:,:3]
        frame_float = frame.astype(np.float64)

        frame_float[y:ey, x:ex] *= 1 - mask[:(ey-y),:(ex-x)]
        frame_float[y:ey, x:ex] += img[:(ey-y),:(ex-x)] * mask[:(ey-y),:(ex-x)]

        return frame_float

これらを組み合わせると、自分の顔画像が見つかったときに笑い男の画像を貼り付けます。

f:id:fortis3m:20180506213809p:plain

ただ、今回過学習しているためか変な領域を自分の顔と認識したり、カメラからの距離に応じて認識精度が変わってしまいました。
対策としてはシンプルに画像量を増やすか、顔認識で優秀なモデルを借りて、追加で自分の顔を認識させるとかが考えられます。

また顔候補領域が顔の向きによっては出にくい、処理が重くてカクカクするなどの問題もありました。

やっぱり、顔認識とかの論文を読んだ方が良さそうだなというのが素直な感想です。
一応以下に全コードをあげておきます。

import cv2
import time
import numpy as np

from cnn import TensorCNN

class FaceRecognition:
    def __init__(self, size):
        self.IMG_HEIGHT = 64
        self.IMG_WIDTH = 64
        self.CH = 3
        self.MARGIN = 0

        self.MYIMG_LABEL = 1
        
        self.WIDTH = size[0]
        self.HEIGHT = size[1]

        self.exit_prob = 0
        self.judge = 0

    def cnn_init(self):
        TRAIN_BATCH = 100
        TEST_BATCH = 90
        self.cnn_net = TensorCNN(TRAIN_BATCH, TEST_BATCH, self.IMG_WIDTH, self.IMG_HEIGHT)
        self.cnn_net.net_init()

        self.cnn_recoglog = []

    def load_cnnmodel(self, path):
        self.cnn_net.load_model(path)
        print('CNN model downloaded is success')
    
    def rect_cnn(self, imgs, rects):
        get_area = lambda tmprect: (tmprect[2] - tmprect[0]) * (tmprect[3] - tmprect[1])

        myimg_rect = []
        rect_area = []
        for img,rect in zip(imgs,rects):
            res = self.predict_cnn(img)
            print(res)
            if np.argmax(res) == self.MYIMG_LABEL:
                self.exit_prob += 1
                myimg_rect.append(rect)
                rect_area.append(get_area(rect))

        return myimg_rect, rect_area

    def predict_cnn(self, img):
        input_img = cv2.resize(img,(self.IMG_WIDTH, self.IMG_HEIGHT))
        input_img = np.reshape(input_img,(1,self.IMG_HEIGHT,self.IMG_WIDTH,self.CH))
        predict_dict = {self.cnn_net.predict_input: input_img,
        self.cnn_net.keep_prob: 1.0}
        return self.cnn_net.sess.run(self.cnn_net.simple_predict, feed_dict=predict_dict)

    #profile 横顔
    #frontalface 正面顔
    def cascade_init(self):
        profilecascade_path = 'haarcascade_profileface.xmlへのパス'
        frontalcascade_path = 'haarcascade_frontalface_alt2.xmlへのパス'

        self.profile_cascade = cv2.CascadeClassifier(profilecascade_path)
        self.frontal_cascade = cv2.CascadeClassifier(frontalcascade_path)

        self.pro_cascade_log = []
        self.front_cascade_log = []

        print('Cascade init succeded...')

    def execute_cascade(self, frame):
        profile_facerect = self.profile_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(20, 20))

        frontal_facerect = self.frontal_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(20, 20))

        return profile_facerect, frontal_facerect
        #return frontal_facerect

    def set_cascade_log(self, pro_facerect, fro_facerect):
        #if len(pro_facerect) > 0:
        #    self.pro_cascade_log = [rect for rect in pro_facerect]

        if len(fro_facerect) > 0:
            self.front_cascade_log = [rect for rect in fro_facerect]
    
    def set_cnn_rectlog(self, rects, areas):
        self.judge += 1
        if len(rects) > 0:
            self.exit_prob += 1
            self.cnn_recoglog = []
            index = np.argmax(areas)
            self.cnn_recoglog = rects[index]

    #候補領域周辺から取得する
    def sample_img(self, frame):
        img_list = []
        rect_list = []

        if len(self.pro_cascade_log) > 0:
            for rect in self.pro_cascade_log:
                img_list.append(self.rect2img(rect, frame))
                rect_list.append(rect)

        if len(self.front_cascade_log) > 0:
            for rect in self.front_cascade_log:
                img_list.append(self.rect2img(rect, frame))
                rect_list.append(rect)

        if len(self.cnn_recoglog) > 0:
            img_list.append(self.rect2img(self.cnn_recoglog, frame))
            rect_list.append(rect)        
        return img_list,rect_list

    def rect2img(self,rect,frame):
        zero_check = lambda x: 0 if x < 0 else x
        height_over_check = lambda x: self.HEIGHT if x > self.HEIGHT else x
        width_over_check = lambda x: self.WIDTH if x > self.WIDTH else x

        sx = zero_check(rect[0] - self.MARGIN)
        sy = zero_check(rect[1] - self.MARGIN)

        ex = width_over_check(rect[0] + rect[2] + self.MARGIN)
        ey = height_over_check(rect[1] + rect[3] + self.MARGIN)

        return frame[sy:ey,sx:ex]

    def combine_img(self, frame, img, x,y):
        zero_check = lambda x: 0 if x < 0 else x
        height_over_check = lambda x: self.HEIGHT if x > self.HEIGHT else x
        width_over_check = lambda x: self.WIDTH if x > self.WIDTH else x

        bias = 200
        x = zero_check(x-bias)
        y = zero_check(y-bias)

        img = cv2.resize(img, (int(self.WIDTH/1.2), int(self.HEIGHT/1.2)))
        height,width = img.shape[:2]
        
        ex = width_over_check(x+width)
        ey = height_over_check(y+height)

        mask = img[:,:,3]   #これでアルファチャンネルのみの行列が抽出。
        mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)  #maskを3色分にした。0 ->(B0, G0, R0)になる。
        mask = mask/255.0   #正規化

        img = img[:,:,:3]
        frame_float = frame.astype(np.float64)

        frame_float[y:ey, x:ex] *= 1 - mask[:(ey-y),:(ex-x)]
        frame_float[y:ey, x:ex] += img[:(ey-y),:(ex-x)] * mask[:(ey-y),:(ex-x)]

        return frame_float

    def capture_camera(self,size=None):
        cap_flag = False
        cv2.namedWindow("Get_faceimg")
        cap = cv2.VideoCapture(0)

        #-1をつけることでαチャネルを読み込む
        waraiotoko = cv2.imread('warai_flat.png',-1)
        check_rate = 0
                
        while True:
            # retは画像を取得成功フラグ
            ret, frame = cap.read()
            frame = cv2.resize(frame, (self.WIDTH,self.HEIGHT))

            check_rate += 1
            #毎フレーム処理すると重いので適当なタイミングで検出処理を行う
            if check_rate > 3:
                image_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                pro_facerect,fro_facerect = self.execute_cascade(image_gray)
                self.set_cascade_log(pro_facerect,fro_facerect)
                check_rate = 0

            imgs,rects = self.sample_img(frame)
            rects, areas = self.rect_cnn(imgs, rects)
            
            self.set_cnn_rectlog(rects, areas)

            if len(self.cnn_recoglog) > 0 and self.exit_prob/self.judge > 0.8:
                frame = self.combine_img(frame,waraiotoko,
                self.cnn_recoglog[0],self.cnn_recoglog[1])

            frame= frame.astype(np.uint8)
            cv2.imshow('camera capture', frame)

            k = cv2.waitKey(10) # 10msec待つ
            if k == 27: # ESCキーで終了
                break
            if k == 0x73:
               cv2.imwrite('result/' + 'res' + '.png',frame)

        # キャプチャを解放する
        cap.release()
        cv2.destroyAllWindows()

if __name__ == '__main__':
    face_recog = FaceRecognition(size = (800,600))
    face_recog.cascade_init()
    face_recog.cnn_init()
    face_recog.load_cnnmodel('models/')
    face_recog.capture_camera()