论文阅读二——基于全脸外观的凝视估计

论文阅读二——基于全脸外观的凝视估计

    • 基础知识
    • 主要内容
    • 文章中需要学习的架构
      • AlexNet
    • 代码复现

该论文是2017年在CVPR中发表的一篇关于 “gaze estimation” 的文章,其论文地址与代码地址如下:

论文地址

代码地址

论文特点:文章提出了一种基于外观的方法,只将完整的人脸图像作为输入,使用卷积神经网络对人脸图像进行编码,在特征图上应用空间权重,以灵活地抑制或增强不同面部区域的信息。

基础知识

凝视估计的方法主要可以分成两种:基于模型的方法和基于外观的方法。

  • 基于模型的方法:使用眼睛和面部的几何模型来估计注视方向。(会受到图像质量低和光照条件变化的影响)

    • 基于角膜反射的方法:依靠外部光源来检测眼睛的特征。
    • 基于形状的方法:从观察到的眼睛形状,如瞳孔中心和虹膜边缘推断凝视方向。
  • 基于外观的方法:直接从眼部图像到注视方向进行回归,根据回归目标是2D还是3D还可以进一步进行分类,需要更大量的特定于用户的训练数据。如下:

特征2D眼动估计方法3D眼动估计方法
基本假设目标头部姿势固定目标头部姿势可以自由移动
任务重点(估计器)输出屏幕上的2D注视位置(x, y)输出在摄像机坐标系中的3D注视方向(x, y, z)
摄像机移动限制不允许自由摄像机移动允许自由摄像机移动
技术挑战可能受到头部姿势变化的限制如何在不需要大量训练数据的情况下有效训练估计器

“单眼基线法”:指使用只有一个眼睛的信息进行眼动估计的基本方法。(使用单个眼睛)

iTracker (AlexNet) :一种基于深度学习(卷积神经网络)的眼动估计方法,仅用眼睛和脸部的裁剪来训练网络,旨在利用单眼图像进行注视点的预测。
在这里插入图片描述

“Two eyes” :指的是同时利用两只眼睛的信息进行眼动估计的方法,两只眼睛提供了更多的深度和空间信息,有助于更准确地估计用户的注视点和眼动轨迹。

主要内容

文章指出,在机器学习方法能够从面部的其他区域获得额外的信息,采用一个多区域的卷积神经网络(CNN)架构,将眼部图像和面部图像同时作为输入,有助于提高眼动估计的性能。这些区域可能包括头部姿势或在整个图像区域内编码照明特定信息(例如:头部姿势、凝视方向和光照)的部分。相比于眼部区域,这些区域可能覆盖更大的图像范围。因此,文章作者针对MPIIGaze数据集,引入了全脸CNN架构与空间权重机制:

  • 基于空间权重CNN的人脸凝视估计

在这里插入图片描述

如上图,输入图像通过多个卷积层来生成特征张量U。所提出的空间权重机制以U为输入来生成使用逐元素乘法将权重映射W应用于U。输出特征张量V被馈送到根据任务的不同,按照全连接层输出最终的2D或3D凝视估计。上图中使用了三个1×1卷积层加上校正的线性单元层(Relu激活函数)的概念作为基础,将其适应于全人脸凝视估计任务,迫使网络更明确地学习和理解面部的不同区域对估计给定测试样本的凝视的不同重要性,而这部分也就是空间权重机制的结构。

全脸估计带来了头部姿势信息,可以作为眼球凝视方向的一个先验信息。

1. 直接将头部姿势作为凝视方向的朴素估计器
1. 通过训练从头部姿势输入输出凝视方向的线性回归函数。
1. 结论:不同的面部区域在推断注视方向时具有不同的重要性。(当注视方向直视前方时,眼睛区域最为重要,而当注视方向变得更极端时,模型对其他区域的重要性提高。)
  • 空间权重机制

从3个1×1卷积层获得的激活张量 U(大小为 N×H×W)用于生成一个大小为 H×W 的空间权重矩阵 W。通过将 WU 逐元素相乘,得到加权激活图 Vc=WUc,其中 U**cU 的第 c 个通道,而 Vc 对应于相同通道的加权激活图。这些加权激活图堆叠形成加权激活张量 V,然后传递到下一层。在训练期间,前两个卷积层的过滤权重从均值为0、方差为0.01的高斯分布中随机初始化,偏差为0.1。最后一个卷积层的过滤权重从均值为0、方差为0.001的高斯分布中随机初始化,偏差为1。计算相对于 UW 的梯度,其中∂U/∂V 等于梯度 ,而 ∂W/∂V 等于所有通道的梯度∂W/∂Uc 的平均值。与权重相关的梯度 ∂W/∂V 被总特征图的数量 N 归一化。这个机制通过学习连续的空间权重,保持来自不同面部区域的信息,提高了网络对全脸外观的眼动估计任务的性能。

  • 图像归一化:对输入图像应用透视变换,使得估计可以在具有固定摄像机参数和参考点位置的归一化空间中进行。通过这种方法,可以处理不同摄像机参数,并在归一化的空间中进行训练和估计。

文章中需要学习的架构

AlexNet

该架构由八层组成:五个卷积层和三个全连接层。其结构图,如下:
在这里插入图片描述
以ImageNet数据集为例子,对AlexNet每一层进行分析:

  • 第一层:输入层
    输入图像的大小为227x227x3(RGB三通道)。

  • 第二层:卷积层(Convolutional Layer)
    96个大小为11x11x3的卷积核,步长为4。
    使用ReLU激活函数。
    池化层(Max Pooling):3x3大小,步长为2。

  • 第三层:卷积层
    256个大小为5x5x48的卷积核。
    使用ReLU激活函数。
    池化层:3x3大小,步长为2。

  • 第四层:卷积层
    384个大小为3x3x256的卷积核。
    使用ReLU激活函数。

  • 第五层:卷积层
    384个大小为3x3x192的卷积核。
    使用ReLU激活函数。

  • 第六层:卷积层
    256个大小为3x3x192的卷积核。
    使用ReLU激活函数。
    池化层:3x3大小,步长为2。

  • 第七层:全连接层(Fully Connected Layer)
    4096个神经元。
    使用ReLU激活函数。
    Dropout:为了减少过拟合,引入了Dropout操作。

  • 第八层:全连接层
    4096个神经元。
    使用ReLU激活函数,Dropout。

  • 第九层:全连接层,也是输出层
    1000个神经元,对应ImageNet数据集中的类别数。
    使用Softmax激活函数进行分类。

代码复现

首先,根据下载后的代码进行搭建环境,最好配置一个专门的虚拟环境,防止因为环境紊乱而无法完成操作。这里主要强调:需要根据代码的README.md文档进行操作。
其次,在搭建好环境后,我主要遇到了一个问题,就是gazenet.py中的models.mobilenet.ConvBNReLU(320, 256, kernel_size=1)已经不用了,需要进行以下操作:

# 添加以下代码
class ConvBNReLU(nn.Sequential):
    def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1):
        padding = (kernel_size - 1) // 2
        super(ConvBNReLU, self).__init__(
            nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False),
            nn.BatchNorm2d(out_channel),
            nn.ReLU6(inplace=True)
        )
# 将原文中的`model.mobilenet.convBNReLU`进行替换
model.features[-1] = ConvBNReLU(320, 256, kernel_size=1)

最后的gazenet.py文件的内容为:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models

import numpy as np 

from PIL import Image
from torchvision import transforms

class ConvBNReLU(nn.Sequential):
    def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1):
        padding = (kernel_size - 1) // 2
        super(ConvBNReLU, self).__init__(
            nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False),
            nn.BatchNorm2d(out_channel),
            nn.ReLU6(inplace=True)
        )


class GazeNet(nn.Module):

    def __init__(self, device):    
        super(GazeNet, self).__init__()
        self.device = device
        self.preprocess = transforms.Compose([
            transforms.Resize((112,112)),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])

        model = models.mobilenet_v2(pretrained=True)
        model.features[-1] = ConvBNReLU(320, 256, kernel_size=1)


        self.backbone = model.features

        self.Conv1 = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=1, stride=1, padding=0)
        self.Conv2 = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=1, stride=1, padding=0)
        self.Conv3 = nn.Conv2d(in_channels=256, out_channels=1, kernel_size=1, stride=1, padding=0)

        self.fc1 = nn.Sequential(
            nn.Linear(256*4*4, 512),
            nn.ReLU(),
            nn.Dropout(0.5)
        )
        self.fc2 = nn.Sequential(
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Dropout(0.5)
        )   
        self.fc_final = nn.Linear(512, 2)

        self._initialize_weight()
        self._initialize_bias()
        self.to(device)


    def _initialize_weight(self):
        nn.init.normal_(self.Conv1.weight, mean=0.0, std=0.01)
        nn.init.normal_(self.Conv2.weight, mean=0.0, std=0.01)
        nn.init.normal_(self.Conv3.weight, mean=0.0, std=0.001)

    def _initialize_bias(self):
        nn.init.constant_(self.Conv1.bias, val=0.1)
        nn.init.constant_(self.Conv2.bias, val=0.1)
        nn.init.constant_(self.Conv3.bias, val=1)

    def forward(self, x):
        
        x = self.backbone(x)
        y = F.relu(self.Conv1(x))
        y = F.relu(self.Conv2(y))
        y = F.relu(self.Conv3(y))
        
        x = F.dropout(F.relu(torch.mul(x, y)), 0.5)
        x = x.view(x.size(0), -1)
        x = self.fc1(x)
        x = self.fc2(x)
        gaze = self.fc_final(x)

        return gaze

    def get_gaze(self, img):
        img = Image.fromarray(img)
        img = self.preprocess(img)[np.newaxis,:,:,:]
        x = self.forward(img.to(self.device))
        return x

最终运行cam_demo.py,最终可以实现实时检测人眼眼球凝视方向。

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

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

相关文章

IT和业务部门都想要的数据摆渡产品是这样的!

企业只要进行了网络隔离,就必然会需要数据摆渡相关产品,不管是免费的也好,专业收费的也好,总之都是需要将数据流转起来进行使用。 传统的数据摆渡产品也不少,比如FTP,甚至是U盘拷贝,在功能上来说…

Java数组(2)

我是南城余!阿里云开发者平台专家博士证书获得者! 欢迎关注我的博客!一同成长! 一名从事运维开发的worker,记录分享学习。 专注于AI,运维开发,windows Linux 系统领域的分享! 本…

Docker容器如何优雅地访问宿主机网络

# 前言 某些时候,我们会有在容器内容访问宿主机某个服务的需求,比如现在 openai 无法直接访问,需要给项目添加代理,我的 chatgpt-dingtalk (opens new window) 项目支持了通过环境变量指定代理地址。 添加方式如下: …

微信小程序(五)地图

一、引言 作者开发《目的地到了》需要满足用户选取地址作为目的地的需求,所以需要使用到地图。作者用的是腾讯地图,这里介绍下技术实现。 二、引包 引入腾讯地图的组件包微信小程序JavaScript SDK | 腾讯位置服务,根据经纬度调用里面的api才…

5款不可或缺的办公软件,好用且使用频率超高,几乎每个人都需要

在当今数字化时代,办公软件成为了现代职场必备的工具。这些软件可以大大提高我们的办公效率,简化工作流程,使我们更加高效地完成任务。今天给大家分享5款不可或缺的办公软件,它们不仅好用,而且使用频率极高&#xff0c…

【PWN】学习笔记(三)【返回导向编程】(中)

目录 课程回顾动态链接过程 课程 课程链接:https://www.bilibili.com/video/BV1854y1y7Ro/?vd_source7b06bd7a9dd90c45c5c9c44d12e7b4e6 课程附件: https://pan.baidu.com/s/1vRCd4bMkqnqqY1nT2uhSYw 提取码: 5rx6 回顾 管道符 | 把前一个指令的输出作…

【C++高阶(七)】C++异常处理的方式

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C   🔝🔝 异常处理的方式 1. 前言2. C语言处理异常的方式…

JVM-1-运行时数据区

程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里[1],字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令&…

centos安装了curl却报 -bash: curl: command not found

前因 我服务器上想用curl下载docker-compress,发现没有curl命令,就去下载安装,安装完成之后,报-bash: curl: command not found 解决方法 [rootcentos ~]# rpm -e --nodeps curl warning: file /usr/bin/curl: remove failed: …

AIGC实战——条件生成对抗网络(Conditional Generative Adversarial Net, CGAN)

AIGC实战——条件生成对抗网络 0. 前言1. CGAN架构2. 模型训练3. CGAN 分析小结系列链接 0. 前言 我们已经学习了如何构建生成对抗网络 (Generative Adversarial Net, GAN) 以从给定的训练集中生成逼真图像。但是,我们无法控制想要生成的图像类型,例如控…

Nat Med | Tau靶向反义寡核苷酸

今天给同学们分享一篇实验文章“Tau-targeting antisense oligonucleotide MAPTRx in mild Alzheimers disease: a phase 1b, randomized, placebo-controlled trial”,这篇文章发表在Nat Med期刊上,影响因子为82.9。 结果解读: 患者 从201…

防止反编译,保护你的SpringBoot项目

ClassFinal-maven-plugin插件是一个用于加密Java字节码的工具,它能够保护你的Spring Boot项目中的源代码和配置文件不被非法获取或篡改。下面是如何使用这个插件来加密test.jar包的详细步骤: 安装并设置Maven: 首先确保你已经在你的开发环境中…

关于#c语言#的问题:分析递归调用的过程◇画出调用过程各语句执行过程

关于#c语言#的问题&#xff1a;分析递归调用的过程◇画出调用过程各语句执行过程 当涉及到递归调用的过程时&#xff0c;可以通过绘制函数调用栈来分析和理解递归的执行过程。下面是一个示例的C语言递归函数和相应的调用过程&#xff1a; #include <stdio.h>void recurs…

数据结构 AVL树概念以及实现插入的功能(含Java代码实现)

为啥要有avl树 avl树是在二叉搜索树下的一种进阶形式,是为了防止二叉搜索树在极端情况下产生的链表化的场景,从而在二叉搜索树的基础上,加上了某些条件来阻止这种极端情况的产生,但不是保证完全平衡,而是放开了一定的条件,使得这种情况不那么难以满足.(条件:左右子树的高度差的…

【【UART 传输数据实验】】

UART 传输数据实验 通信方式在日常的应用中一般分为串行通信&#xff08;serial communication&#xff09;和并行通信&#xff08;parallel communication&#xff09;。 我们再来了解下串行通信的特点。串行通信是指数据在一条数据线上&#xff0c;一比特接一比特地按顺序传…

Python 爬虫开发完整环境部署,爬虫核心框架安装

Python 爬虫开发完整环境部署 前言&#xff1a; ​ 关于本篇笔记&#xff0c;参考书籍为 《Python 爬虫开发实战3 》 笔记做出来的一方原因是为了自己对 Python 爬虫加深认知&#xff0c;一方面也想为大家解决在爬虫技术区的一些问题&#xff0c;本篇文章所使用的环境为&#x…

Esxi中的AlmaLinux硬盘扩容

Esxi中的AlmaLinux硬盘扩容 通过本文能学习到 虚拟机中的AlmaLinux硬盘扩容 本文主要包括3部分内容&#xff1a; 1. 需要进行扩容的原因 2. 写这篇文章的目的 3. 扩容实操需要进行扩容的原因 近日&#xff0c;使用Jenkins部署时&#xff0c;出现镜像向Nexus私服推送镜像时…

展示一段比较简单的人工智能自动做模型的程序

人工智能是一种模拟或模仿人类智能的技术。它通过使计算机系统具有一定的认知能力和学习能力&#xff0c;使其能够自动完成一系列复杂的任务。人工智能可以在各个领域应用&#xff0c;包括图像识别、语音识别、自然语言处理、机器学习等。人工智能还可以用于解决各种问题&#…

互联网加竞赛 python 机器视觉 车牌识别 - opencv 深度学习 机器学习

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于python 机器视觉 的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;3分 &#x1f9ff; 更多资…

高通平台开发系列讲解(AI篇)SNPE工作流程介绍

文章目录 一、转换网络模型二、量化2.1、选择量化或非量化模型2.2、使用离线TensorFlow或Caffe模型2.3、使用非量化DLC初始化SNPE2.4、使用量化DLC初始化SNPE三、准备输入数据四、运行加载网络沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要介绍SNPE模型工作…