从CSV到可视化:Python实战FER2013表情数据集预处理
第一次接触FER2013数据集的研究者往往会遇到一个令人困惑的场景——下载后的数据集不是常见的图片文件夹,而是一个结构复杂的CSV文件。这种数据格式对于刚入门深度学习的新手来说,确实是个不小的挑战。本文将带你一步步解析这个特殊的数据结构,并用Python将其转换为可直接用于模型训练的图片集。
1. 理解FER2013数据集的结构
FER2013是计算机视觉领域广泛使用的人脸表情识别基准数据集,包含35,886张48×48像素的灰度人脸图像,标注为7种基本表情类别。但与常见图像数据集不同,它采用了一种紧凑的CSV存储格式。
打开fer2013.csv文件,你会发现三列关键数据:
emotion:表情类别标签(0-6的整数值)pixels:图像像素数据(以空格分隔的字符串)Usage:数据用途标记(Training/PublicTest/PrivateTest)
7种表情对应的标签和含义如下表所示:
| 标签数字 | 英文标签 | 中文含义 |
|---|---|---|
| 0 | anger | 生气 |
| 1 | disgust | 厌恶 |
| 2 | fear | 恐惧 |
| 3 | happy | 开心 |
| 4 | sad | 伤心 |
| 5 | surprised | 惊讶 |
| 6 | neutral | 中性 |
注意:原始数据存在明显的类别不平衡问题,特别是"disgust"类样本数量仅为其他类别的1/10左右,这在后续模型训练时需要特别关注。
2. 环境准备与工具选择
在开始转换前,我们需要配置合适的Python环境。推荐使用Anaconda创建虚拟环境,避免包冲突:
conda create -n fer2013 python=3.8 conda activate fer2013必需的核心库及其作用:
- Pandas:高效读取和处理CSV数据
- NumPy:数值计算和数组操作
- OpenCV:图像处理和保存
- Matplotlib(可选):图像可视化检查
安装命令:
pip install pandas numpy opencv-python matplotlib对于深度学习后续应用,还需要准备:
pip install tensorflow keras3. CSV到图像的完整转换流程
下面我们分步骤实现从CSV到图像文件夹的转换,每个表情类别会自动创建独立子目录。
3.1 数据加载与解析
首先创建一个Python脚本(如convert_fer2013.py),导入必要的库:
import os import pandas as pd import numpy as np import cv2 from tqdm import tqdm # 进度条显示定义主转换函数:
def convert_fer2013(csv_path, output_dir="fer2013_images"): """ 将FER2013 CSV文件转换为分类图像文件夹 :param csv_path: fer2013.csv文件路径 :param output_dir: 输出目录名称 """ # 创建输出目录(如果不存在) os.makedirs(output_dir, exist_ok=True) # 读取CSV数据 df = pd.read_csv(csv_path) # 表情标签到名称的映射 emotion_map = { 0: "anger", 1: "disgust", 2: "fear", 3: "happy", 4: "sad", 5: "surprised", 6: "neutral" } # 遍历每一行数据 for idx, row in tqdm(df.iterrows(), total=len(df)): try: # 解析像素字符串 pixels = np.array([int(p) for p in row["pixels"].split()]) # 重塑为48x48图像 image = pixels.reshape(48, 48).astype(np.uint8) # 创建表情类别子目录 emotion_label = row["emotion"] emotion_name = emotion_map[emotion_label] emotion_dir = os.path.join(output_dir, emotion_name) os.makedirs(emotion_dir, exist_ok=True) # 生成唯一文件名 filename = f"{row['Usage']}_{idx}.jpg" filepath = os.path.join(emotion_dir, filename) # 保存图像 cv2.imwrite(filepath, image) except Exception as e: print(f"处理第{idx}行时出错: {str(e)}")3.2 执行转换与验证
运行转换脚本:
if __name__ == "__main__": convert_fer2013("fer2013.csv") print("转换完成!")转换完成后,检查输出目录结构应如下:
fer2013_images/ ├── anger/ ├── disgust/ ├── fear/ ├── happy/ ├── sad/ ├── surprised/ └── neutral/为了验证转换结果,可以添加一个简单的可视化检查函数:
import matplotlib.pyplot as plt def visualize_samples(image_dir, samples_per_class=3): """ 从每个类别随机显示样本图像 """ emotions = os.listdir(image_dir) plt.figure(figsize=(15, 10)) for i, emotion in enumerate(emotions): emotion_path = os.path.join(image_dir, emotion) image_files = os.listdir(emotion_path)[:samples_per_class] for j, img_file in enumerate(image_files): img_path = os.path.join(emotion_path, img_file) img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) plt.subplot(len(emotions), samples_per_class, i*samples_per_class + j + 1) plt.imshow(img, cmap="gray") plt.title(emotion) plt.axis("off") plt.tight_layout() plt.show()4. 高级处理与数据增强
得到图像文件后,我们还可以进行一些进阶处理,为后续的模型训练做好准备。
4.1 数据集统计分析
了解数据分布对于构建平衡的训练策略至关重要:
def analyze_distribution(image_dir): """ 分析各类别样本分布情况 """ emotion_counts = {} total = 0 for emotion in os.listdir(image_dir): emotion_path = os.path.join(image_dir, emotion) count = len(os.listdir(emotion_path)) emotion_counts[emotion] = count total += count # 打印统计信息 print(f"总样本数: {total}") for emotion, count in emotion_counts.items(): print(f"{emotion}: {count} ({count/total*100:.1f}%)") # 绘制分布图 plt.bar(emotion_counts.keys(), emotion_counts.values()) plt.title("FER2013数据集类别分布") plt.ylabel("样本数量") plt.xticks(rotation=45) plt.show()4.2 数据增强策略
针对类别不平衡问题,可以使用Keras的ImageDataGenerator实现实时数据增强:
from tensorflow.keras.preprocessing.image import ImageDataGenerator def create_augmentor(): """ 创建数据增强生成器 """ return ImageDataGenerator( rotation_range=15, width_shift_range=0.1, height_shift_range=0.1, shear_range=0.1, zoom_range=0.1, horizontal_flip=True, fill_mode="nearest" )4.3 高效数据加载方案
对于大型数据集,建议使用生成器方式加载数据,减少内存占用:
def image_generator(image_dir, batch_size=32, target_size=(48,48)): """ 创建高效图像数据生成器 """ datagen = ImageDataGenerator(rescale=1./255) generator = datagen.flow_from_directory( image_dir, target_size=target_size, color_mode="grayscale", batch_size=batch_size, class_mode="categorical" ) return generator5. 常见问题与解决方案
在实际操作中,你可能会遇到以下典型问题:
5.1 路径相关问题
- 问题表现:
FileNotFoundError或权限错误 - 解决方案:
- 使用
os.path模块处理路径,而非硬编码 - 确保输出目录有写入权限
- 在Windows系统中注意反斜杠转义
- 使用
# 安全的路径拼接方式 output_dir = os.path.join("data", "fer2013")5.2 像素值范围异常
- 问题表现:保存的图像全黑或全白
- 解决方案:
- 确保像素值在0-255范围内
- 转换前检查数据范围:
print(np.min(pixels), np.max(pixels)) - 必要时进行归一化或反归一化
5.3 内存不足问题
- 问题表现:处理大型CSV时内存溢出
- 解决方案:
- 使用Pandas的
chunksize参数分批读取 - 考虑使用Dask等大数据处理库
- 使用Pandas的
# 分批读取大型CSV for chunk in pd.read_csv("large_fer2013.csv", chunksize=1000): process_chunk(chunk)5.4 类别不平衡处理
针对FER2013中disgust类别样本少的问题:
- 过采样少数类
- 使用类别权重(class_weight)
- 采用分层抽样确保训练/测试集分布一致
from sklearn.utils import class_weight # 计算类别权重 class_weights = class_weight.compute_class_weight( "balanced", classes=np.unique(train_labels), y=train_labels )6. 完整代码整合
将所有功能整合到一个实用类中,方便复用:
import os import cv2 import numpy as np import pandas as pd from tqdm import tqdm from tensorflow.keras.preprocessing.image import ImageDataGenerator class FER2013Converter: """ FER2013数据集转换与处理工具类 """ EMOTION_MAP = { 0: "anger", 1: "disgust", 2: "fear", 3: "happy", 4: "sad", 5: "surprised", 6: "neutral" } def __init__(self, csv_path): self.csv_path = csv_path self.df = pd.read_csv(csv_path) def convert_to_images(self, output_dir, show_progress=True): """ 转换CSV到分类图像文件夹 """ os.makedirs(output_dir, exist_ok=True) iterator = tqdm(self.df.iterrows(), total=len(self.df)) if show_progress else self.df.iterrows() for idx, row in iterator: try: pixels = np.array([int(p) for p in row["pixels"].split()]) image = pixels.reshape(48, 48).astype(np.uint8) emotion_name = self.EMOTION_MAP[row["emotion"]] emotion_dir = os.path.join(output_dir, emotion_name) os.makedirs(emotion_dir, exist_ok=True) filename = f"{row['Usage']}_{idx}.jpg" cv2.imwrite(os.path.join(emotion_dir, filename), image) except Exception as e: print(f"Error processing row {idx}: {str(e)}") def get_class_distribution(self): """ 获取类别分布统计 """ return self.df["emotion"].value_counts().sort_index() @staticmethod def create_generator(image_dir, batch_size=32, augment=False): """ 创建数据生成器 """ if augment: datagen = ImageDataGenerator( rescale=1./255, rotation_range=15, width_shift_range=0.1, height_shift_range=0.1, shear_range=0.1, zoom_range=0.1, horizontal_flip=True, fill_mode="nearest" ) else: datagen = ImageDataGenerator(rescale=1./255) return datagen.flow_from_directory( image_dir, target_size=(48,48), color_mode="grayscale", batch_size=batch_size, class_mode="categorical" )使用示例:
# 初始化转换器 converter = FER2013Converter("fer2013.csv") # 转换为图像 converter.convert_to_images("fer2013_images") # 创建数据生成器 train_generator = converter.create_generator("fer2013_images", augment=True)