Paddle实现人脸对比

news2025/1/16 21:58:52

人脸对比

人脸对比,顾名思义,就是对比两个人脸的相似度。本文将用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 shuffle


class 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 = 0
        with 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 += 1
                        pil_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 -= 1
                        pil_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 pickle

train_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 = margin

    def 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 paddle


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


# 损失函数定义
class FaceLoss(paddle.nn.Layer):

    def __init__(self, margin=(512 ** 0.5)):
        super(FaceLoss, self).__init__()
        self.margin = margin

    def 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 += 1

print(f'训练集相同人脸个数{count}')

count = 0
for context1, context2, label in test_dataset:
    if label == 1:
        count += 1

print(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.coloradmin.cn/o/1569382.html

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

相关文章

基于SSM的教材管理系统的设计与实现(论文+源码)_kaic

基于SSM的教材管理系统的设计与实现 摘 要 当下,正处于信息化的时代,许多行业顺应时代的变化,结合使用计算机技术向数字化、信息化建设迈进。以前学校对于教材信息的管理和控制,采用人工登记的方式保存相关数据,这种以…

Spring Boot 整合 RabbitMQ 实现延迟消息

关于 RabbitMQ 消息队列(Message Queuing,简写为 MQ)最初是为了解决金融行业的特定业务需求而产生的。慢慢的,MQ 被应用到了更多的领域,然而商业 MQ 高昂的价格让很多初创公司望而却步,于是 AMQP&#xff0…

人工智能改变教育:理解和在课堂上使用 ChatGPT 的指南

原文:Talking to Machines: The Fascinating Story of ChatGPT and AI Language Models 译者:飞龙 协议:CC BY-NC-SA 4.0 这本直言不讳、幽默风趣的指南充满了可操作的提示、技巧和策略,帮助你在业务中充分利用 ChatGPT 的优势。在…

【氮化镓】GaN SP-HEMT的栅极可靠性

概括总结: 本文研究了氮化镓(GaN)肖特基型p-栅高电子迁移率晶体管(GaN SP-HEMT)的栅极鲁棒性和可靠性,通过一种新的电路方法评估了在实际转换器中栅极电压(VGS)过冲波形的栅极电压应…

网络基础二——TCP可靠性实现机制补充

11.3.4确认应答机制 ​ 1.双方通信时要返回确认应答报文,保证对方发送的报文是有效的;尽管整个通信过程中无法保证数据全部可靠,但是可以保证单个方向发送的数据是可靠的; ​ 发送的报文要设置序号,如果是应答报文要…

一、持续集成介绍

持续集成介绍 一、什么是持续集成二、持续集成的流程三、持续集成的组成要素四、持续集成的好处 一、什么是持续集成 持续集成(CI)指的是,频繁地(一天多次)将代码集成到主干。持续集成的目的,就是让产品可…

《Java面试自救指南》(专题二)计算机网络

文章目录 力推的计网神课get请求和post请求的区别在浏览器网址输入一个url后直到浏览器显示页面的过程常用状态码session 和 cookie的区别TCP的三次握手和四次挥手七层OSI模型(TCP/IP协议模型)各种io模型的知识http协议和tcp协议的区别https和http的区别…

理解pytorch的广播语义

目录 什么是广播运算 广播的条件 示例 示例1 示例2 示例3 补1 示例4 原位运算 示例5 参与广播运算的两个tensor,必须是从右向左对齐 总结规律 两个tensor可以做广播运算的条件: 两个可以互相广播的tensor运算的步骤: 例子&#x…

Java | Leetcode Java题解之第8题字符串转换整数atoi

题目&#xff1a; 题解&#xff1a; class Solution {public int myAtoi(String str) {Automaton automaton new Automaton();int length str.length();for (int i 0; i < length; i) {automaton.get(str.charAt(i));}return (int) (automaton.sign * automaton.ans);} …

Scala第二十章节(Akka并发编程框架、Akka入门案例、Akka定时任务代码实现、两个进程间通信的案例以及简易版spark通信框架案例)

Scala第二十章节 章节目标 理解Akka并发编程框架简介掌握Akka入门案例掌握Akka定时任务代码实现掌握两个进程间通信的案例掌握简易版spark通信框架案例 1. Akka并发编程框架简介 1.1 Akka概述 Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用工具包。Akka是…

MySQL 导入库/建表时/出现乱码

问题描述&#xff1a; 新建不久的项目在使用Navicat for MySQL进行查看数据&#xff0c;发现表中注释的部分乱码&#xff0c;但是项目中获取的数据使用不会。 猜测因为是数据库编码和项目中使用的不一样&#xff0c;又因为项目的连接语句定义了需要编码&#xff0c;故项目运行…

Golang实现一个聊天工具

简介 聊天工具作为实时通讯的必要工具&#xff0c;在现代互联网世界中扮演着重要的角色。本博客将指导如何使用 Golang 构建一个简单但功能完善的聊天工具&#xff0c;利用 WebSocket 技术实现即时通讯的功能。 项目源码 点击下载 为什么选择 Golang Golang 是一种高效、简…

win10+Intel显卡安装配置stable-diffusion-webui绘画网页

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

【opencv】教程代码 —video(3) 视频背景剔除

bg_sub.cpp 这段代码的功能是把视频中的背景和前景分离&#xff0c;提取出前景的运动物体。根据用户选择的不同的模式&#xff0c;可以选择基于MOG2或者基于KNN的方法来进行背景减除。在处理每一帧图像的过程中&#xff0c;首先使用背景减除模型对图像帧进行处理&#xff0c;得…

ChatGPT 与 OpenAI 的现代生成式 AI(下)

原文&#xff1a;Modern Generative AI with ChatGPT and OpenAI Models 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 七、通过 ChatGPT 掌握营销技巧 在本章中&#xff0c;我们将重点介绍营销人员如何利用 ChatGPT&#xff0c;在这一领域中查看 ChatGPT 的主要用例…

[RK3128_LINUX5.1] 关于 RetroArch 使用

问题描述 查看文档 docs\cn\Linux\ApplicationNote\Rockchip_Use_Guide_Linux_RetroArch_CN.pdf&#xff0c;描述为实验 make menuconfig 后勾选选项 Libretro cores and retroarch -> retroarch 但是SDK中并没有这个选项 解决方案&#xff1a; 目前发布的buildroot SDK…

4核8G服务器配置性能怎么样?4核8G12M配置服务器能干啥?

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…

flex:1是干嘛的

直接上图&#xff1a; flex:1实际代表的是三个属性的简写&#xff0c;如上图所示。 其中flex-grow是用来增大盒子的&#xff0c;比如&#xff0c;当子盒子的宽度小于父盒子的宽度&#xff0c;父盒子的剩余空间可以 利用flex-grow来设置子盒子增大的占比&#xff1b; flex-shri…

每日五道java面试题之ZooKeeper篇(二)

目录&#xff1a; 第一题. 客户端注册 Watcher 实现第二题. 服务端处理 Watcher 实现第三题. ACL 权限控制机制第四题. Chroot 特性第五题. 客户端回调 Watcher 第一题. 客户端注册 Watcher 实现 &#xff08;1&#xff09;调用 getData()/getChildren()/exist()三个 API&…

腾讯云4核8g服务器价格,CVM和轻量哪个优惠?

2024年腾讯云4核8G服务器租用优惠价格&#xff1a;轻量应用服务器4核8G12M带宽646元15个月&#xff0c;CVM云服务器S5实例优惠价格1437.24元买一年送3个月&#xff0c;腾讯云4核8G服务器活动页面 txybk.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云4核8G服务器优惠价格 轻…