相关文章:
【深度学习项目一】全连接神经网络实现mnist数字识别
【深度学习项目二】卷积神经网络LeNet实现minst数字识别
【深度学习项目三】ResNet50多分类任务【十二生肖分类】
『深度学习项目四』基于ResNet101人脸特征点检测
项目链接:https://aistudio.baidu.com/aistudio/projectdetail/1932295

一、人脸检测原理简介

人脸关键点检测,是输入一张人脸图片,模型会返回人脸关键点的一系列坐标,从而定位到人脸的关键信息。

1.1 图像分类和回归的区别

1.2 损失函数

图像分类CrossEntropyLoss :信息熵的计算

loss

j

=

input

[

class

]

+

log

(

i

=

0

K

exp

(

input

i

)

)

,

j

=

1

,

,

K

\operatorname{loss}_{j}=-\text { input }[\text { class }]+\log \left(\sum_{i=0}^{K} \exp \left(\text { input }_{i}\right)\right), j=1, \ldots, K

lossj​=− input [ class ]+log(∑i=0K​exp( input i​)),j=1,…,K

人脸关键点检测: L1Loss、L2Loss、SmoothL1Loss :距离的计算

Loss_1:

loss

(

x

,

y

)

=

1

n

i

=

1

n

y

i

f

(

x

i

)

\operatorname{loss}(x, y)=\frac{1}{n} \sum_{i=1}^{n}\left|y_{i}-f\left(x_{i}\right)\right|

loss(x,y)=n1​∑i=1n​∣yi​−f(xi​)∣

Loss_2:

loss

(

x

,

y

)

=

1

n

i

=

1

n

(

y

i

f

(

x

i

)

)

2

\operatorname{loss}(x, y)=\frac{1}{n} \sum_{i=1}^{n}\left(y_{i}-f\left(x_{i}\right)\right)^{2}

loss(x,y)=n1​∑i=1n​(yi​−f(xi​))2

Loss_3:分段loss

loss

(

x

,

y

)

=

1

n

i

=

1

n

{

.

5

(

y

i

f

(

x

i

)

)

2

,

if

y

i

f

(

x

)

y

i

f

(

x

i

)

0.5

,

otherwise

\operatorname{loss}(x, y)=\frac{1}{n} \sum_{i=1}^{n}\left\{\begin{array}{ll} .5 *\left(y_{i}-f\left(x_{i}\right)\right)^{2}, & \text { if } \mid y_{i}-f(x)| \\ \left|y_{i}-f\left(x_{i}\right)\right|-0.5, & \text { otherwise } \end{array}\right.

loss(x,y)=n1​∑i=1n​{.5∗(yi​−f(xi​))2,∣yi​−f(xi​)∣−0.5,​ if ∣yi​−f(x)∣ otherwise ​

有利于快速收敛!

1.3 评估指标 NME

# 环境导入
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg import cv2
import paddle #paddle.set_device('gpu') # 手动设置设置为GPU import warnings
warnings.filterwarnings('ignore') # 忽略 warning

二、数据准备

2.1 下载数据集

本次实验所采用的数据集来源为github的开源项目

加载后可以直接使用下面的命令解压。
unzip是一个常见的解压缩命令:

-l:显示压缩文件内所包含的文件;

-t:检查压缩文件是否正确;

-o:不必先询问用户,unzip执行后覆盖原有的文件;

-n:解压缩时不要覆盖原有的文件;

-q:执行时不显示任何信息;

-d<目录>:指定文件解压缩后所要存储的目录;
解压后的数据集结构为

data/
|—— test
| |—— Abdel_Aziz_Al-Hakim_00.jpg
... ...
|—— test_frames_keypoints.csv
|—— training
| |—— Abdullah_Gul_10.jpg
... ...
|—— training_frames_keypoints.csv

其中,trainingtest 文件夹分别存放训练集和测试集。training_frames_keypoints.csvtest_frames_keypoints.csv 存放着训练集和测试集的标签。首先看一下训练集的标签training_frames_keypoints.csv 文件,是如何定义的

key_pts_frame = pd.read_csv('data/training_frames_keypoints.csv') # 读取数据集
print('Number of images: ', key_pts_frame.shape[0]) # 输出数据集大小
key_pts_frame.head(5) # 看前五条数据


上表中每一行都代表一条数据,其中,第一列是图片的文件名,之后从第0列到第135列,就是该图的关键点信息。因为每个关键点可以用两个坐标【横纵坐标】表示,所以 136/2 = 68,就可以看出这个数据集为68点人脸关键点数据集。

Tips1: 目前常用的人脸关键点标注,有如下点数的标注

  • 5点
  • 21点
  • 68点
  • 98点

Tips2:本次所采用的68标注,标注顺序如下

# 计算标签的均值和标准差,用于标签的归一化
key_pts_values = key_pts_frame.values[:,1:] # 取出标签信息
data_mean = key_pts_values.mean() # 计算均值
data_std = key_pts_values.std() # 计算标准差
print('标签的均值为:', data_mean)
print('标签的标准差为:', data_std)

标签的均值为: 104.4724870017331
标签的标准差为: 43.17302271754281

2.2 查看图像

对以下函数的几点解释:
len(key_pts)//2 :因为key_pts里面是一个128的一维数组,本次人脸检测是68个关键点,128个数据里面应该是两个两个一组,分别组成一个关键点的(x,y)坐标。

扩展,图像的坐标分布:
图像的坐标是从左上角开始,一般以水平向右为x轴正方向,竖直向下为y轴正方向。

def show_keypoints(image, key_pts):
"""
Args:
需要打印 image: 图像信息
key_pts: 关键点信息,
展示图片和关键点信息
"""
plt.imshow(image.astype('uint8')) # 展示图片信息
for i in range(len(key_pts)//2,):
plt.scatter(key_pts[i*2], key_pts[i*2+1], s=20, marker='.', c='b') # 展示关键点信息
# 展示多条数据

index = [5,10,15,20] # n为数据在表格中的索引
for n in index:
image_name = key_pts_frame.iloc[n, 0] # 获取图像名称
#key_pts = key_pts_frame.iloc[n, 1:].as_matrix() # 将图像label格式转为numpy.array的格式 会报错'Series' object has no attribute 'as_matrix'改成下面
key_pts = key_pts_frame.iloc[n, 1:].values #主要原因是库版本升级,'as_matrix()‘改为了’values’。
key_pts = key_pts.astype('float').reshape(-1) # 获取图像关键点信息
print("the image's name is : {}, key_pts is : {}".format(image_name, key_pts.shape)) # 打印图像信息
plt.figure(figsize=(5, 5)) # 展示的图像大小
images = show_keypoints(mpimg.imread(os.path.join('data/training/', image_name)), key_pts) # 展示图像与关键点信息
plt.show(images) # 展示图像


the image’s name is : Albert_Brooks_12.jpg, key_pts is : (136,)

the image’s name is : Paul_Otellini_01.jpg, key_pts is : (136,)

2.3 数据集定义

使用飞桨框架高层API的 ``paddle.io.Dataset`` 自定义数据集类,具体可以参考官网文档 [自定义数据集](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/02_paddle2.0_develop/02_data_load_cn.html#id3)。
import paddle
from paddle.io import Dataset BATCH_SIZE = 64
BATCH_NUM = 20 IMAGE_SIZE = (28, 28)
CLASS_NUM = 10 class MyDataset(Dataset):
"""
步骤一:继承paddle.io.Dataset类
"""
def __init__(self, num_samples):
"""
步骤二:实现构造函数,定义数据集大小
"""
super(MyDataset, self).__init__()
self.num_samples = num_samples def __getitem__(self, index):
"""
步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签)
"""
data = paddle.uniform(IMAGE_SIZE, dtype='float32')
label = paddle.randint(0, CLASS_NUM-1, dtype='int64') return data, label def __len__(self):
"""
步骤四:实现__len__方法,返回数据集总数目
"""
return self.num_samples # 测试定义的数据集
custom_dataset = MyDataset(BATCH_SIZE * BATCH_NUM) print('=============custom dataset=============')
for data, label in custom_dataset:
print(data.shape, label.shape)
break
# 按照Dataset的使用规范,构建人脸关键点数据集

from paddle.io import Dataset

class FacialKeypointsDataset(Dataset):
# 人脸关键点数据集
"""
步骤一:继承paddle.io.Dataset类
"""
def __init__(self, csv_file, root_dir, transform=None):
"""
步骤二:实现构造函数,定义数据集大小
Args:
csv_file (string): 带标注的csv文件路径
root_dir (string): 图片存储的文件夹路径
transform (callable, optional): 应用于图像上的数据处理方法
"""
self.key_pts_frame = pd.read_csv(csv_file) # 读取csv文件
self.root_dir = root_dir # 获取图片文件夹路径
self.transform = transform # 获取 transform 方法 def __getitem__(self, idx):
"""
步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签)
""" image_name = os.path.join(self.root_dir,
self.key_pts_frame.iloc[idx, 0]) #文件名
# 获取图像
image = mpimg.imread(image_name) # 图像格式处理,如果包含 alpha 通道,那么忽略它
if(image.shape[2] == 4):
image = image[:,:,0:3] # 获取关键点信息
#key_pts = self.key_pts_frame.iloc[idx, 1:].as_matrix() #第一列到最后一列,转为numpy
key_pts = self.key_pts_frame.iloc[idx, 1:].values
key_pts = key_pts.astype('float').reshape(-1) # [136, 1] 136个关键点 # 如果定义了 transform 方法,使用 transform方法
if self.transform:
image, key_pts = self.transform([image, key_pts]) # 转为 numpy 的数据格式
image = np.array(image, dtype='float32')
key_pts = np.array(key_pts, dtype='float32') return image, key_pts def __len__(self):
"""
步骤四:实现__len__方法,返回数据集总数目
"""
return len(self.key_pts_frame) # 返回数据集大小,即图片的数量

2.4 训练集可视化

实例化数据集并显示一些图像。

# 构建一个数据集类
face_dataset = FacialKeypointsDataset(csv_file='data/training_frames_keypoints.csv',
root_dir='data/training/') # 输出数据集大小
print('数据集大小为: ', len(face_dataset))
# 根据 face_dataset 可视化数据集
num_to_display = 3 for i in range(num_to_display): # 定义图片大小
fig = plt.figure(figsize=(20,10)) # 随机选择图片
rand_i = np.random.randint(0, len(face_dataset))
sample = face_dataset[rand_i] # 输出图片大小和关键点的数量
print(i, sample[0].shape, sample[1].shape) #图片和label # 设置图片打印信息
ax = plt.subplot(1, num_to_display, i + 1)
ax.set_title('Sample #{}'.format(i)) # 输出图片
show_keypoints(sample[0], sample[1])
数据集大小为:  3462
0 (99, 89, 3) (136,)
1 (259, 243, 3) (136,)
2 (275, 254, 3) (136,)



上述代码虽然完成了数据集的定义,但是还有一些问题,如:

  • 每张图像的大小不一样,图像大小需要统一以适配网络输入要求 /不然网络会爆炸
  • 图像格式需要适配模型的格式输入要求 / 转化成CHW
  • 数据量比较小,没有进行数据增强 /3000条数据

这些问题都会影响模型最终的性能,所以需要对数据进行预处理。

2.5 Transforms

对图像进行预处理,包括灰度化、归一化、重新设置尺寸、随机裁剪,修改通道格式等等,以满足数据要求;每一类的功能如下:

  • 灰度化:丢弃颜色信息,保留图像边缘信息;识别算法对于颜色的依赖性不强,加上颜色后鲁棒性会下降,而且灰度化图像维度下降(3->1),保留梯度的同时会加快计算。 #人脸特征对颜色依赖不强主要看特征不是色彩
  • 归一化:加快收敛
  • 重新设置尺寸:数据增强 对图像进行改变大小resize后,label会对应不上,也需要一一映射
  • 随机裁剪:数据增强
  • 修改通道格式:改为模型需要的结构
# 标准化自定义 transform 方法

class TransformAPI(object):
"""
步骤一:继承 object 类
"""
def __call__(self, data): """
步骤二:在 __call__ 中定义数据处理方法
""" processed_data = data
return processed_data
import paddle.vision.transforms.functional as F

class GrayNormalize(object):
# 将图片变为灰度图,并将其值放缩到[0, 1]
# 将 label 放缩到 [-1, 1] 之间 def __call__(self, data):
image = data[0] # 获取图片
key_pts = data[1] # 获取标签 image_copy = np.copy(image)
key_pts_copy = np.copy(key_pts) # 灰度化图片
gray_scale = paddle.vision.transforms.Grayscale(num_output_channels=3) #灰度正常情况通道数设置成为1 但是resnet50输入要求3因此设定
image_copy = gray_scale(image_copy) # 将图片值放缩到 [0, 1],归一化直接除就行
image_copy = image_copy / 255.0 # 将坐标点放缩到 [-1, 1]
mean = data_mean # 获取标签均值
std = data_std # 获取标签标准差
key_pts_copy = (key_pts_copy - mean)/std return image_copy, key_pts_copy class Resize(object):
# 将输入图像调整为指定大小 def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
self.output_size = output_size def __call__(self, data): image = data[0] # 获取图片
key_pts = data[1] # 获取标签 image_copy = np.copy(image)
key_pts_copy = np.copy(key_pts) h, w = image_copy.shape[:2]
if isinstance(self.output_size, int):
if h > w:
new_h, new_w = self.output_size * h / w, self.output_size
else:
new_h, new_w = self.output_size, self.output_size * w / h
else:
new_h, new_w = self.output_size new_h, new_w = int(new_h), int(new_w) img = paddle.vision.transforms.resize(image_copy, (new_h, new_w)) # scale the pts, too 同比例尺放缩
key_pts_copy[::2] = key_pts_copy[::2] * new_w / w
key_pts_copy[1::2] = key_pts_copy[1::2] * new_h / h return img, key_pts_copy class RandomCrop(object):
# 随机位置裁剪输入的图像 def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
if isinstance(output_size, int):
self.output_size = (output_size, output_size)
else:
assert len(output_size) == 2
self.output_size = output_size def __call__(self, data):
image = data[0]
key_pts = data[1] image_copy = np.copy(image)
key_pts_copy = np.copy(key_pts) h, w = image_copy.shape[:2]
new_h, new_w = self.output_size
#h=256 new_h=224 h - new_h=32
top = np.random.randint(0, h - new_h) #(0,32)随机生成一个点
left = np.random.randint(0, w - new_w)
#裁剪方式
image_copy = image_copy[top: top + new_h,
left: left + new_w]
#关键点的值,减去左上角和上面的值得到新的值
key_pts_copy[::2] = key_pts_copy[::2] - left
key_pts_copy[1::2] = key_pts_copy[1::2] - top return image_copy, key_pts_copy class ToCHW(object):
# 将图像的格式由HWC改为CHW
def __call__(self, data): image = data[0]
key_pts = data[1] transpose = paddle.vision.transforms.Transpose((2, 0, 1)) # 改为CHW
image = transpose(image) return image, key_pts

看一下每种图像预处理方法的的效果。

import paddle.vision.transforms as T

# 测试 Resize
resize = Resize(256) # 测试 RandomCrop
random_crop = RandomCrop(128) # 测试 GrayNormalize 灰度+归一化
norm = GrayNormalize() # 测试 Resize + RandomCrop,图像大小变到256*256, 然后截取出224*224的图像块
composed = paddle.vision.transforms.Compose([Resize(256), RandomCrop(224)]) #list一次做变化 test_num = 500 # 测试的数据下标
data = face_dataset[test_num] transforms = {'None': None,
'norm': norm,
'random_crop': random_crop,
'resize': resize ,
'composed': composed}
for i, func_name in enumerate(['None', 'norm', 'random_crop', 'resize', 'composed']): #enumerate进行枚举 # 定义图片大小
fig = plt.figure(figsize=(20,10)) # 处理图片
if transforms[func_name] != None:
transformed_sample = transforms[func_name](data)
else:
transformed_sample = data # 设置图片打印信息
ax = plt.subplot(1, 5, i + 1)
ax.set_title(' Transform is #{}'.format(func_name)) # 输出图片
show_keypoints(transformed_sample[0], transformed_sample[1])




2.6 使用数据预处理的方式完成数据定义

让我们将 Resize、RandomCrop、GrayNormalize、ToCHW 应用于新的数据集

from paddle.vision.transforms import Compose

data_transform = Compose([Resize(256), RandomCrop(224), GrayNormalize(), ToCHW()])

# create the transformed dataset
train_dataset = FacialKeypointsDataset(csv_file='data/training_frames_keypoints.csv',
root_dir='data/training/',
transform=data_transform)
print('Number of train dataset images: ', len(train_dataset)) for i in range(5):
sample = train_dataset[i]
print(i, sample[0].shape, sample[1].shape) test_dataset = FacialKeypointsDataset(csv_file='data/test_frames_keypoints.csv',
root_dir='data/test/',
transform=data_transform) print('Number of test dataset images: ', len(test_dataset))

三、模型组建

3.1 组网

根据前文的分析可知,人脸关键点检测和分类,可以使用同样的网络结构,如LeNet、Resnet50等完成特征的提取,只是在原来的基础上,需要修改模型的最后部分,将输出调整为 人脸关键点的数量*2,即每个人脸关键点的横坐标与纵坐标,就可以完成人脸关键点检测任务了,具体可以见下面的代码,也可以参考官网案例:人脸关键点检测

网络结构如下:

为了加强网络的训练效果,可以考虑换用Resnet101

利用paddle高层API,一行代码就可以实现对网络的调用:paddle.vision.models.resnet101(pretrained=True) ,pretrained,表示model使用预训练好的参数

import paddle.nn as nn
from paddle.vision.models import resnet50
class SimpleNet(nn.Layer): def __init__(self, key_pts): super(SimpleNet, self).__init__() # 使用resnet50作为backbone
self.backbone = paddle.vision.models.resnet50(pretrained=True) #输出为1000 ResNet-1 [[1, 3, 224, 224]] [1, 1000] 0
#paddle.vision.models.resnet101(pretrained=True)
# 添加第一个线性变换层
self.linear1 = nn.Linear(in_features=1000, out_features=512) # 使用 ReLU 激活函数
self.act1 = nn.ReLU() # 添加第二个线性变换层作为输出,输出元素的个数为 key_pts*2,代表每个关键点的坐标 68点
self.linear2 = nn.Linear(in_features=512, out_features=key_pts*2) def forward(self, x): x = self.backbone(x)
x = self.linear1(x)
x = self.act1(x)
x = self.linear2(x) return x

3.2 网络结构可视化

使用model.summary可视化网络结构。

model = paddle.Model(SimpleNet(key_pts=68))
#random_crop[224,224,3]
#toCHW[3,224,224]
model.summary((-1, 3, 224, 224)) #--1 batchsize可以后续自己定义大小

四、模型训练

4.1 模型配置

训练模型前,需要设置训练模型所需的优化器,损失函数和评估指标。

  • 优化器:Adam优化器,快速收敛。
  • 损失函数:SmoothL1Loss
  • 评估指标:NME

4.2 自定义评估指标

特定任务的 Metric 计算方式在框架既有的 Metric接口中不存在,或算法不符合自己的需求,那么需要我们自己来进行Metric的自定义。这里介绍如何进行Metric的自定义操作,更多信息可以参考官网文档自定义Metric;首先来看下面的代码。


from paddle.metric import Metric class NME(Metric):
"""
1. 继承paddle.metric.Metric
"""
def __init__(self, name='nme', *args, **kwargs):
"""
2. 构造函数实现,自定义参数即可
"""
super(NME, self).__init__(*args, **kwargs)
self._name = name
self.rmse = 0
self.sample_num = 0 def name(self):
"""
3. 实现name方法,返回定义的评估指标名字
"""
return self._name def update(self, preds, labels):
"""
4. 实现update方法,用于单个batch训练时进行评估指标计算。
- 当`compute`类函数未实现时,会将模型的计算输出和标签数据的展平作为`update`的参数传入。
"""
N = preds.shape[0] preds = preds.reshape((N, -1, 2))
labels = labels.reshape((N, -1, 2)) self.rmse = 0 for i in range(N):
pts_pred, pts_gt = preds[i, ], labels[i, ]
interocular = np.linalg.norm(pts_gt[36, ] - pts_gt[45, ]) self.rmse += np.sum(np.linalg.norm(pts_pred - pts_gt, axis=1)) / (interocular * preds.shape[1])
self.sample_num += 1 return self.rmse / N def accumulate(self):
"""
5. 实现accumulate方法,返回历史batch训练积累后计算得到的评价指标值。
每次`update`调用时进行数据积累,`accumulate`计算时对积累的所有数据进行计算并返回。
结算结果会在`fit`接口的训练日志中呈现。
"""
return self.rmse / self.sample_num def reset(self):
"""
6. 实现reset方法,每个Epoch结束后进行评估指标的重置,这样下个Epoch可以重新进行计算。
"""
self.rmse = 0
self.sample_num = 0
# 使用 paddle.Model 封装模型
model = paddle.Model(SimpleNet(key_pts=68)) # 定义Adam优化器
optimizer = paddle.optimizer.Adam(learning_rate=0.001,
weight_decay=5e-4,
parameters=model.parameters())
# 定义SmoothL1Loss
loss = nn.SmoothL1Loss() # 使用自定义metrics
metric = NME() model.prepare(optimizer=optimizer, loss=loss, metrics=metric)

损失函数的选择:L1Loss、L2Loss、SmoothL1Loss的对比

  • L1Loss: 在训练后期,预测值与ground-truth差异较小时, 损失对预测值的导数的绝对值仍然为1,此时如果学习率不变,损失函数将在稳定值附近波动,难以继续收敛达到更高精度。
  • L2Loss: 在训练初期,预测值与ground-truth差异较大时,损失函数对预测值的梯度十分大,导致训练不稳定。
  • SmoothL1Loss: 在x较小时,对x梯度也会变小,而在x很大时,对x的梯度的绝对值达到上限 1,也不会太大以至于破坏网络参数。

4.2 模型训练

callback= paddle.callbacks.VisualDL(log_dir='visualdl_log')
model.fit(train_dataset, epochs=50, batch_size=64, verbose=1,callbacks=callback)
Epoch 40/50
step 55/55 [==============================] - loss: 0.0231 - nme: 3.8331e-04 - 528ms/step
Epoch 41/50
step 55/55 [==============================] - loss: 0.0449 - nme: 4.5357e-04 - 506ms/step
Epoch 42/50
step 55/55 [==============================] - loss: 0.0281 - nme: 3.3320e-04 - 506ms/step
Epoch 43/50
step 55/55 [==============================] - loss: 0.0198 - nme: 3.0033e-04 - 513ms/step
Epoch 44/50
step 55/55 [==============================] - loss: 0.0263 - nme: 3.9367e-04 - 509ms/step
Epoch 45/50
step 55/55 [==============================] - loss: 0.0448 - nme: 4.6078e-04 - 518ms/step
Epoch 46/50
step 55/55 [==============================] - loss: 0.0321 - nme: 3.2545e-04 - 522ms/step
Epoch 47/50
step 55/55 [==============================] - loss: 0.0249 - nme: 3.2067e-04 - 520ms/step
Epoch 48/50
step 55/55 [==============================] - loss: 0.0270 - nme: 3.2314e-04 - 520ms/step
Epoch 49/50
step 55/55 [==============================] - loss: 0.0183 - nme: 2.7263e-04 - 531ms/step
Epoch 50/50
step 55/55 [==============================] - loss: 0.0166 - nme: 2.7703e-04 - 542ms/step

4.3 模型保存

checkpoints_path = './checkpoints/models'
model.save(checkpoints_path)

五、模型预测

# 定义功能函数

def show_all_keypoints(image, predicted_key_pts):
"""
展示图像,预测关键点
Args:
image:裁剪后的图像 [224, 224, 3]
predicted_key_pts: 预测关键点的坐标
"""
# 展示图像
plt.imshow(image.astype('uint8')) # 展示关键点
for i in range(0, len(predicted_key_pts), 2):
plt.scatter(predicted_key_pts[i], predicted_key_pts[i+1], s=20, marker='.', c='m') def visualize_output(test_images, test_outputs, batch_size=1, h=20, w=10):
"""
展示图像,预测关键点
Args:
test_images:裁剪后的图像 [224, 224, 3]
test_outputs: 模型的输出
batch_size: 批大小
h: 展示的图像高
w: 展示的图像宽
""" if len(test_images.shape) == 3:
test_images = np.array([test_images]) for i in range(batch_size): plt.figure(figsize=(h, w))
ax = plt.subplot(1, batch_size, i+1) # 随机裁剪后的图像
image = test_images[i] # 模型的输出,未还原的预测关键点坐标值
predicted_key_pts = test_outputs[i] # 还原后的真实的关键点坐标值
predicted_key_pts = predicted_key_pts * data_std + data_mean # 展示图像和关键点
show_all_keypoints(np.squeeze(image), predicted_key_pts) plt.axis('off') plt.show()
# 读取图像
img = mpimg.imread('4.jpg') # 关键点占位符
kpt = np.ones((136, 1)) transform = Compose([Resize(256), RandomCrop(224)])
#【224,224,3】
# 对图像先重新定义大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt]) norm = GrayNormalize()
to_chw = ToCHW() # 对图像进行归一化和格式变换
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt]) img = np.array([img], dtype='float32') # 加载保存好的模型进行预测
model = paddle.Model(SimpleNet(key_pts=68))
model.load(checkpoints_path)
model.prepare() # 预测结果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1)) # 可视化
visualize_output(rgb_img, out, batch_size=1)

# 读取图像
img = mpimg.imread('3.jpg') # 关键点占位符
kpt = np.ones((136, 1)) transform = Compose([Resize(256), RandomCrop(224)])
#【224,224,3】
# 对图像先重新定义大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt]) norm = GrayNormalize()
to_chw = ToCHW() # 对图像进行归一化和格式变换
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt]) img = np.array([img], dtype='float32') # 加载保存好的模型进行预测
model = paddle.Model(SimpleNet(key_pts=68))
model.load(checkpoints_path)
model.prepare() # 预测结果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1)) # 可视化
visualize_output(rgb_img, out, batch_size=1)

# 读取图像
img = mpimg.imread('1.jpg') # 关键点占位符
kpt = np.ones((136, 1)) transform = Compose([Resize(256), RandomCrop(224)])
#【224,224,3】
# 对图像先重新定义大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt]) norm = GrayNormalize()
to_chw = ToCHW() # 对图像进行归一化和格式变换
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt]) img = np.array([img], dtype='float32') # 加载保存好的模型进行预测
model = paddle.Model(SimpleNet(key_pts=68))
model.load(checkpoints_path)
model.prepare() # 预测结果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1)) # 可视化
visualize_output(rgb_img, out, batch_size=1)

六、扩充应用

当我们得到关键点的信息后,就可以进行一些扩充的应用。

# 定义功能函数

def show_fu(image, predicted_key_pts):
"""
展示加了贴纸的图像
Args:
image:裁剪后的图像 [224, 224, 3]
predicted_key_pts: 预测关键点的坐标
"""
# 计算坐标,15 和 34点的中间值
x = (int(predicted_key_pts[28]) + int(predicted_key_pts[66]))//2
y = (int(predicted_key_pts[29]) + int(predicted_key_pts[67]))//2 # 打开 春节小图
star_image = mpimg.imread('light.jpg') # 处理通道
if(star_image.shape[2] == 4):
star_image = star_image[:,:,1:4] # 将小图放到原图上
image[y:y+len(star_image[0]), x:x+len(star_image[1]),:] = star_image # 展示处理后的图片
plt.imshow(image.astype('uint8')) # 展示关键点信息
for i in range(len(predicted_key_pts)//2,):
plt.scatter(predicted_key_pts[i*2], predicted_key_pts[i*2+1], s=20, marker='.', c='m') # 展示关键点信息 def custom_output(test_images, test_outputs, batch_size=1, h=20, w=10):
"""
展示图像,预测关键点
Args:
test_images:裁剪后的图像 [224, 224, 3]
test_outputs: 模型的输出
batch_size: 批大小
h: 展示的图像高
w: 展示的图像宽
""" if len(test_images.shape) == 3:
test_images = np.array([test_images]) for i in range(batch_size): plt.figure(figsize=(h, w))
ax = plt.subplot(1, batch_size, i+1) # 随机裁剪后的图像
image = test_images[i] # 模型的输出,未还原的预测关键点坐标值
predicted_key_pts = test_outputs[i] # 还原后的真实的关键点坐标值
predicted_key_pts = predicted_key_pts * data_std + data_mean # 展示图像和关键点
show_fu(np.squeeze(image), predicted_key_pts) plt.axis('off') plt.show() # 读取图像
img = mpimg.imread('4.jpg') # 关键点占位符
kpt = np.ones((136, 1)) transform = Compose([Resize(256), RandomCrop(224)]) # 对图像先重新定义大小,并裁剪到 224*224的大小
rgb_img, kpt = transform([img, kpt]) norm = GrayNormalize()
to_chw = ToCHW() # 对图像进行归一化和格式变换
img, kpt = norm([rgb_img, kpt])
img, kpt = to_chw([img, kpt]) img = np.array([img], dtype='float32') # 加载保存好的模型进行预测
# model = paddle.Model(SimpleNet())
# model.load(checkpoints_path)
# model.prepare() # 预测结果
out = model.predict_batch([img])
out = out[0].reshape((out[0].shape[0], 136, -1)) # 可视化
custom_output(rgb_img, out, batch_size=1)

总结

上面换用了resnet101,为了缩短训练时间将Epoch设定为25.但是经过实验发现模型效果不理想,还是改为50个EPOCH。并且开启VisualDL,便于观察模型训练情况。训练情况可视化,可以发现loss下降的很快。使用resnet101并且Epoch为50的时候,模型是过拟合的,因为另一方面数据集的量很少。所以建议还是使用Resnet50就行,调优可以从优化方法,batch,等下手。
效果不是很好可能训练样本有很大关系!

可以看到识别精度不是很好,尤其是遇到人脸有角度的情况下准确率十分低,造成这个情况可能是数据样本太少以及网络结构简单。需要更大的数据集以及更好的网络进行调试

『深度学习项目四』基于ResNet101人脸特征点检测的更多相关文章

  1. 『NiFi 学习之路』简介

    『NiFi 学习之路』简介 『NiFi 学习之路』入门 -- 下载.安装与简单使用 『NiFi 学习之路』资源 -- 资料汇总 『NiFi 学习之路』把握 -- 架构及主要组件 『NiFi 学习之路』 ...

  2. GitHub 上 57 款最流行的开源深度学习项目

    转载:https://www.oschina.net/news/79500/57-most-popular-deep-learning-project-at-github GitHub 上 57 款最 ...

  3. GitHub 上 57 款最流行的开源深度学习项目【转】

    GitHub 上 57 款最流行的开源深度学习项目[转] 2017-02-19 20:09 334人阅读 评论(0) 收藏 举报 分类: deeplearning(28) from: https:// ...

  4. 『NiFi 学习之路』资源 —— 资料汇总

    一.概述 由于 NiFi 是一个比较新的开源项目,国内的相关资料少之又少. 加之,大家都知道,国内的那么些个教程,原创都只是停留在初级使用阶段,没有更多深入的介绍. 再者,其余的文章不是东抄抄就是西抄 ...

  5. 『NiFi 学习之路』入门 —— 下载、安装与简单使用

    一.概述 "光说不练假把式." 官网上的介绍多少让人迷迷糊糊的,各种高大上的词语仿佛让 NiFi 离我们越来越远. 实践是最好的老师.那就让我们试用一下 NiFi 吧! 二.安装 ...

  6. github上热门深度学习项目

    github上热门深度学习项目 项目名 Stars 描述 TensorFlow 29622 使用数据流图进行可扩展机器学习的计算. Caffe 11799 Caffe:深度学习的快速开放框架. [Ne ...

  7. 基于深度学习的恶意样本行为检测(含源码) ----采用CNN深度学习算法对Cuckoo沙箱的动态行为日志进行检测和分类

    from:http://www.freebuf.com/articles/system/182566.html 0×01 前言 目前的恶意样本检测方法可以分为两大类:静态检测和动态检测.静态检测是指并 ...

  8. 『NiFi 学习之路』自定义 —— 组件的自定义及使用

    一.概述 许多业务仅仅使用官方提供的组件不能够满足性能上的需求,往往要通过高度可定制的组件来完成特定的业务需求. 而 NiFi 提供了自定义组件的这种方式. 二.自定义 Processor 占坑待续 ...

  9. 『NiFi 学习之路』使用 —— 主要组件的使用

    一.概述 大部分 NiFi 使用者都是通过 NiFi 的 Processor 来实现自己的业务的.因此,我也主要就 NiFi 官方提供的 Porcessor 进行介绍. 二.Processor 如果你 ...

  10. 『NiFi 学习之路』把握 —— 架构及主要部件

    一.概述 通过前面几篇文章的学习,相信你对 NiFi 有了一个基础性的了解. 数据处理和分发系统 是什么概念? NiFi 系统中数据的传递方式是怎样的? NiFi 的重要 Processor 有哪些? ...

随机推荐

  1. 【JAVA基础】JAVA基础知识整理

    https://www.weixueyuan.net/java/rumen_1/ JAVA的三大版本 JDK.JRE.JVM JAVA安装与卸载 JDK结构 HelloWorld运行 编译型与解释型 ...

  2. HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Java

    Viterbi 维特比算法解决的是篱笆型的图的最短路径问题,图的节点按列组织,每列的节点数量可以不一样,每一列的节点只能和相邻列的节点相连,不能跨列相连,节点之间有着不同的距离,距离的值就不在 题目背 ...

  3. AcWing 第 1 场周赛补题记录(A~C)

    比赛链接:Here AcWing 3577. 选择数字 排序,然后选取两个数组的最大值 void solve() { int n; cin >> n; vector<int>a ...

  4. 换架 3D 飞机,继续飞呀飞

    相信大多数图扑 HT 用户都曾见过这个飞机的 Demo,在图扑发展的这十年,这个 Demo 是许多学习 HT 用户一定会参考的经典 Demo 之一. 这个 Demo 用简洁的代码生动地展示了 OBJ ...

  5. 7、SpringBoot-mybatis-plus引入

    系列导航 springBoot项目打jar包 1.springboot工程新建(单模块) 2.springboot创建多模块工程 3.springboot连接数据库 4.SpringBoot连接数据库 ...

  6. 一、redis单例安装(linux)

    系列导航 一.redis单例安装(linux) 二.redis主从环境搭建 三.redis集群搭建 四.redis增加密码验证 五.java操作redis 环境:centos7.5需要的安装包: re ...

  7. el-date-picker 组件时间格式化方式

    1 <el-form-item label="安放龙骨时间"> 2 <el-date-picker 3 v-model="baseInfoForm.se ...

  8. python连接liunx主机:paramiko类基本操作

    一.下载paramiko类 pip install paramiko 二.实现过程 # coding utf-8# author:Mr.white import paramiko # 创建SSHCli ...

  9. Keep English Level-01

    state -- 声称,宣称,国家,政府 state-owned -- 国有的 He stated that "hell will break loose,politically and m ...

  10. MyBatis02:CRUD操作

    CRUD操作 namespace 这个是命名空间,不可缺少,更不能写错 配置文件中namespace中的名称为对应Mapper接口或者Dao接口的全限定类名,完整包名,必须一致! Select(用于查 ...