├── .gitignore ├── .idea ├── .gitignore └── CNN-face-emotion-recognition-ez_version.iml ├── README.md ├── demo ├── demo.py └── train.py └── face_emotion_recongnition ├── __init__.py ├── abstract ├── __init__.py └── loader.py ├── datasets ├── __init__.py └── ferplus.py ├── face_classification └── train.py ├── models ├── __init__.py ├── vgg.py └── xception.py └── utils ├── __init__.py ├── extractLabels.py ├── get_cfg ├── ___init__.py ├── get_emotions.py ├── get_name.py ├── get_trans.py └── get_vgg.py ├── image ├── __init__.py ├── cut_img.py ├── greyToRGB.py ├── plot_boxes.py ├── resize_image.py └── trans_predict.py └── normalization.py /.gitignore: -------------------------------------------------------------------------------- 1 | 参考/ 2 | data/ 3 | 4 | face_emotion_recongniton/test.ipynb 5 | test.ipynb 6 | *.pyc 7 | test2_0.ipynb 8 | copy.csv 9 | testFer.csv 10 | *.xml 11 | 5555.py 12 | *.pyc 13 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/CNN-face-emotion-recognition-ez_version.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 通过YOLOv8在摄像上对人脸面都进行情绪识别,并显示出来,目前使用的情绪分析模型VGG在ferplus的PublicTest上的正确率为0.84 2 | 3 | 4 | Using YOLOv8 to perform emotion recognition on faces in real-time via camera and displaying the results. Currently, the VGG emotion analysis model on the ferplus PublicTest dataset achieves an accuracy of 0.84. 5 | -------------------------------------------------------------------------------- /demo/demo.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | import cv2 4 | from PIL import Image 5 | from ultralytics import YOLO 6 | 7 | sys.path.append('.') 8 | 9 | from train import train 10 | from face_emotion_recongnition.utils import trans_predict 11 | from face_emotion_recongnition.utils import get_emotions 12 | from face_emotion_recongnition.utils import cut_image 13 | from face_emotion_recongnition.utils import plot_boxes 14 | 15 | if __name__ == '__main__': 16 | 17 | yolo = YOLO("../data/best.pt") # 或者加载预训练好的模型 18 | 19 | model = train(load=True) 20 | 21 | cap = cv2.VideoCapture(0) # 0表示默认摄像头,如果有多个摄像头,可以尝试使用1, 2, 等 22 | 23 | # 遍历视频帧 24 | 25 | while cap.isOpened(): 26 | # 从视频中读取一帧 27 | success, frame = cap.read() 28 | # print(type(frame)) 29 | if success: 30 | # 在该帧上运行YOLOv8推理 31 | results = yolo(frame) 32 | 33 | result = results[0] 34 | 35 | imgs = cut_image(result) 36 | labels = [] 37 | for i in imgs: 38 | most = [] 39 | for n in range(7): 40 | pre_image = trans_predict(i) 41 | ans = model.predict(pre_image) 42 | most.append(ans) 43 | print(most) 44 | most_num = max(most, key=most.count) 45 | labels.append(get_emotions(most_num)) 46 | 47 | # 在帧上可视化结果 48 | print(labels) 49 | annotated_frame = plot_boxes(labels, result) 50 | 51 | # 显示带注释的帧 52 | cv2.imshow("YOLOv8推理", annotated_frame) 53 | 54 | # 如果按下'q'则中断循环 55 | if cv2.waitKey(1) & 0xFF == ord("q"): 56 | break 57 | 58 | # 释放视频捕获对象并关闭显示窗口 59 | cap.release() 60 | cv2.destroyAllWindows() 61 | -------------------------------------------------------------------------------- /demo/train.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('.') 3 | 4 | from face_emotion_recongnition.datasets import FER_plus_dataSet 5 | from face_emotion_recongnition.utils import get_trans 6 | from face_emotion_recongnition.models import EmotionRecognition 7 | from torch.utils.data import DataLoader 8 | 9 | 10 | def train(load=True, evaluate=False): 11 | """ 12 | 训练模型 13 | 14 | # 参数: 15 | load: 是否加载训练好的模型 16 | evaluate: 是否用测试集来测试模型预测成功率并打印 17 | 18 | # return: 19 | 返回训练好的模型 20 | """ 21 | trans = get_trans() 22 | trainLoader = 0 23 | 24 | if load==False: 25 | # 加载数据集 26 | fer = FER_plus_dataSet('data/fer2013/', split='train', class_names="ALL") 27 | 28 | # 实例化DataLoader 29 | trainLoader = DataLoader(fer, batch_size=128, shuffle=True) 30 | 31 | # 训练模型(True 为加载训练好的模型) 32 | vgg = EmotionRecognition(cfg='VGG16') 33 | vgg.train(trainLoader, 100, True) 34 | 35 | if evaluate==True: 36 | fertest = FER_plus_dataSet('data/fer2013/', split='test', class_names="ALL") 37 | testLoader = DataLoader(fertest, batch_size=1, shuffle=False) 38 | vgg.evaluate(testLoader) 39 | 40 | return vgg 41 | 42 | if __name__ == '__main__': 43 | vgg = train(load=True, evaluate=True) -------------------------------------------------------------------------------- /face_emotion_recongnition/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.1' -------------------------------------------------------------------------------- /face_emotion_recongnition/abstract/__init__.py: -------------------------------------------------------------------------------- 1 | from torch.utils.data import Dataset, DataLoader 2 | from .loader import Loader -------------------------------------------------------------------------------- /face_emotion_recongnition/abstract/loader.py: -------------------------------------------------------------------------------- 1 | class Loader: 2 | """ 3 | 加载数据集的抽象方法 4 | 5 | # 参数 6 | path: 数据集的相对路径 7 | split: 用来区分加载的数据集是用于训练,还是验证或者测试的 8 | class_names: 一组关于数据集标签的字符串列表 9 | name: 数据集的名字 10 | 11 | # 方法 12 | load_data() 13 | 14 | """ 15 | 16 | def __init__(self, path, split, class_names, name) -> None: 17 | self.path = path 18 | self.split = split 19 | self.class_names = class_names 20 | self.name = name 21 | 22 | def load_data(self): 23 | """ 24 | 用于加载数据集的抽象方法 25 | """ 26 | 27 | raise NotImplementedError() 28 | 29 | # 虽然这么写对咱们没什么用就是了 30 | 31 | @property 32 | def path(self): 33 | return self._path 34 | 35 | @path.setter 36 | def path(self, path): 37 | self._path = path 38 | 39 | 40 | @property 41 | def split(self): 42 | return self._split 43 | 44 | @split.setter 45 | def split(self, split): 46 | self._split = split 47 | 48 | 49 | @property 50 | def class_names(self): 51 | return self._class_names 52 | 53 | @class_names.setter 54 | def class_names(self, class_names): 55 | self._class_names = class_names 56 | 57 | 58 | @property 59 | def name(self): 60 | return self._name 61 | 62 | @name.setter 63 | def name(self, name): 64 | self._name = name 65 | 66 | 67 | # 用来查看有多少个类别 68 | @property 69 | def class_nums(self): 70 | if isinstance(self.class_names, list): 71 | return len(self.class_names) 72 | 73 | else: 74 | raise ValueError('class_names 不是一个列表') -------------------------------------------------------------------------------- /face_emotion_recongnition/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | from .ferplus import FER_plus 2 | from .ferplus import FER_plus_dataSet -------------------------------------------------------------------------------- /face_emotion_recongnition/datasets/ferplus.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ..abstract import Loader, Dataset 4 | from ..utils import get_class_names, get_trans, resize_image 5 | from ..utils import greyToRGB, extractLabels 6 | from ..utils import numpy as np 7 | from ..utils import pandas as pd 8 | 9 | class FER_plus(Loader): 10 | """ 11 | 用来加载FER2013 人脸情绪数据集的类, 标签为FERpuls 12 | 数据集来自:https://github.com/microsoft/FERPlus 13 | 数据集的第二列特征就是改数据的用处, 因此用split来区分训练集,测试集,验证集 14 | 15 | # 参数: 16 | path: 含有icml_face_data.csv(数据集) 和 fer2013new.csv(标签)的路径 17 | split: 用来区分数据集用途的 18 | class_names: 一个含有数据集中标签种类的列表 19 | 20 | image_size: 图片将要被改变的大小 21 | 22 | # return 23 | data: 返回处理好的数据,data是列表, 里面的每个图片和标签用字典分别来保存 24 | 'image': 图片像素的numpy数组 25 | 'emotion': 关于各项情绪的可能性的, 一行numpy数组 26 | """ 27 | 28 | def __init__(self, path, split='train', class_names="ALL", 29 | image_size=(48, 48)) -> None: 30 | 31 | # 如果是另取的类名的话就用另取的 32 | if class_names == "ALL": 33 | class_names = get_class_names('FERplus') 34 | 35 | super(FER_plus,self).__init__(path, split, class_names, 'FERplus') 36 | 37 | self.image_size = image_size 38 | self.images_path = os.path.join(self.path, 'fer2013.csv') 39 | self.labels_path = os.path.join(self.path, 'fer2013new.csv') 40 | 41 | self.split_to_filter = { 42 | 'train' : 'Training', 'val': 'PublicTest', 'test': 'PrivateTest', 'all': 'All' 43 | } 44 | 45 | def load_data(self): 46 | 47 | # 读取数据和标签 48 | images = pd.read_csv(self.images_path) 49 | labels= pd.read_csv(self.labels_path) 50 | 51 | # 提取像素和标签,并把标签归一化 52 | new_fer = extractLabels(images=images, labels=labels, split=self.split_to_filter[self.split]) 53 | 54 | # 将图片放入data中, reshape成(-1, 48, 48) 55 | faces = np.zeros((len(new_fer['pixels']), *self.image_size)) 56 | for sample_arg, face in enumerate(new_fer['pixels']): 57 | face = np.array(face.split(), dtype=int).reshape(48, 48) 58 | face = resize_image(face, self.image_size) 59 | 60 | faces[sample_arg, :, :] = face 61 | 62 | # 获取标签 63 | emotions = new_fer[['neutral', 'happiness', 'surprise', 'sadness', 64 | 'anger', 'disgust', 'fear', 'contempt']].values 65 | 66 | # 返回处理好的数据,data是列表,里面的每个图片和标签用字典分别来保存 67 | data = [] 68 | for face, emotion in zip(faces, emotions): 69 | sample = {'image': face, 'label': emotion} 70 | data.append(sample) 71 | return data 72 | 73 | class FER_plus_dataSet(Dataset): 74 | """ 75 | 调用了同文件的FER_plus类来获取数据集, 然后转化成适合用于加载DataLoader类的Dataset类型 76 | 后续应该将其并入到FER_plus类中, FER_plus同时继承两个类 77 | """ 78 | 79 | def __init__(self, path, split='train', class_names="ALL", 80 | image_size=(48, 48)) -> None: 81 | 82 | fer_plus = FER_plus(path, split, class_names, image_size) 83 | self.data = fer_plus.load_data() 84 | self.trans = get_trans() 85 | 86 | def __getitem__(self, index): 87 | 88 | data = self.data[index]['image'] 89 | 90 | data = greyToRGB(data) 91 | data = self.trans(data) 92 | 93 | target = self.data[index]['label'] 94 | 95 | return data, target 96 | 97 | def __len__(self): 98 | 99 | return len(self.data) -------------------------------------------------------------------------------- /face_emotion_recongnition/face_classification/train.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monikaBeizi/CNN-face-emotion-recognition/e0f92691b33d79c7548d8e8215dd857c7cb8348a/face_emotion_recongnition/face_classification/train.py -------------------------------------------------------------------------------- /face_emotion_recongnition/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .vgg import EmotionRecognition -------------------------------------------------------------------------------- /face_emotion_recongnition/models/vgg.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from ..utils import get_vgg 5 | 6 | class VGG(nn.Module): 7 | 8 | def __init__(self, cfg) -> None: 9 | super(VGG, self).__init__() 10 | self.features = self._make_layers(cfg) 11 | self.fc = nn.Linear(512, 8) 12 | 13 | def _make_layers(self, cfg): 14 | layers = [] 15 | in_channels = 3 16 | 17 | for x in cfg: 18 | if x == 'M': 19 | layers += [nn.MaxPool2d(kernel_size=2, stride=2)] 20 | 21 | else: 22 | layers += [ 23 | nn.Conv2d(in_channels, x, kernel_size=3, stride=1, padding=1), 24 | nn.BatchNorm2d(x), 25 | nn.ReLU(inplace=True)] 26 | in_channels = x 27 | 28 | layers += [nn.AvgPool2d(kernel_size=1, stride=1)] 29 | return nn.Sequential(*layers) 30 | 31 | def forward(self, x): 32 | out = self.features(x.float()) 33 | out = out.view(out.size(0), -1) 34 | out = nn.functional.dropout(out, p=0.5, training=self.training) 35 | out = self.fc(out) 36 | return out 37 | 38 | 39 | class EmotionRecognition: 40 | ''' 41 | 使用VGG模型 42 | 43 | # __init__参数 44 | cfg: 模型的配置文件'VGG16' 或者 'VGG19' 45 | 46 | # train: 47 | 用于训练模型 48 | trainLoader: pytorch的DataLoader, 用来载入训练集 49 | num_epochs: epoch 的数量 50 | load: True or Not, 用于判断类是否加载本地之前训练好的模型参数 51 | 52 | # test: 53 | 用于检测模型的正确率, return accuracy of model 54 | ''' 55 | def __init__(self, cfg) -> None: 56 | self.cfg = get_vgg(cfg) 57 | 58 | self.device = torch.device('cuda:0' if torch.cuda.is_available else 'cpu') 59 | self.model = VGG(self.cfg).to(self.device) 60 | self.criterion = nn.CrossEntropyLoss() 61 | self.optimizer = torch.optim.Adam(self.model.parameters(), lr = 0.001, weight_decay=5e-4) 62 | self.scheduler = torch.optim.lr_scheduler.StepLR(self.optimizer, step_size=30, gamma=0.1) 63 | 64 | # 用于训练模型 65 | def train(self, trainLoader, num_epochs, load=True): 66 | if load == True: 67 | self.model.load_state_dict(torch.load('../data/emotion_v0.pth')) 68 | print('load finish') 69 | 70 | else: 71 | self.model.train() 72 | for epoch in range(num_epochs): 73 | running_loss = 0.0 74 | for i,data in enumerate(trainLoader, 0): 75 | images, labels = data[0].to(self.device), data[1].to(self.device) 76 | self.optimizer.zero_grad() 77 | outputs = self.model.forward(images) 78 | loss = self.criterion(outputs, labels.reshape(-1, 8).float()) 79 | loss.backward() 80 | self.optimizer.step() 81 | if i % 100 == 0: 82 | print(f'epoch: {epoch + 1}/{num_epochs}, step: {i}/{len(trainLoader)}, loss: {loss.item()}') 83 | self.scheduler.step() 84 | torch.save(self.model.state_dict(), '../data/emotion_v0.pth') 85 | print('finish') 86 | 87 | # 用于评估模型的方法 88 | def evaluate(self, test_loader): 89 | self.model.eval() 90 | correct = 0 91 | total = 0 92 | with torch.no_grad(): 93 | for data in test_loader: 94 | images, labels = data[0].to(self.device), data[1].to(self.device) 95 | outputs = self.model.forward(images) 96 | _, predicted = torch.max(outputs.data, 1) 97 | total += 1 98 | _, labels = torch.max(labels, 1) 99 | correct += (predicted == labels).sum().item() 100 | accuracy = correct / total 101 | print("The accuracy of model is:", accuracy) 102 | return accuracy 103 | 104 | # 用于使用模型 105 | def predict(self, image): 106 | """ 107 | 用这个方法来使用模型预测传入的image图像 108 | 不过因为训练的图片都是灰度图像, 所以期望传进来的图片也最好是 109 | 三个灰度图像拼接成的RGB图像 110 | 111 | # 参数: 112 | image: 形状为torch.Size([3, 44, 44]) 113 | 114 | # return : 115 | 返回的是预测值 116 | """ 117 | 118 | self.model.eval() 119 | with torch.no_grad(): 120 | image = image.to(self.device) 121 | outputs = self.model.forward(image) 122 | _, predicted = torch.max(outputs.data, 1) 123 | return int(predicted) -------------------------------------------------------------------------------- /face_emotion_recongnition/models/xception.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | class Xception(nn.Module): 5 | """ 6 | 用于构建Xception模型的 7 | 8 | # 参数: 9 | input_shape : 一个有三个整数的列表[H, W, 3] 10 | num_classes : 整数, 具体用处如名 11 | load: 默认是None, 或者输入已经训练的模型的对应数据集的名字。 12 | (目前没有, 后续添加) 13 | 14 | """ 15 | 16 | def __init__(self, input_shape, num_classes, load=None): 17 | 18 | pass 19 | 20 | def cfg_Xception(self, input_shape, num_classes): 21 | 22 | stem_kernels = [32, 64] 23 | block_data = [128, 128, 256, 256, 512, 512, 1024] 24 | model_inputs = (input_shape, num_classes, stem_kernels, block_data) 25 | 26 | model = self.build_xception(*model_inputs) 27 | 28 | def build_xception(self, input_shape, num_classes, stem_kernels, 29 | block_data, l2_reg=0.01): 30 | """ 31 | 实例化xception模型 32 | 33 | # 参数: 34 | 35 | input_shape : 列表,对应 36 | """ 37 | pass 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import os 3 | import cv2 4 | import pandas 5 | 6 | from .get_cfg.get_name import get_class_names 7 | from .image.resize_image import resize_image 8 | from .get_cfg.get_vgg import get_vgg 9 | from .get_cfg.get_trans import get_trans 10 | from .get_cfg.get_emotions import get_emotions 11 | from .image.greyToRGB import greyToRGB 12 | from .image.trans_predict import trans_predict 13 | from .extractLabels import extractLabels 14 | from .normalization import normalization 15 | from .image.cut_img import cut_image 16 | from .image.plot_boxes import plot_boxes 17 | 18 | from PIL import Image 19 | from sklearn.preprocessing import LabelEncoder 20 | from torchvision import transforms 21 | -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/extractLabels.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from .normalization import normalization 4 | 5 | def extractLabels(images, labels, split): 6 | """ 7 | 分别提取images列表中的像素, 和labels中的标签, 并将其归一化 8 | 9 | # 参数: 10 | images: 含有pixels的列表 11 | labels: 含有ferplus的八种情绪的标签的列表 12 | split: 字符串,用来区分提取的数据集是用来训练的还是测试的 13 | 14 | 15 | # return: 16 | 返回的是一个第一行为像素,第二行后到最后一行为八个标签的csv列表 17 | """ 18 | # 去除表格中目标数据以外的数据 19 | if split != 'All': 20 | images = images[images['Usage'] == split] 21 | labels = labels[labels['Usage'] == split] 22 | 23 | # 提取出像素和标签 24 | new_fer = pd.DataFrame() 25 | new_fer['pixels'] = images['pixels'] 26 | new_fer[['neutral', 'happiness', 'surprise', 'sadness', 27 | 'anger', 'disgust', 'fear', 'contempt']] = labels[['neutral', 'happiness', 'surprise', 'sadness', 28 | 'anger', 'disgust', 'fear', 'contempt']] 29 | 30 | # 去除new_fer中无效行(即8个标签都是0) 31 | new_fer = new_fer[new_fer[['neutral', 'happiness', 'surprise', 'sadness', 32 | 'anger', 'disgust', 'fear', 'contempt']].sum(axis=1) != 0] 33 | 34 | new_fer = new_fer.apply(normalization, axis=1) 35 | 36 | return new_fer -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/get_cfg/___init__.py: -------------------------------------------------------------------------------- 1 | from .get_name import get_class_names 2 | from .get_trans import get_trans 3 | from .get_vgg import get_vgg 4 | from .get_emotions import get_emotions -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/get_cfg/get_emotions.py: -------------------------------------------------------------------------------- 1 | def get_emotions(predict): 2 | """ 3 | 用于数字0~7对应的情绪 4 | 5 | # 参数: 6 | 模型predict的结果 7 | 8 | # return: 9 | 对应predict的emotion 10 | 11 | # emotions对应的中文(放在这方便改代码): 12 | emotions = { 13 | 0: '中立', 1: '快乐', 2: '惊讶', 3: '悲伤', 14 | 4: '愤怒', 5: '厌恶', 6: '恐惧', 7:'蔑视' 15 | } 16 | 17 | """ 18 | 19 | emotions = { 20 | 0: 'neutral', 1: 'happiness', 2: 'surprise', 3: 'sadness', 21 | 4: 'anger', 5: 'anger', 6: 'fear', 7:'contempt' 22 | } 23 | 24 | return emotions[predict] 25 | 26 | -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/get_cfg/get_name.py: -------------------------------------------------------------------------------- 1 | def get_class_names(datasets_name='FERplus'): 2 | """ 3 | 用来存数据集对应的标签名 4 | 5 | 参数: 6 | datasets_name: 数据集的名字 7 | 8 | 返回: 9 | 一个关于数据集标签的字符串列表 10 | 11 | 报错: 12 | 输入了一个暂时还没有存储数据集的名 13 | 14 | """ 15 | 16 | if datasets_name == 'FERplus': 17 | return ['neutral', 'happiness', 'surprise', 'sadness', 18 | 'anger', 'disgust', 'fear', 'contempt'] 19 | 20 | elif datasets_name == 'FER': 21 | return ['angry', 'disgust', 'fear', 'happy', 22 | 'sad', 'surprise', 'neutral'] 23 | 24 | 25 | else: 26 | raise ValueError('输入的数据集名错误', datasets_name) 27 | -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/get_cfg/get_trans.py: -------------------------------------------------------------------------------- 1 | from torchvision import transforms 2 | 3 | def get_trans(): 4 | ''' 5 | 用于获取配置好的torchvision的transform 6 | ''' 7 | 8 | trans = transforms.Compose([ 9 | transforms.ToTensor(), 10 | transforms.RandomHorizontalFlip(), 11 | transforms.RandomCrop(44) 12 | ]) 13 | 14 | return trans -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/get_cfg/get_vgg.py: -------------------------------------------------------------------------------- 1 | def get_vgg(cfg): 2 | """ 3 | 用来获取vgg的配置文件 4 | 5 | # 参数: 6 | cfg: 一个用来描述VGG16还是VGG19的字符串 7 | 8 | # return : 9 | vgg相应版本的配置, 一个列表 10 | 11 | """ 12 | 13 | if cfg == 'VGG16': 14 | return [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'] 15 | 16 | elif cfg == 'VGG19': 17 | return [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'] 18 | 19 | else: 20 | raise ValueError('输入的配置文件名错误', cfg) -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/image/__init__.py: -------------------------------------------------------------------------------- 1 | from .greyToRGB import greyToRGB 2 | from .resize_image import resize_image 3 | from .trans_predict import trans_predict -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/image/cut_img.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from PIL import Image 3 | 4 | 5 | def cut_image(result): 6 | ''' 7 | 将识别出的人脸剪切出来,并添加到一个列表中 8 | 9 | # 参数:yolov8的预测结果 10 | 11 | # 返回值:包含所有人脸的灰度图像,保存到了一个列表中 12 | 13 | # result.boxes.xyxy:包含了所要裁剪的图片的位置信息 14 | 15 | # result.orig_img:原始图片,为ndarray格式 16 | 17 | ''' 18 | imgs = [] 19 | for i in range(len(result.boxes.xyxy)): 20 | xyxy = result.boxes.xyxy[i] 21 | img = result.orig_img 22 | img1 = img[int(xyxy[1]): int(xyxy[3]), int(xyxy[0]): int(xyxy[2])] 23 | img1 = np.array(Image.fromarray(img1).convert('L')) 24 | # img = Image.fromarray(a) 25 | # img.show() 26 | imgs.append(img1) 27 | return imgs -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/image/greyToRGB.py: -------------------------------------------------------------------------------- 1 | from ...utils import numpy as np 2 | 3 | def greyToRGB(image): 4 | """ 5 | 将单通道的image 灰度图像, 复制到三个RGB通道上 6 | 变成彩色图像 7 | 8 | # 参数: 9 | image : np数组 10 | 11 | # return: 12 | 返回RGB图像 13 | """ 14 | 15 | image = np.array(image)[:, :, np.newaxis] 16 | image = np.concatenate((image, image, image), axis=2) 17 | 18 | 19 | return image -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/image/plot_boxes.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | 5 | def plot_boxes(labels, result): 6 | ''' 7 | # 将识别出来的标签标到原始图像上,并画框框出人脸 8 | 9 | # labels: VGG网络识别出来的各个脸的情绪的标签,为列表格式 10 | 11 | # result: yolov8的目标检测结果 12 | 13 | # result.boxes.xyxy:包含了所要裁剪的图片的位置信息 14 | 15 | # result.orig_img:原始图片,为ndarray格式 16 | 17 | # 返回值:标好框和标签的图像 18 | ''' 19 | 20 | 21 | img = result.orig_img 22 | for i in range(len(result.boxes.xyxy)): 23 | xyxy = result.boxes.xyxy[i] 24 | tl = round(0.002 * max(img.shape[0:2])) + 1 25 | color = (0, 0, 255) 26 | c1, c2 = ((int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3]))) 27 | cv2.rectangle(img, c1, c2, color, lineType=cv2.LINE_AA, thickness=tl) 28 | 29 | tf = max(tl-1, 1) 30 | t_size = cv2.getTextSize(labels[i], 0, fontScale=tl/3, thickness=tf)[0] 31 | c2 = (c1[0] + t_size[0], c1[1] - t_size[1] - 3) 32 | cv2.rectangle(img, c1, c2, -1, cv2.LINE_AA) # filled 33 | cv2.putText(img, labels[i], (c1[0], c1[1] - 2), cv2.FONT_HERSHEY_SIMPLEX, tl / 3, [225, 255, 255], thickness=tf, 34 | lineType=cv2.LINE_AA) 35 | 36 | return img -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/image/resize_image.py: -------------------------------------------------------------------------------- 1 | from ...utils import os, cv2 2 | from ...utils import numpy as np 3 | 4 | BILINEAR = cv2.INTER_LINEAR 5 | 6 | def resize_image(image, size, method=BILINEAR): 7 | """ 8 | resize_image 9 | 10 | 参数: 11 | image: numpy数组 12 | size: 输出图片的尺寸 13 | methon: 使用的插值方法 14 | 15 | return: 16 | 返回的是一个numpy数组 17 | """ 18 | 19 | if( type(image) != np.ndarray): 20 | raise ValueError( 21 | "输入的image不是一个numpy数组", type(image) 22 | ) 23 | 24 | else: 25 | return cv2.resize(image, size, interpolation=method).astype(int) -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/image/trans_predict.py: -------------------------------------------------------------------------------- 1 | from .resize_image import resize_image 2 | from .greyToRGB import greyToRGB 3 | 4 | from ..get_cfg.get_trans import get_trans 5 | 6 | def trans_predict(image): 7 | """ 8 | 接收一个原始的图像, 并将其转化为可以提供给模型预测的大小 9 | 10 | # 参数: 11 | image: 由np数组表示的图片type:ndarra 12 | 13 | # return: 14 | 返回一个48 * 48大小的图片 15 | """ 16 | trans = get_trans() 17 | small_image = resize_image(image, (48, 48)) 18 | small_image = greyToRGB(small_image) 19 | small_image = trans(small_image) 20 | small_image = small_image.reshape(1, 3, 44, 44) 21 | 22 | return small_image.float() -------------------------------------------------------------------------------- /face_emotion_recongnition/utils/normalization.py: -------------------------------------------------------------------------------- 1 | def normalization(x): 2 | """ 3 | 用于apply方法的函数 4 | 5 | # 参数: 6 | x :为列表的每一行 7 | 8 | # return 9 | 10 | x归一化后 11 | """ 12 | if x[1:].sum() == 0: 13 | raise ValueError("该行为无效值", x) 14 | x[1:] = x[1:] / x[1:].sum() 15 | 16 | return x --------------------------------------------------------------------------------