classdef EmotionRecognitionSystem < handle
% 面部表情识别系统主类
% 支持自定义数据集训练和表情识别
properties
fig % 主窗口句柄
net % 表情识别模型
axImg % 图像显示区域
txtResult % 结果显示文本框
cam % 摄像头对象
videoPlayer % 视频播放器
isCapturing = false; % 是否正在捕获视频
end
methods
function obj = EmotionRecognitionSystem()
% 构造函数 - 创建GUI并加载模型
% 创建主窗口
obj.fig = figure('Name', '面部表情识别系统', ...
'Position', [300 200 900 550], ...
'NumberTitle', 'off', ...
'MenuBar', 'none', ...
'CloseRequestFcn', @obj.closeApp);
% 尝试加载现有模型
try
load('customEmotionNet.mat', 'net');
obj.net = net;
disp('自定义模型加载成功');
catch
obj.net = [];
end
% 创建UI控件
uicontrol('Style', 'pushbutton', ...
'Position', [30 480 120 40], ...
'String', '选择图片', ...
'Callback', @obj.loadImage, ...
'FontSize', 11);
uicontrol('Style', 'pushbutton', ...
'Position', [170 480 120 40], ...
'String', '摄像头捕获', ...
'Callback', @obj.captureFromCam, ...
'FontSize', 11);
uicontrol('Style', 'pushbutton', ...
'Position', [310 480 120 40], ...
'String', '实时识别', ...
'Callback', @obj.realTimeRecognition, ...
'FontSize', 11);
uicontrol('Style', 'pushbutton', ...
'Position', [450 480 120 40], ...
'String', '训练模型', ...
'Callback', @obj.trainModel, ...
'FontSize', 11);
uicontrol('Style', 'pushbutton', ...
'Position', [590 480 120 40], ...
'String', '帮助', ...
'Callback', @obj.showHelp, ...
'FontSize', 11);
% 创建图像显示区域
obj.axImg = axes('Parent', obj.fig, ...
'Position', [0.05 0.1 0.5 0.7], ...
'Box', 'on');
axis(obj.axImg, 'image');
title(obj.axImg, '输入图像');
% 创建结果显示区域
uicontrol('Style', 'text', ...
'Position', [600 400 250 40], ...
'String', '识别结果:', ...
'FontSize', 14, ...
'FontWeight', 'bold', ...
'BackgroundColor', [0.9 0.9 0.9]);
obj.txtResult = uicontrol('Style', 'text', ...
'Position', [600 300 250 90], ...
'String', '等待识别...', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'ForegroundColor', [0 0.5 0], ...
'BackgroundColor', [0.95 0.95 0.95], ...
'HorizontalAlignment', 'center');
% 置信度指示器
uicontrol('Style', 'text', ...
'Position', [600 200 250 30], ...
'String', '置信度:', ...
'FontSize', 12, ...
'BackgroundColor', [0.9 0.9 0.9]);
% 表情标签
emotions = {'平静', '大笑', '微笑', '哭泣', '悲伤'};
for i = 1:length(emotions)
uicontrol('Style', 'text', ...
'Position', [600 170-(i-1)*30 100 25], ...
'String', emotions{i}, ...
'FontSize', 11, ...
'HorizontalAlignment', 'left', ...
'BackgroundColor', [0.95 0.95 0.95]);
end
end
function loadImage(obj, ~, ~)
% 从文件选择图像
[file, path] = uigetfile({'*.jpg;*.png;*.bmp;*.png', '图像文件'});
if isequal(file, 0)
return;
end
imgPath = fullfile(path, file);
obj.processImage(imread(imgPath));
end
function captureFromCam(obj, ~, ~)
% 从摄像头捕获图像
if isempty(webcamlist)
errordlg('未检测到摄像头', '硬件错误');
return;
end
try
obj.cam = webcam;
preview(obj.cam);
uicontrol('Style', 'text', ...
'Position', [300 10 300 30], ...
'String', '正在捕获...3秒后自动拍照', ...
'FontSize', 12, ...
'ForegroundColor', 'r');
pause(3);
img = snapshot(obj.cam);
clear obj.cam;
obj.processImage(img);
delete(findobj(obj.fig, 'Type', 'uicontrol', 'Position', [300 10 300 30]));
catch ME
errordlg(['摄像头错误: ' ME.message], '硬件错误');
if ~isempty(obj.cam)
clear obj.cam;
end
end
end
function realTimeRecognition(obj, ~, ~)
% 实时表情识别
if obj.isCapturing
obj.isCapturing = false;
return;
end
if isempty(webcamlist)
errordlg('未检测到摄像头', '硬件错误');
return;
end
if isempty(obj.net)
warndlg('请先训练模型', '模型缺失');
return;
end
obj.isCapturing = true;
obj.cam = webcam;
obj.videoPlayer = vision.DeployableVideoPlayer;
% 创建停止按钮
stopBtn = uicontrol('Style', 'pushbutton', ...
'Position', [310 480 120 40], ...
'String', '停止识别', ...
'Callback', @obj.stopRealTime, ...
'FontSize', 11, ...
'BackgroundColor', [1 0.6 0.6]);
% 实时识别循环
while obj.isCapturing && ishandle(obj.fig)
img = snapshot(obj.cam);
[emotion, confidence, faceImg] = obj.recognizeEmotion(img);
% 在图像上标注结果
if ~isempty(faceImg)
bbox = faceImg.bbox;
label = sprintf('%s (%.1f%%)', emotion, confidence*100);
img = insertObjectAnnotation(img, 'rectangle', bbox, ...
label, 'Color', 'green', 'FontSize', 18);
end
step(obj.videoPlayer, img);
end
% 清理
release(obj.videoPlayer);
clear obj.cam;
delete(stopBtn);
end
function stopRealTime(obj, ~, ~)
% 停止实时识别
obj.isCapturing = false;
end
function processImage(obj, img)
% 处理单张图像
if isempty(obj.net)
warndlg('请先训练模型', '模型缺失');
return;
end
axes(obj.axImg);
imshow(img);
title('输入图像');
% 表情识别
[emotion, confidence, faceImg] = obj.recognizeEmotion(img);
% 显示结果
resultStr = sprintf('表情: %s\n置信度: %.2f%%', emotion, confidence*100);
set(obj.txtResult, 'String', resultStr);
% 绘制检测框
if ~isempty(faceImg)
hold on;
rectangle('Position', faceImg.bbox, ...
'EdgeColor', 'g', 'LineWidth', 2);
text(faceImg.bbox(1), faceImg.bbox(2)-20, ...
[emotion ' (' num2str(round(confidence*100)) '%)'], ...
'Color', 'g', 'FontSize', 14, 'FontWeight', 'bold');
hold off;
end
end
function [emotion, confidence, faceImg] = recognizeEmotion(obj, img)
% 表情识别函数
faceImg = [];
% 人脸检测
faceDetector = vision.CascadeObjectDetector();
bbox = step(faceDetector, img);
if isempty(bbox)
emotion = '未检测到人脸';
confidence = 0;
return;
end
% 选择最大的人脸区域
[~, idx] = max(bbox(:,3).*bbox(:,4));
mainBbox = bbox(idx,:);
% 裁剪人脸区域
faceImg.img = imcrop(img, mainBbox);
faceImg.bbox = mainBbox;
% 预处理 - 保持RGB三通道
resizedImg = imresize(faceImg.img, [48 48]); % 模型输入尺寸
normalizedImg = im2single(resizedImg); % 归一化
% 表情分类
[pred, scores] = classify(obj.net, normalizedImg);
confidence = max(scores);
% 映射到表情标签
emotionMap = containers.Map(...
{'neutral', 'happy', 'smile', 'cry', 'sad'}, ...
{'平静', '大笑', '微笑', '哭泣', '悲伤'});
if isKey(emotionMap, char(pred))
emotion = emotionMap(char(pred));
else
emotion = '未知表情';
end
end
function trainModel(obj, ~, ~)
% 训练自定义模型
answer = questdlg('训练模型需要较长时间,是否继续?', ...
'模型训练', '是', '否', '是');
if ~strcmp(answer, '是')
return;
end
% 创建训练进度窗口
trainFig = figure('Name', '模型训练', ...
'Position', [400 300 400 200], ...
'NumberTitle', 'off', ...
'MenuBar', 'none');
uicontrol('Style', 'text', ...
'Position', [50 150 300 30], ...
'String', '正在训练模型,请稍候...', ...
'FontSize', 12);
progressBar = uicontrol('Style', 'text', ...
'Position', [50 100 300 20], ...
'String', '', ...
'BackgroundColor', [0.8 0.8 1]);
% 在单独的函数中训练模型
try
obj.net = obj.trainCustomModel(progressBar);
save('customEmotionNet.mat', 'net');
msgbox('模型训练完成并已保存!', '训练成功');
catch ME
errordlg(['训练错误: ' ME.message], '训练失败');
end
close(trainFig);
end
function net = trainCustomModel(obj, progressBar)
% 自定义模型训练函数
datasetPath = uigetdir(pwd, '选择表情数据集目录');
if datasetPath == 0
error('未选择数据集');
end
imds = imageDatastore(datasetPath, ...
'IncludeSubfolders', true, ...
'LabelSource', 'foldernames');
% 检查类别数量
classes = categories(imds.Labels);
if numel(classes) ~= 5
error('数据集必须包含5个类别: neutral, happy, smile, cry, sad');
end
% 数据集分割
[trainImgs, valImgs] = splitEachLabel(imds, 0.7, 'randomized');
% 更新进度
set(progressBar, 'Position', [50 100 100 20], 'String', '数据加载完成');
pause(0.5);
% 数据增强
augmenter = imageDataAugmenter(...
'RandXReflection', true, ...
'RandRotation', [-15 15], ...
'RandXScale', [0.8 1.2], ...
'RandYScale', [0.8 1.2]);
% 使用RGB图像 (48x48x3)
augTrainImgs = augmentedImageDatastore([48 48], trainImgs, ...
'DataAugmentation', augmenter, ...
'OutputSizeMode', 'resize');
augValImgs = augmentedImageDatastore([48 48], valImgs, ...
'OutputSizeMode', 'resize');
% 更新进度
set(progressBar, 'Position', [50 100 150 20], 'String', '数据预处理完成');
pause(0.5);
% 构建自定义CNN模型 (支持RGB输入)
layers = [
% 修改输入层为48x48x3以支持RGB图像
imageInputLayer([48 48 3], 'Name', 'input')
convolution2dLayer(5, 32, 'Padding', 'same', 'Name', 'conv1')
batchNormalizationLayer('Name', 'bn1')
reluLayer('Name', 'relu1')
maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool1')
convolution2dLayer(3, 64, 'Padding', 'same', 'Name', 'conv2')
batchNormalizationLayer('Name', 'bn2')
reluLayer('Name', 'relu2')
maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool2')
convolution2dLayer(3, 128, 'Padding', 'same', 'Name', 'conv3')
batchNormalizationLayer('Name', 'bn3')
reluLayer('Name', 'relu3')
maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool3')
fullyConnectedLayer(256, 'Name', 'fc1')
reluLayer('Name', 'relu4')
dropoutLayer(0.5, 'Name', 'dropout1')
fullyConnectedLayer(5, 'Name', 'fc2') % 5个输出类别
softmaxLayer('Name', 'softmax')
classificationLayer('Name', 'classoutput')
];
% 训练选项
options = trainingOptions('adam', ...
'InitialLearnRate', 0.001, ...
'LearnRateSchedule', 'piecewise', ...
'LearnRateDropFactor', 0.5, ...
'LearnRateDropPeriod', 5, ...
'MaxEpochs', 20, ...
'MiniBatchSize', 64, ...
'Shuffle', 'every-epoch', ...
'ValidationData', augValImgs, ...
'ValidationFrequency', 50, ...
'Verbose', true, ...
'Plots', 'none', ...
'OutputFcn', @(info)obj.trainingProgress(info, progressBar));
% 更新进度
set(progressBar, 'Position', [50 100 200 20], 'String', '开始训练模型...');
drawnow;
% 训练模型
net = trainNetwork(augTrainImgs, layers, options);
end
function trainingProgress(obj, info, progressBar)
% 训练进度回调函数
if strcmp(info.State, 'iteration')
% 计算整个训练过程的进度
progress = (info.Epoch - 1 + info.Iteration/info.NumIterations) / info.NumEpochs;
set(progressBar, 'Position', [50 100 round(300*progress) 20], ...
'String', sprintf('训练进度: %.1f%%', progress*100));
drawnow;
end
end
function showHelp(obj, ~, ~)
% 显示帮助信息
helpText = {
'面部表情识别系统使用指南'
'1. 选择图片: 从文件系统加载图像进行识别'
'2. 摄像头捕获: 使用摄像头拍摄照片进行识别'
'3. 实时识别: 开启摄像头进行实时表情识别'
'4. 训练模型: 使用自定义数据集训练新模型'
''
'使用说明:'
'- 首次使用需点击"训练模型"创建新模型'
'- 训练完成后模型将保存为customEmotionNet.mat'
'- 支持识别表情: 平静、大笑、微笑、哭泣、悲伤'
''
'数据集要求:'
'- 创建文件夹结构:'
' dataset/neutral'
' dataset/happy'
' dataset/smile'
' dataset/cry'
' dataset/sad'
'- 每个子文件夹包含对应表情的图片'
'- 图片格式支持JPG/PNG/BMP'
'- 建议每个类别至少100张图片'
'- 图像尺寸建议为48x48像素(系统会自动调整)'
};
helpdlg(helpText, '系统帮助');
end
function closeApp(obj, ~, ~)
% 关闭应用程序
if obj.isCapturing
obj.isCapturing = false;
pause(0.5);
end
if ~isempty(obj.cam)
clear obj.cam;
end
if ishandle(obj.fig)
delete(obj.fig);
end
end
end
end保持训练图像与输入层一致matlab完整代码