『深度学习项目四』基于ResNet101人脸特征点检测

2023-12-29 20:38

本文主要是介绍『深度学习项目四』基于ResNet101人脸特征点检测,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

相关文章:
【深度学习项目一】全连接神经网络实现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=0Kexp( 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)=n1i=1nyif(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)=n1i=1n(yif(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)=n1i=1n{.5(yif(xi))2,yif(xi)0.5, if yif(x) otherwise 

有利于快速收敛!

1.3 评估指标 NME

# 环境导入
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimgimport cv2
import paddle#paddle.set_device('gpu') # 手动设置设置为GPUimport 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标注,标注顺序如下

PaddleX

# 计算标签的均值和标准差,用于标签的归一化
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 DatasetBATCH_SIZE = 64
BATCH_NUM = 20IMAGE_SIZE = (28, 28)
CLASS_NUM = 10class MyDataset(Dataset):"""步骤一:继承paddle.io.Dataset类"""def __init__(self, num_samples):"""步骤二:实现构造函数,定义数据集大小"""super(MyDataset, self).__init__()self.num_samples = num_samplesdef __getitem__(self, index):"""步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签)"""data = paddle.uniform(IMAGE_SIZE, dtype='float32')label = paddle.randint(0, CLASS_NUM-1, dtype='int64')return data, labeldef __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 Datasetclass 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()  #第一列到最后一列,转为numpykey_pts = self.key_pts_frame.iloc[idx, 1:].valueskey_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_ptsdef __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 = 3for 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 = datareturn  processed_data
import paddle.vision.transforms.functional as Fclass 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)/stdreturn image_copy, key_pts_copyclass Resize(object):# 将输入图像调整为指定大小def __init__(self, output_size):assert isinstance(output_size, (int, tuple))self.output_size = output_sizedef __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_sizeelse:new_h, new_w = self.output_size, self.output_size * w / helse:new_h, new_w = self.output_sizenew_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 / wkey_pts_copy[1::2] = key_pts_copy[1::2] * new_h / hreturn img, key_pts_copyclass 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) == 2self.output_size = output_sizedef __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=32top = 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] - leftkey_pts_copy[1::2] = key_pts_copy[1::2] - topreturn image_copy, key_pts_copyclass ToCHW(object):# 将图像的格式由HWC改为CHWdef __call__(self, data):image = data[0]key_pts = data[1]transpose = paddle.vision.transforms.Transpose((2, 0, 1)) # 改为CHWimage = 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 Composedata_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作为backboneself.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 Metricclass NME(Metric):"""1. 继承paddle.metric.Metric"""def __init__(self, name='nme', *args, **kwargs):"""2. 构造函数实现,自定义参数即可"""super(NME, self).__init__(*args, **kwargs)self._name = nameself.rmse = 0self.sample_num = 0def name(self):"""3. 实现name方法,返回定义的评估指标名字"""return self._namedef 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 = 0for 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 += 1return self.rmse / Ndef accumulate(self):"""5. 实现accumulate方法,返回历史batch训练积累后计算得到的评价指标值。每次`update`调用时进行数据积累,`accumulate`计算时对积累的所有数据进行计算并返回。结算结果会在`fit`接口的训练日志中呈现。"""return self.rmse / self.sample_numdef reset(self):"""6. 实现reset方法,每个Epoch结束后进行评估指标的重置,这样下个Epoch可以重新进行计算。"""self.rmse = 0self.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]))//2y = (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,等下手。
效果不是很好可能训练样本有很大关系!

PaddleX PaddleX PaddleX PaddleX PaddleX

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

这篇关于『深度学习项目四』基于ResNet101人脸特征点检测的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/550657

相关文章

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

用Microsoft.Extensions.Hosting 管理WPF项目.

首先引入必要的包: <ItemGroup><PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" /><PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /><PackageReference Include="Serilog

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

《offer来了》第二章学习笔记

1.集合 Java四种集合:List、Queue、Set和Map 1.1.List:可重复 有序的Collection ArrayList: 基于数组实现,增删慢,查询快,线程不安全 Vector: 基于数组实现,增删慢,查询快,线程安全 LinkedList: 基于双向链实现,增删快,查询慢,线程不安全 1.2.Queue:队列 ArrayBlockingQueue:

硬件基础知识——自学习梳理

计算机存储分为闪存和永久性存储。 硬盘(永久存储)主要分为机械磁盘和固态硬盘。 机械磁盘主要靠磁颗粒的正负极方向来存储0或1,且机械磁盘没有使用寿命。 固态硬盘就有使用寿命了,大概支持30w次的读写操作。 闪存使用的是电容进行存储,断电数据就没了。 器件之间传输bit数据在总线上是一个一个传输的,因为通过电压传输(电流不稳定),但是电压属于电势能,所以可以叠加互相干扰,这也就是硬盘,U盘