
简介这份资源面向具备一定Python基础的图像处理学习者与开发者聚焦证件照自动化处理这一高频实用场景整合OpenCV与face_recognition两套工具链解决人工扶正、裁剪与换底色耗时且标准不统一的问题。压缩包共4个文件以3个py脚本与1个md说明文档为主脚本分别承担人脸关键点定位、旋转角度计算与旋转裁剪等环节说明文档提供整体流程与使用指引包体仅约5KB轻量易读适合直接嵌入既有项目二次改造。目前已有3431人学习下载说明该方向具备较强的实用需求。读者可通过脚本理解基于关键点角度估计的扶正逻辑、按证件照规格的人脸区域裁剪方法以及阈值分割、颜色空间转换等背景替换思路并顺带获得光照不均、遮挡等异常情况的预处理与性能优化方向便于快速搭建可复用的证件照批处理流程。1. 歪头证件照的自动修复这套 Python 图片处理脚本到底能接住什么活某公司行政丢过来一个压缩包里面是三百多张员工证件照有人头歪了五度有人背景从蓝到灰都有尺寸从 600×800 到 1200×1600 不等。手工一张张扶正、裁切、换底两天都干不完。这套资源就是针对这种场景的 Python 图片处理脚本用 OpenCV 把证件照自动人脸扶正、裁剪尺寸、背景更换串成一条流水线。它适合需要批量处理证件照的开发者、行政或摄影工作室不适合遮挡严重、侧脸超过三十度、或者要求发丝级抠图的艺术照。流程顺序比算法选型更重要顺序错了后面参数怎么调都像玄学。2. 流水线拆解检测、对齐、裁剪、换底四步的先后顺序与参数边界证件照自动处理最容易被低估的是顺序。很多人拿到图先按固定比例裁一刀再送进检测器结果人脸只剩半张关键点自然拟合不出来。更稳的顺序是原图检测人脸框在原图上拟合关键点按关键点算倾斜角先做小角度仿射扶正再根据扶正后的人脸框扩展出裁剪框最后把裁剪结果缩放到目标像素尺寸换底放在裁剪之后做。这个顺序的好处是每一步的输入都保留完整人脸信息裁剪和换底不会反过来污染检测结果。2.1 人脸检测器的选型Haar、DNN 与关键点模型的边界OpenCV 生态里常见的人脸检测路线有三类Haar 级联、DNN 检测器、以及带关键点的人脸模型。Haar 级联速度快、模型小适合 CPU 批处理但对侧脸、低头、强逆光的召回率明显下降DNN 检测器在复杂光照下更稳但需要额外模型文件单张耗时更高关键点模型通常依赖一个初始人脸框不能独立完成检测。证件照场景大多是正脸或轻微侧脸背景相对干净Haar 级联配合合理的 minNeighbors 已经够用真正需要盯的是检测框是否把下巴和头顶完整包住。检测路线优点局限适用场景Haar 级联自带模型、CPU 快、依赖少侧脸和遮挡容易漏检正脸证件照批处理DNN 检测器光照鲁棒性更好模型文件额外准备、耗时高背景复杂、光照不均关键点模型可直接给五点或六十八点依赖检测框不能单独检测扶正角度计算我一般会先跑 Haar把scaleFactor设在 1.1 到 1.2 之间minNeighbors设 5 到 6minSize不要小于 60×60。scaleFactor越小检测越细但越慢minNeighbors越高误检越少但漏检越多。下面这段是检测阶段的骨架先灰度化再做直方图均衡是为了让逆光或偏暗的证件照更稳定地进入检测器。import cv2 import numpy as np def detect_largest_face(gray, cascade): # 直方图均衡化缓解逆光和偏暗对检测的影响 gray_eq cv2.equalizeHist(gray) faces cascade.detectMultiScale( gray_eq, scaleFactor1.1, # 每次图像尺寸缩小比例越小越慢越细 minNeighbors5, # 邻域阈值越高误检越少、漏检越多 minSize(60, 60), # 小于该尺寸的人脸不参与避免背景误检 flagscv2.CASCADE_SCALE_IMAGE ) if len(faces) 0: return None # 证件照通常只有一个人取面积最大的框 return max(faces, keylambda r: r[2] * r[3])这段代码只做检测不直接裁剪。equalizeHist对灰度图做全局均衡适合曝光不足的证件照但如果原图已经过曝均衡化会把高光推得更白这时可以跳过均衡化改用cv2.createCLAHE做局部对比度增强。minSize不是越小越好设得太小会把背景纹理当成人脸尤其是蓝色背景布上的褶皱。检测结果按面积取最大是因为证件照场景一般只有主体人物但如果原图里有两个人需要先做人数判断不能盲目取最大。2.2 扶正角度怎么算双眼连线与五点关键点的取舍扶正角度的本质是估计人脸在图像平面内的旋转角。最稳的做法是用左右眼关键点连线计算atan2(dy, dx)因为双眼相对刚性鼻尖和嘴角在表情变化时位移更明显。五点模型通常给出左右眼、鼻尖、左右嘴角取前两个点就够了六十八点模型可以取眼中心但计算量更大。角度算出来之后不要直接全量旋转先做阈值保护绝对值超过 30 度时大概率是检测框偏了或关键点拟合失败这时宁可回退到不旋转也不要把图转飞。def compute_roll_angle(landmarks, max_angle30.0): # landmarks 约定第 0 个是左眼第 1 个是右眼 left_eye np.array(landmarks[0], dtypenp.float32) right_eye np.array(landmarks[1], dtypenp.float32) dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(dy, dx)) # 角度保护超过阈值视为关键点异常回退到 0 度 if abs(angle) max_angle: return 0.0 return anglemax_angle是经验参数正脸证件照一般落在 ±10 度以内超过 30 度要么是侧脸要么是检测框把肩膀或头发误当成了人脸。atan2返回的是弧度np.degrees转成角度后正值表示人脸向右倾斜负值表示向左倾斜。旋转时用图像中心作为旋转中心而不是人脸框中心这样旋转后的画布扩展更均匀后续裁剪不容易在边角出现黑边。2.3 证件照尺寸映射一寸、二寸与像素密度的换算证件照的“尺寸”有两层含义物理毫米尺寸和像素尺寸。物理尺寸决定打印比例像素尺寸决定文件能不能过线上系统。常见的一寸照是 25×35 毫米二寸照是 35×49 毫米按 300 dpi 换算一寸约 295×413 像素二寸约 413×579 像素。不同系统还会要求 480×640、600×800 等像素规格所以脚本里不要把像素写死而是把目标宽高作为参数传进去。规格物理尺寸300 dpi 像素头顶留白比例面部宽度占比一寸25×35 mm295×4130.100.150.550.65二寸35×49 mm413×5790.100.150.500.60小二寸33×48 mm390×5670.100.150.520.62大寸50×70 mm591×8270.080.120.480.58裁剪框不是直接等于人脸框而是以人脸框中心为基准向外扩展。face_scale控制裁剪框宽度是人脸框宽度的多少倍常见取值范围 2.0 到 2.6top_margin控制人脸框中心到裁剪框顶部的距离占裁剪框高度的比例常见 0.30 到 0.40。这两个参数直接决定“头大不大、头顶留白够不够”比换底算法更影响观感。def calc_crop_box(face_box, target_size, face_scale2.2, top_margin0.35): x, y, w, h face_box cx x w / 2.0 cy y h / 2.0 crop_w w * face_scale # 按目标宽高比反推裁剪高度保证缩放后不变形 crop_h crop_w * target_size[1] / target_size[0] left int(round(cx - crop_w / 2.0)) top int(round(cy - crop_h * top_margin)) right int(round(left crop_w)) bottom int(round(top crop_h)) return left, top, right, bottomface_scale越大裁剪框越宽人脸在成片里越小top_margin越大头顶留白越多但下巴可能被裁掉。实际调参时先固定target_size拿 10 张不同头型的样本跑一遍看头顶留白和下巴到边框的距离再把face_scale微调 0.1 到 0.2。不要用一张图调到底证件照的头型差异比想象中大圆脸和长脸的合适比例不一样。3. 代码骨架把检测、扶正、裁剪串成一条可复现的 Python 链路这一章把上一章的参数落成可运行的脚本。整条链路分成五个函数加载模型、检测并拟合关键点、仿射扶正、按规格裁剪、批量入口。每个函数只做一件事失败时返回None或抛出明确异常批处理层统一记录失败文件名。这样出问题时能快速定位是检测失败、关键点失败还是裁剪越界而不是在一大坨代码里猜。3.1 环境准备与目录约定依赖尽量少。OpenCV 的 contrib 包包含 face 模块关键点模型需要单独准备如果只用 Haar 检测加双眼关键点也可以先用检测框内部的眼睛检测做简化版。模型文件不要散落在脚本目录统一放到models/下输入输出分开批处理才不会把原图覆盖。# 建议使用虚拟环境避免和系统里的 OpenCV 冲突 python -m venv venv source venv/bin/activate # opencv-contrib-python 包含 face 模块numpy 用于矩阵计算 pip install opencv-contrib-python numpy # 目录约定 mkdir -p models input output failedopencv-contrib-python和opencv-python不要同时安装否则 face 模块可能被覆盖。模型文件常见做法是从 OpenCV 的 contrib 模型目录获取放到models/下代码里用相对路径加载。input/放原图output/放成片failed/放检测或裁剪失败的原图方便人工回看。文件名统一用英文和数字避免中文路径在某些 OpenCV 版本下读写异常。3.2 人脸检测与关键点提取检测和关键点拟合放在一个函数里先检测最大人脸再把检测框送进关键点模型。关键点模型返回的格式在不同版本里略有差异代码里只取前两个点作为眼睛后面的点不用减少格式依赖。如果关键点拟合失败直接返回None让上层决定是跳过还是回退到不旋转。import cv2 import numpy as np def detect_and_landmark(gray, face_cascade, facemark): faces face_cascade.detectMultiScale( cv2.equalizeHist(gray), scaleFactor1.1, minNeighbors5, minSize(60, 60) ) if len(faces) 0: return None, None face_box max(faces, keylambda r: r[2] * r[3]) x, y, w, h face_box rects np.array([[x, y, w, h]], dtypenp.int32) ok, landmarks facemark.fit(gray, rects) if not ok or len(landmarks) 0: return face_box, None # 只保留第一张人脸的五个关键点 return face_box, landmarks[0].astype(np.float32)facemark.fit的第二个参数必须是int32的人脸框数组传浮点会直接报类型错误。ok为False时不要强行取landmarks[0]不同版本可能返回空列表。如果原图是彩色先转灰度再检测关键点拟合也用灰度图颜色信息对关键点没有帮助。检测框face_box要保留后面裁剪还要用关键点只用来算角度。3.3 仿射变换扶正旋转中心、缩放与边界填充扶正用cv2.getRotationMatrix2D生成旋转矩阵再用warpAffine做变换。旋转中心用原图中心旋转后画布要扩展否则边角会被裁掉。扩展尺寸按旋转矩阵的余弦和正弦绝对值计算再把平移量补到矩阵里。边界填充用白色是因为证件照背景通常是白、蓝、红白色填充在后续换底时最容易被阈值识别为背景。def rotate_face(img, landmarks, max_angle30.0): left_eye, right_eye landmarks[0], landmarks[1] dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(dy, dx)) if abs(angle) max_angle: angle 0.0 h, w img.shape[:2] center (w / 2.0, h / 2.0) M cv2.getRotationMatrix2D(center, angle, 1.0) cos abs(M[0, 0]) sin abs(M[0, 1]) new_w int(h * sin w * cos) new_h int(h * cos w * sin) # 把旋转后的图像平移到新画布中心 M[0, 2] new_w / 2.0 - center[0] M[1, 2] new_h / 2.0 - center[1] rotated cv2.warpAffine( img, M, (new_w, new_h), flagscv2.INTER_LINEAR, borderModecv2.BORDER_CONSTANT, borderValue(255, 255, 255) ) return rotated, anglemax_angle在这里再做一次保护防止关键点异常导致大角度旋转。INTER_LINEAR比INTER_NEAREST平滑证件照放大后不会出现明显锯齿如果原图分辨率很低可以用INTER_CUBIC但耗时更高。borderValue用白色只是临时填充后续换底会重新生成背景所以不用纠结填充色和最终背景色不一致。3.4 按规格裁剪与安全边距裁剪函数接收扶正后的图像和人脸框但要注意扶正后的人脸框坐标已经变了。稳妥做法是在扶正后的图像上重新检测一次人脸或者把原人脸框按同一个旋转矩阵变换。重新检测会增加耗时但实现简单、容错高变换坐标更快但容易在边界处算错。批量处理里我一般选重新检测因为证件照单张耗时本来就不高。def crop_and_resize(img, face_box, target_size(295, 413), face_scale2.2, top_margin0.35): x, y, w, h face_box cx, cy x w / 2.0, y h / 2.0 crop_w int(round(w * face_scale)) crop_h int(round(crop_w * target_size[1] / target_size[0])) left int(round(cx - crop_w / 2.0)) top int(round(cy - crop_h * top_margin)) # 先裁再补边避免越界直接报错 src_h, src_w img.shape[:2] x1, y1 max(0, left), max(0, top) x2, y2 min(src_w, left crop_w), min(src_h, top crop_h) crop img[y1:y2, x1:x2] # 如果裁剪框超出原图用白色补齐到目标比例 pad_left max(0, -left) pad_top max(0, -top) pad_right max(0, left crop_w - src_w) pad_bottom max(0, top crop_h - src_h) if any([pad_left, pad_top, pad_right, pad_bottom]): crop cv2.copyMakeBorder( crop, pad_top, pad_bottom, pad_left, pad_right, cv2.BORDER_CONSTANT, value(255, 255, 255) ) return cv2.resize(crop, target_size, interpolationcv2.INTER_AREA)target_size是(宽, 高)不要传反。INTER_AREA适合缩小能减少摩尔纹如果目标尺寸比裁剪结果大改用INTER_CUBIC。补边逻辑是为了处理头顶留白过大导致裁剪框超出原图的情况补白色比直接裁掉更安全后续换底会把白色背景替换掉。裁剪框越界时不要静默截断至少在日志里记一笔方便发现top_margin设得过大。3.5 输出与批量入口批处理层负责遍历、异常捕获、命名和失败归档。每张图独立try/except一张失败不影响整批。输出文件名保留原文件名加后缀不要用自增序号否则和原始名单对不上。失败文件复制到failed/同时写一行日志记录失败阶段和异常信息。import os import shutil def batch_process(input_dir, output_dir, failed_dir, face_cascade, facemark, target_size(295, 413)): os.makedirs(output_dir, exist_okTrue) os.makedirs(failed_dir, exist_okTrue) for name in sorted(os.listdir(input_dir)): if not name.lower().endswith((.jpg, .jpeg, .png)): continue src_path os.path.join(input_dir, name) img cv2.imread(src_path) if img is None: shutil.copy(src_path, os.path.join(failed_dir, name)) continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face_box, landmarks detect_and_landmark(gray, face_cascade, facemark) if face_box is None or landmarks is None: shutil.copy(src_path, os.path.join(failed_dir, name)) continue rotated, _ rotate_face(img, landmarks) gray_rot cv2.cvtColor(rotated, cv2.COLOR_BGR2GRAY) new_box, _ detect_and_landmark(gray_rot, face_cascade, facemark) if new_box is None: shutil.copy(src_path, os.path.join(failed_dir, name)) continue out crop_and_resize(rotated, new_box, target_size) out_path os.path.join(output_dir, os.path.splitext(name)[0] _id.jpg) cv2.imwrite(out_path, out, [int(cv2.IMWRITE_JPEG_QUALITY), 95])IMWRITE_JPEG_QUALITY设 95 是证件照的常用质量再高文件变大但肉眼差异不明显。失败文件用shutil.copy保留原图方便人工重跑。批量入口里没有做多线程因为 OpenCV 的检测器本身不是线程安全的多进程要每个进程独立加载模型复杂度高先用单进程跑通再考虑按目录分片。4. 背景更换从阈值分割到边缘羽化的工程做法换底是证件照处理里最容易“看起来能用、放大全是毛边”的一步。证件照背景通常是纯蓝、纯白或纯红前景是头发、皮肤和衣服。直接上 GrabCut 的问题不是效果差而是慢且不稳定初始框稍微偏一点头发边缘就会被判成背景衣服纹理也会被切碎。更务实的做法是先用人脸框和肤色先验圈定前景区域再用 HSV 阈值处理背景最后做边缘收缩和羽化。4.1 为什么证件照换底不建议直接上 GrabCutGrabCut 需要用户画框或给初始掩膜迭代次数多单张 600×800 的图在 CPU 上可能要几百毫秒到一秒批量三百张就是几分钟到十几分钟。更关键的是证件照的头发边缘细碎GrabCut 的能量函数对细发丝不友好容易出现“头发被啃掉一块”或“背景残留一圈”。纯色背景的证件照HSV 阈值加形态学处理已经能覆盖大多数情况速度还快一个数量级。方案速度头发边缘参数敏感度适用场景GrabCut慢一般高背景复杂、主体清晰HSV 阈值快依赖阈值中纯色背景证件照肤色先验 阈值较快较好中蓝底、白底、红底深度学习抠图慢好低发丝级要求不建议一上来就上深度学习抠图模型体积和依赖会拖慢批处理部署。证件照换底的核心不是“抠得多细”而是“边缘不脏、颜色不溢、尺寸不变”。先把阈值法调到 90 分再考虑要不要换更重的模型。4.2 基于 HSV 与肤色先验的前景掩膜HSV 阈值的关键是取背景色的色相范围。蓝底证件照的色相通常落在 100 到 130 之间白底靠高亮度和低饱和度区分红底色相在 0 到 10 和 170 到 180 两个区间。先对原图做 HSV 转换生成背景掩膜再取反得到前景。为了防止人脸区域被误判成背景用检测框中心区域做一个保护掩膜把脸部强制保留。def build_foreground_mask(img, face_box, bg_colorblue): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) if bg_color blue: lower np.array([100, 60, 40]) upper np.array([130, 255, 255]) elif bg_color red: lower1 np.array([0, 70, 50]); upper1 np.array([10, 255, 255]) lower2 np.array([170, 70, 50]); upper2 np.array([180, 255, 255]) bg_mask cv2.inRange(hsv, lower1, upper1) | cv2.inRange(hsv, lower2, upper2) return cv2.bitwise_not(bg_mask) else: # 白底高亮度、低饱和度 lower np.array([0, 0, 200]) upper np.array([180, 30, 255]) bg_mask cv2.inRange(hsv, lower, upper) fg_mask cv2.bitwise_not(bg_mask) # 人脸区域保护检测框内缩后强制设为前景 x, y, w, h face_box protect np.zeros(fg_mask.shape, dtypenp.uint8) cv2.rectangle(protect, (x, y), (x w, y h), 255, -1) fg_mask cv2.bitwise_or(fg_mask, protect) # 形态学闭运算填补头发内部的小孔 kernel np.ones((3, 3), np.uint8) fg_mask cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel, iterations2) return fg_masklower和upper的饱和度、明度阈值要按实际背景调不能一套参数打天下。蓝底如果偏紫色相上限要往下收白底如果背景发灰明度下限要降低否则背景会被当成前景。人脸保护框直接用人脸框会带上脖子实际可以按 0.9 倍内缩避免把背景边缘也保护进去。闭运算的核不要太大3×3 跑两次比 5×5 跑一次更不容易把手指缝糊掉。4.3 边缘收缩、羽化与颜色溢出处理阈值掩膜直接合成会在边缘留下 1 到 2 像素的背景色尤其是蓝底换白底时头发边缘会有一圈蓝边。做法是先对前景掩膜做轻微腐蚀把边缘往里收 1 像素再高斯模糊生成 alpha 通道最后用 alpha 混合。收缩太多会啃掉头发收缩太少会留色边1 到 2 像素是证件照的常用范围。def replace_background(img, fg_mask, bg_color(255, 255, 255), feather3): # 收缩 1 像素减少背景色残留 kernel np.ones((3, 3), np.uint8) fg_mask cv2.erode(fg_mask, kernel, iterations1) # 羽化 alphafeather 必须为奇数 if feather % 2 0: feather 1 alpha cv2.GaussianBlur(fg_mask, (feather, feather), 0).astype(np.float32) / 255.0 alpha alpha[..., None] bg np.full_like(img, bg_color, dtypenp.uint8) out img.astype(np.float32) * alpha bg.astype(np.float32) * (1 - alpha) return np.clip(out, 0, 255).astype(np.uint8)feather设 3 到 5 比较自然太大边缘会发虚证件照看起来像抠图没扣干净。bg_color用 BGR 顺序白色是(255, 255, 255)蓝色常见(255, 0, 0)附近红色(0, 0, 255)附近。腐蚀操作会让前景整体小一圈如果人脸保护框做得太紧下巴边缘可能被吃掉所以腐蚀前要确认保护框覆盖了完整脸部。5. 避坑与排查五个翻车现场和对应的回退方案证件照脚本在 demo 阶段看起来都很顺一上批量就开始翻车。下面五条是血泪经验里出现频率最高的每条按现象、原因、解决写。排查顺序建议从检测阶段往换底阶段走因为检测错了后面的角度、裁剪、换底全是连锁反应。5.1 检测阶段漏检、多检与侧脸误判现象一整批图里有几张直接进failed/打开看人脸明明很清楚。原因通常是原图曝光不足或背景和肤色太接近Haar 在灰度图上找不到足够强的边缘。解决是先看失败图的直方图如果集中在低亮度区加cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8))做局部增强再送检测如果背景是浅灰、人脸也偏灰minNeighbors从 5 降到 4同时把minSize调到 80×80减少背景误检。不要一上来改scaleFactor那个参数对漏检的改善不如预处理明显。现象二一张图检测出三四个框取最大框之后裁到了肩膀。原因多半是背景纹理或衣服图案被当成人脸minNeighbors太低。解决是把minNeighbors提到 6 到 8并加一条面积过滤人脸框面积不能超过整图面积的 60%宽度不能超过图像宽度的 70%。如果仍然多检按框的长宽比过滤正脸框的宽高比通常在 0.75 到 1.3 之间太扁或太长的框直接丢掉。现象三侧脸照片被检测到了但关键点拟合明显偏扶正后脸是歪的。原因是 Haar 对侧脸的框本身就偏关键点模型在偏框上拟合更偏。解决是加角度保护max_angle设 20 到 25 度超过就不旋转同时用双眼距离和人脸框宽度的比值做 sanity check正常正脸双眼距离约占脸宽的 0.35 到 0.55偏差太大就回退到不旋转、只裁剪。证件照场景里宁可输出一张轻微倾斜但五官完整的图也不要输出一张转飞了的图。5.2 裁剪与换底阶段头顶切掉、绿边、尺寸错现象四成片头顶被切掉一块或者下巴贴边。原因不是裁剪函数写错而是top_margin和face_scale的组合不适合当前头型。top_margin太大裁剪框整体上移头顶留白多但下巴被切face_scale太小裁剪框太窄脸两侧被切。解决是先用 10 张样本跑网格face_scale取 2.0、2.2、2.4top_margin取 0.30、0.35、0.40输出九宫格对照图人工选一组最稳的。不要用一张图定参数圆脸和长脸的最优值能差 0.2。现象五蓝底换白底后头发边缘有一圈蓝边放大看像描边。原因是前景掩膜边缘包含了 1 到 2 像素的背景色alpha 混合时把蓝色带进了白底。解决是腐蚀 1 像素再羽化feather设 3如果蓝边仍然明显在合成前对前景边缘做一次颜色去溢把 alpha 在 0.2 到 0.8 之间的像素按背景色的补色做轻微去饱和。更简单的做法是换底前先把原图背景区域涂成目标色再做一次掩膜合成边缘会干净很多。现象六输出尺寸明明是 295×413上传系统却提示比例不对。原因是cv2.resize的dsize参数是(宽, 高)而很多系统文档写的是“高×宽”。代码里如果按(height, width)传输出就会变成 413×295。解决是把target_size统一约定为(width, height)函数入口加一句断言assert target_size[0] target_size[1]竖版证件照宽一定小于高传反了直接报错比事后人工发现强。6. 批量落地两个让脚本从能跑到可交付的技巧批量跑通之后真正耗时间的是“哪张需要人工回看”。我的习惯是每批先跑 20 张输出一张 CSV 对照表字段包括文件名、检测框坐标、倾斜角、裁剪框是否越界、输出尺寸、是否进失败目录。倾斜角绝对值大于 15 度的标黄裁剪框越界的标红失败目录里的单独列一张表。人工只看标色行剩下的直接过。import csv def write_report(rows, pathreport.csv): fields [file, angle, crop_oob, out_size, status] with open(path, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesfields) writer.writeheader() writer.writerows(rows)crop_oob记录裁剪框是否超出原图边界out_size记录实际输出宽高status写ok或失败阶段。批处理队列按目录分片每片 50 张跑完一片检查一次失败率失败率超过 10% 就停下来看样本不要硬跑完整批。从那以后我每次接证件照批处理都会先抽 10 张跑一遍角度分布和背景色直方图再决定阈值和face_scale不再凭感觉调参。希望帮到你。本文还有配套的精品资源点击获取