ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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系列是当前最稳的没有遇到什么幺蛾子。如果你只是单纯保存图片其实也可以用Pillowpip 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_okTrue) # 读取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, dtypenp.uint8).reshape(48, 48) # 构造保存路径按 Usage 和 emotion 分组 sub_dir os.path.join(output_dir, usage, str(emotion)) os.makedirs(sub_dir, exist_okTrue) # 文件名用全局索引避免重名 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(..., dtypenp.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), dtypenp.uint8) for i, p in enumerate(df[pixels]): pixel_matrix[i] np.array(p.split(), dtypenp.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_okTrue) 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/0Angry为例随机抽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, cmapgray) 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), borderModecv2.BORDER_REPLICATE) cv2.imwrite(output_path.replace(.png, f_{tag}.png), rotated) # 亮度扰动 bright cv2.convertScaleAbs(img, alpha1.0, beta15) 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), dtypenp.uint8) for i, p in enumerate(self.df[pixels]): self.pixel_matrix[i] np.array(p.split(), dtypenp.uint8) return self.pixel_matrix def save_images(self, output_dirfer2013_images): 按Usage/emotion目录保存图片 os.makedirs(output_dir, exist_okTrue) 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_okTrue) cv2.imwrite(os.path.join(save_path, f{idx}.png), img) def get_split(self, usageTraining): 返回对应划分的标签和像素矩阵便于直接训练 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提取代码的记录能帮你把第一步走顺。本文还有配套的精品资源点击获取
返回列表