一、项目背景

在AI与创意结合的热潮下,让机器“读懂”人类情绪并自动生成对应表情包,是一件既有趣又有实际意义的事。本项目利用深度学习卷积神经网络(CNN),在Kaggle平台上构建了一个完整的“表情包生成器”——从人脸图像中识别出7种基本情绪(生气、厌恶、恐惧、开心、悲伤、惊讶、平静),然后根据情绪生成专属的表情包(含Emoji和ASCII艺术)。

为什么选择Kaggle? Kaggle提供免费GPU和数据集托管,非常适合快速验证想法。本文所有代码均可在Notebook中一键运行,无需本地环境。

fer2013/
├── train/
│   ├── 0/  (生气)
│   ├── 1/  (厌恶)
│   ├── 2/  (恐惧)
│   ├── 3/  (开心)
│   ├── 4/  (悲伤)
│   ├── 5/  (惊讶)
│   └── 6/  (平静)
└── test/
    └── (同上)
  • 图像尺寸:48×48 像素,灰度图

  • 训练集约2.8万张,测试集约7千张

  • 类别均衡性良好

2.2 技术栈

工具/库 用途
TensorFlow / Keras 构建和训练CNN模型
OpenCV 图像预处理(读取、缩放)
Matplotlib 可视化训练曲线
IPywidgets 构建交互式前端界面
PIL 图像格式转换
NumPy 数组运算

三、模型设计:轻量级CNN

考虑到数据集规模不大(2.8万张48×48小图),我们设计了一个4层卷积+全连接的轻量网络,既能保证精度,又能快速训练(GPU下约5~8分钟)。

网络结构

python

model = keras.Sequential([
    # 第一层卷积 + BN + 池化 + Dropout
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(48,48,1)),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Dropout(0.25),
    
    # 第二层
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Dropout(0.25),
    
    # 第三层
    layers.Conv2D(128, (3,3), activation='relu'),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Dropout(0.25),
    
    # 全连接层
    layers.Flatten(),
    layers.Dense(256, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.5),
    layers.Dense(7, activation='softmax')
])

设计亮点

  • BatchNormalization 加速收敛,稳定训练。

  • Dropout 防止过拟合(卷积层0.25,全连接层0.5)。

  • 使用 relu 激活函数,输出层 softmax 多分类。


四、数据加载与增强

使用 ImageDataGenerator 直接从文件夹读取,并应用数据增强提升泛化能力:

python

train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=10,        # 小角度旋转
    width_shift_range=0.1,
    height_shift_range=0.1,
    zoom_range=0.1,
    horizontal_flip=True,
    fill_mode='nearest'
)
  • 训练集:增强后随机变换。

  • 测试集:仅归一化,不增强。


五、训练过程与结果

训练15个epoch,batch_size=64,优化器Adam,损失函数为分类交叉熵。

训练日志(部分):

text

448/448 [==============================] - 49s 109ms/step - accuracy: 0.4898 - loss: 1.3374 - val_accuracy: 0.5091 - val_loss: 1.2875

最终验证准确率约 51%~55%(受限于数据集难度和网络复杂度)。对于7分类任务,随机猜测准确率约14%,因此模型已学到有效特征。若需提升,可增加epoch或使用预训练模型。


六、核心功能:情绪识别与表情包生成

6.1 预测函数

python

def predict_emotion(face_img):
    face = cv2.resize(face_img, (48, 48))
    face = face / 255.0
    face = face.reshape(1, 48, 48, 1)
    pred = model.predict(face, verbose=0)
    emotion_idx = np.argmax(pred)
    confidence = np.max(pred)
    return emotion_idx, confidence

6.2 表情包映射

为每种情绪准备了Emoji和ASCII艺术:

python

emotion_art = {
    0: """
    ╭━━━━━━━━╮
    ┃  😠      ┃
    ┃  生气   ┃
    ╰━━━━━━━━╯
    """,
    # ... 其余类似
}

七、交互式前端(无摄像头版)

由于Kaggle Notebook环境通常无法调用摄像头,我们改为从测试集中随机抽取一张图片,识别后展示图片、情绪和表情包。

使用 ipywidgets 构建简单界面:

  • 一个“🎲 随机测试”按钮

  • 图片展示区(48×48放大显示)

  • 情绪Emoji + 置信度

  • ASCII艺术表情包

点击按钮时,执行 random_test() 函数,随机选择一张测试图片,调用预测并更新UI。


八、完整代码展示

以下是完整Notebook代码(已精简注释),可直接复制到Kaggle运行:

python

# ============================================================
# 表情包生成器 - 完整Kaggle Notebook (图片识别版本,无摄像头)
# ============================================================

# -------------------- 1. 安装依赖 --------------------
!pip install opencv-python-headless ipywidgets pillow -q

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import cv2
import matplotlib.pyplot as plt
from IPython.display import display, clear_output
import ipywidgets as widgets
from PIL import Image as PILImage
import io
import os
import random

# -------------------- 2. 数据集路径 --------------------
data_dir = '/kaggle/input/datasets/msambare/fer2013'
train_dir = os.path.join(data_dir, 'train')
test_dir = os.path.join(data_dir, 'test')

print("训练集目录:", os.listdir(train_dir)[:5])
print("测试集目录:", os.listdir(test_dir)[:5])

emotion_labels = {0: '生气', 1: '厌恶', 2: '恐惧', 3: '开心', 
                  4: '悲伤', 5: '惊讶', 6: '平静'}

# -------------------- 3. 数据加载 --------------------
train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=10,
    width_shift_range=0.1,
    height_shift_range=0.1,
    zoom_range=0.1,
    horizontal_flip=True,
    fill_mode='nearest'
)
test_datagen = ImageDataGenerator(rescale=1./255)

batch_size = 64
target_size = (48, 48)
color_mode = 'grayscale'

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=target_size,
    color_mode=color_mode,
    batch_size=batch_size,
    class_mode='categorical',
    shuffle=True
)

test_generator = test_datagen.flow_from_directory(
    test_dir,
    target_size=target_size,
    color_mode=color_mode,
    batch_size=batch_size,
    class_mode='categorical',
    shuffle=False
)

print("类别索引:", train_generator.class_indices)

# -------------------- 4. 构建CNN模型 --------------------
model = keras.Sequential([
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(48,48,1)),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Dropout(0.25),
    
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Dropout(0.25),
    
    layers.Conv2D(128, (3,3), activation='relu'),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Dropout(0.25),
    
    layers.Flatten(),
    layers.Dense(256, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.5),
    layers.Dense(7, activation='softmax')
])

model.compile(optimizer='adam', 
              loss='categorical_crossentropy', 
              metrics=['accuracy'])
model.summary()

# -------------------- 5. 训练模型 --------------------
steps_per_epoch = train_generator.samples // batch_size
validation_steps = test_generator.samples // batch_size

history = model.fit(
    train_generator,
    steps_per_epoch=steps_per_epoch,
    epochs=15,
    validation_data=test_generator,
    validation_steps=validation_steps,
    verbose=1
)

model.save('emotion_model.h5')

# -------------------- 6. 预测函数 --------------------
def predict_emotion(face_img):
    face = cv2.resize(face_img, (48, 48))
    face = face / 255.0
    face = face.reshape(1, 48, 48, 1)
    pred = model.predict(face, verbose=0)
    emotion_idx = np.argmax(pred)
    confidence = np.max(pred)
    return emotion_idx, confidence

# -------------------- 7. 表情包生成 --------------------
emotion_emojis = {
    0: '😠', 1: '🤢', 2: '😨', 3: '😊',
    4: '😢', 5: '😲', 6: '😐'
}

emotion_art = {
    0: """
    ╭━━━━━━━━╮
    ┃  😠      ┃
    ┃  生气   ┃
    ╰━━━━━━━━╯
    """,
    1: """
    ╭━━━━━━━━╮
    ┃  🤢      ┃
    ┃  厌恶   ┃
    ╰━━━━━━━━╯
    """,
    2: """
    ╭━━━━━━━━╮
    ┃  😨      ┃
    ┃  恐惧   ┃
    ╰━━━━━━━━╯
    """,
    3: """
    ╭━━━━━━━━╮
    ┃  😊      ┃
    ┃  开心   ┃
    ╰━━━━━━━━╯
    """,
    4: """
    ╭━━━━━━━━╮
    ┃  😢      ┃
    ┃  悲伤   ┃
    ╰━━━━━━━━╯
    """,
    5: """
    ╭━━━━━━━━╮
    ┃  😲      ┃
    ┃  惊讶   ┃
    ╰━━━━━━━━╯
    """,
    6: """
    ╭━━━━━━━━╮
    ┃  😐      ┃
    ┃  平静   ┃
    ╰━━━━━━━━╯
    """
}

def generate_meme(emotion_idx):
    emoji = emotion_emojis.get(emotion_idx, '❓')
    art = emotion_art.get(emotion_idx, '未知情绪')
    label = emotion_labels.get(emotion_idx, '未知')
    return emoji, art, label

# -------------------- 8. 随机测试功能 --------------------
test_filenames = test_generator.filenames
test_labels = test_generator.classes

def random_test():
    idx = random.randint(0, len(test_filenames)-1)
    img_path = os.path.join(test_dir, test_filenames[idx])
    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        return None, None, None, "图片读取失败"
    emotion_idx, conf = predict_emotion(img)
    return img, emotion_idx, conf, None

# -------------------- 9. 前端界面 (ipywidgets) --------------------
image_widget = widgets.Image(width=200, height=200, format='png')
status_label = widgets.Label(value="点击下方按钮随机测试")
emotion_display = widgets.HTML(value="<h2>等待测试...</h2>")
meme_display = widgets.HTML(value="")
test_btn = widgets.Button(description="🎲 随机测试", 
                           button_style='success',
                           layout=widgets.Layout(width='200px'))

output = widgets.Output()

def on_test_clicked(b):
    with output:
        clear_output()
        status_label.value = "正在随机选择图片..."
        img, emotion_idx, conf, err = random_test()
        if err:
            status_label.value = f"❌ {err}"
            return
        
        pil_img = PILImage.fromarray(img, mode='L')
        buf = io.BytesIO()
        pil_img.save(buf, format='png')
        image_widget.value = buf.getvalue()
        
        emoji, art, label = generate_meme(emotion_idx)
        status_label.value = f"✅ 识别完成! 置信度: {conf:.2%}"
        emotion_display.value = f"""
        <div style='text-align:center; font-size:48px;'>
            {emoji}<br>
            <span style='font-size:24px;'>{label}</span>
            <br><span style='font-size:14px; color:gray;'>置信度: {conf:.2%}</span>
        </div>
        """
        meme_display.value = f"""
        <div style='text-align:center; 
                    background:#f0f0f0; 
                    padding:20px; 
                    border-radius:10px;
                    font-family:monospace;
                    font-size:16px;
                    white-space:pre;'>
            {art}
        </div>
        """

test_btn.on_click(on_test_clicked)

title = widgets.HTML("<h1 style='text-align:center;'>🎭 表情包生成器</h1>")
desc = widgets.HTML("<p style='text-align:center;'>从测试数据集中随机选取一张人脸图片,识别情绪并生成表情包</p>")
ui = widgets.VBox([
    title, desc,
    widgets.HBox([test_btn], layout=widgets.Layout(justify_content='center')),
    status_label,
    widgets.HBox([image_widget], layout=widgets.Layout(justify_content='center')),
    emotion_display,
    meme_display,
    output
])

display(ui)

# -------------------- 10. 初次自动测试 --------------------
print("\n自动进行一次随机测试...")
on_test_clicked(None)

九、运行效果演示

界面截图(文字描述)

  • 页面顶部标题“🎭 表情包生成器”

  • 中央绿色按钮“🎲 随机测试”

  • 每次点击后,上方显示一张48×48的人脸灰度图(放大至200×200)

  • 下方显示大号Emoji(如😊)和中文情绪标签,以及置信度百分比

  • 最下方是带边框的ASCII艺术表情包

测试结果示例

  • 随机抽到一张“开心”表情,置信度78%,输出:

    text

    ╭━━━━━━━━╮
    ┃  😊      ┃
    ┃  开心   ┃
    ╰━━━━━━━━╯

十、优化与扩展方向

  1. 提升准确率

    • 增加训练轮数至30~50,配合EarlyStopping。

    • 使用预训练模型(如MobileNetV2)进行迁移学习。

    • 调整网络结构(增加卷积层或滤波器数量)。

  2. 更丰富的表情包

    • 调用Stable Diffusion API根据情绪生成定制图片。

    • 使用GIF动图或自定义贴纸。

  3. 支持用户上传图片

    • 添加 FileUpload 控件,让用户上传本地人脸图片进行识别。

  4. 部署为Web应用

    • 使用Streamlit或Gradio快速包装成在线服务。


十一、总结

本项目从零开始,在Kaggle上实现了一个完整的“情绪识别→表情包生成”流程,涵盖了数据加载、CNN建模、训练、预测和交互界面。尽管模型准确率有待提高,但整个框架清晰,易于扩展,非常适合作为深度学习入门项目。

核心收获

  • 掌握 ImageDataGenerator 处理文件夹数据集。

  • 理解CNN在图像分类中的应用。

  • 学会使用 ipywidgets 构建简单的交互界面。

  • 体验从数据到产品的完整闭环。

如果你也对AI+创意感兴趣,不妨动手试试,训练属于自己的表情包生成器!欢迎在评论区交流讨论。


项目源码:见上文完整代码块,可直接复制到Kaggle Notebook运行。

数据集来源:Kaggle - FER2013 (msambare版)

参考链接

更多推荐