基于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 = None
    else:
        y = df[cols].values / 96.0       #将y值缩放到[0,1]区间
    return X, y

#定义卷积操作
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')

#定义打开model1.ckpt.meta等文件
sess = tf.Session()
saver = tf.train.import_meta_graph('./model/model1.ckpt.meta')
saver.restore(sess,tf.train.latest_checkpoint('./model'))
 
X,y = input_data(test=True)
y_pred = [] 

 
x_image = X[...,0][1]
x_image = x_image.reshape((-1,96,96,1))
x_image=tf.image.convert_image_dtype(x_image,tf.float32)
#根据定义模型时对各个变量的命名,将模型复现一遍
W_conv1 = sess.run('w1:0')
b_conv1 = sess.run('b1:0')
cov1=tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)
p1=max_pool_2x2(cov1) 
W_conv2 = sess.run('w2:0')
b_conv2 = sess.run('b2:0')
cov2=tf.nn.relu(conv2d(p1,W_conv2)+b_conv2)
p2=max_pool_2x2(cov2)
W_conv3 = sess.run('w3:0')
b_conv3 = sess.run('b3:0')
cov3=tf.nn.relu(conv2d(p2,W_conv3)+b_conv3)
p3=max_pool_2x2(cov3) 
Wf1 = sess.run('wf1:0')
bf1 = sess.run('bf1:0')
h_pool2_flat = tf.reshape(p3, [-1, 11 * 11 * 128])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, Wf1) + bf1)
Wf2 = sess.run('wf2:0')
bf2 = sess.run('bf2:0')
h_fc2 = tf.nn.relu(tf.matmul(h_fc1, Wf2) + bf2)
h_fc2_drop = tf.nn.dropout(h_fc2, keep_prob=1.0) 
Wf3 = sess.run('wf3:0')
bf3 = sess.run('bf3:0')
y_conv = tf.nn.relu(tf.matmul(h_fc2_drop, Wf3) + bf3)

result=sess.run(y_conv)
print(sess.run(y_conv))
print("the result of predict is:",np.argmax(result))
x_image= X[...,0][1]
plt.imshow(x_image)
pt = np.vstack(np.split(result[0]*96,15)).T
plt.scatter(pt[0],pt[1],c='red',marker = '*')   

下图为上述程序的结果
在这里插入图片描述
以上便是这篇博客的全部内容。

猜你喜欢

转载自blog.csdn.net/weixin_40336829/article/details/85456271