news 2026/9/3 2:16:58

fer2013数据集Python提取与可视化:CSV转图片完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
fer2013数据集Python提取与可视化:CSV转图片完整指南

简介:fer2013数据集及配套Python提取代码,是一份面向面部表情识别与深度学习入门实践的完整资料包,适合计算机视觉学习者、算法初学者及科研人员快速上手。压缩包共2000个文件,以jpg表情图片、fer2013.csv原始标注、Python提取脚本、README说明及bib文献条目为主,整体约130MB,目录结构清晰,便于直接读取与二次开发。目前已有8000余人浏览学习,热度较高。包内提供七类基本表情(高兴、中性、惊讶、悲伤、生气、害怕、厌恶)的大规模图像数据,并围绕数据预处理、标签编码、训练/验证/测试集划分提供了参考代码,能够帮助读者理解从原始CSV到图像文件、再到模型输入的完整流程。对打算用卷积神经网络开展表情识别实验的用户而言,这份资源可显著节省数据整理与格式转换时间,同时附带标准文献引用信息,兼顾实践与论文写作需求。 我最初跑表情识别项目的时候,第一步就把我卡了好几天——不是模型搭不出来,而是fer2013数据集的原始格式让我无从下手。它不像普通的图片文件夹那样直接给你一堆jpg,而是把所有图片的像素值塞进一个CSV文件里,每条记录一长串数字。你拿去训练模型没问题,但想看一看图片长什么样、想单独抽几张做可视化、想转成通用图片格式喂给其他网络,就必须要自己动手写python提取代码。这篇文章就专门记录我在这件事上踩过的坑和最终稳定跑通的做法,包括怎么解析CSV、怎么还原成48x48的灰度图、怎么按类别整理目录,以及中途容易翻车的那些小细节。不管是刚入门想做fer2013可视化,还是急着把数据转成jpg格式跑迁移学习,这篇都能直接参考。

1. 整体设计与思路拆解

1.1 先搞清楚fer2013的来龙去脉

fer2013全称是Facial Expression Recognition 2013,来自Kaggle上一个经典的人脸表情识别比赛。整个数据集包含35887张人脸表情图,统一尺寸是48x48像素,灰度图,每张图对应一个表情标签。标签一共7类:0代表愤怒(Angry)、1代表厌恶(Disgust)、2代表恐惧(Fear)、3代表开心(Happy)、4代表悲伤(Sad)、5代表惊讶(Surprise)、6代表中性(Neutral)。

单看这个规模,35887张图并不算大,但它的价值在于:这是早期少有的带公开标签的人脸表情数据集,很多论文和开源项目都拿它当基准。还有一点很多人没注意到——fer2013的表情分布很不均衡,Disgust类只有几百张,而Happy和Neutral类各有一万多张。做分类任务时如果不做任何处理,模型天然会对样本多的类别偏向,这点后面你跑精度时会特别有体会。

1.2 为什么非要把CSV提取成图片

fer2013的原始数据是CSV格式,每行三列:emotion(标签)、pixels(像素)、Usage(数据集划分)。pixels列是一串用空格分隔的灰度值,每张图2304个数字,正好对应48x48。你要是直接把整条pixel字符串丢给深度学习框架,也能解析,但有几个场景会非常难受:

第一,可视化困难。你没法快速"看一眼"某张图,必须先把字符串切分、转数组、reshape、再显示,每回调试都重复写这段代码。第二,模型接口不通用。很多预训练模型或现成工具链默认从图片文件读取数据,而不是从内存数组读取,转成图片文件后可以直接套用。第三,调试方便。把图片落盘之后,你可以用看图软件快速浏览每一类样本,检查有没有损坏数据、标签是否对得上,这在数据清洗阶段特别重要。

所以整体思路很清晰:读CSV -> 解析像素列 -> 按48x48形状还原成灰度矩阵 -> 按Usage和emotion分类保存成PNG或JPG。整个过程代码量不多,但细节处理到位后可以一劳永逸,后面做可视化、数据增强、迁移学习都轻松很多。

2. 数据格式解析与环境准备

2.1 CSV结构细节与容易被忽视的坑

拿到fer2013的CSV文件后,先别急着写代码,花两分钟把结构看清楚。用文本编辑器打开文件头几行,你能看到类似这样的内容:

emotion,pixels,Usage 0,70 80 82 72 58 58 60 63 54 60 48 89 115 121 119 ...,Training 2,151 150 147 155 148 133 111 140 170 174 182 150 105 86 70 68 65 ...,Training

注意几个关键点:

  • 第一行是表头,pandas读进来会默认当成列名,解析时要小心。
  • pixels列里所有值都是用单个空格分隔的,不是逗号,也不是制表符。别用split(',')去切,否则你会得到一整个字符串。
  • 像素值的范围是0到255,但CSV里存的是字符串。直接用np.array(list(map(int, pixel_str.split())))转成整数数组,再以uint8类型reshape成48x48。
  • Usage列有3个值:Training、PublicTest、PrivateTest。其中Training有28709张,PublicTest和PrivateTest各3589张,三者加和正好是35887张。

还有个容易踩的坑:有些网上下载的fer2013文件可能被重新导出过,列顺序、分隔符、表情标签定义可能不完全一样。稳妥起见,建议先打印CSV的形状和前几行确认一遍:

import pandas as pd df = pd.read_csv('fer2013.csv') print(df.shape) # (35887, 3) print(df.head()) print(df['Usage'].value_counts())

2.2 依赖安装与版本选择建议

提取图片需要的库非常少,核心就是pandas、numpy、opencv-python(也可以用Pillow替代)。如果你本机还没有这些库,直接装:

pip install pandas numpy opencv-python

如果你机器上同时存在多个Python环境,或者用的是Anaconda,建议先确认当前解释器,再安装。我为这个项目单独建了一个虚拟环境,避免和主环境冲突:

python -m venv fer_env source fer_env/bin/activate # Windows下是 fer_env\Scripts\activate pip install pandas numpy opencv-python

关于OpenCV的版本,我实测下来4.x系列是当前最稳的,没有遇到什么幺蛾子。如果你只是单纯保存图片,其实也可以用Pillow:

pip install pillow

两种方案我都在后面给出代码示例,凭个人习惯选。OpenCV在灰度图保存、resize、图像预处理上功能更强,后续如果要继续做数据增强,直接复用OpenCV更方便。

3. 图片提取代码实现

3.1 基础版:完整提取到单目录

这段代码我把注释写得很全,可以直接复制运行。核心流程就是逐行读CSV,解析pixels,还原成48x48,再按行号命名保存:

import pandas as pd import numpy as np import cv2 import os # 保存根目录 output_dir = 'fer2013_images' os.makedirs(output_dir, exist_ok=True) # 读取CSV df = pd.read_csv('fer2013.csv') # 遍历每一行 for idx, row in df.iterrows(): emotion = row['emotion'] usage = row['Usage'] pixels = row['pixels'] # 解析像素字符串 -> 48x48 灰度图 pixel_list = list(map(int, pixels.split(' '))) image_array = np.array(pixel_list, dtype=np.uint8).reshape(48, 48) # 构造保存路径,按 Usage 和 emotion 分组 sub_dir = os.path.join(output_dir, usage, str(emotion)) os.makedirs(sub_dir, exist_ok=True) # 文件名用全局索引,避免重名 file_path = os.path.join(sub_dir, f'{idx}.png') cv2.imwrite(file_path, image_array) # 每处理5000行打印一下进度 if (idx + 1) % 5000 == 0: print(f'已处理 {idx + 1} / {len(df)} 张图片') print('全部图片提取完成')

逐行解释几个关键点:

  • pixels.split(' ')是空格切分,注意不要传空字符串'',否则会把连续多个空格也当成空元素,报错。
  • np.array(..., dtype=np.uint8)这一步很重要。如果直接从map(int, ...)生成,默认会得到int64数组,虽然也能保存,但内存占用高一倍,而且后续做图像处理时类型不对会引起各种奇怪问题。
  • cv2.imwrite保存灰度图时,传进去的数组必须是二维的,也就是(48, 48),不能是(48, 48, 1)。如果你reshape成(48, 48, 1),OpenCV有可能报警告,保存出来的图也可能被当成三通道处理,导致文件体积变大。
  • 保存成PNG还是JPG我建议PNG。虽然fer2013本身是灰度图,JPG有损压缩可能导致像素值发生微小变化,而PNG是无损的。特别是在做精度对比实验时,这种像素级差异会直接影响结果。

3.2 进阶版:目录结构、命名策略与内存优化

上面的代码虽然能用,但实际跑的时候你会发现一个问题:df.iterrows()在3万多行数据上跑,速度相对较慢,而且每行都打印进度会让输出刷屏。更高效的做法是一次性把整个pixels列解析成数组,再批量保存。这里我给出一个更工程化的版本:

import pandas as pd import numpy as np import cv2 import os df = pd.read_csv('fer2013.csv') # 一次性解析所有pixels字符串为二维数组 # 构造一个大矩阵,行数=df行数,列数=2304 pixel_matrix = np.zeros((len(df), 48 * 48), dtype=np.uint8) for i, p in enumerate(df['pixels']): pixel_matrix[i] = np.array(p.split(), dtype=np.uint8) # 按 Usage + emotion 组合分组 grouped = df.groupby(['Usage', 'emotion']) for (usage, emotion), indices in grouped.groups.items(): # indices 是行索引的集合 target_dir = os.path.join('fer2013_images', usage, str(emotion)) os.makedirs(target_dir, exist_ok=True) for rank, idx in enumerate(indices): img = pixel_matrix[idx].reshape(48, 48) # 文件名:原CSV行号,既保证唯一又方便溯源 cv2.imwrite(os.path.join(target_dir, f'{idx}.png'), img)

这个版本有两个改进:一是pixel_matrix整体解析后,reshape和保存的循环里不再做字符串切分,速度快很多;二是按groupby组织目录,同一批表情的图片连续落盘,后续检查时很方便。

你也可以把文件名从行号改成“序号-情绪-Usage”这种可读性更高的形式,比如000123_happy_Training.png。不过我不建议这样做,因为CSV行号是唯一的,和原始数据一一对应,如果后续发现某张图有问题,能快速定位回原CSV检查对应行的emotion和pixels,方便排查。

4. 数据集可视化与类别均衡分析

4.1 随机抽样可视化:快速把分类效果印在脑子里

图片提取出来之后,我建议你马上做一件事:随机抽样可视化。这步虽小,但能让你对数据质量有个直观判断,也能发现一些潜在的标注噪声。比如fer2013里某些人脸其实很不清晰,甚至有个别图片看起来像噪点,这些都会影响模型训练。可视化代码很简单:

import matplotlib.pyplot as plt import cv2 import os import random # 以 Training/0(Angry)为例,随机抽9张 emotion_dir = 'fer2013_images/Training/0' imgs = os.listdir(emotion_dir) samples = random.sample(imgs, 9) plt.figure(figsize=(6, 6)) for i, name in enumerate(samples): img = cv2.imread(os.path.join(emotion_dir, name), cv2.IMREAD_GRAYSCALE) plt.subplot(3, 3, i + 1) plt.imshow(img, cmap='gray') plt.axis('off') plt.show()

我跑完之后发现,某些类别(比如Disgust)的图数量非常少,随机抽样时几乎找不到几张清晰的正脸。这其实提示了一个重要问题:如果你要做7分类,Disgust类别的表现大概率不会好,不是说模型能力不行,而是数据本身就稀缺。后面训练时要么做类别加权,要么采用数据增强策略,要么干脆合并某些相近类别,这些都是后话。

4.2 数据增强扩展:从提取到更多训练样本

图片已经落盘,数据增强就非常方便了。常用的增强操作包括水平翻转、小角度旋转、亮度扰动、随机裁剪。以OpenCV为例,一个简单的水平翻转只需要一行:

img_flip = cv2.flip(img, 1)

但是注意,有些表情是有方向性的,比如开心和惊讶左右翻转会引入不合理样本,而愤怒、中性这些受影响较小。所以增强时最好结合语义考虑。如果你想完整跑一个增强流程,可以这样写:

import cv2 import numpy as np def augment_image(img_path, output_path): img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # 水平翻转 flip_img = cv2.flip(img, 1) cv2.imwrite(output_path.replace('.png', '_flip.png'), flip_img) # 旋转正负10度 h, w = img.shape center = (w // 2, h // 2) for angle, tag in [(10, 'rot10'), (-10, 'rot-10')]: M = cv2.getRotationMatrix2D(center, angle, 1.0) rotated = cv2.warpAffine(img, M, (w, h), borderMode=cv2.BORDER_REPLICATE) cv2.imwrite(output_path.replace('.png', f'_{tag}.png'), rotated) # 亮度扰动 bright = cv2.convertScaleAbs(img, alpha=1.0, beta=15) cv2.imwrite(output_path.replace('.png', '_brighter.png'), bright)

增强后的图片数量会成倍增加,但要注意:如果这些增强图片既出现在训练集又出现在测试集(哪怕只是同源的变换),就会造成数据泄漏,评估结果虚高。正确做法是先划分好原始训练集和测试集,再只对训练集做增强。

5. 常见问题与排查技巧实录

5.1 高频报错汇总与解决办法

我在跑提取代码的过程中,以及在给朋友排查时,遇到过下面这些高频问题,整理成表格方便你对照:

问题现象常见原因解决办法
ValueError: could not convert string to floatpixels字符串里混入了空字符或异常字符检查CSV文件是否被改动过,打印df['pixels'][0]看原始内容
FileNotFoundError: fer2013.csv当前工作目录不对用绝对路径读取文件,或先os.getcwd()确认路径
保存出来的图片全黑像素数组没有转成uint8,或者reshape尺寸不对检查dtype和数组形状,打印image_array.min()max()
图片内容错位、像噪点reshape的顺序错了,可能转置确认是先切分再按行主序reshape,不要先reshape再切分
内存溢出(不太常见)一次性加载整个pixel_matrix分批读取,每5000行处理一次
中文路径保存失败OpenCV在Windows上对非ASCII路径支持不佳保存路径不要用中文,或改用cv2.imencode

其中“全黑图片”是我遇到最尴尬的一种。原因是当时我图省事,把pixel_list直接np.array(pixel_list)生成,默认dtype是int64。保存时OpenCV对int64数组的处理方式比较特殊,很容易输出全黑图。加一句astype(np.uint8)就解决了,这个细节一定要记住。

5.2 工程化建议:从一次性脚本到可复用流程

如果你只是临时提取一次,脚本写到上文那步就够了。但如果你要在多个项目里反复使用fer2013,或者想把这个流程做成团队通用工具,我建议模块化封装:

# fer2013_utils.py import pandas as pd import numpy as np import cv2 import os class Fer2013Extractor: def __init__(self, csv_path): self.df = pd.read_csv(csv_path) self.emotion_map = { 0: 'angry', 1: 'disgust', 2: 'fear', 3: 'happy', 4: 'sad', 5: 'surprise', 6: 'neutral' } def parse_pixels(self): """一次性解析所有pixels为二维矩阵""" self.pixel_matrix = np.zeros((len(self.df), 2304), dtype=np.uint8) for i, p in enumerate(self.df['pixels']): self.pixel_matrix[i] = np.array(p.split(), dtype=np.uint8) return self.pixel_matrix def save_images(self, output_dir='fer2013_images'): """按Usage/emotion目录保存图片""" os.makedirs(output_dir, exist_ok=True) for idx, row in self.df.iterrows(): usage = row['Usage'] emotion = self.emotion_map[row['emotion']] img = self.pixel_matrix[idx].reshape(48, 48) save_path = os.path.join(output_dir, usage, emotion) os.makedirs(save_path, exist_ok=True) cv2.imwrite(os.path.join(save_path, f'{idx}.png'), img) def get_split(self, usage='Training'): """返回对应划分的标签和像素矩阵,便于直接训练""" mask = self.df['Usage'] == usage labels = self.df.loc[mask, 'emotion'].values pixels = self.pixel_matrix[mask] return labels, pixels

封装成类之后,你在训练脚本里就可以这样用:

extractor = Fer2013Extractor('fer2013.csv') extractor.parse_pixels() extractor.save_images('fer2013_images') train_labels, train_pixels = extractor.get_split('Training') test_labels, test_pixels = extractor.get_split('PublicTest')

这套封装还有一个额外的好处:如果某个项目只需要读取数据而不需要落盘成图片,get_split方法能直接返回numpy数组,省去重复解析的时间。做实验时,一次解析、多处复用,效率提升非常明显。

5.3 提取之后:数据质量检查清单

图片全部保存完毕后,不要急着开训练,花几分钟做一次质量检查。我给自己定了一套快速检查流程,分享出来:

  • 抽查每个类别目录下的图片数量,跟CSV里的统计值核对,确认没有遗漏或重复。
  • 随机打开若干图片,确认人脸朝向和清晰度,看是否存在极端模糊或错误标注。
  • 用程序统计每张图的像素均值和方差,如果某张图的均值显著偏离整体水平,多半是坏图,考虑剔除。
  • 确认训练集、公开测试集、私有测试集三者的图片数量比例约为8:1:1,如果有偏差,检查CSV的Usage列是否被意外改动。

这套检查看起来很基础,但能帮你避免一个很尴尬的局面:模型训了一半发现数据集有脏数据,比如某张图其实是纯色块,或者标签标错了导致验证集精度上不去。磨刀不误砍柴工,数据集准备阶段多花十分钟,后面能省一天的时间。

我在实际跑完一遍fer2013提取流程后,最大的感受是:这个小数据集虽然是深度学习的入门级练习,但它的数据格式设计其实很有代表性。很多工业场景里的数据不会乖乖地以图片文件夹形式存在,而是被塞进数据库、CSV、JSON这些“非直接可视化”的容器里。能够熟练地完成数据解析、格式转换、目录整理,是比调模型更基础也更重要的工程能力。希望这篇关于fer2013数据集python提取代码的记录,能帮你把第一步走顺。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 2:16:41

反激式开关电源CCM、DCM、BCM工作模式详解与设计选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 2:14:11

Jalium UI:高性能跨平台桌面UI框架的技术解析与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 2:14:09

从零打造智能灯:单片机选型、PWM调光与蓝牙APP控制全解析

简介:本资源是一套完整的智能照明系统毕业设计与课程设计解决方案,面向电子类、自动化及物联网方向的本科生与实践开发者,解决单片机嵌入式系统中灯光控制功能集成、人机交互与软硬件协同开发等典型工程问题。压缩包共584个文件,7…

作者头像 李华
网站建设 2026/9/3 2:14:03

飞行模式离线部署实战:本地AI模型断网推理与自动化控制

这次我们来看一个叫AIRPLANE MODE(飞行模式)的工程主题。标题副句是“我将独自飞行,无人理会”,放到开发场景里其实非常贴切:很多服务要在完全断网、不依赖公网 API 的环境里独立跑起来,没有外部依赖&#…

作者头像 李华
网站建设 2026/9/3 2:13:38

Windows注册表修复与清理全攻略:从原理到高频错误处理

如果你的 Windows 最近总是出现类似“无法读取 USBPerf\Performance 注册表项下的 First Counter 值”“MSI 文件关联不上”“DLL 初始化例程失败”这样的弹窗,又或者部分软件卸载后仍然在开机自启列表里阴魂不散,那问题很可能不是“系统中毒”&#xff…

作者头像 李华
网站建设 2026/9/3 2:13:23

PHP+MySQL旅游景点网站管理系统毕业设计全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华