简介:CK+人脸表情数据集由Cohn-Kanade数据集扩展而来,是人脸表情识别领域广泛使用的标准资源,主要面向计算机视觉研究者、深度学习开发者和相关专业学生。该资源可用于七类基本表情识别模型的训练与评估,帮助解决表情动态变化建模、特征提取和自动化标注等关键问题。压缩包共包含928个文件,以920张PNG序列图像为主体,记录了中性、快乐、悲伤、惊讶、愤怒、厌恶、恐惧表情从起始到峰值的连续变化;同时附带C语言数据整理脚本、PDF参考论文及使用说明文档,便于图像预处理、标注规范查阅和算法复现,整包大小约139.77MB。目前已有2523人学习使用,可应用于学术算法验证、情感计算、人机交互、心理学研究等场景。该数据集提供多帧表情序列、关键点定位、表情强度评分与情感标签等丰富标注,能直接支撑深度学习模型训练、特征提取方法对比及表情识别系统开发,对推动表情识别技术的研究与落地具有重要价值。 从入行做计算机视觉到现在,我见过太多数据集“你方唱罢我登场”,但有一个老面孔始终没从论文和开源项目里消失——CK+人脸表情数据集。十多年过去了,新模型、新榜单层出不穷,但一提“标准benchmark”“基线对比”,还是绕不开它。甚至在一些日常沟通里,同行说到表情识别,第一句就是“你在CK+上跑多少”。
这个数据集的独特之处在于:它不算大,动辄上万的现代数据集比比皆是,但它贵在“干净”和“标注质量极高”。论文里做对比实验,用CK+的人远多于用其他野榜数据的人;面试算法岗时,聊到表情识别,面试官也常拿“你在CK+上怎么调”来起手。如果你要入门人脸表情识别,或者要给论文补一个通用基准,CK+几乎是绕不开的第一步。
不过,越是老牌数据集,资料越分散,很多细节藏在一封封邮件和零散的readme里。这篇文章就把CK+从申请下载、目录结构、标注格式,到训练模型时最容易被忽略的暗坑,完整串一遍,最后聊一聊它和现代真实场景数据之间的边界在哪里。无论你是第一次接触这个数据集的初学者,还是已经跑过但觉得“哪里不对”的进阶老手,应该都能有些收获。
1. 这个“老古董”为什么至今还是默认基准
1.1 一个反直觉的现象:小数据集反而更能说明问题
很多刚接触的人会困惑:现在表情识别数据集不是有AffectNet、RAF-DB这些几万、几十万样本的“大块头”吗?为什么做算法对比时,大家还要回头在CK+上再跑一遍?
我记得早年在组里做表情识别,导师的第一句话说:“先别急着上大模型,把CK+跑通,你看看能不能到90%以上。”当时不理解:测精度不应该是数据集越大越有说服力吗?
后来才琢磨明白这里面的道理。大数据集虽然样本多,但很多是互联网采集,标签噪声不小,光照、姿态、遮挡干扰很大。模型在这些数据上的分数,既可能是学到了真东西,也可能是在“死记硬背”某些场景模式,很难完全理性地归因。而CK+是受控实验室环境下采集的,人脸位置、光照、姿态都比较规整,表情是从中性到峰值的连续帧,标签是经过训练的编码员逐帧标注的。你在它上面提一个点,大概率是算法本身真的有效,而不是恰好适配了某种数据分布。
所以CVPR、ICCV这些顶会论文里,做表情识别或面部动作单元分析的,几乎都要在CK+上报一个数,本质上是给审稿人一个“这方法在公认的干净基准上没崩”的信号。这个作用,是大数据集替代不了的。
1.2 CK+与原始CK的差异:扩展版到底扩展了什么
CK+的全称是Extended Cohn-Kanade Dataset,由卡内基梅隆大学和匹兹堡大学联合发布。它前面还有个CK原始版本,现在基本已经没人用了,但不妨碍我们把历史脉络理清楚。
原始CK发布于2000年左右,包含大约100多人的表情视频序列,侧重在FACS(面部动作编码系统)编码上。而CK+的扩展主要在三块:
- 序列数量从原本的几百增加到593个,覆盖了123个受试者;
- 增加了七类基本表情的标签(愤怒、轻蔑、厌恶、恐惧、快乐、悲伤、惊讶);
- 提供了更完善的68点面部关键点坐标(landmarks),方便做人脸对齐和形状特征提取。
这些看起来只是“多一点数据、多几个标签文件”,但对做研究的人来说,最关键的是表情标签终于可以用了。因为原始CK里面很多序列只有FACS标签,没有对应的离散情绪标签,没法直接用于表情分类任务。CK+把这个问题解决了,这也是它成了默认基准的硬条件之一。
还有一个容易被忽略的点:CK+里每个样本不是一张静态图,而是一个图像序列,通常从中性脸开始,逐步过渡到表情峰值。这意味着它天然适合做成“时序表情识别”的研究,也是后来很多论文说“我们用CK+的峰值帧”这个说法的由来。
2. 数据集的目录结构与标注逻辑
2.1 目录组织与样本命名规律
刚拿到CK+压缩包的时候,如果不看readme,可能会被文件夹层级弄得有点晕。它的目录结构大致如下:
cohn-kanade-images/ S010/ 001/ S010_001_00000001.png S010_001_00000002.png ... S010_001_00000012.png 002/ ... S011/ ... FACS_labels/ landmarks/ Emotion_labels/四类文件的含义分别是:
cohn-kanade-images:原始图像序列,灰度PNG图,分辨率通常是640x490或640x480;FACS_labels:每帧对应的面部动作单元编码文件,A/B/C三种强度示例,文本格式;landmarks:每帧对应的68点面部关键点坐标,文本格式,顺序对应dlib等库输出的索引;Emotion_labels:序列级别的表情标签文件,每个序列包含两个文本文件,一个固定为0,表示第一帧中性;另一个是对应峰值帧的表情编号。
命名规律其实是有信息量的。以S010_001_00000012.png为例,S010是受试者编号,001是序列编号,后面的数字是帧序号。注意:不同受试者、不同序列的帧数不固定,所以看到有些序列只有10帧,有些有20多帧,都是正常的。遍历文件夹时不要假设所有序列长度一致。
2.2 标签文件与7类+1类表情的编码约定
关于Emotion_labels,代码里写的时候要特别小心。它的标签不是全序列都标,每个序列文件夹下通常只有两个txt:
S010_001_00000001_emotion.txt:内容是0,标记中性帧;S010_001_00000012_emotion.txt:内容是具体编号,比如5表示高兴;- 中间那些帧没有表情标签文件,只有图像。
这种做法对训练来说意味着:一个视频序列,往往只能提取出“中性帧+峰值帧”两个可用分类样本。这也解释了为什么CK+给人感觉“很大”,但真正能当单帧分类用的样本数其实不多。
标签数值的对应关系是固定的,写代码时最好直接定义成常量,别用魔法数字:
EMOTION_MAP = { 0: 'neutral', 1: 'anger', 2: 'contempt', 3: 'disgust', 4: 'fear', 5: 'happy', 6: 'sadness', 7: 'surprise' }这里有个容易踩的坑:很多人写分类代码时以为表情类别是1到7,把中性帧扔掉;有人则反过来,把0也当成一类训练。这都没错,取决于你要做什么任务。但如果要和论文里的数字对齐,就得先看清对比论文是“7类”(不含中性)还是“8类”(含中性)。CK+官方论文里常用的是7类基本表情,因为中性本身不是基本表情,但实际做模型时,很多人会把中性单列一类,因为应用场景里总需要识别“没表情”。
FACS标签的格式更复杂一点,它是用类似1+4+25这样的组合,代表多个动作单元同时激活。做AU检测的人才会用到,这里不展开。但值得注意的是,FACS文件是按帧可用的,每个视频帧都有对应的AU编码文件,所以做“动作单元强度回归”的人更喜欢用CK+,因为它是少见的帧级能对上AU标注的数据集。
3. 获取授权与落地前的预处理
3.1 申请下载流程中的几个要点
CK+不是直接注册就能下载的公共数据集,它需要向发布方申请。这个流程经常被论文复现的人忽略,结果卡在“没有数据集”这一步。
申请需要访问卡内基梅隆大学相关页面的数据集申请表,填写姓名、机构、邮箱、使用目的等信息。有些高校或公司的公共邮箱会被拦截此类邮件,建议用机构域名邮箱,并在垃圾邮件箱里翻一翻。审核周期不等,快的一两天,慢的一两周,申请主要用于科研用途,商业用途基本会被拒绝。
我个人的经验是:申请邮件里把你的研究方向、具体要做什么任务写清楚,并说明会遵循数据使用条款,通过的效率会高一些。另外,下载链接有时会用压缩包分卷的形式给,下载后要仔细比对校验文件。如果发现某个分卷损坏,不要用浏览器续传,直接重新下载那个分卷,否则解压时会报“unexpected end of data”。
拿到压缩包后建议先解压到一个稳定路径,因为后面所有预处理脚本都会依赖这个路径结构。如果换了机器、换了目录,一堆硬编码路径的脚本就会全挂。
3.2 图像帧选取:从视频序列到静态样本的取舍
CK+原始数据是序列帧,但绝大多数表情识别模型输入是静态图像。所以“从序列中取哪一帧作为训练样本”是一个从一开始就要想清楚的决策。
三种常见做法:
- 只取峰值帧:每个序列最后一帧就是表情最明显的帧,用这一帧对应一个表情标签。这样每个有表情标签的序列只贡献1个样本,样本量最小,但标签最干净。
- 取中性帧+峰值帧:中性帧作为“负样本”,峰值帧作为“正样本”,相当于把每个序列变成两个样本。这是很多论文的做法,也是我推荐的做法,因为模型可以学到“从平静到激动”的对比。
- 取中间所有帧:数据量最大,但中间帧的表情强度参差不齐,标签存在模糊性。有些帧你可能看不出是什么表情,但被强行标成了某个类。
第一种和第二种比较常用,第三种一般配合“自监督预训练”或者“标签分布学习”使用。我后来回头看,发现很多排行榜上虚高的数字,其实是用第一种取完峰值帧后、又做了随机裁剪等增强刷出来的,泛化性经不起考验。所以在写复现说明时,一定要标明“本文取峰值帧”还是“取中性+峰值帧”。
预处理中的另一步是人脸对齐。CK+自带landmarks,只是文本文件不是图片标注框。常见操作是读取68个关键点,然后用标准模板做相似变换,把眼睛对准同一个位置,再裁剪到224x224输入网络。这里要注意:某些landmarks文件的坐标值不是整数分辨率,而是浮点型,读取时别顺手转int,否则对齐完会发现头部角度偏了。
4. 用CK+训练一个表情识别模型的全流程
4.1 数据划分与类别平衡处理
CK+一共593个序列,但不是所有序列都有表情标签。按官方统计,只有327个序列带七类基本表情标签,其余序列要么只有FACS编码,要么未标定情绪类别。所以如果你打算做基本表情分类,实际可用的序列数量约327个。
这327个序列中,类别分布也不均匀。粗略来看,快乐和惊讶样本比较多,轻蔑很少,恐惧也不算多。如果用简单的随机划分,很容易出现某一类在验证集中只有一两个样本的情况,评估结果方差很大。
我推荐两条划分策略:
- 按受试者划分:把同一个人的所有序列放进同一个fold,避免同一个人同时出现在训练集和验证集中,否则模型会因为“见过这人”而虚高几个百分点。这一点很多人会忽略,但论文审稿人越来越看重。
- 采用交叉验证或多组随机划分:因为数据量小,一次性划分的运气成分很大。我习惯跑5次不同种子的划分,取平均值和标准差来报告。
如果你的任务类别数实在太少(比如蔑视类只有几十个样本),有几个补救办法:
- 将蔑视类合并到邻近类别,变成6类;
- 做数据增强(水平翻转、轻微旋转、亮度抖动),但尽量在训练时做,别在提前扩到磁盘上,否则验证集里可能出现增强后的样本泄漏;
- 使用类别加权损失函数,让少数类的loss贡献更大。
我自己最终用的方案是6类分类(砍掉蔑视),加上中性帧,一共7类。因为蔑视这个类别本身在训练和测试中都极其不稳定,丢掉之后模型稳定性反而更好。
4.2 模型选型与训练实录
CK+是灰度图,单通道,分辨率不高。不需要一上来就上Swin Transformer或者ViT-Huge,我记得第一次用ResNet18做实验,就能跑到接近95%的准确率。真正拉开差距的不是模型大小,而是输入预处理和对齐质量。
一个可复现的baseline配置如下:
import torch from torchvision.models import resnet18, ResNet18_Weights model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1) model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False) num_features = model.fc.in_features model.fc = torch.nn.Linear(num_features, 7)这里有个细节:ImageNet预训练模型输入是3通道,而CK+是灰度图。直接把灰度图复制成三通道再输入是最省事的做法,也能用上预训练权重;但如果你想从第一层开始重新调灰度特征,就把conv1改成1通道输入。实测下来,前者收敛更快,后者最终精度略高一点点,但差距通常在1%以内。可以按算力预算选。
训练时的超参数我有几个实践经验:
- 输入尺寸:224x224够用,不需要更大;
- 优化器:Adam,初始学习率1e-4或1e-3都行,加一个StepLR或者余弦退火;
- Batch size:32或64,数据量小,bn层一定不要dropout开太大;
- Epoch:30-50个epoch之间就够,太久了会明显过拟合。CK+数据量小,loss下降很快,但val准确率容易停在95%附近波动。
如果训练过程中发现训练集准确率已经99%,而验证集只有80%多,那就是过拟合了。除了加正则化和数据增强,还有一种很有效的方式:做人脸关键点对齐后的crop,不要丢整张图进去。因为原始图像中背景占比高,模型很容易学到“背景不同→表情不同”这种虚假关联。
4.3 评估指标与过拟合判断
在CK+上做表情分类,最常见的评估指标就是Overall Accuracy和混淆矩阵。一个成熟的实验,除了给平均准确率,还应该看每个类别的recall和precision。因为类别不平衡,准确率高并不代表个别类不崩。
比如很多论文报CK+的平均准确率在98%以上,但只要看混淆矩阵就会发现,恐惧和悲伤经常互相混,惊讶和快乐相对容易区分。如果你的验证集里恰好恐惧类样本少,准确率会显得非常好看。
我自己测试时,会打印每个epoch里验证集每一类的recall,并观察训练集和验证集的loss曲线。CK+上有一个典型现象:训练集loss和验证集loss在前几个epoch同步下降,但10个epoch后验证集loss开始反弹,这就是过拟合的明确信号。此时不要盲目加epoch,而是考虑:
- 降低学习率;
- 增加数据增强强度;
- 缩小模型容量;
- 提前停止。
还有一点值得单独说:不要只用一组固定划分的结果就下结论。CK+太小,单次划分的验证集可能只有30-40张图,一个样本的预测翻转都可能造成2-3个百分点的波动。所以正式汇报时,最好跑多个随机种子取均值,用标准差作为误差棒,这比单次结果可信得多。
5. CK+的边界在哪里:实验室数据与真实场景的鸿沟
5.1 为什么在CK+上表现良好的模型落地会翻车
在CK+上练出来的模型,拿去做校园课堂专注度分析、车载驾驶员状态监测,常常会出现“实验室精度97%,现场表现惨不忍睹”的情况。这不是模型没调好,而是CK+本身的分布和现实世界存在系统性差异。
这背后有几条很扎实的原因:
- 采集环境是实验室,光线均匀,背景单一,正面朝向,无遮挡。真实场景里,逆光、低头、侧脸、戴口罩、手机遮挡,随便一个条件都可能让模型失效;
- 样本以欧美面孔为主,肤色、五官比例比较同质。换到东亚人群,跨域性能明显下降;
- 表情是表演或引导出来的,具有明显的“峰值形态”,而真实表情往往强度低、混杂、持续时间短。CK+里“平静的伤心”和“强颜欢笑”几乎不存在;
- 图像是灰度且在受控分辨率下采集,和手机拍摄的彩色、压缩、低光照图像差异巨大。
所以,如果你要在真实产品里做表情识别,我的建议是不要只用CK+训练,而是把它作为“模型是否学到基本表情能力”的一个离散指标,再混入AffectNet、RAF-DB等真实场景数据做联合训练或者领域自适应。
5.2 面向真实场景的多数据集混合策略
我实践过比较可行的方案是:用CK+做模型的“初始化校准”,再用真实场景数据做迁移微调。具体来说:
第一步,在CK+上做预实验,快速验证网络结构、增强策略、超参数是否正常工作。CK+数据量小,训练一轮快的几分钟,能迅速暴露代码bug和方向错误。
第二步,把训练好的模型在RAF-DB或AffectNet这些真实场景数据上继续微调。预训练权重有了一个“表情语义”的初始化点,收敛比完全从头训快很多。RAF-DB包含真实世界的人脸表情,样本背景复杂、姿态多样,非常适合跨域迁移。
第三步,如果目标场景非常特定(比如“驾驶员疲劳检测”),还要用自己的业务数据做最后一层的fine-tune,甚至做伪标注联合训练。
这样做的效果,我实测过比单独用CK+训练或单独用RAF-DB从零训练都要好。原因也不复杂:CK+虽然量少但标注质量极高,等于给模型输入了“什么是一个标准表情”的先验;真实数据量大,但噪声多,模型在两者之间取了一个平衡。
还有一个细节:由于CK+是灰度图,如果你全程用CK+预训练,模型第一个卷积层的通道数是1,迁移到彩色数据时要先复制通道或者改回3通道。建议预训练阶段就用3通道输入(灰度图复制三次),这样后续迁移不用改动网络结构。
5.3 给新手的配套工具箱
如果你正打算复现CK+上的表情识别实验,下面这些小工具和经验可以省不少时间:
- 人脸检测与对齐:可以用OpenCV的DNN人脸检测器或者MediaPipe,检测人脸并提取68点关键点,然后做相似变换对齐。如果不做检测直接整图训练,可能会被背景干扰;
- 数据读取脚本:建议写一个Dataset类,按受试者分组划分索引,避免后续代码越写越乱;
- 存储格式:小规模实验直接用文件夹+命名规则就行,不必转成LMDB或TFRecord,反而增加调试成本;
- 可视化工具:用matplotlib把每个序列的中性帧和峰值帧并排打印出来,检查标签对齐是否准确。这一步看似简单,但能避免大量“标签对错帧”的隐蔽bug。
我记得有一次跑实验,怎么调准确率都上不去,后来把所有峰值帧打印出来一看,发现脚本里frame序号取错了,取成了中性帧。这种错误只有可视化才能快速暴露。
结尾
用CK+这么多年,我的体会是:它不像那些新兴的大规模数据集那样能让你“大力出奇迹”,但它是一块极好的试金石,逼着你在数据预处理、标签对齐、训练策略这些基础细节上下硬功夫。谁能在这么小的数据上稳定刷出高分数,谁才敢说自己是真把表情识别这件事理解了。
最后再分享一个小技巧:如果你决定用CK+做论文实验,记得在论文中写明“we use the final frame of each sequence for evaluation”,并注明是否包含中性类。这两个注释能省去审稿人反复猜疑的麻烦,也能让复现的人少走很多弯路。
本文还有配套的精品资源,点击获取