Python深度学习实战:人脸关键点(15点)检测pytorch实现

news2024/10/6 1:46:24

引言

人脸关键点检测即对人类面部若干个点位置进行检测,可以通过这些点的变化来实现许多功能,该技术可以应用到很多领域,例如捕捉人脸的关键点,然后驱动动画人物做相同的面部表情;识别人脸的面部表情,让机器能够察言观色等等。
在这里插入图片描述

如何检测人脸关键点

本文是实现15点的检测,至于N点的原理都是一样的,使用的算法模型是深度神经网络,使用CV也是可以的。

如何检测

这个问题抽象出来,就是一个使用神经网络来进行预测的功能,只不过输出是15个点的坐标,训练数据包含15个面部的特征点和面部的图像(大小为96x96),15个特征点分别是:left_eye_center, right_eye_center, left_eye_inner_corner, left_eye_outer_corner, right_eye_inner_corner, right_eye_outer_corner, left_eyebrow_inner_end, left_eyebrow_outer_end, right_eyebrow_inner_end, right_eyebrow_outer_end, nose_tip, mouth_left_corner, mouth_right_corner, mouth_center_top_lip, mouth_center_bottom_lip
因此神经网络需要学习一个从人脸图像到15个关键点坐标间的映射。

使用的网络结构

在本文中,我们使用深度神经网络来实现该功能,基本卷积块使用Google的Inception网络,也就是使用GoogLeNet网络,该结构的网络是基于卷积神经网络来改进的,是一个含有并行连接的网络。
众所周知,卷积有滤波、提取特征的作用,但到底采用多大的卷积来提取特征是最好的呢?这个问题没有确切的答案,那就集百家之长:使用多个形状不一的卷积来提取特征并进行拼接,从而学习到更为丰富的特征;特别是里面加上了1x1的卷积结构,能够实现跨通道的信息交互和整合(其本质就是在多个channel上的线性求和),同时能在feature map通道数上的降维(读者可以验证计算一下,能够极大减少卷积核的参数),也能够增加非线性映射次数使得网络能够更深。
下面是Inception块的示意图:
在这里插入图片描述
整个GoogLeNet的结构如下所示:
在这里插入图片描述
接下来是代码实现部分,后续作者会补充神经网络的相关原理知识,若对此感兴趣的读者也可继续关注支持~

代码实现

import torch as tc
from torch import nn
from torch.nn import functional as F
from torch.utils.data import DataLoader
from torch.utils.data import TensorDataset
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.utils import shuffle

# 对图片像素的处理
def proFunc1(data,testFlag:bool=False) -> tuple:
    data['Image'] = data['Image'].apply(lambda im: np.fromstring(im, sep=' '))
    # 处理na
    data = data.dropna()  
    # 神经网络对数据范围较为敏感 /255 将所有像素都弄到[0,1]之间
    X = np.vstack(data['Image'].values) / 255
    X = X.astype(np.float32)
    # 特别注意 这里要变成 n channle w h 要跟卷积第一层相匹配
    X = X.reshape(-1, 1,96, 96) 
    # 等会神经网络的输入层就是 96 96 黑白图片 通道只有一个
    # 只有训练集才有y 测试集返回一个None出去
    if not testFlag:  
        y = data[data.columns[:-1]].values
        # 规范化
        y = (y - 48) / 48  
        X, y = shuffle(X, y, random_state=42)  
        y = y.astype(np.float32)
    else:
        y = None

    return X,y

# 工具类
class UtilClass:

    def __init__(self,model,procFun,trainFile:str='data/training.csv',testFile:str='data/test.csv') -> None:
        self.trainFile = trainFile
        self.testFile = testFile
        self.trainData = None
        self.testData = None
        self.trainTarget = None
        self.model = model
        self.procFun = procFun

    @staticmethod
    def procData(data, procFunc ,testFlag:bool=False) -> tuple:
        return procFunc(data,testFlag)

    def loadResource(self):
        rawTrain = pd.read_csv(self.trainFile)
        rawTest = pd.read_csv(self.testFile)
        self.trainData , self.trainTarget = self.procData(rawTrain,self.procFun)
        self.testData , _ = self.procData(rawTest,self.procFun,testFlag=True)

    def getTrain(self):
        return tc.from_numpy(self.trainData), tc.from_numpy(self.trainTarget)

    def getTest(self):
        return tc.from_numpy(self.testData)

    @staticmethod
    def plotData(img, keyPoints, axis):
        axis.imshow(np.squeeze(img), cmap='gray') 
        # 恢复到原始像素数据 
        keyPoints = keyPoints * 48 + 48 
        # 把keypoint弄到图上面
        axis.scatter(keyPoints[0::2], keyPoints[1::2], marker='o', c='c', s=40)

# 自定义的卷积神经网络
class MyCNN(tc.nn.Module):
    def __init__(self,imgShape = (96,96,1),keyPoint:int = 15):
        super(MyCNN, self).__init__()
        self.conv1 = tc.nn.Conv2d(in_channels=1, out_channels =10, kernel_size=3)
        self.pooling = tc.nn.MaxPool2d(kernel_size=2)
        self.conv2 = tc.nn.Conv2d(10, 5, kernel_size=3)
        # 这里的2420是通过下面的计算得出的 如果改变神经网络结构了 
        # 需要计算最后的Liner的in_feature数量 输出是固定的keyPoint*2
        self.fc = tc.nn.Linear(2420, keyPoint*2)

    def forward(self, x):
        # print("start----------------------")
        batch_size = x.size(0)
        # x = x.view((-1,1,96,96))
        # print('after view shape:',x.shape)
        x = F.relu(self.pooling(self.conv1(x)))
        # print('conv1 size',x.shape)
        x = F.relu(self.pooling(self.conv2(x)))
        # print('conv2 size',x.shape)
        # print('end--------------------------')
        # 改形状
        x = x.view(batch_size, -1)
        # print(x.shape)
        x = self.fc(x)
        # print(x.shape)
        return x

# GoogleNet基本的卷积块
class MyInception(nn.Module):

    def __init__(self,in_channels, c1, c2, c3, c4,) -> None:
        super().__init__()
        
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)

    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 在通道维度上连结输出
        return tc.cat((p1, p2, p3, p4), dim=1)

# GoogLeNet的设计 此处参数结果google大量实验得出
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                   nn.ReLU(),
                   nn.Conv2d(64, 192, kernel_size=3, padding=1),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b3 = nn.Sequential(MyInception(192, 64, (96, 128), (16, 32), 32),
                   MyInception(256, 128, (128, 192), (32, 96), 64),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b4 = nn.Sequential(MyInception(480, 192, (96, 208), (16, 48), 64),
                   MyInception(512, 160, (112, 224), (24, 64), 64),
                   MyInception(512, 128, (128, 256), (24, 64), 64),
                   MyInception(512, 112, (144, 288), (32, 64), 64),
                   MyInception(528, 256, (160, 320), (32, 128), 128),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b5 = nn.Sequential(MyInception(832, 256, (160, 320), (32, 128), 128),
                   MyInception(832, 384, (192, 384), (48, 128), 128),
                   nn.AdaptiveAvgPool2d((1,1)),
                   nn.Flatten())


uClass = UtilClass(model=None,procFun=proFunc1)
uClass.loadResource()
xTrain ,yTrain = uClass.getTrain()
xTest = uClass.getTest()

dataset = TensorDataset(xTrain, yTrain)
trainLoader = DataLoader(dataset, 64, shuffle=True, num_workers=4)

# 训练net并进行测试 由于显示篇幅问题 只能打印出极为有限的若干测试图片效果
def testCode(net):
    optimizer = tc.optim.Adam(params=net.parameters())
    criterion = tc.nn.MSELoss()
        
    for epoch in range(30):
        trainLoss = 0.0
        # 这里是用的是mini_batch 也就是说 每次只使用mini_batch个数据大小来计算
        # 总共有total个 因此总共训练 total/mini_batch 次
        # 由于不能每组数据只使用一次 所以在下面还要使用一个for循环来对整体训练多次
        for batchIndex, data in enumerate(trainLoader, 0):
            input_, y = data
            yPred = net(input_)
            loss = criterion(yPred, y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            trainLoss += loss.item()
            # 只在每5个epoch的最后一轮打印信息
            if batchIndex % 30 ==29 and not epoch % 5 :
                print("[{},{}] loss:{}".format(epoch + 1, batchIndex + 1, trainLoss / 300))
                trainLoss = 0.0
    # 测试
    print("-----------test begin-------------")
    # print(xTest.shape)
    yPost = net(xTest)
    # print(yPost.shape)
    import matplotlib.pyplot as plt
    %matplotlib inline
        
    fig = plt.figure(figsize=(20,20))
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
    for i in range(9,18):
        ax = fig.add_subplot(3, 3, i - 9 + 1, xticks=[], yticks=[])
        uClass.plotData(xTest[i], y[i], ax)
    print("-----------test end-------------")
    

if __name__ == "__main__":
    # 训练MyCNN网络 并可视化在9个测试数据的效果图
    myNet = MyCNN()
    testCode(myNet)
    inception = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 30))
	testCode(inception)

本文使用的数据可在此找到两个data文件,本文有你帮助的话,就给个点赞关注支持一下吧!

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

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

相关文章

北邮22信通:(13)二叉树 统计结点总数 深度和叶子结点数

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 上一篇文章: 下一篇文章: 目录 一.统计结点总个数 二.统计二叉树深度 三.统计叶子结点总数 四.完整代码 4.1测试int存储类型&…

基于via的课堂学生行为数据标注 与yolov7目标检测与自动标注系统

目录0 相关链接1. 总体功能描述2.软件安装说明2.1. 环境搭建2.2. 激活环境2.3. 退出环境2.4. 删除环境2.5. 安装opencv3.标注数据处理3.1. 收集3.2.via对标注举手3.3. via 举手标注转化yolo格式3.4. via动作标注扩展3.5. via 举手看书写字标注转化yolo格式4. 标注数据检查与可视…

Vue3.0中的响应式原理

回顾Vue2的响应式原理 实现原理: - 对象类型:通过 Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。 - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹&…

jekins部署java和vue

一、安装jekins,下面安装的是2.387.2版本 必须安装有jdk11或jdk17 二、部署java项目 1.安装插件:Maven Integration plugin和Deploy to container Plugin 2.配置全局环境变量:jdk、git、maven 3.创建maven项目 cd docus-server/docus-serv…

深入底层谈谈String

深入底层谈谈String一、聊聊字符串拼接【底层】二、聊聊String实现(源码分析)实现的接口内部属性及其部分构造函数部分方法说明明明replace,replaceAll,substring等方法得到了新的字符串,为什么说String是不变的呢&…

kali设置静态ip地址

Kali设置静态ip地址 本篇文章主要分3部分讲解如何为Kali虚拟机配置静态IP地址,使其能够与主机和外网进行通信。首先需要配置VMware的虚拟网卡,然后配置Kali虚拟机,最后配置主机。 一、配置VMware 打开VMware,选择【编辑】—【虚…

C++游戏分析与破解方法介绍

1、C游戏简介 目前手机游戏直接用C开发的已经不多,使用C开发的多是早期的基于cocos2dx的游戏,因此我们这里就以cocos2d-x为例讲解C游戏的分析与破解方法。 Cocos2d-x是一个移动端游戏开发框架,可以使用C或者lua进行开发,也可以混…

Spring框架核心与设计思想

文章目录一、Spring是什么?二、什么是IoC容器?什么是IOC?Spring IoC三、DI总结一、Spring是什么? 我们一般所说的Spring指的是Spring Framework(Spring 框架),它是一个开源的框架,Spring支持广泛的应用场景…

Spring事务详解

🏆今日学习目标: 🍀Spring事务详解 ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪发光…

贯穿设计模式第三话--依赖倒转原则

🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳 从今天开始,将…

【GPT4】GPT4 官方报告解读

欢迎关注【youcans的AGI学习笔记】原创作品 【GPT4】GPT-4 官方报告解读1. GPT-4 官方介绍2. GPT-4 的性能2.1 GPT-4 在各种学术和专业考试中的性能2.2 GPT-4 在传统机器学习测试中的性能2.3 GPT-4 在不同语言测试中的性能3. GPT-4 的图像输入功能3.1 GPT-4 图像输入案例3.2 GP…

《分解因数》:质因数分解

目录 一、题目&#xff1a; 二、思路&#xff1a; 三、代码&#xff1a; 一、题目&#xff1a; 分解因数 《分解因数》题目链接 所谓因子分解&#xff0c;就是把给定的正整数a&#xff0c;分解成若干个素数的乘积&#xff0c;即 a a1 a2 a3 ... an,并且 1 < a1…

<数据结构> 链表 - 单链表(c语言实现)

B.最简单结构的链表——不带哨兵位单链表的实现&#xff08;关于哨兵位结点&#xff09; 一、不带哨兵位单链表结点的创建1.1 typedef 链表的数据类型 1.2 结点的结构体创建 二、单链表要实现的功能 三、需要包含的头文件四、函数接口一览为什么有些函数参数传递的是二级指针&a…

【FreeRTOS(二)】FreeRTOS新手入门——计数型信号量和二进制信号量的基本使用并附代码解析

写在前面&#xff1a; 本文章如有错漏之处&#xff0c;敬请指正&#xff0c;另外本文为网络材料整理&#xff0c;侵删。 FreeRTOS信号量的基本使用&代码解析一、信号量概述二、计数型信号量三、二进制信号量四、信号量函数API1、创建信号量2、删除一个信号量3、信号量释放4…

ASP.NET动态Web开发技术第5章

第5章数据验证一.预习笔记 1.验证控件概述&#xff1a; 2.RequiredFieldValidator&#xff08;必填验证&#xff09; 常用属性1&#xff1a;ControlToValidator:被验证的输入控件的ID 常用属性2&#xff1a;Text&#xff1a;验证失败时&#xff0c;验证控件显示的文本 常用…

8.3 总体分布的假设检验

学习目标&#xff1a; 如果我要学习总体分布的假设检验&#xff0c;我会采取以下步骤&#xff1a; 掌握基础概念&#xff1a;学习和掌握统计学中基础的概念&#xff0c;如总体、样本、假设检验、p值等等。 学习检验方法&#xff1a;了解和学习不同的总体分布假设检验方法&…

亚信科技AntDB数据库荣膺第十二届数据技术嘉年华(DTC 2023)“最具潜力数据库”大奖

近日&#xff0c;亚信科技AntDB数据库产品在第十二届数据技术嘉年华&#xff08;DTC 2023&#xff09;峰会上斩获“2022年度最具潜力数据库”大奖。亚信安慧副总裁张桦先生受邀参会&#xff0c;并发表了《AntDB数据库通信行业核心系统应用与创新》的主题演讲&#xff0c;分享了…

vue实现好看的相册、图片网站

目录 一、效果图 1.项目访问地址 2.画虫官方效果图&#xff1a; 3.作者实现的效果图&#xff1a; 二、代码实现 1.项目结构截图 2.路由配置代码&#xff1a; 3. 头部底部主页面内容显示容器的代码 4.首页&#xff0c;即标签页的代码 三、项目启动说明 四、总结 一、…

Android---MVC/MVP/MVVM的演进

目录 一个文件打天下 一个文件--->MVC MVC--->MVP MVP--->MVVM 6大设计原则 完整demo 我们通过"#字棋"游戏来展现MVC-->MVP-->MVVM 之间的演进 一个文件打天下 数据、视图以及逻辑都放在一个 class 里面。而一个 class 里最多 500 行代码&…

springboot 密码加密

首先介绍一下jasypt的使用方法 版本对应的坑 使用的时候还是遇到一个坑&#xff0c;就是jasypt的版本与spring boot版本存在对应情况。可以看到jasypt是区分java7和java8的&#xff0c;也存在依赖spring版本的情况。 自己尝试了一下 在使用jasypt-spring-boot-starter的前提…