AffectNet表情识别数据集预处理全流程:从数据划分、人脸裁剪到面部对齐

📅 发布时间:2026/9/1 1:04:06
AffectNet表情识别数据集预处理全流程:从数据划分、人脸裁剪到面部对齐
简介本资源是一套面向计算机视觉初学者与进阶研究者的Python工程实践方案聚焦AffectNet面部表情识别数据集的预处理全流程解决手动标注子集筛选、面部裁剪对齐及训练/验证集科学划分等关键问题。项目共11个文件含7个核心Python脚本如aligner.py实现关键点对齐、sava_crop_imgs_affectnet.py执行批量裁剪、cal_each_category_img_nums.py统计类别分布及4份Markdown说明文档总大小仅10KB轻量易读、结构清晰便于快速复现与二次开发。已有400人学习下载适用于人脸分析课程实验、表情识别模型训练前的数据准备阶段。读者可直接获取完整可运行代码链从原始标注解析pickle_annotations_affectnet.py、噪声标签生成generate_noise_label.py到最终生成283901张训练图与3500张均衡验证图的标准化流程涵盖中性、开心等7类表情的精确数量统计与目录组织逻辑。1. 项目缘起为什么AffectNet数据预处理是个技术活如果你正在做面部表情识别尤其是基于深度学习的研究或应用AffectNet这个名字你肯定不陌生。作为目前规模最大、标注最丰富的公开表情数据集之一它包含了超过一百万张来自互联网的面部图像并标注了离散的七类基本表情如高兴、悲伤、愤怒等和连续的维度值效价和唤醒度。然而当你兴冲冲地从官网下载了那几十个G的压缩包准备大干一场时第一个拦路虎往往不是模型设计而是数据预处理。原始AffectNet数据集的目录结构复杂图像尺寸、人脸位置、光照条件千差万别直接扔给模型训练效果大概率会惨不忍睹。这就是为什么一个专门针对AffectNet的数据预处理工具包如此重要。它要干的活儿远不止简单的“读图片-打标签”那么简单。核心任务至少包括三块数据划分、面部裁剪和面部对齐。数据划分要保证训练集、验证集、测试集的样本分布均衡避免因划分不当引入模型偏差面部裁剪需要精准地从原始图像中框出人脸区域去除大量无关的背景噪声面部对齐则通过关键点检测和仿射变换将所有裁剪后的人脸“摆正”消除姿态和角度的差异让模型专注于表情本身的变化。网上能找到的很多教程和代码要么只解决了其中一环要么耦合了特定的深度学习框架如PyTorch或TensorFlow的数据加载器通用性和灵活性不足。更常见的情况是代码里隐藏着一些“坑”比如对齐后图像质量下降、划分策略有漏洞、内存消耗过大等这些都需要在实际操作中才能发现。因此我把自己在多个表情识别项目中反复打磨、验证过的一套预处理流程整理成了这个Python项目。它力求模块清晰、配置灵活、结果可靠目标是让你拿到AffectNet数据后能快速、高质量地得到一份“模型友好型”的数据把精力真正投入到模型调优上。2. 环境搭建与项目结构解析工欲善其事必先利其器。这个项目虽然核心逻辑不复杂但依赖的几个关键库需要正确安装项目的目录结构也决定了后续操作的流畅度。2.1 核心依赖库安装与版本管理首先强烈建议使用虚拟环境如venv或conda来管理依赖避免与系统或其他项目的Python环境冲突。这里以venv为例# 创建并激活虚拟环境 python -m venv affectnet_preprocess_env source affectnet_preprocess_env/bin/activate # Linux/macOS # affectnet_preprocess_env\Scripts\activate # Windows # 升级pip pip install --upgrade pip接下来安装核心依赖。除了基础的numpy和opencv-python本项目的一个关键依赖是dlib库及其预训练的人脸关键点检测模型。dlib的安装有时会因系统环境而报错以下是稳妥的安装方案# 安装基础依赖 pip install numpy opencv-python-headless pandas tqdm scikit-learn # 安装dlib。如果直接pip install dlib失败可以尝试以下方法 # 方法一推荐使用预编译的wheel # 访问 https://pypi.org/project/dlib/ 查看对应你Python版本和系统的版本或直接尝试 pip install dlib19.24.2 # 一个较稳定且兼容性好的版本 # 方法二macOS/Linux可能需要先安装CMake和Boost # macOS: brew install cmake boost # Ubuntu/Debian: sudo apt-get install cmake libboost-all-dev # 然后再 pip install dlib # 安装Pillow用于更丰富的图像操作可选但推荐 pip install Pillow注意opencv-python-headless是不包含GUI功能的OpenCV版本更适合服务器环境。如果你需要在本地显示图片调试可以安装opencv-python。dlib的安装是最大的坎如果遇到编译错误优先考虑寻找对应你Python版本如Python 3.9和系统Windows 10/11, Ubuntu 20.04等的预编译.whl文件进行离线安装。2.2 项目目录结构设计一个清晰的目录结构是项目可维护性的基础。建议按如下方式组织你的工作区affectnet_preprocessor/ ├── src/ # 源代码目录 │ ├── __init__.py │ ├── data_splitter.py # 数据划分模块 │ ├── face_cropper.py # 面部裁剪模块 │ ├── face_aligner.py # 面部对齐模块 │ └── utils.py # 通用工具函数如日志、路径处理 ├── configs/ # 配置文件目录 │ └── preprocessing_config.yaml # 所有可配置参数 ├── scripts/ # 执行脚本目录 │ └── run_preprocessing.py # 主运行脚本 ├── input_data/ # 原始AffectNet数据需手动创建并放入数据 │ ├── training/ # 训练集原始图像和标注 │ └── validation/ # 验证集原始图像和标注 ├── processed_data/ # 处理后的输出目录程序自动生成 │ ├── train/ # 处理后的训练集 │ ├── val/ # 处理后的验证集 │ └── test/ # 处理后的测试集从验证集划分 ├── dlib_models/ # 存放dlib预训练模型文件 │ └── shape_predictor_68_face_landmarks.dat # 关键点检测模型 ├── logs/ # 日志文件目录 ├── requirements.txt # 项目依赖列表 └── README.md # 项目说明文档你需要手动完成以下几件事从AffectNet官网下载数据解压后将其中的training和validation文件夹包含图片和对应的标注文件annotation.csv放入input_data/目录。下载dlib的68点人脸关键点预测器模型shape_predictor_68_face_landmarks.dat.bz2解压后得到.dat文件放入dlib_models/目录。这是面部对齐步骤的核心。根据你的需求编辑configs/preprocessing_config.yaml文件调整各项参数。这种结构将配置、源代码、数据、模型和日志分离符合现代机器学习项目的惯例也便于后续的版本控制和协作。3. 核心模块一科学的数据划分策略AffectNet官方提供了训练集和验证集validation set的划分。但在实际模型开发中我们通常需要三个集合训练集Train、验证集Val和测试集Test。官方的验证集常被我们用作测试集Test以评估模型的最终泛化能力。那么用于模型调参的验证集Val从哪里来答案是从官方训练集中再划分出一部分。3.1 从官方划分到实际需求的映射我们的目标是保持测试集的纯粹性只用官方验证集从官方训练集中科学地划分出新的训练集和验证集。这里的关键在于“科学”二字。表情识别数据集普遍存在类别不均衡问题“高兴”的图片远多于“恐惧”如果随机划分可能导致某个表情在验证集中样本极少无法有效评估模型在该类上的性能。因此我采用了分层抽样Stratified Sampling策略。具体思路是读取官方训练集的标注文件通常是一个包含图像路径和表情标签的CSV文件以表情类别为分层依据使用scikit-learn的train_test_split函数按预设比例如8:2进行划分。这样可以确保划分后的训练集和验证集中各个表情类别的比例与原始训练集基本一致。# 代码示例data_splitter.py 中的核心划分函数 import pandas as pd from sklearn.model_selection import train_test_split def create_custom_split(annotation_path, train_ratio0.8, seed42): 从官方训练集标注文件创建新的训练/验证划分。 参数: annotation_path: 官方训练集标注CSV文件路径。 train_ratio: 新训练集占原始训练集的比例。 seed: 随机种子确保结果可复现。 返回: train_df, val_df: 包含image_path和expression_label的DataFrame。 df pd.read_csv(annotation_path) # 假设DataFrame包含image_path和expression列 # 清理数据移除标签为-1无效或8 contempt contempt类别在7类分类中通常不用的样本 df df[df[expression].between(0, 6)] # 保留0-6共7类基本表情 # 使用分层抽样划分 train_df, val_df train_test_split( df, test_size1-train_ratio, stratifydf[expression], # 关键按表情标签分层 random_stateseed ) # 重置索引 train_df train_df.reset_index(dropTrue) val_df val_df.reset_index(dropTrue) # 可选保存划分结果到文件方便后续步骤直接读取 train_df.to_csv(processed_data/splits/train_split.csv, indexFalse) val_df.to_csv(processed_data/splits/val_split.csv, indexFalse) return train_df, val_df3.2 划分策略的细节与陷阱这里有几个容易踩坑的细节无效样本过滤AffectNet的标注中-1表示“无法识别”8表示“ contempt” contempt。在7类基本表情分类任务中通常需要过滤掉这些样本。这一步必须在划分之前进行否则无效样本会被随机分到训练集或验证集污染数据。随机种子务必固定random_state如seed42。这是实验结果可复现的生命线。不同的随机种子会产生不同的数据划分从而导致模型性能波动让你无法判断性能提升是来自模型改进还是运气。路径处理标注文件中的图像路径可能是相对路径。在划分后需要根据你的项目目录结构更新或保存正确的图像绝对路径或相对于新根目录的路径供后续裁剪和对齐模块读取。内存考虑AffectNet训练集有近30万张图片对应的标注文件也很大。直接使用pandas读取整个CSV可能内存占用较高。如果内存紧张可以考虑分块读取chunksize或使用dask库进行处理。通过这个模块我们得到了三个清晰的数据列表train_split.csvval_split.csv以及直接将官方验证集作为test_split.csv。这为后续处理奠定了坚实的基础。4. 核心模块二精准的面部检测与裁剪拿到划分好的图像路径列表后下一步是从原始图片中把人脸区域“抠”出来。这一步的目标是最大化保留表情信息同时最小化背景干扰。我们选用OpenCV的CascadeClassifier结合dlib的get_frontal_face_detector进行人脸检测这是一种兼顾速度和精度的常见方案。4.1 人脸检测器的选型与级联为什么选择两种检测器因为各有优劣。OpenCV的Haar级联分类器速度极快但对于侧脸、遮挡、极端光照的检测效果会下降。dlib的HOGSVM检测器精度更高尤其是对正面和稍侧的人脸非常稳健但速度稍慢。在实际应用中我采用了一种级联Cascade策略先用快的OpenCV检测器尝试如果检测到人脸且置信度通过检测框大小和位置初步判断较高则直接使用如果OpenCV没检测到或结果可疑则启用更准的dlib检测器作为后备。# 代码示例face_cropper.py 中的检测与裁剪函数 import cv2 import dlib import numpy as np from pathlib import Path class FaceCropper: def __init__(self, dlib_detector, cv_classifier_pathhaarcascade_frontalface_default.xml): 初始化人脸检测器。 dlib_detector: dlib.get_frontal_face_detector() 返回的检测器对象。 cv_classifier_path: OpenCV Haar级联分类器XML文件路径。 该文件通常位于OpenCV安装目录的data/haarcascades/下需要复制到项目内。 self.dlib_detector dlib_detector self.cv_classifier cv2.CascadeClassifier(cv_classifier_path) def detect_face(self, image): 级联人脸检测。 返回: 检测到的人脸矩形框 (x, y, w, h)如果未检测到则返回None。 gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 第一级OpenCV快速检测 faces_cv self.cv_classifier.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(30, 30) ) if len(faces_cv) 1: # 理想情况只检测到一张脸 x, y, w, h faces_cv[0] # 简单启发式规则检查人脸框是否过小或位置过于边缘 height, width gray.shape if w 50 and h 50 and x 10 and y 10 and (xw) width-10 and (yh) height-10: return (x, y, w, h) # 第二级OpenCV检测失败或不可信使用dlib faces_dlib self.dlib_detector(gray, 1) # 第二个参数是上采样次数有助于检测小脸 if len(faces_dlib) 0: # 选择面积最大的脸假设这是主要表情主体 face max(faces_dlib, keylambda rect: rect.area()) # 将dlib的矩形格式转换为(x, y, w, h) x, y face.left(), face.top() w, h face.width(), face.height() return (x, y, w, h) return None # 未检测到人脸 def crop_and_save(self, image_path, output_dir, margin_ratio0.2): 检测人脸根据边界框裁剪并保存。 margin_ratio: 在边界框外扩展的比例以保留更多上下文信息如头发、下巴。 image cv2.imread(str(image_path)) if image is None: print(f警告无法读取图像 {image_path}) return False bbox self.detect_face(image) if bbox is None: print(f警告在 {image_path} 中未检测到人脸) # 可以选择保存原图或跳过这里选择跳过 return False x, y, w, h bbox height, width image.shape[:2] # 计算扩展后的边界框并确保不超出图像范围 margin_x int(w * margin_ratio) margin_y int(h * margin_ratio) x1 max(0, x - margin_x) y1 max(0, y - margin_y) x2 min(width, x w margin_x) y2 min(height, y h margin_y) cropped_face image[y1:y2, x1:x2] # 构建输出路径并保存 rel_path Path(image_path).relative_to(self.input_root_dir) output_path Path(output_dir) / rel_path output_path.parent.mkdir(parentsTrue, exist_okTrue) cv2.imwrite(str(output_path), cropped_face) return True4.2 边界框扩展与失败处理上面的代码中有两个关键点边界框扩展Margin直接裁剪检测到的人脸框可能会切掉额头、下巴或部分脸颊这些区域对某些表情如惊讶时的额头皱纹也有贡献。因此我引入margin_ratio参数通常设为0.2将框按比例向外扩展保留更多上下文。同时必须进行边界检查max(0, ...)和min(width, ...)防止扩展后坐标越界。失败处理不是每张图片都能成功检测到人脸。可能是极端姿态、严重遮挡、图像质量太差。对于这些“漏网之鱼”我的策略是记录日志并跳过。在后续统计中你需要关注失败率。如果失败率很高比如超过5%可能需要回查原因是检测器参数太严格还是这批数据本身质量有问题对于跳过的样本在最终的划分CSV文件中也应该被移除保证数据列表的纯净。这个模块会遍历所有划分好的图像列表将成功裁剪的人脸保存到processed_data/train/,processed_data/val/,processed_data/test/的对应子目录中目录结构与原输入保持一致。5. 核心模块三基于关键点的面部对齐裁剪出的人脸其姿态如倾斜、抬头、低头仍然不一致。面部对齐的目的就是通过几何变换将所有脸“标准化”到同一个正面的参考坐标系中。这是提升模型性能非常有效的一步因为它减少了类内差异同一个表情因姿态不同而产生的变化让模型更容易学习到表情的本质特征。5.1 对齐原理与仿射变换对齐的核心是人脸关键点检测。我们使用dlib的68点预测器就是之前下载的shape_predictor_68_face_landmarks.dat来定位人脸上的68个特征点包括眼睛、眉毛、鼻子、嘴巴和脸部轮廓。对齐的思路是选取一张“标准正面脸”作为模板其眼睛、嘴巴等关键点处于理想位置。然后对于每一张待处理的脸计算其检测到的关键点与模板关键点之间的映射关系最后通过仿射变换Affine Transformation将待处理脸“扭曲”到模板的姿势上。仿射变换是一种线性变换包含旋转、缩放、平移和剪切可以用一个2x3的变换矩阵表示。OpenCV的cv2.getAffineTransform(src_points, dst_points)可以根据三对匹配点计算这个矩阵cv2.warpAffine()则应用这个矩阵进行图像变换。# 代码示例face_aligner.py 中的对齐函数 import cv2 import dlib import numpy as np class FaceAligner: def __init__(self, predictor_path, desired_face_width256, desired_face_heightNone): 初始化对齐器。 predictor_path: dlib 68点预测器模型文件路径。 desired_face_width: 对齐后输出图像的宽度。 desired_face_height: 对齐后输出图像的高度如果为None则与宽度相同生成正方形。 self.predictor dlib.shape_predictor(predictor_path) self.detector dlib.get_frontal_face_detector() # 对齐前可能需要再次检测 self.desired_face_width desired_face_width if desired_face_height is None: self.desired_face_height desired_face_width else: self.desired_face_height desired_face_height # 定义模板脸的坐标基于 desired_face_width 和 desired_face_height 计算 # 这里是一个常见的模板双眼水平位于图片上半部分 self.desired_left_eye (0.35, 0.35) # 左眼在输出图像中的相对位置 self.desired_right_eye (0.65, 0.35) # 右眼在输出图像中的相对位置 # 计算实际的像素坐标 self.desired_left_eye_x self.desired_left_eye[0] * self.desired_face_width self.desired_left_eye_y self.desired_left_eye[1] * self.desired_face_height self.desired_right_eye_x self.desired_right_eye[0] * self.desired_face_width self.desired_right_eye_y self.desired_right_eye[1] * self.desired_face_height def align(self, image): 对齐单张图像。 返回: 对齐后的人脸图像如果失败则返回None。 gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) rects self.detector(gray, 0) if len(rects) ! 1: # 对齐要求检测到且仅检测到一张脸否则返回None return None shape self.predictor(gray, rects[0]) shape self._shape_to_np(shape) # 将dlib的shape对象转为numpy数组 # 获取左右眼的中心坐标使用68点模型中的第36-41点为左眼42-47点为右眼 left_eye_center shape[36:42].mean(axis0).astype(int) right_eye_center shape[42:48].mean(axis0).astype(int) # 计算双眼连线的角度 dY right_eye_center[1] - left_eye_center[1] dX right_eye_center[0] - left_eye_center[0] angle np.degrees(np.arctan2(dY, dX)) # 计算两眼之间的中点 eyes_center ((left_eye_center[0] right_eye_center[0]) // 2, (left_eye_center[1] right_eye_center[1]) // 2) # 计算缩放比例使变换后双眼间的距离等于模板中的距离 dist np.sqrt((dX ** 2) (dY ** 2)) desired_dist self.desired_right_eye_x - self.desired_left_eye_x scale desired_dist / dist # 构造旋转矩阵以两眼中心为旋转中心 M cv2.getRotationMatrix2D(eyes_center, angle, scale) # 调整平移量使旋转缩放后两眼中心对准模板位置 tX self.desired_face_width * 0.5 tY self.desired_face_height * self.desired_left_eye[1] # 使用左眼的y相对位置 M[0, 2] (tX - eyes_center[0]) M[1, 2] (tY - eyes_center[1]) # 执行仿射变换 (w, h) (self.desired_face_width, self.desired_face_height) output cv2.warpAffine(image, M, (w, h), flagscv2.INTER_CUBIC) return output def _shape_to_np(self, shape, dtypeint): 将dlib的shape对象转换为(x, y)坐标的numpy数组。 coords np.zeros((shape.num_parts, 2), dtypedtype) for i in range(0, shape.num_parts): coords[i] (shape.part(i).x, shape.part(i).y) return coords5.2 对齐过程中的质量把控与参数调优对齐步骤虽然自动化程度高但仍有几个需要仔细考量的地方模板点的选择上面的代码以双眼为基准进行对齐这是最常用的方法。模板中双眼的相对位置desired_left_eye和desired_right_eye决定了对齐后脸在图片中的位置和大小。(0.35, 0.35)和(0.65, 0.35)意味着双眼位于图片宽度35%和65%的位置高度在35%的位置这样通常能保证脸在图片中央偏上留出额头和下巴空间。你可以根据后续模型的需求调整这些值。仅处理单张人脸对齐逻辑默认只处理包含恰好一张人脸的图片。如果检测到多张脸或没有人脸则跳过。这是因为AffectNet数据集中绝大多数是单人脸且多张脸的情况下难以确定应对齐哪一张。对于检测失败或多人脸的图片处理方式应与裁剪模块保持一致记录并跳过。图像插值方法cv2.warpAffine中的flagscv2.INTER_CUBIC指定了插值方法用于计算变换后非整数坐标像素点的值。INTER_CUBIC双三次插值质量较好但速度稍慢INTER_LINEAR双线性插值是速度和质量的一个平衡点。对于表情识别任务INTER_CUBIC通常是更好的选择因为它能更好地保留面部细节。输出尺寸desired_face_width和desired_face_height决定了输出图像的大小。这个尺寸需要与后续模型输入的期望尺寸匹配。常见的尺寸有224x224适配许多CNN骨干网络、112x112或96x96用于更轻量的模型。统一尺寸也便于批量加载和GPU内存利用。对齐模块会读取裁剪后的人脸图片进行对齐操作并将结果保存到新的目录例如processed_data/aligned_train/或者直接覆盖原裁剪文件根据配置决定。至此我们得到了经过划分、裁剪、对齐的“干净”数据集。6. 实战整合配置化流程与批量处理脚本将三个核心模块串联起来并处理数十万张图片需要一个稳健的、可配置的批量处理流程。我采用配置文件YAML来管理所有参数并通过一个主脚本协调整个流程。6.1 配置文件设计configs/preprocessing_config.yaml文件包含了所有可调参数使得实验可复现也方便他人使用。# preprocessing_config.yaml paths: input_root: ./input_data # 原始AffectNet数据根目录 dlib_model: ./dlib_models/shape_predictor_68_face_landmarks.dat # dlib模型路径 output_root: ./processed_data # 处理结果输出根目录 haar_cascade: ./models/haarcascade_frontalface_default.xml # OpenCV模型路径 data_split: seed: 42 # 随机种子 train_ratio: 0.8 # 新训练集占原始训练集的比例 original_train_annotation: training/annotation.csv # 官方训练集标注文件相对路径 original_val_annotation: validation/annotation.csv # 官方验证集标注文件相对路径 face_crop: enable: true # 是否启用裁剪 margin_ratio: 0.2 # 边界框扩展比例 min_face_size: 50 # 最小人脸尺寸像素小于此值视为检测失败 use_cascade: true # 是否使用OpenCVdlib级联检测 face_align: enable: true # 是否启用对齐 desired_size: 224 # 对齐后图像尺寸正方形 desired_left_eye_pos: [0.35, 0.35] # 模板左眼相对位置 desired_right_eye_pos: [0.65, 0.35] # 模板右眼相对位置 interpolation: cubic # 插值方法可选 linear, cubic, area logging: level: INFO # 日志级别 file: ./logs/preprocess.log # 日志文件路径 processing: num_workers: 4 # 并行处理的进程数用于加速 batch_size: 32 # 批处理大小用于进度显示 skip_existing: true # 是否跳过已处理过的文件根据输出文件是否存在判断6.2 主流程脚本与并行加速主脚本scripts/run_preprocessing.py负责读取配置按顺序调用各个模块并加入日志记录和进度条。# 代码示例run_preprocessing.py 主流程骨架 import yaml import logging from pathlib import Path from tqdm import tqdm import multiprocessing as mp from src.data_splitter import create_custom_split from src.face_cropper import FaceCropper from src.face_aligner import FaceAligner def process_single_image(args): 包装函数用于多进程池中处理单张图片。 img_path, cropper, aligner, crop_enable, align_enable, output_dir args # 具体的裁剪、对齐、保存逻辑... # 返回处理结果成功/失败 pass def main(config): # 1. 设置日志 logging.basicConfig(...) # 2. 数据划分 logging.info(开始数据划分...) train_df, val_df create_custom_split(...) test_df load_official_validation(...) logging.info(f划分完成: 训练集 {len(train_df)} 张, 验证集 {len(val_df)} 张, 测试集 {len(test_df)} 张) # 3. 初始化处理工具 cropper FaceCropper(...) if config[face_crop][enable] else None aligner FaceAligner(...) if config[face_align][enable] else None # 4. 为每个数据集train/val/test创建任务列表 all_tasks [] for split_name, df in [(train, train_df), (val, val_df), (test, test_df)]: output_subdir Path(config[paths][output_root]) / split_name output_subdir.mkdir(parentsTrue, exist_okTrue) for img_rel_path in df[image_path]: img_full_path Path(config[paths][input_root]) / img_rel_path all_tasks.append((img_full_path, cropper, aligner, ..., output_subdir)) # 5. 使用多进程并行处理 logging.info(f开始处理 {len(all_tasks)} 张图片使用 {config[processing][num_workers]} 个进程...) with mp.Pool(processesconfig[processing][num_workers]) as pool: # 使用imap_unordered可以配合tqdm显示进度条 results list(tqdm(pool.imap_unordered(process_single_image, all_tasks), totallen(all_tasks), desc处理进度)) # 6. 统计结果 success_count sum(results) failure_count len(all_tasks) - success_count logging.info(f处理完成成功: {success_count}, 失败: {failure_count}, 失败率: {failure_count/len(all_tasks):.2%}) # 7. 根据处理结果更新数据列表移除失败样本并保存最终可用的划分文件 # ... (代码略) if __name__ __main__: with open(configs/preprocessing_config.yaml, r) as f: config yaml.safe_load(f) main(config)并行处理的关键处理几十万张图片是IO密集型和计算密集型混合的任务。使用Python的multiprocessing.Pool可以充分利用多核CPU显著加速。注意dlib和OpenCV的部分操作可能释放了GIL全局解释器锁在多进程中能获得较好的加速比。我将每张图片的处理包装成一个独立的函数process_single_image然后提交给进程池。tqdm库提供了美观的进度条让你能实时了解处理进度。6.3 处理后的数据整理与校验所有图片处理完毕后最后一步是整理。因为会有处理失败的图片被跳过所以最初的数据列表train_df等需要被更新移除那些没有对应输出图片的条目。生成最终的train_processed.csv、val_processed.csv、test_processed.csv其中包含成功处理图片的路径和对应的标签。这个文件才是你后续构建PyTorch的Dataset或TensorFlow的tf.datapipeline时真正需要读取的清单。此外强烈建议进行人工抽样检查。随机从每个集合中挑选几十张处理前后的图片进行对比观察裁剪是否准确、对齐是否端正、图像质量是否下降。这是保证预处理质量不可或缺的一环。本文还有配套的精品资源点击获取