基于 Pytorch 的全卷积网络人脸表情识别:从数据到部署的实战之旅

前言

本文将详细介绍基于 Pytorch 框架,利用全卷积网络进行人脸表情识别的完整过程,涵盖从数据集的准备、模型的设计与训练,再到模型的部署与预测,通过代码实现以及详细讲解,帮助读者深入理解并掌握这一技术。

一、引言

人脸表情是人类情感交流的重要方式,不同的表情能够传达出丰富的情感信息。人脸表情识别在智能交互、安防监控、心理健康分析等众多领域有着广泛的应用前景。随着深度学习技术的发展,基于卷积神经网络的人脸表情识别方法取得了显著的成果。本篇博客将基于 Pytorch 框架,介绍全卷积网络在人脸表情识别中的应用,从数据、模型设计与训练、部署与预测等方面展开,带领读者一步步实现一个人脸表情识别系统。

二、人脸表情数据集

2.1 公开人脸表情数据库

人的面部由 44 块肌肉构成,可以产生超过 5000 多种表情,至少有 18 个独特的微表情,这些微表情能够反映人的心理活动与情绪。在人脸表情识别领域,有许多公开的数据库可供使用,例如 AffectNet,其官网地址为AffectNet – Mohammad H. Mahoor, PhD 。这些公开数据集为我们的研究和模型训练提供了丰富的数据资源1。

2.2 自定义表情数据集

除了公开数据集,我们还可以创建自己的自定义表情数据集。本次使用的自定义表情数据集包含 4300 张人脸表情图像,涵盖了八种表情类别,分别是 ["neutral", "anger", "disdain", "disgust", "fear", "happy", "sadness", "surprise"],图像大小均为 64x64。以下是加载自定义数据集的代码示例:

import torch
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import os

class FaceEmotionDataset(Dataset):
    def __init__(self, data_dir, transform=None):
        self.data_dir = data_dir
        self.transform = transform
        self.image_paths = []
        self.labels = []
        emotion_labels = ["neutral", "anger", "disdain", "disgust", "fear", "happy", "sadness", "surprise"]
        for emotion in emotion_labels:
            emotion_dir = os.path.join(data_dir, emotion)
            for file_name in os.listdir(emotion_dir):
                image_path = os.path.join(emotion_dir, file_name)
                self.image_paths.append(image_path)
                self.labels.append(emotion_labels.index(emotion))

    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, idx):
        image_path = self.image_paths[idx]
        image = Image.open(image_path).convert("RGB")
        label = self.labels[idx]
        if self.transform:
            image = self.transform(image)
        return image, label

# 示例用法
data_dir = "your_data_dir"  # 替换为实际数据集路径
transform = torchvision.transforms.Compose([
    torchvision.transforms.Resize((64, 64)),
    torchvision.transforms.ToTensor()
])
dataset = FaceEmotionDataset(data_dir, transform=transform)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

上述代码首先定义了一个FaceEmotionDataset类,继承自Dataset类。在__init__函数中,遍历数据集目录,获取所有图像的路径和对应的标签。__len__函数返回数据集的大小,__getitem__函数根据索引获取图像和标签,并在有变换操作时对图像进行变换。最后,通过示例展示了如何使用这个数据集类加载数据,并创建数据加载器。

三、全卷积网络设计

3.1 深度神经网络基础

在深入了解全卷积网络之前,先回顾一下深度神经网络的基本组成部分。深度神经网络中的卷积神经网络(CNN)由卷积层(Conv)和池化层(Pool)等组成,能够自动提取图像的特征。全连接层(FC)则用于对提取的特征进行分类等任务。常见的网络结构如Input -> Conv -> Pool -> Conv -> Pool -> FC -> FC -> Softmax,输入图像经过卷积层和池化层的多次处理,提取到高级特征,再通过全连接层进行分类,最后经过 Softmax 函数进行概率分布,输出各类别的概率。

3.2 全卷积神经网络(FCN)

全卷积神经网络(FCN)是一种特殊的神经网络结构,它将传统卷积神经网络中的全连接层替换为卷积层。在本文的人脸表情识别模型中,全卷积神经网络的结构如下:
输入图像大小为224x224x3,经过一系列的卷积层和 ReLU 激活函数,以及最大池化层的操作,图像的尺寸逐渐减小,通道数逐渐增加,具体变化为224x224x3 -> 224x224x64 -> 112x112x128 -> 56x56x256 -> 28x28x512 -> 14x14x512 -> 7x7x512 -> 1x1x4096 -> 1x1x1000,最后通过 Softmax 函数进行分类。此外,模型中还包含残差 block 和输出头,输出头的形状为Nx8x1x1,其中 8 对应八种表情类别。模型使用交叉熵损失作为分类损失函数789。
以下是构建全卷积神经网络模型的代码示例:

import torch
import torch.nn as nn

class FaceEmotionFCN(nn.Module):
    def __init__(self):
        super(FaceEmotionFCN, self).__init__()
        self.conv_layers = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(512, 4096, kernel_size=7),
            nn.ReLU(),
            nn.Conv2d(4096, 1000, kernel_size=1),
            nn.ReLU()
        )
        self.output_head = nn.Conv2d(1000, 8, kernel_size=1)

    def forward(self, x):
        x = self.conv_layers(x)
        x = self.output_head(x)
        return x

# 示例用法
model = FaceEmotionFCN()
input_tensor = torch.randn(1, 3, 224, 224)
output = model(input_tensor)
print(output.shape)

上述代码定义了FaceEmotionFCN类,继承自nn.Module。在__init__函数中,构建了卷积层序列conv_layers和输出头output_headforward函数定义了数据的前向传播过程,前向传播计算传播计算损失率,反馈传播计算每个函数的梯度,来达到调节参数的目的。输入数据经过卷积层序列处理后,再通过输出头得到最终的输出。最后通过示例展示了如何创建模型实例,并输入一个随机张量,查看输出的形状。

四、模型训练

在定义好模型结构后,我们需要对模型进行训练。训练过程主要包括以下几个步骤:

  1. 定义损失函数和优化器
    使用交叉熵损失函数nn.CrossEntropyLoss()作为模型的损失函数,优化器选择随机梯度下降(SGD),学习率设置为 0.001,动量设置为 0.9。
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
  1. 训练循环
num_epochs = 10
for epoch in range(num_epochs):
    running_loss = 0.0
    for i, (inputs, labels) in enumerate(dataloader):
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
        if (i + 1) % 100 == 0:
            print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, i + 1, len(dataloader), running_loss / 100))
            running_loss = 0.0

上述代码首先定义了训练的轮数num_epochs,在每一轮训练中,遍历数据加载器dataloader。在每次迭代中,先将优化器的梯度清零,然后将输入数据传入模型得到输出,计算损失函数,进行反向传播计算梯度,最后使用优化器更新模型参数。每 100 步打印一次训练的损失信息。

五、表情识别模型导出

训练好模型后,我们可以将其导出为 ONNX 格式,以便在其他平台或框架中使用。以下是导出模型为 ONNX 格式的代码:

import torch

# 假设已经训练好的模型为model
dummy_input1 = torch.randn(1, 3, 64, 64)
torch.onnx.export(model, (dummy_input1), "face_emotions_model.onnx", verbose=True)

上述代码中,首先创建一个形状为(1, 3, 64, 64)的随机张量dummy_input1作为模型的输入示例,然后使用torch.onnx.export函数将模型导出为face_emotions_model.onnx文件,verbose=True表示打印详细的导出信息。导出后的模型输入为input.1,类型为float32[1,3,64,64],输出为output,类型为float32[1,8]

六、模型部署与预测

6.1 借用 OpenCV4.x DNN 的人脸检测模型

在进行实时人脸表情识别时,首先需要检测图像中的人脸。我们可以借用 OpenCV4.x DNN 模块中的人脸检测模型来实现人脸检测。以下是使用 OpenCV 进行人脸检测的代码示例:

import cv2

# 加载人脸检测模型
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel")

# 读取图像
image = cv2.imread("test_image.jpg")
(h, w) = image.shape[:2]
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0))

# 进行人脸检测
net.setInput(blob)
detections = net.forward()

上述代码首先使用cv2.dnn.readNetFromCaffe函数加载人脸检测模型,模型的配置文件为deploy.prototxt,权重文件为res10_300x300_ssd_iter_140000.caffemodel。然后读取一张测试图像,将其转换为模型所需的 blob 格式,通过net.setInput设置输入,net.forward进行前向传播,得到人脸检测结果detections

6.2 截取人脸 ROI 区域

检测到人脸后,我们需要截取人脸的感兴趣区域(ROI),以便后续输入到表情预测模型中。以下是截取人脸 ROI 区域的代码:

for i in range(0, detections.shape[2]):
    confidence = detections[0, 0, i, 2]
    if confidence > 0.5:
        box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
        (startX, startY, endX, endY) = box.astype("int")
        face_roi = image[startY:endY, startX:endX]

上述代码遍历人脸检测结果,对于置信度大于 0.5 的检测框,获取其坐标信息,并从原始图像中截取人脸 ROI 区域。

6.3 调用表情预测模型预测人脸表情并显示

截取到人脸 ROI 区域后,将其输入到训练好的表情预测模型中,预测人脸的表情,并在图像上显示出来。以下是完整的实时人脸表情检测代码:

import cv2
import torch
import torchvision.transforms as transforms

# 加载人脸检测模型
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel")

# 加载表情预测模型
model = torch.load("face_emotions_model.pth")
model.eval()

# 定义图像变换
transform = transforms.Compose([
    transforms.Resize((64, 64)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    if not ret:
        break
    (h, w) = frame.shape[:2]
    blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0))
    net.setInput(blob)
    detections = net.forward()
    for i in range(0, detections.shape[2]):
        confidence = detections[0, 0, i, 2]
        if confidence > 0.5:
            box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
            (startX, startY, endX, endY) = box.astype("int")
            face_roi = frame[startY:endY, startX:endX]
            face_roi = cv2.cvtColor(face_roi, cv2.COLOR_BGR2RGB)
            face_tensor = transform(face_roi).unsqueeze(0)
            with torch.no_grad():
                output = model(face_tensor)
                _, predicted = torch.max(output, 1)
                emotion = ["neutral", "anger", "disdain", "disgust", "fear", "happy", "sadness", "surprise"][predicted.item()]
                cv2.rectangle(frame, (startX, startY), (endX, endY), (0, 255, 0), 2)
                cv2.putText(frame, emotion, (startX, startY - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
    cv2.imshow("Face Emotion Detection", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

上述代码首先加载人脸检测模型和表情预测模型,定义图像变换操作。然后打开摄像头,在每一帧图像中进行人脸检测,截取人脸 ROI 区域,将其转换为模型所需的张量格式,输入到表情预测模型中得到预测结果,最后在图像上绘制检测框和表情标签,并显示图像。按q键可以退出程序。

在实际应用中,可能会遇到准确性问题,此时可以使用更大的数据集来训练模型,以提高模型的性能。同时,还可以尝试其他的模型结构和训练方法,进一步优化模型。

可视化:

基于 Pytorch 的全卷积网络人脸表情识别:从数据到部署的实战之旅_第1张图片

七、总结

本文基于 Pytorch 框架,详细介绍了全卷积网络在人脸表情识别中的应用,从人脸表情数据集的准备,到全卷积网络的设计与训练,再到模型的导出、部署与预测,通过完整的代码示例展示了整个过程。

你可能感兴趣的:(Pytorch理论+实践,pytorch,网络,人工智能)