Paddle实现人脸对比

人脸对比

人脸对比,顾名思义,就是对比两个人脸的相似度。本文将用Paddle实现这一功能。

PS:作者肝了整整3天才稍微搞明白实现方法

数据集准备

这里使用百度AI Studio的开源数据集:

人脸数据_数据集-飞桨AI Studio星河社区 (baidu.com)

这个数据集提供了500个人的人脸各100张,比较适合我们的项目。

根据这个数据集,很容易写出数据集类,文件名为face_dataset.py:

import numpy as np
from PIL import Image
import paddle
from random import shuffleclass FaceData(paddle.io.Dataset):def __init__(self, mode, num):super().__init__()# 训练集/测试集file = 'facecap/train_list.txt' if mode == 'train' else 'facecap/test_list.txt'self.imgs1 = []self.imgs2 = []self.labels = []# 控制相同人脸个数与不同人脸个数各占一半_1_count = 0with open(file) as f:# 读取数据集文件信息数据并洗牌lines = f.readlines()shuffle(lines)lines = lines[:num]print('read down')# 加载数据集for line1 in lines:line1 = line1.strip()img1, label1 = line1.split(' ')pil_img1 = Image.open(f'facecap\\{img1}').convert('RGB').resize((96, 96))for line2 in lines:line2 = line2.strip()img2, label2 = line2.split(' ')if label1 == label2:_1_count += 1pil_img2 = Image.open(f'facecap\\{img2}').convert('RGB').resize((96, 96))self.imgs1.append(np.array(pil_img1).transpose((2, 0, 1)) / 255.0)self.imgs2.append(np.array(pil_img2).transpose((2, 0, 1)) / 255.0)self.labels.append(1)elif _1_count > 0:_1_count -= 1pil_img2 = Image.open(f'facecap\\{img2}').convert('RGB').resize((96, 96))self.imgs1.append(np.array(pil_img1).transpose((2, 0, 1)) / 255.0)self.imgs2.append(np.array(pil_img2).transpose((2, 0, 1)) / 255.0)self.labels.append(0)self.imgs1 = np.array(self.imgs1, dtype=np.float32)self.imgs2 = np.array(self.imgs2, dtype=np.float32)self.labels = np.array(self.labels, dtype=np.float32)print('load down')def __getitem__(self, idx):return self.imgs1[idx], self.imgs2[idx], self.labels[idx]def __len__(self):return len(self.labels)

需要注意的是,PIL的图片维度与paddle CNN的维度不一样,需要使用transpose改变 

当然,使用这个数据集类读取数据是非常漫长的,因此我们创建了一个face_create_dataset.py,创建数据集对象并保存到本地:

from face_dataset import FaceData
import pickletrain_dataset = FaceData(mode='train', num=2000)
test_dataset = FaceData(mode='test', num=200)pickle.dump(train_dataset, open('./database/train.data', 'wb'), protocol=4)
pickle.dump(test_dataset, open('./database/test.data', 'wb'), protocol=4)

 这里我们使用pickle保存对象,注意这里要指定protocol=4,以保证可以存储超过4G的大文件

最后,这个脚本会在本地的database文件夹下生成两个data文件,使用时只需要加载即可

孪生网络

既然要输入两张图片,就自然需要使用两张卷积网络,分别处理两张图片。但是人脸对比与输入顺序无关,这就要求两个网络对于同一张图片的输出是相同的,也就是这两个网络是相同的。即共享权重的网络。因此我们可以定义网络如下:

class FaceNet(paddle.nn.Layer):def __init__(self):super().__init__()# 共享权重的cnn网络self.cnn = paddle.nn.Sequential(paddle.nn.Conv2D(3, 16, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Conv2D(16, 32, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Conv2D(32, 64, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Conv2D(64, 128, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Flatten(),paddle.nn.Linear(4608, 5))def forward(self, face1, face2):# 前向传播:使用cnn网络分别输出两个结果并返回n1r = self.cnn(face1)n2r = self.cnn(face2)return n1r, n2r

这个网络还需要有特殊的损失函数。这个损失函数将会使相同的人脸距离相近,不同的人脸距离更远。我们采用勾股定理计算距离,这样的距离也叫欧氏距离。

因此,对于一个在n维空间上的两个点(x1, x2, x3, ..., xn), (y1, y2, y3, ..., yn),就有:

d = \sqrt{(x_1-y_1)^2+(x_2-y_2)^2+...+(x_n-y_n)^2}

因此,如果人脸相同,损失函数将会输出的损失值是:

\left | 0-d \right |

这样的话,如果距离过远,损失值就会偏大 ,从而使输出更接近0

如果人脸不同,输出的损失值是:

max(m-d, 0)

这样的话,只有当距离比设定阈值远时,损失才为0.

另外,损失最好使用乘方而不是绝对值,这样的话就可以增加远离时的梯度。

我们定义其损失函数如下:

# 损失函数定义
class FaceLoss(paddle.nn.Layer):def __init__(self, margin=(512 ** 0.5)):super(FaceLoss, self).__init__()self.margin = margindef forward(self, output1, output2, label):# 计算欧式距离(勾股定理)euclidean_distance = paddle.norm(output1 - output2, axis=1)# 损失值# 在数据集中,1为相同,0为不同。但是输出要求相似的图片距离更近loss_contrastive = label * paddle.square(euclidean_distance) + \(1 - label) * paddle.square(paddle.maximum(self.margin - euclidean_distance, paddle.to_tensor(0.0)))# 损失函数应对同一批次取一个损失值return paddle.mean(loss_contrastive)

在paddle中,可以使用paddle.norm计算距离。axis=1表示只对第1维度计算距离,因为第0维度是数据批次。

在数据集中,我们定义1为相同,0为不同。根据我们之前的分析,很容易算出损失值的公式。

接下来就可以把这两个整合在一个py文件中,起名face_layers.py:

import paddleclass FaceNet(paddle.nn.Layer):def __init__(self):super().__init__()# 共享权重的cnn网络self.cnn = paddle.nn.Sequential(paddle.nn.Conv2D(3, 16, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Conv2D(16, 32, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Conv2D(32, 64, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Conv2D(64, 128, 3, padding=1),paddle.nn.ReLU(),paddle.nn.MaxPool2D(2, 2),paddle.nn.Flatten(),paddle.nn.Linear(4608, 5))def forward(self, face1, face2):# 前向传播:使用cnn网络分别输出两个结果并返回n1r = self.cnn(face1)n2r = self.cnn(face2)return n1r, n2r# 损失函数定义
class FaceLoss(paddle.nn.Layer):def __init__(self, margin=(512 ** 0.5)):super(FaceLoss, self).__init__()self.margin = margindef forward(self, output1, output2, label):# 计算欧式距离(勾股定理)euclidean_distance = paddle.norm(output1 - output2, axis=1)# 损失值# 在数据集中,1为相同,0为不同。但是输出要求相似的图片距离更近loss_contrastive = label * paddle.square(euclidean_distance) + \(1 - label) * paddle.square(paddle.maximum(self.margin - euclidean_distance, paddle.to_tensor(0.0)))# 损失函数应对同一批次取一个损失值return paddle.mean(loss_contrastive)

训练

接下来我们需要编写训练脚本face.py:

import paddle
from face_dataset import FaceData
from face_layers import FaceNet, FaceLoss
import pickle# 加载数据集
train_dataset = pickle.load(open('./database/train.data', 'rb'))
test_dataset = pickle.load(open('./database/test.data', 'rb'))# 输出数据集信息
print(f'加载数据完毕,训练集数据个数:{len(train_dataset)};测试集数据个数:{len(test_dataset)}')count = 0
for context1, context2, label in train_dataset:if label == 1:count += 1print(f'训练集相同人脸个数{count}')count = 0
for context1, context2, label in test_dataset:if label == 1:count += 1print(f'测试集相同人脸个数{count}')# 指定设备
paddle.device.set_device('gpu')# 创建模型
model = paddle.Model(FaceNet())# 打印模型信息
print(model.summary(((1, 3, 96, 96), (1, 3, 96, 96))))# 模型训练的配置准备,准备损失函数,优化器和评价指标
model.prepare(paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=0.00001),FaceLoss())# 模型训练
model.fit(train_dataset, epochs=50, batch_size=64, verbose=1)
# 模型评估
model.evaluate(test_dataset, batch_size=64, verbose=1)# 保存模型
model.save('./output/face-compare')

这里需要注意,我们需要使用FaceLoss作为损失函数

训练完毕后,训练数据将被存储在本地的output文件夹下,使用时加载即可

接下来我们可以编写face_use.py使用这个模型:

import paddle
from face_dataset import FaceData
from face_layers import FaceNet
from PIL import Image
import numpy as np# 加载模型
model = paddle.Model(FaceNet())
model.load('./output/face-compare')print('加载模型完毕')# 打开图片
pil_img1 = Image.open(f'facecap\\003\\30.jpg').convert('RGB').resize((96, 96))
pil_img2 = Image.open(f'facecap\\003\\27.jpg').convert('RGB').resize((96, 96))# 转np数组
np_img1 = np.array(pil_img1, dtype=np.float32).transpose((2, 0, 1)) / 255.0
np_img2 = np.array(pil_img2, dtype=np.float32).transpose((2, 0, 1)) / 255.0# 预测
pred = model.predict_batch((np.array([np_img1], dtype=np.float32), np.array([np_img2], dtype=np.float32)))# 计算距离
euclidean_distance = paddle.norm(paddle.to_tensor([pred[0]]) - paddle.to_tensor([pred[1]]))
print(euclidean_distance.numpy())

这里只以两张相同人的人脸的图片做测试,最后输出:

加载模型完毕
[0.1978856]

改用两张不同人的人脸做测试,最后输出:

加载模型完毕
[1.1059165]

可以看到,这个模型的效果还不错。但是经过我的多次测试,发现这个模型还有一定的提升空间。这需要更大的数据集、更深的模型和更多的训练次数

总结

我们使用孪生网络技术,成功实现了人脸对比模型,并有一定的准确性,可以应用于人脸比对等场景。但是,由于数据集、模型和训练次数有限,还难以实现更准确的人脸对比

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/792946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C#]OpenCvSharp使用HoughCircles霍夫圆检测算法找出圆位置并计数

【效果展示】 原图: 找出位置: 【测试环境】 vs2019,netframework4.7.2,opencvsharp4.8.0 【函数用法】 cv2提供了一种圆检测的方法:HoughCircles。该函数的返回结果与参数设置有很大的关系。 检测的图像时9枚钱币,分别使用了…

特征融合篇 | 结合内容引导注意力 DEA-Net 思想 实现双主干特征融合新方法 | IEEE TIP 2024

本篇改进已集成到 YOLOv8-Magic 框架。 摘要—单幅图像去雾是一个具有挑战性的不适定问题,它从观察到的雾化图像中估计潜在的无雾图像。一些现有的基于深度学习的方法致力于通过增加卷积的深度或宽度来改善模型性能。卷积神经网络(CNN)结构的学习能力仍然未被充分探索。本文…

SpringAI如何集成Ollama开发AI应用

文章目录 spring AI 介绍1. Spring ML2. Spring Data3. Spring Integration4. Spring Boot5. Spring Cloud如何开始使用 Spring AI注意事项 Spring AI集成Ollama1. 添加依赖2. 配置应用3. 注入和使用 AiClient4. 运行和测试注意事项 spring AI 介绍 Spring AI 是一个基于 Spri…

技术人的清明节:数字哀思与虚拟纪念

清明节,这是一个寄托哀思、缅怀先人的日子。对于我们这些日夜与代码为伴的技术人来说,这个节日不仅仅是对亲人的怀念,更是一次对生命、时间和记忆的深刻反思。 技术人的情感表达:独特而真挚 我们技术人,常被误解为情…

【算法题】换水问题 II

> 插:AI时代,程序员或多或少要了解些人工智能,前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 坚持不懈,越努力越幸运,大家…

代理模式:全局 规则 直连 脚本四种模式的区别

当涉及到网络代理时,不同模式具有不同的设置,选择不同模式应根据实际需求和网络环境来决定。 代理模式的区别如下: 1. 全局模式 整个设备的所有流量都会通过代理服务器转发。无论是浏览器、应用程序还是系统其他组件,都将通过代理…

Linux第3课 Linux系统安装及换源方法

文章目录 Linux第3课 Linux系统安装及换源方法一、VMware虚拟机下系统的安装及配置(一)创建新的虚拟机 二、换源三、初次配置四、修改分辨率五、共享文件夹的实现(一)创建并查看共享文件夹 Linux第3课 Linux系统安装及换源方法 用…

阿里云PAI + pytorch大语言模型开发环境简介

文章目录 阿里云PAI pytorch大语言模型开发环境简介PAI-DSW 快速入门1. 安装和配置2. 模型训练2.1 数据集准备2.2 模型训练脚本准备2.3 提交训练作业 3. 部署模型为推理服务4. 调用推理服务 阿里云PAI pytorch大语言模型开发环境简介 PAI-DSW 快速入门 阿里云机器学习PAI&a…

Node.js 与 webpack(四)

上一篇:Node.js与webpack(三)-CSDN博客 webpack原理 yu 优化 本章节主要介绍 Webpack 高级配置。 所谓高级配置其实就是进行 Webpack 优化,让我们代码在编译/运行时性能更好~ 我们会从以下角度来进行优化: 提升开发体…

如何在 Ubuntu 12.04 VPS 上使用 LDAP 对客户端计算机进行身份验证

简介 LDAP(轻量级目录访问协议)是将认证信息保存在单一集中位置的一种方式。在之前的一篇文章中,我们讨论了如何在 Ubuntu 12.04 VPS 上设置 LDAP 服务器。这解释了实际的服务器配置。 在本文中,我们将讨论如何配置客户端机器以远…

Data-efficient Fine-tuning for LLM-based Recommendation

目录 Introduction 利用大型语言模型(LLM)进行推荐最近引起了相当大的关注,其中微调在 LLM 的适应中发挥着关键作用。然而,在快速扩展的推荐数据上微调LLMs的成本限制了其实际应用。为了应对这一挑战,小样本微调提供了…

【深入理解计算机系统第3版】有符号数和无符号数转换以及移位运算练习题2.23

题目 考虑下面的C函数&#xff1a; int fun1(unsigned word) {return (int) ((word << 24) >> 24); }int fun2(unsigned word) {return ((int) word << 24) >> 24; } 假设一个采用补码运算的机器上以32位程序来执行这些函数。还假设有符号数值的右移…

代码审计-PHP原生开发篇SQL注入数据库监控正则搜索文件定位静态分析

文章目录 前言1、Bluecms-CNVD-1Day-常规注入审计分析2、emlog-CNVD-1Day-常规注入审计分析3、emlog-CNVD-1Day-2次注入审计分析 前言 挖掘技巧&#xff1a; -语句监控-数据库SQL监控排查可利用语句定向分析 -功能追踪-功能点文件SQL执行代码函数调用链追踪 -正则搜索-(update…

QT----opencv4.8.0编译cuda版本,QTcreater使用

目录 1 编译opencv4.8.02 验证能否加载GPU cuda12.1 opencv4.8.0 vs2019 cmake3.29 1 编译opencv4.8.0 打开cmake&#xff0c;选择opencv480路径&#xff0c;build路径随意 点击configure后&#xff0c;选择这些选项&#xff0c;opencv_word&#xff0c;cuda全选&#xff0c;…

工业组态 物联网组态 组态编辑器 web组态 组态插件 编辑器

体验地址&#xff1a;by组态[web组态插件] BY组态是一款非常优秀的纯前端的【web组态插件工具】&#xff0c;可无缝嵌入到vue项目&#xff0c;react项目等&#xff0c;由于是原生js开发&#xff0c;对于前端的集成没有框架的限制。同时由于BY组态只是一个插件&#xff0c;不能独…

【Linux】Ubuntu 文件权限管理

Linux 系统对文件的权限有着严格的控制&#xff0c;用于如果相对某个文件执行某种操作&#xff0c;必须具有对应的权限方可执行成功&#xff0c;这也是Linux有别于Windows的机制&#xff0c;也是基于这个权限机制&#xff0c;Linux可以有效防止病毒自我运行。因为运行的条件是必…

二十一、Rust 反射 获取类型

不同于 java 中的反射&#xff0c;Rust 没有提供以往意义上的运行时反射&#xff0c;取而代之的是 “编译期反射”&#xff0c;如 类型分析、类型转换、类型签名。但即便如此&#xff0c;也已经能对 Rust元编程 提供很多助力了。 这种操作&#xff0c;主要通过 Any 来实现&…

【接口】HTTP(1)|请求|响应

1、概念 Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;用于从万维网&#xff08;就是www&#xff09;服务器传输超文本到本地浏览器的传送协议。 HTTP协议是基于TCP的应用层协议&#xff0c;它不关心数据传输的细节&#xff0c;主要是用来规定客户端和…

详解python中的迭代

如果给定一个list或tuple&#xff0c;我们可以通过for循环来遍历这个list或tuple&#xff0c;这种遍历我们称为迭代&#xff08;Iteration&#xff09;。 在Python中&#xff0c;迭代是通过for ... in来完成的&#xff0c;而很多语言比如C语言&#xff0c;迭代list是通过下标完…

4月4号总结

java学习 一.接口 1.介绍 定义接口需要使用到关键字interface去定义接口。 格式如下&#xff1a; 类与接口的关系不是继承&#xff0c;而是实现&#xff0c;用关键字 implements &#xff0c;格式如下&#xff1a; 这个类去实现接口&#xff0c;其中的关系就相当于&#xf…