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

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

\left | 1-d \right |

这样的话,如果距离离1过远(不考虑大于1的情况下,离0更近),损失值就会偏大,从而使输出更远离0(接近1)

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

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

    def __init__(self):
        super(FaceLoss, self).__init__()

    def forward(self, output1, output2, label):
        # 计算欧式距离(勾股定理)
        euclidean_distance = paddle.norm(output1 - output2, axis=1)
        # 损失值
        # 在数据集中,1为相同,0为不同。但是输出要求相似的图片距离更近
        loss_contrastive = paddle.abs((1 - label) - euclidean_distance)
        # 损失函数应对同一批次取一个损失值
        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):
        super(FaceLoss, self).__init__()

    def forward(self, output1, output2, label):
        # 计算欧式距离(勾股定理)
        euclidean_distance = paddle.norm(output1 - output2, axis=1)
        # 损失值
        # 在数据集中,1为相同,0为不同。但是输出要求相似的图片距离更近
        loss_contrastive = paddle.abs((1 - label) - euclidean_distance)
        # 损失函数应对同一批次取一个损失值
        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]

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

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【React】vite + react 项目,配置项目路径别名 @

vite react 项目,配置项目路径别名 1 安装 types/node2 在 vite.config.ts 中添加配置:3 配置路径别名的提示 使用 vite 开发 react 项目时,可以通过一下步骤配置路径别名: 1 安装 types/node npm i -D types/node2 在 vite.con…

Lumos学习王佩丰Excel第一讲:认识Excel

最近发现自己在操作excel的一些特殊功能时会有些不顺手,所以索性找了一个比较全的教程(王佩丰excel24讲)拿来学习,刚好形成文档笔记,分享给有需要但没有时间看视频的朋友们。整体笔记以王老师授课的知识点去记录&#…

Spring拓展点之SmartLifecycle如何感知容器启动和关闭

Spring为我们提供了拓展点感知容器的启动与关闭,从而使我们可以在容器启动或者关闭之时进行定制的操作。Spring提供了Lifecycle上层接口,这个接口只有两个方法start和stop两个方法,但是这个接口并不是直接提供给开发者做拓展点,而…

算法基础--递推

😀前言 递推算法在计算机科学中扮演着重要的角色。通过递推,我们可以根据已知的初始条件,通过一定的规则推导出后续的结果,从而解决各种实际问题。本文将介绍递推算法的基础知识,并通过一些入门例题来帮助读者更好地理…

力扣 392. 判断子序列

题目来源:https://leetcode.cn/problems/is-subsequence/description/ C题解1:在t中按顺序一个一个寻找s的元素。 class Solution { public:bool isSubsequence(string s, string t) {bool flg false;int m s.size(), n t.size();if(m 0) return tr…

vue项目打包优化之-productionSourceMap设置

productionSourceMap 是一个用于配置生产环境下是否生成 source map 文件的选项。在 webpack 中,source map 文件是一种映射关系文件,可以将编译后的代码映射回原始源代码,方便开发者在调试时定位问题。 在生产环境中,通常不建议暴…

线程池小项目【Linux C/C++】(踩坑分享)

目录 前提知识: 一,线程池意义 二,实现流程 阶段一,搭建基本框架 1. 利用linux第三方库,将pthread_creat线程接口封装 2. 实现基本主类ThreadPool基本结构 阶段二,完善多线程安全 1. 日志信息打印…

大模型放进推荐系统怎么玩?微软亚研全面总结

在大模型时代,似乎任何自然语言处理任务在大模型加持下都完成了一轮升级改造,展现出前所未有的高效与效果。语义理解、情感分析还是文本生成这些常规任务自然是不必说,但也有一些任务比如推荐,简单粗暴的训练LLMs的思路并非明智之…

回溯算法|78.子集

力扣题目链接 class Solution { private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex) {result.push_back(path); // 收集子集&#xff0c;要放在终止添加的上面&#xff0c;否则会漏掉自…

HarmonyOS 应用开发之非线性容器

非线性容器实现能快速查找的数据结构&#xff0c;其底层通过hash或者红黑树实现&#xff0c;包括HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet、PlainArray七种。非线性容器中的key及value的类型均满足ECMA标准。 HashMap HashMap 可用来存储具有关联…

门控循环单元(GRU)

概述 门控循环单元&#xff08;Gated Recurrent Unit, GRU&#xff09;由Junyoung Chung等人于2014年提出&#xff0c;原论文为《Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling》。GRU是循环神经网络&#xff08;Recurrent Neural Network, …

定时器-间歇函数

1.开启定时器 setInterval(function (){console.log(一秒执行一次)},1000) function fn(){console.log(一秒执行一次) } setInterval(fn,1000) //调用有名的函数&#xff0c;只写函数名 1.函数名字不需要加小括号 2.定时器返回是一个id数字 每个定时器的序号是不一样的 2.关…

成都直播基地出租:天府新区兴隆湖天府锋巢直播产业基地

天府新区兴隆湖天府锋巢直播产业基地&#xff0c;作为成都乃至西部地区的一颗璀璨明珠&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;吸引着越来越多的目光。这里不仅是成都直播产业的聚集地&#xff0c;更是传统企业转型升级的摇篮&#xff0c;是新媒体时代下的创新高…

浙江大学蒋超课题组合作EST:开发使用可穿戴被动采样器对个体生物和化学暴露组与转录组进行纵向测绘

我们实验室的子诺师姐开发了一种新型的用于个体生物和化学暴露组纵向测绘的可穿戴被动采样器&#xff1a;AirPie。 目前可以在一个驱蚊扣差不多大小的device上分析出数千种化合物和微生物信号&#xff0c;非常&#x1f42e;。 我们将该采样器应用于某封闭环境&#xff0c;对19…

嵌入式门槛高吗?

一般来说&#xff0c;普通的嵌入式岗位相对而言比较容易入门&#xff0c;通常会要求掌握一定的 C 语言编程以及单片机相关的知识&#xff0c;能够制作出较为简单的电子产品&#xff0c;不过与之相对应的工资水平也会偏低一些。 然而&#xff0c;像大疆、华为、小米、英伟达、高…

C++万物起源:类与对象(三)拷贝构造、赋值重载

目录 一、拷贝构造函数 1.1拷贝构造函数的概念与特征 1.2拷贝构造的实现 1.3默认构造函数 1.4拷贝构造函数典型调用场景 二、赋值运算符重载 2.1赋值运算符重载的格式 一、拷贝构造函数 1.1拷贝构造函数的概念与特征 在c语言语法中&#xff0c;我们可以将一个变量赋值给…

2024 ccfcsp认证打卡 2023 05 01 重复局面

2023 05 01 重复局面 题目题解1题解2区别&#xff1a;数据存储方式&#xff1a;时间复杂度&#xff1a;空间复杂度&#xff1a; 总结&#xff1a; 题目 题解1 import java.util.*;public class Main {public static void main(String[] args) {Scanner input new Scanner(Sys…

自动驾驶传感器:传感的本质

自动驾驶传感器&#xff1a;传感的本质 附赠自动驾驶学习资料和量产经验&#xff1a;链接 0. 前言 这个系列的背景是&#xff1a;工作时候需要攒一台数据采集车辆&#xff0c;那段时间需要熟悉感知硬件&#xff0c;写了不少笔记&#xff0c;都是些冗长的文章&#xff0c;感兴…

【pysurvival Python 安装失败】

这个错误与 sklearn 包的名称更改有关&#xff0c;导致 pysurvival 在构建元数据时失败。现在&#xff0c;你需要修改 pysurvival 的安装文件以使用正确的 scikit-learn 包名 编辑安装文件&#xff1a;找到 pysurvival 的安装文件&#xff0c;可能是 setup.py 或 pyproject.to…

OpenAI 15秒重建逼真人声,百度早就实现啦!只需2秒生成完美音色,免费使用

大家好&#xff0c;我是卖萌酱。 这两天&#xff0c;卖萌酱发现有不少读者小伙伴都在关注几天前我们介绍的OpenAI刚刚发布的这个名为Voice Engine 的语音引擎。这个听起来颇为“Amazing”的“黑科技”&#xff0c;可以仅仅凭借一段15秒的声音样本&#xff0c;就能精准模仿这段…