1. 程式人生 > >基於OpenCV和tensorflow的人臉關鍵點檢測

基於OpenCV和tensorflow的人臉關鍵點檢測

基於OpenCV和tensorflow的人臉關鍵點檢測


轉載請註明出處https://blog.csdn.net/weixin_40336829/article/details/85456271

引言

最近利用tengine平臺做了一個移植到andriod端的app。本人負責訓練人臉關鍵點檢測的模型。考慮到tengine平臺對tensorflow支援較少,故實現的模型也較為簡單。這裡主要記錄基於tensorflow生成ckpt模型和pb模型的一些方法以及結合opencv在PC端實現實時的人臉關鍵點檢測。

人臉關鍵點資料集

資料集主要使用了kaggle上Facial Keypoints Detection網址: [https://www.kaggle.com/c/facial-keypoints-detection].比賽提供的資料集。該資料集包含包括7,049幅影象,96 x 96畫素的灰度影象。預測15個人臉關鍵點。資料集中每一張圖片剛好包含整個人臉,需要檢測的15個人臉關鍵點如下圖所示。
資料集圖片:
在這裡插入圖片描述

tensorflow模型搭建

這次的人臉關鍵點檢測可以看作是一個迴歸問題。本文使用3層CNN和3層全連線層作為一個baseline。
首先先對資料集進行預處理 預處理程式碼如下.input_data函式主要是將輸入96*96圖片的畫素值歸一化到[0,1]的區間內,而要預測的15個關鍵點的座標(x,y)也歸一化到[0,1]的區間內。

import pandas as pd
import numpy as np

def input_data(test=False):
    file_name = TEST_FILE if test else TRAIN_FILE
    df = pd.read_csv(file_name)
    cols = df.columns[:-1]
    #dropna()是丟棄有缺失資料的樣本,這樣最後7000多個樣本只剩2140個可用的。
    df = df.dropna()    
    df['Image'] = df['Image'].apply(lambda img: np.
fromstring(img, sep=' ') / 255.0) X = np.vstack(df['Image']) X = X.reshape((-1,96,96,1)) if test: y = None else: y = df[cols].values / 96.0 #將y值縮放到[0,1]區間 return X, y

定義卷積池化操作

import tensorflow as tf
#根據給定的shape定義並初始化卷積核的權值變數
def weight_variable(shape,namew='w'):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial,name=namew)

#根據shape初始化bias變數
def bias_variable(shape,nameb='b'):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial,name=nameb)

#定義卷積操作
def conv2d(x,W):
    return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='VALID')
#定義池化操作
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1], padding='SAME')

pb檔案的生成

pb檔案相比於ckpt檔案較為簡化,所有的網路結構及引數均儲存為一個pb檔案。利用tensorflow訓練模型並生成.pb檔案,這裡主要介紹一個簡單的方法來生成。

//生成pb檔案所需要的graph_util庫,用於在pb模型中儲存模型的名稱,方便呼叫pb模型
from tensorflow.python.framework import graph_util

下面為完整的模型訓練及儲存程式碼

import tensorflow as tf

TRAIN_FILE = 'training.csv'
TEST_FILE = 'test.csv'
SAVE_PATH = './model2' #模型儲存路徑
VALIDATION_SIZE = 100    #驗證集大小
EPOCHS = 100           #迭代次數
BATCH_SIZE = 64          #每個batch大小,稍微大一點的batch會更穩定
EARLY_STOP_PATIENCE = 20 #控制early stopping的引數

#儲存模型函式
def save_model(saver,sess,save_path):
    path = saver.save(sess, save_path)
    print('model save in :{0}'.format(path))

with tf.Session(graph=tf.Graph()) as sess:
	#定義的模型,生成pb檔案需要對輸入x和輸出y命名,這裡將輸入x命名為input,輸入y命名為output,同時如果有dropout引數也需進行命名。
    x = tf.placeholder("float", shape=[None, 96, 96, 1],name='input')#輸入佔位
    y_ = tf.placeholder("float", shape=[None, 30],name='y')#輸出佔位
    keep_prob = tf.placeholder("float",name='keep_prob')#dropout概率值
    W_conv1 = weight_variable([3, 3, 1, 32],'w1')#323*3*1的卷積核
    b_conv1 = bias_variable([32],'b1')
    h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)
    W_conv2 = weight_variable([2, 2, 32, 64],'w2')#643*3*32的卷積核
    b_conv2 = bias_variable([64],'b2')
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)
    W_conv3 = weight_variable([2, 2, 64, 128],'w3')#1283*3*32的卷積核
    b_conv3 = bias_variable([128],'b3')
    h_conv3 = tf.nn.relu(conv2d(h_pool2, W_conv3) + b_conv3)
    h_pool3 = max_pool_2x2(h_conv3)
    W_fc1 = weight_variable([11 * 11 * 128, 500],'wf1')#全連線層
    b_fc1 = bias_variable([500],'bf1')
   h_pool3_flat = tf.reshape(h_pool3, [-1, 11 * 11 * 128])#把第三層卷積的輸出一維向量
    h_fc1 = tf.nn.relu(tf.matmul(h_pool3_flat, W_fc1) + b_fc1)
    W_fc2 = weight_variable([500, 500],'wf2')
    b_fc2 = bias_variable([500],'bf2')
    h_fc2 = tf.nn.relu(tf.matmul(h_fc1, W_fc2) + b_fc2,name='hfc2')
    h_fc2_drop = tf.nn.dropout(h_fc2, keep_prob)
    W_fc3 = weight_variable([500, 30],'wf3')
    b_fc3 = bias_variable([30],'bf3')
    y_conv = tf.add(tf.matmul(h_fc2_drop, W_fc3) + b_fc3,0.0,name='output')
    #以均方根誤差為代價函式,Adam為
    rmse = tf.sqrt(tf.reduce_mean(tf.square(y_ - y_conv)))
    train_step = tf.train.AdamOptimizer(1e-3).minimize(rmse)

    #變數都要初始化 
    sess.run(tf.initialize_all_variables())
    X,y = input_data()
    X_valid, y_valid = X[:VALIDATION_SIZE], y[:VALIDATION_SIZE]
    X_train, y_train = X[VALIDATION_SIZE:], y[VALIDATION_SIZE:]

    best_validation_loss = 1000000.0
    current_epoch = 0
    TRAIN_SIZE = X_train.shape[0]
    train_index = list(range(TRAIN_SIZE))
    np.random.shuffle(train_index)
    X_train, y_train = X_train[train_index], y_train[train_index]

    saver = tf.train.Saver()
    print('begin training..., train dataset size:{0}'.format(TRAIN_SIZE))
    for i in range(EPOCHS):
    	#進行每一輪訓練都需將模型的'input','keep_prob','output'儲存。
        constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ['input','keep_prob','output'])
        
        np.random.shuffle(train_index)  #每個epoch都shuffle一下效果更好
        X_train, y_train = X_train[train_index], y_train[train_index]

        for j in range(0,TRAIN_SIZE,BATCH_SIZE):
            print ('epoch {0}, train {1} samples done...'.format(i,j))

            train_step.run(feed_dict={x:X_train[j:j+BATCH_SIZE], 
                y_:y_train[j:j+BATCH_SIZE], keep_prob:0.5})

            
        train_loss = rmse.eval(feed_dict={x:X_train, y_:y_train, keep_prob: 1.0})
        validation_loss = rmse.eval(feed_dict={x:X_valid, y_:y_valid, keep_prob: 1.0})
        print('epoch {0} done! validation loss:{1}'.format(i, train_loss*96.0))        
        print('epoch {0} done! validation loss:{1}'.format(i, validation_loss*96.0))
        if validation_loss < best_validation_loss:
            best_validation_loss = validation_loss
            current_epoch = i
            #儲存pb檔案。
            with tf.gfile.FastGFile(SAVE_PATH+'model.pb', mode='wb') as f: #模型的名字是model.pb
                f.write(constant_graph.SerializeToString())

        elif (i - current_epoch) >= EARLY_STOP_PATIENCE:
            print ('early stopping')
            break

從上面的程式碼可以看到在訓練是儲存點.pb模型需要注意3點
(1)對模型的輸入及輸出需要命名,如果有dropout等引數也需命名儲存。

 x = tf.placeholder("float", shape=[None, 96, 96, 1],name='input')#輸入命名
 keep_prob = tf.placeholder("float",name='keep_prob')#dropout概率值
 y_conv = tf.add(tf.matmul(h_fc2_drop, W_fc3) + b_fc3,0.0,name='output')#輸出

(2)在每一次訓練時均需宣告在pb模型需要儲存的命名。即上面的變數

#進行每一輪訓練都需將模型的'input','keep_prob','output'儲存。
        constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ['input','keep_prob','output'])

(3)在儲存模型時執行下列語句

#儲存pb檔案。模型的名字是model.pb
with tf.gfile.FastGFile(SAVE_PATH+'model.pb', mode='wb') as f: 
       f.write(constant_graph.SerializeToString())

執行上面完整的訓練及儲存程式碼,可以生成.pb模型。

ckpt檔案的生成

生成ckpt檔案,也需要注意幾點。定義模型時需要將模型的每一層進行命名,如上面定義模型時對每一層的命名。同時在將模型儲存.pb檔案的程式碼

#儲存pb檔案。模型的名字是model.pb
with tf.gfile.FastGFile(SAVE_PATH+'model.pb', mode='wb') as f: 
       f.write(constant_graph.SerializeToString())

改為

#'model/model1.ckpt'為模型儲存路徑及名稱。
save_model(saver,sess,'model/model1.ckpt')   

即可。

結合pb檔案和opencv的人臉關鍵點實時檢測

通過上次完整的模型訓練及儲存程式碼,可以生成model2model.pb的檔案,本文將opencv自帶的人臉檢測器haarcascade_frontalface_default.xml,實現圖片中的人臉檢測,框出人臉,並利用model2model.pb對框出的人臉進行特徵點預測

import tensorflow as tf
import cv2
import numpy as np

with tf.Graph().as_default():
        output_graph_def = tf.GraphDef()
		#開啟.pb檔案
        with open('model2model.pb', "rb") as f:
            output_graph_def.ParseFromString(f.read())
            _ = tf.import_graph_def(output_graph_def, name="")

        with tf.Session() as sess:
        #讀取.pb檔案中的模型引數
            input_x = sess.graph.get_tensor_by_name("input:0")
            print(input_x)
            keep_prob=sess.graph.get_tensor_by_name("keep_prob:0")
            output_y=sess.graph.get_tensor_by_name("output:0")
            print(output_y)
            #讀取人臉檢測器
            face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
            #開啟攝像頭
            cap=cv2.VideoCapture(0)
            while True:
                #sess = tf.Session()
                ret,img=cap.read()
                #檢察攝像頭是否採集到影象
                if ret:
                    gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
                    #檢測人臉
                    faces = face_cascade.detectMultiScale(gray,1.1,5)
                    #對每一張人臉進行關鍵點檢測
                    if len(faces)>0:
                        for faceRect in faces:
                            x,y,w,h = faceRect
                            crop=img[x:x+w,y:y+h]
                            crop = cv2.resize(crop, (96, 96), interpolation=cv2.INTER_CUBIC )
                            crop=crop/255.0
                            #關鍵點檢測結果
                            output_y1= sess.run(output_y, feed_dict={input_x:np.reshape(crop, [-1, 96, 96, 1]),keep_prob:1.0})
                            pt = np.vstack(np.split(output_y1[0]*96,15)).T
                            #顯示關鍵點檢測結果
                            for i in range(15):
                                cv2.circle(img,(int(pt[0][i]/96*w+x),int(pt[1][i]/96*h+y)),2, (255, 0, 0),-1)
                            print(crop.shape)
                    cv2.imshow("img",img)
                    if cv2.waitKey(1) & 0xFF == ord('q'):
                        break
        
            cap.release()
            cv2.destroyAllWindows()    

執行程式可得到下圖的結果
在這裡插入圖片描述
使用.pb檔案需要注意,使用該pb檔案的tensorflow版本必須大於等於訓練時的版本。呼叫時只需要將之前儲存在pb檔案中的input和output等變數取出來,再喂值即可。

CKPT檔案的呼叫

這裡提供一種簡單的方法,對ckpt模型的呼叫不限於此方法。

import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

TRAIN_FILE = 'training.csv'
TEST_FILE = 'test.csv'

def input_data(test=False):
    file_name = TEST_FILE if test else TRAIN_FILE
    df = pd.read_csv(file_name)
    cols = df.columns[:-1]
    #dropna()是丟棄有缺失資料的樣本,這樣最後7000多個樣本只剩2140個可用的。
    df = df.dropna()    
    df['Image'] = df['Image'].apply(lambda img: np.fromstring(img, sep=' ') / 255.0)
    X = np.vstack(df['Image'])
    X = X.reshape((-1,96,96,1))
    if test:
        y =