欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Tensorflow】人脸128个关键点识别基于卷积神经网络实现

程序员文章站 2022-07-12 08:53:17
...

引言:

卷积神经网络

  • 卷积神经网络最早是为了解决图像识别的问题,现在也用在时间序列数据和文本数据处理当中,卷积神经网络对于数据特征的提取不用额外进行,在对网络的训练的过程当中,网络会自动提取主要的特征.
  • 卷积神经网络直接用原始图像的全部像素作为输入,但是内部为非全连接结构.因为图像数据在空间上是有组织结构的,每一个像素在空间上和周围的像素是有关系的,和相距很远的像素基本上是没什么联系的,每个神经元只需要接受局部的像素作为输入,再将局部信息汇总就能得到全局信息. 权值共享和池化两个操作使网络模型的参数大幅的减少,提高了模型的训练效率.

卷积神经网络主要特点

  • 权值共享: 在卷积层中可以有多个卷积核,每个卷积核与原始图像进行卷积运算后会映射出一个新的2D图像,新图像的每个像素都来自同一个卷积核.这就是权值共享.
  • 池化: 降采样,对卷积(滤波)后,经过**函数处理后的图像,保留像素块中灰度值最高的像素点(保留最主要的特征),比如进行 2X2的最大池化,把一个2x2的像素块降为1x1的像素块.

进入内容

首先项目

实现架构和结果图

【Tensorflow】人脸128个关键点识别基于卷积神经网络实现

1.训练模型

train.py

"""
训练模型
"""
from keras.callbacks import TensorBoard, ModelCheckpoint
from generator import get_train_test, create_generator  # 数据切分,创建生成器
from model_framework.frontend import get_model  # 导入模型


def train():
    """
    功能:训练模型,并保存模型
    """
    model = get_model((64, 64, 3)) # 获取模型
    X_train, X_test, y_train, y_test= get_train_test('data/dataset') #   完成数据集切分
    tbCallBack = TensorBoard( log_dir='./logs')
    model_checkpoint = ModelCheckpoint(f'trained_model/checkpoint_model.h5',
                                       monitor='val_loss',
                                       verbose=0,
                                       save_weights_only=False,
                                       save_best_only=True) # 注:save_weights_only = False,表示保存的由ModelCheckpoint()保存的模型既可以用load_model加载,也可以用load_weights加载

    model.fit_generator(create_generator( X_train, y_train, 40),
                        steps_per_epoch=50,
                        epochs=30,
                        validation_data=create_generator( X_test,y_test,40),
                        validation_steps=10,
                        callbacks=[tbCallBack, model_checkpoint]) # TODO完成训练模型代码

    model.save_weights('trained_model/weight.h5') # 保存权重
    model.save( './trained_model/model.h5')#   完成保存模型文件代码



if __name__ == '__main__':
    train()

【Tensorflow】人脸128个关键点识别基于卷积神经网络实现

 

2.数据处理相关功能


import os
import cv2
import numpy as np
from sklearn.model_selection import train_test_split


def read_img(path):
    """
    功能:读取图片
    # 参数:
        path: 数据集的路径
    # 返回:
        res: 不同人的图片。
    """
    res = []

    for (root, dirs, files) in os.walk(path):
        if files:
            tmp = []
            files = np.random.choice(files, 4)
            for f in files:
                img = os.path.join(root, f)
                image = cv2.imread(img)
                image = cv2.resize(image, (64, 64),
                                   interpolation=cv2.INTER_CUBIC)
                image = np.array(image, dtype='float32')
                image /= 255.
                tmp.append(image)

            res.append(tmp)

    return res


def get_paris(path):
    """
    功能:构造用于训练的成对数据
    # 参数:
        path: 数据集的路径
    # 返回:
        sm1: 一对数据中的第一个对象
        sm2: 一对数据中的第二个对象
        y1: 成对数据的标签,相同为1,不同为0.
    """
    sm1, sm2, df1, df2 = [], [], [], []
    res = read_img(path)

    persons = len(res)

    for i in range(persons):
        for j in range(i, persons):
            p1 = res[i]
            p2 = res[j]

            if i == j:
                for pi in p1:
                    for pj in p2:
                        sm1.append(pi)
                        sm2.append(pj)
            else:
                df1.append(p1[0])
                df2.append(p2[0])

    df1 = df1[:len(sm1)]
    df2 = df2[:len(sm2)]
    y1 = list(np.zeros(len(sm1)))
    y2 = list(np.ones(len(df1)))

    sm1.extend(df1)
    sm2.extend(df2)
    y1.extend(y2)

    return sm1, sm2, y1


def create_generator(x, y, batch):
    """
    功能:构造数据生成器
    # 参数:
        x: 数据
        y: 标签
        batch: 数据生成器每次生成数据的个数
    # 返回:
        [x1, x2]: 成对数据
        yb: 数据标签
    """
    while True:
        index = np.random.choice(len(y), batch) # 每次选择batch个数据索引
        x1, x2, yb = [], [], []
        for i in index:
            x1.append(x[i][0])
            x2.append(x[i][1])
            yb.append(y[i])
        x1 = np.array(x1)
        x2 = np.array(x2)

        yield [x1, x2], yb


def get_train_test(path):
    """
    功能:切分数据集
    # 参数:
        path: 数据集的路径
    # 返回:
        X_train: 用于训练的数据
        X_test: 用于测试的数据
        y_train: 用于训练的标签
        y_test: 用于测试的标签
    """
    im1, im2, y = get_paris(path) # 构造成对数据
    im = list(zip(im1, im2))

    X_train, X_test, y_train, y_test = train_test_split(
        im, y, test_size=0.33)  # 利用sklearn切分数据集
    return X_train, X_test, y_train, y_test

3.工具包相关内容

"""
工具包:提取特征、图像处理
"""
import cv2
import numpy as np
from model_framework.frontend import get_model,contrastive_loss
from keras.models import Model,load_model

from keras.applications.mobilenet import relu6,DepthwiseConv2D


def get_feature_model(url,mode = 'weight'):
    """
    功能:获取提取特征模型
    # 参数:
        url:模型或模型权重的路径
        mode:'weight'或'model_framework'
    # 返回:
        feat_model: 返回提取特征模型
    """
    if mode == 'weight':  # 加载权重
        model = get_model((64, 64, 3),plot_model_path='data/images/face_net.png')
        model.load_weights(url)
    elif mode == 'model': # 加载模型  注:加载模型时,若包含自定义层或自定义对象时,需要使用custom_objects参数。
        model = load_model(url,custom_objects={
                                     'contrastive_loss': contrastive_loss,
                                     'relu6': relu6,
                                     'DepthwiseConv2D':DepthwiseConv2D})

    feat_model = Model(inputs=model.get_layer('model_1').get_input_at(0),outputs=model.get_layer('model_1').get_output_at(0))
    return feat_model


def process_image(img):
    """
    功能:预处理图像

    # 返回:
        image: 处理后的图像
    """
    image = cv2.resize(img, (64, 64),
                       interpolation=cv2.INTER_CUBIC)  # 重新设置图像大小
    image = np.array(image, dtype='float32') # 将图像格式改为array
    image /= 255. # 归一化
    image = np.expand_dims(image, axis=0) # 增加维度
    return image

4.用到两个模型框架

siamese network 和 MobileNet v2 模型框架的具体使用如下

"""
前端模型,siamese network
功能:该网络使得相似特征距离更近,否则尽可能远。
"""
from .backend import MobileNetv2 # 后端模型,用来提取人脸特征。
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam


def euclidean_distance(inputs):
    """
    欧氏距离
    功能:该函数计算两个特征之间的欧氏距离。
    # 参数:
        inputs: 两个特征,list类型.
    # 返回:
        Output: 欧氏距离,double类型.
    """
    u, v = inputs
    return K.sqrt(K.sum((K.square(u - v)), axis=1, keepdims=True))


def contrastive_loss(y_true, y_pred):
    """
    对比损失
    功能:计算对比损失.

    # 参数:
        y_true:表示样本是否匹配的标签,y_true = 1 表示匹配,0表示不匹配.整数类型。
        y_pred:欧氏距离,double类型.
    # Returns
        Output:contrastive loss,double类型.
    """
    margin = 1. # 阈值
    return K.mean((1. - y_true) * K.square(y_pred) + y_true * K.square(K.maximum(margin - y_pred, 0.)))


def get_model(shape,plot_model_path='data/images/face_net.png'):
    """
    人脸识别网络
    功能:该网络使得相似特征距离更近,否则尽可能远。

    # 参数:
        shape: 输入图像input的形状,彩色图像或灰度图像.
    # 返回:
        模型model.
    """
    mn = MobileNetv2(shape) # 后端模型,用来提取特征。

    im1 = Input(shape=shape)
    im2 = Input(shape=shape)

    feat1 = mn(im1) # 提取特征,feat1和feat2分别为提取到的特征。
    feat2 = mn(im2)

    distance = Lambda(euclidean_distance)([feat1, feat2])# Lambda层,在此处用于欧氏距离的计算,该方式为函数式编程。

    face_net = Model(inputs=[im1, im2], outputs=distance) # 构造siamese network模型
    adam = Adam(lr = 0.0012,beta_1=0.9, beta_2=0.999)
    face_net.compile(optimizer=adam, loss=contrastive_loss) # 编译模型,损失函数为contrastive_loss

    return face_net
"""
MobileNet v2 模型框架
"""
from keras.models import Model
from keras.layers import Input, Conv2D, GlobalAveragePooling2D
from keras.layers import Activation, BatchNormalization, add, Reshape
from keras.regularizers import l2
from keras.layers.advanced_activations import LeakyReLU
from keras.applications.mobilenet import relu6, DepthwiseConv2D
from keras import backend as K


def _conv_block(inputs, filters, kernel, strides):
    channel_axis = 1 if K.image_data_format() == 'channels_first' else -1
    x = Conv2D(filters, kernel, padding='same', strides=strides)(inputs)
    x = BatchNormalization(axis=channel_axis)(x)
    return Activation(relu6)(x)


def _bottleneck(inputs, filters, kernel, t, s, r=False):
    channel_axis = 1 if K.image_data_format() == 'channels_first' else -1
    tchannel = K.int_shape(inputs)[channel_axis] * t

    x = _conv_block(inputs, tchannel, (1, 1), (1, 1))

    x = DepthwiseConv2D(kernel, strides=(s, s),
                        depth_multiplier=1, padding='same')(x)
    x = BatchNormalization(axis=channel_axis)(x)
    x = Activation(relu6)(x)

    x = Conv2D(filters, (1, 1), strides=(1, 1), padding='same')(x)
    x = BatchNormalization(axis=channel_axis)(x)

    if r:
        x = add([x, inputs])
    return x


def _inverted_residual_block(inputs, filters, kernel, t, strides, n):
    x = _bottleneck(inputs, filters, kernel, t, strides)
    for i in range(1, n):
        x = _bottleneck(x, filters, kernel, t, 1, True)
    return x


def MobileNetv2(input_shape):
    """
    MobileNetv2框架
    # 参数:
        input_shape: 输入值的shape
    # 返回:
        model_framework:MobileNetv2模型
    """

    inputs = Input(shape=input_shape, name='single_input')
    x = _conv_block(inputs, 32, (3, 3), strides=(2, 2))

    x = _inverted_residual_block(x, 64, (3, 3), t=5, strides=2, n=2)
    x = _inverted_residual_block(x, 128, (3, 3), t=5, strides=2, n=2)
    x = _inverted_residual_block(x, 256, (3, 3), t=5, strides=1, n=1)

    x = _conv_block(x, 1280, (1, 1), strides=(1, 1))
    x = GlobalAveragePooling2D()(x)
    x = Reshape((1, 1, 1280))(x)
    x = Conv2D(512, (1, 1), padding='same', kernel_regularizer=l2(5e-4))(x)
    x = LeakyReLU(alpha=0.1)(x)
    x = Conv2D(128, (1, 1), padding='same', kernel_regularizer=l2(5e-4))(x)

    output = Reshape((128,), name='feat_out')(x)

    model = Model(inputs, output)
    return model

5.结果输入进行可视化操作

"""
结果可视化
"""
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from utils.utils import get_feature_model, process_image


def plot_reduce_dimension(model):
    """Plot reduced dimension result wiht t-SNE.
    功能:用t-SNE算法对预测结果降维,并可视化显示
    # 参数:
        model_framework: Model, 提取特征的模型
    """
    outputs = []
    n = 5 # 类别
    paths = 'data/dataset'
    dirs = np.random.choice(os.listdir(paths), n) # 随机选择n个类别

    for d in dirs:
        p = os.path.join(paths, str(d))
        files = os.listdir(p)
        if files:
            for f in files:
                img = os.path.join(p, f) # 获取图像url
                image = cv2.imread(img) # 读取图像
                image = process_image(image) # 图像预处理
                output = model.predict(image)[0] # 显示预测结果
                outputs.append(output)

    embedded = TSNE(2).fit_transform(outputs) # 进行数据降维,降成两维

    colors = ['b', 'g', 'r', 'k', 'y']

    for i in range(n):
        m, n = i * 20, (i + 1) * 20
        plt.scatter(embedded[m: n, 0], embedded[m: n, 1],
                    c=colors[i], alpha=0.5)

    plt.title('T-SNE')
    plt.grid(True)
    plt.show()


def compare_distance(model,paths):
    """
    功能:对比人与人之间的不同,即计算欧氏距离并可视化
    # 参数:
        model_framework: 特征提取模型
    """
    dists = []
    outputs = []
    paths = paths # 预测数据的地址

    for img in os.listdir(paths):
        # img = paths + img + '.jpg' # 获取图片路径
        img = os.path.join(paths,img)
        image = cv2.imread(img) # 读取图片
        image = process_image(image) # 图片预处理

        output = model.predict(image) # 预测结果
        outputs.append(output)

    vec1 = outputs[0]
    for vec2 in outputs:
        dist = np.linalg.norm(vec1 - vec2) # 计算L2范数,即欧氏距离
        dists.append(dist)

    print(dists[1:])

    plt.bar(range(1, 6), (dists[1:]), color='lightblue')
    plt.xlabel('Person')
    plt.ylabel('Euclidean distance')
    plt.title('Similarity')
    plt.grid(True)
    plt.show()


if __name__ == '__main__':
    # model_framework = get_feature_model(url = 'trained_model/weight.h5',mode='weight') # 加载模型
    model = get_feature_model(url='trained_model/model.h5 ',mode = 'model') #  完成加载模型代码

    # TODO完成可视化提取的样本特征代码
    plot_reduce_dimension(model)
    # TODO完成可视化人脸的相似度
    compare_distance(model,'./data/images/person')

以上就是本文的全部内容,希望对大家的学习有所帮助。