py视觉识别项目:摄像头表情识别

导包

import cv2
import numpy as np
import os
import shutil
import threading
import tkinter as tk
from PIL import ImageFont, ImageDraw
from PIL import Image, ImageTk
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader  
from torchvision import datasets, transforms
from torch.optim import Adam
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

项目初始化

加载中文字体文件

# 加载一个支持中文的字体文件,并将其设置为30磅大小,以便在Pillow绘图中使用。
font_path = "SourceHanSansSC-Bold.otf"
font = ImageFont.truetype(font_path, 30)

模型部署

我这里是直接使用训练好的模型参数放入到模型网络中所以要先部署神经网络模型,你们可以使用自己的也可以使用我的模型跑完了之后保存模型并应用

# 定义了一个名为CNNModel的卷积神经网络

# 定义了一个新的类,名为CNNModel,它继承自nn.Module。nn.Module是PyTorch中所有神经网络模块的基类。

class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        # 定义了第一个卷积层,它接受3个输入通道(例如RGB图像),输出32个特征图,使用3x3的卷积核,并且使用相同的填充(padding)以保持输入和输出的空间维度相同。
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding='same')
# 定义了更多的卷积层和归一化层(Batch Normalization)以及池化层和dropout层
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding='same')
        self.conv3 = nn.Conv2d(64, 128, kernel_size=5, padding='same')
        self.conv4 = nn.Conv2d(128, 512, kernel_size=3, padding='same')
        self.conv5 = nn.Conv2d(512, 512, kernel_size=3, padding='same')

        # Batch normalization layers
        self.bn1 = nn.BatchNorm2d(32)
        self.bn2 = nn.BatchNorm2d(64)
        self.bn3 = nn.BatchNorm2d(128)
        self.bn4 = nn.BatchNorm2d(512)
        self.bn5 = nn.BatchNorm2d(512)
        self.bn6 = nn.BatchNorm1d(256)
        self.bn7 = nn.BatchNorm1d(512)

        # Pooling and dropout
        self.pool = nn.MaxPool2d(2, 2)
        self.dropout = nn.Dropout(0.25)
        
        # 定义了第一个全连接层,它接受512个特征图,每个特征图的大小是6x6
        self.fc1 = nn.Linear(512 * 6 * 6, 256)  # Adjusted the input features
        self.fc2 = nn.Linear(256, 512)
        self.fc3 = nn.Linear(512, 7)

    # 定义了前向传播函数,它接受输入数据x
# 这个函数中,输入数据通过一系列的卷积层、批量归一化层、池化层、dropout层和全连接层。在每个卷积层和全连接层之后使用ReLU激活函数

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = F.relu(self.bn3(self.conv3(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = F.relu(self.bn4(self.conv4(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = F.relu(self.bn5(self.conv5(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = x.view(x.size(0), -1)  # Flatten the tensor
        x = F.relu(self.bn6(self.fc1(x)))
        x = self.dropout(x)
        x = F.relu(self.bn7(self.fc2(x)))
        x = self.dropout(x)
        x = self.fc3(x)
        return x

# 代码创建了CNNModel类的一个实例,名为model。这个实例可以用于进一步的训练和预测任务。
model = CNNModel()

加载PyTorch模型

model.load_state_dict(torch.load('sjnet_final2.pth'))
# 设置模型为评估模式
model.eval()  

创建一个PIL Image对象,用于绘制文字

# 创建了一个1x1像素的PIL图像对象,所有的像素值都是0,这意味着图像是全黑的
img_pil = Image.fromarray(np.zeros((1, 1), dtype=np.uint8))
# 创建了一个ImageDraw对象,它可以在PIL图像上进行绘图操作
draw = ImageDraw.Draw(img_pil)
# 设置了图像的原始大小为100x100像素
img_size = 100
# 这两个变量可能代表了目标坐标,可能是图像处理或绘图操作的目标位置
targetx = 100
targety = 100
# 表示在机器学习模型训练中将要进行的迭代次数。每个epoch都是一次完整的训练数据集的遍历。
epochs = 100   
# 这个变量表示在训练过程中,每个batch中将包含64个样本
batch_size = 64

数据增强和预处理

transform = transforms.Compose([
# 这个变换会将输入图像的大小调整为targetx和targety指定的尺寸
    transforms.Resize((targetx, targety)),
# 这个变换会将PIL图像或者NumPy数组转换为浮点Tensor,其像素值会在[0, 1]范围内
    transforms.ToTensor(),
])
# 代码检查是否有可用的GPU,如果有,则将device设置为"cuda",表示使用GPU进行计算;如果没有可用的GPU,则将device设置为"cpu",表示使用CPU进行计算
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model.to(device)

启动摄像头

cap = cv2.VideoCapture(0)

定义表情列表

EMOTIONS = ['惊讶', '恐惧', '厌恶', '开心', '伤心', '愤怒', '中性 ']

主循环

主循环完整代码

try:
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

        # 在检测到的人脸区域进行面部表情识别
        for (x, y, w, h) in faces:
            roi_gray = gray[y:y+h, x:x+w]
            roi_rgb = cv2.cvtColor(roi_gray, cv2.COLOR_GRAY2BGR)  # 转换为RGB图像
            roi_pil = Image.fromarray(roi_rgb)
            # 加载图像并进行预测
            image = transform(roi_pil).unsqueeze(0).to(device)  # 添加batch维度
            output = model(image)
            
            # 获取最可能的表情
            _, predicted = torch.max(output, 1)

            # 预测结果是一个数组,其中包含了不同表情的概率
            emotion_label = EMOTIONS[predicted]  

            # 在视频帧上显示表情标签
            cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
            pil_img = Image.fromarray(frame)
            draw = ImageDraw.Draw(pil_img)
            draw.text((x, y - 10), emotion_label, font=font, fill=(0, 255, 0))
            # 将PIL图像转换回OpenCV格式
            cv_img = np.array(pil_img)
        
        # 调整窗口大小为800x800
        cv2.namedWindow("Camera expression recognition", cv2.WINDOW_NORMAL)
        cv2.resizeWindow("Camera expression recognition", 900, 700)
            
        # 显示图像
        cv2.imshow("Camera expression recognition", cv_img)
        # cv2.waitKey(1)


        # 按'q'退出循环
        if cv2.waitKey(1) & 0xFF == 27:
            break

主循环代码详解

1.加入错误排除

try:
#这里写可能出问题的代码
finally:
#这里写上面出现问题后的解决方案

这样就可以正常在代码出问题时关闭摄像头等等组件

2主循环头部初始化

在程序主循环的头部我们需要对摄像头进行判断查看是否正常打开

try:
    while True:
        ret, frame = cap.read()
        if not ret:
            break

cap.read()方法返回两个值:第一个值ret表示读取是否成功,第二个值frame表示读取到的帧。如果not ret则break关闭

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

这段代码使用OpenCV库将彩色图像转换为灰度图像,然后使用面部级联分类器(face cascade classifier)来检测灰度图像中的人脸。cv2.cvtColor()函数用于颜色空间转换,将BGR(蓝绿红)格式的图像转换为灰度图像。face_cascade.detectMultiScale()函数用于检测图像中的人脸,其中scaleFactor表示在每个图像尺度下的缩放比例,minNeighbors表示每个候选矩形应该保留的邻居数,minSize表示检测到的对象的最小尺寸。但是这里检测到的faces只是人脸的坐标

3.主循环核心部分解释

        # 在检测到的人脸区域进行面部表情识别
        for (x, y, w, h) in faces:
            roi_gray = gray[y:y+h, x:x+w]
            roi_rgb = cv2.cvtColor(roi_gray, cv2.COLOR_GRAY2BGR)  # 转换为RGB图像
            roi_pil = Image.fromarray(roi_rgb)
            # 加载图像并进行预测
            image = transform(roi_pil).unsqueeze(0).to(device)  # 添加batch维度
            output = model(image)

人脸坐标提取:

由于faces只是人脸的坐标所以需要将人脸坐标进行提取
• gray[y:y+h, x:x+w]:
o y:y+h 表示行的范围,从第 y 行开始,到第 y+h 行结束(不包括第 y+h 行)。
o x:x+w 表示列的范围,从第 x 列开始,到第 x+w 列结束(不包括第 x+w 列)。
因此,gray[y:y+h, x:x+w] 提取的是从 (x, y) 位置开始,宽度为 w,高度为 h 的子区域。这通常用于从图像中提取特定的区域,例如在面部表情识别中提取人脸区域。

将RGB图像转换为PIL图像:

            roi_pil = Image.fromarray(roi_rgb)

roi_rgb 是一个NumPy数组,这一步将其转换为PIL图像对象,方便后续使用PIL的功能进行处理

加载图像并进行预测:

            image = transform(roi_pil).unsqueeze(0).to(device)  # 添加batch维度

o transform(roi_pil):将PIL图像进行预处理(例如归一化、调整大小等),transform 是一个预处理函数或管道。
o .unsqueeze(0):在第0维添加一个维度,将图像转换为具有批次维度的张量,这样模型可以处理单张图像。
o .to(device):将图像张量移动到指定的设备(例如GPU或CPU),以便模型进行处理。

            # 在视频帧上显示表情标签
            cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
            pil_img = Image.fromarray(frame)
            draw = ImageDraw.Draw(pil_img)
            draw.text((x, y - 10), emotion_label, font=font, fill=(0, 255, 0))

在视频帧上绘制矩形框:

cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)

o frame 是当前的视频帧。
o (x, y) 是矩形框的左上角坐标。
o (x + w, y + h) 是矩形框的右下角坐标。
o (255, 0, 0) 是矩形框的颜色,这里是蓝色(BGR格式)。
o 2 是矩形框的线条宽度。

将视频帧转换为PIL图像:

pil_img = Image.fromarray(frame)

在PIL图像上绘制文本:

draw = ImageDraw.Draw(pil_img)
draw.text((x, y - 10), emotion_label, font=font, fill=(0, 255, 0))

o ImageDraw.Draw(pil_img) 创建一个绘图对象 draw,用于在 pil_img 上绘制图形和文本。
o draw.text((x, y - 10), emotion_label, font=font, fill=(0, 255, 0)) 在指定位置绘制文本。
 (x, y - 10) 是文本的起始位置,位于矩形框的上方。
 emotion_label 是要显示的表情标签,例如 “Happy”、“Sad” 等。
 font 是字体对象,指定文本的字体和大小。
 fill=(0, 255, 0) 是文本的颜色,这里是绿色(RGB格式)。

可控窗口实现:

        # 调整窗口大小为900X700
        cv2.namedWindow("Camera expression recognition", cv2.WINDOW_NORMAL)
        cv2.resizeWindow("Camera expression recognition", 900, 700)

这两行代码将显示窗口调整为可控大小窗口,以便显示视频帧。

cv2.namedWindow("Camera expression recognition", cv2.WINDOW_NORMAL)

o cv2.namedWindow 函数用于创建一个窗口。
o “Camera expression recognition” 是窗口的名称。
o cv2.WINDOW_NORMAL 是一个标志,表示创建一个可以调整大小的窗口。

        cv2.resizeWindow("Camera expression recognition", 900, 700)

o cv2.resizeWindow 函数用于调整窗口的大小。
o “Camera expression recognition” 是要调整大小的窗口的名称。
o 900 是窗口的宽度(以像素为单位)。
o 700 是窗口的高度(以像素为单位)。

按esc按钮退出程序:

        # 按'q'退出循环
        if cv2.waitKey(1) & 0xFF == 27:
            break

完整代码:

import cv2
import numpy as np
from PIL import ImageFont, ImageDraw
from PIL import Image
import torch
import torch.nn as nn
import torch.nn.functional as F 
from torchvision import  transforms
#%%
# 加载中文字体文件
font_path = "SourceHanSansSC-Bold.otf"
font = ImageFont.truetype(font_path, 30)
#%%
class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        # Convolutional layers
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding='same')
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding='same')
        self.conv3 = nn.Conv2d(64, 128, kernel_size=5, padding='same')
        self.conv4 = nn.Conv2d(128, 512, kernel_size=3, padding='same')
        self.conv5 = nn.Conv2d(512, 512, kernel_size=3, padding='same')

        # Batch normalization layers
        self.bn1 = nn.BatchNorm2d(32)
        self.bn2 = nn.BatchNorm2d(64)
        self.bn3 = nn.BatchNorm2d(128)
        self.bn4 = nn.BatchNorm2d(512)
        self.bn5 = nn.BatchNorm2d(512)
        self.bn6 = nn.BatchNorm1d(256)
        self.bn7 = nn.BatchNorm1d(512)

        # Pooling and dropout
        self.pool = nn.MaxPool2d(2, 2)
        self.dropout = nn.Dropout(0.25)
        
        # Fully connected layers
        self.fc1 = nn.Linear(512 * 6 * 6, 256)  # Adjusted the input features
        self.fc2 = nn.Linear(256, 512)
        self.fc3 = nn.Linear(512, 7)

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = F.relu(self.bn3(self.conv3(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = F.relu(self.bn4(self.conv4(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = F.relu(self.bn5(self.conv5(x)))
        x = self.pool(x)
        x = self.dropout(x)

        x = x.view(x.size(0), -1)  # Flatten the tensor
        x = F.relu(self.bn6(self.fc1(x)))
        x = self.dropout(x)
        x = F.relu(self.bn7(self.fc2(x)))
        x = self.dropout(x)
        x = self.fc3(x)
        return x
model = CNNModel()
#%%
# 加载预训练的人脸检测模型
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

# 加载PyTorch模型
model.load_state_dict(torch.load('sjnet_final2.pth'))
model.eval()  # 设置模型为评估模式

# 创建一个PIL Image对象,用于绘制文字
img_pil = Image.fromarray(np.zeros((1, 1), dtype=np.uint8))
draw = ImageDraw.Draw(img_pil)

img_size = 100 #original size of the image
targetx = 100
targety = 100
epochs = 100   
batch_size = 64
# 数据增强和预处理
transform = transforms.Compose([
    transforms.Resize((targetx, targety)),
    transforms.ToTensor(),
])
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model.to(device)
# 启动摄像头
cap = cv2.VideoCapture(0)

# 定义表情列表
EMOTIONS = ['惊讶', '恐惧', '厌恶', '开心', '伤心', '愤怒', '中性 ']

try:
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

        # 在检测到的人脸区域进行面部表情识别
        for (x, y, w, h) in faces:
            roi_gray = gray[y:y+h, x:x+w]
            roi_rgb = cv2.cvtColor(roi_gray, cv2.COLOR_GRAY2BGR)  # 转换为RGB图像
            roi_pil = Image.fromarray(roi_rgb)
            # 加载图像并进行预测
            image = transform(roi_pil).unsqueeze(0).to(device)  # 添加batch维度
            output = model(image)
            
            # 获取最可能的表情
            _, predicted = torch.max(output, 1)

            # 预测结果是一个数组,其中包含了不同表情的概率
            emotion_label = EMOTIONS[predicted]  

            # 在视频帧上显示表情标签
            cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
            pil_img = Image.fromarray(frame)
            draw = ImageDraw.Draw(pil_img)
            draw.text((x, y - 10), emotion_label, font=font, fill=(0, 255, 0))
            # 将PIL图像转换回OpenCV格式
            cv_img = np.array(pil_img)
        
        # 调整窗口大小为800x800
        cv2.namedWindow("Camera expression recognition", cv2.WINDOW_NORMAL)
        cv2.resizeWindow("Camera expression recognition", 900, 700)
            
        # 显示图像
        cv2.imshow("Camera expression recognition", cv_img)
        # cv2.waitKey(1)


        # 按'q'退出循环
        if cv2.waitKey(1) & 0xFF == 27:
            break

# 释放摄像头和销毁所有OpenCV窗口
finally:
    cap.release()
    cv2.destroyAllWindows()
  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值