Retinaface+CurricularFace实战教程:构建CLI工具支持命令行批量人脸比对
RetinafaceCurricularFace实战教程构建CLI工具支持命令行批量人脸比对你是不是遇到过这样的场景手头有一堆照片需要快速找出哪些是同一个人的或者需要批量验证身份证照片和现场照片是否匹配。一张张手动比对效率太低。用在线工具又担心隐私和数据安全。今天我们就来解决这个问题。我将带你一步步构建一个基于Retinaface和CurricularFace模型的命令行工具让你能在自己的电脑或服务器上通过一行命令就能完成批量人脸比对。这个工具特别适合需要处理大量人脸图片的开发者、运维人员或安全工程师。1. 为什么需要命令行批量比对工具在开始动手之前我们先聊聊为什么这个工具值得你花时间。想象一下你负责一个考勤系统每天有上千张打卡照片需要和员工档案照进行比对。或者你在做一个相册整理工具想把同一个人的照片自动归类到一起。手动操作显然不现实而图形界面工具在处理大批量文件时往往不够灵活和高效。命令行工具的优势就体现出来了自动化可以轻松集成到脚本、定时任务或自动化流程中。批量处理一次性处理成百上千张图片效率极高。资源可控在自己的环境中运行数据不出本地安全可控。灵活定制可以根据具体需求定制输出格式、日志和判定逻辑。我们使用的技术核心是RetinaFace和CurricularFace。简单来说RetinaFace 负责在图片中精准地“找到人脸”而 CurricularFace 则负责“认识这张脸”并提取出独一无二的特征。两者结合就是一套完整且强大的人脸识别流水线。接下来我们从环境准备开始一步步构建这个工具。2. 环境准备与快速上手2.1 启动与激活环境首先你需要一个已经部署好RetinafaceCurricularFace 镜像的环境。如果你在CSDN星图等平台使用该镜像启动后通常已经万事俱备。打开终端我们只需要做两件事进入工作目录所有代码和模型都已经预置好了。cd /root/Retinaface_CurricularFace激活Python环境镜像预置了包含所有依赖的 Conda 环境。conda activate torch25执行完这两条命令你的环境就准备好了。可以运行python --version和pip list | grep torch简单验证一下。2.2 测试单张图片比对在改造之前我们先验证一下基础功能是否正常。镜像里已经有一个测试脚本inference_face.py。运行以下命令使用内置的示例图片进行测试python inference_face.py你会看到终端输出相似度分数以及“同一人”或“不同人”的判定。同时脚本可能会显示检测到的人脸框让你直观地看到模型“找到”了哪里。你也可以用自己的图片测试python inference_face.py --input1 /你的路径/照片A.jpg --input2 /你的路径/照片B.jpg如果一切正常恭喜你核心的识别引擎已经就绪。接下来我们要把它从一个“单次对话”的脚本升级成一个能“处理整本通讯录”的批量工具。3. 设计批量比对CLI工具我们的目标是输入一个包含多张图片的文件夹工具能自动进行两两比对或者与指定的单张图片进行比对最后输出一份清晰的报告。3.1 工具功能规划这个批量工具应该具备以下核心功能模式一N:1 比对。将文件夹内所有图片与一张指定“目标脸”进行比对。适用于身份核验场景如所有打卡照 vs 员工档案照。模式二N:N 比对。对文件夹内所有图片进行两两组合比对找出所有可能的匹配对。适用于人脸聚类场景如整理相册找出所有同一个人的照片。灵活的参数允许设置判定阈值、指定输入输出路径、选择比对模式。清晰的输出生成一个结构化的结果文件如CSV或JSON包含文件名、相似度、判定结果等信息。3.2 创建批量处理脚本我们在工作目录下创建一个新脚本比如叫batch_face_match.py。#!/usr/bin/env python3 RetinafaceCurricularFace 批量人脸比对命令行工具 支持文件夹对单张图片、文件夹内图片两两比对两种模式。 import argparse import os import sys import glob import json import csv from pathlib import Path from itertools import combinations import cv2 import numpy as np # 导入镜像中预置的推理模块 sys.path.append(.) from inference_face import FaceRecognizer # 假设原脚本中的核心类叫这个可能需要根据实际代码调整 def parse_arguments(): 解析命令行参数 parser argparse.ArgumentParser(description批量人脸比对工具) parser.add_argument(-m, --mode, choices[one2many, many2many], requiredTrue, help比对模式: one2many (一张目标图 vs 一个文件夹) 或 many2many (文件夹内图片两两比对)) parser.add_argument(-t, --target, typestr, help目标图片路径 (在one2many模式下必须提供)) parser.add_argument(-f, --folder, typestr, requiredTrue, help待比对图片所在的文件夹路径) parser.add_argument(-th, --threshold, typefloat, default0.4, help判定为同一人的相似度阈值 (默认: 0.4)) parser.add_argument(-o, --output, typestr, default./match_results.csv, help结果输出文件路径 (支持.csv或.json格式默认: ./match_results.csv)) parser.add_argument(--extensions, typestr, defaultjpg,jpeg,png,bmp, help支持的图片文件扩展名逗号分隔 (默认: jpg,jpeg,png,bmp)) return parser.parse_args() def load_images_from_folder(folder_path, extensions): 从文件夹加载所有指定格式的图片路径 exts [ext.strip().lower() for ext in extensions.split(,)] image_paths [] for ext in exts: pattern os.path.join(folder_path, f*.{ext}) image_paths.extend(glob.glob(pattern, recursiveFalse)) # 添加对子目录的搜索可选根据需求开启 # pattern os.path.join(folder_path, **, f*.{ext}) # image_paths.extend(glob.glob(pattern, recursiveTrue)) image_paths sorted(list(set(image_paths))) # 去重并排序 print(f[INFO] 在文件夹 {folder_path} 中找到 {len(image_paths)} 张图片。) return image_paths def run_one2many_mode(recognizer, target_img_path, folder_img_paths, threshold, output_path): 模式一一张目标图 vs 文件夹内所有图 print(f[INFO] 开始 1:N 比对模式。目标图片: {target_img_path}) results [] # 提取目标图片的人脸特征只处理最大人脸 print(f[INFO] 正在提取目标图片特征...) target_feature recognizer.extract_feature(target_img_path) if target_feature is None: print(f[ERROR] 无法从目标图片中检测到人脸: {target_img_path}) return target_name os.path.basename(target_img_path) for img_path in folder_img_paths: img_name os.path.basename(img_path) print(f[PROCESSING] 正在比对: {img_name}, end\r) # 提取待比对图片特征 query_feature recognizer.extract_feature(img_path) if query_feature is None: similarity None is_match NO_FACE else: # 计算余弦相似度 similarity float(np.dot(target_feature, query_feature.T)) is_match YES if similarity threshold else NO result { target_image: target_name, query_image: img_name, similarity: similarity, is_match: is_match, threshold: threshold } results.append(result) print(f\n[INFO] 1:N 比对完成共处理 {len(results)} 张图片。) save_results(results, output_path) def run_many2many_mode(recognizer, folder_img_paths, threshold, output_path): 模式二文件夹内所有图片两两比对 print(f[INFO] 开始 N:N 两两比对模式。共有 {len(folder_img_paths)} 张图片将进行 {len(list(combinations(folder_img_paths, 2)))} 次比对。) # 第一步批量提取所有图片的特征 print(f[INFO] 正在批量提取所有人脸特征...) features_dict {} valid_images [] for img_path in folder_img_paths: img_name os.path.basename(img_path) feature recognizer.extract_feature(img_path) if feature is not None: features_dict[img_name] feature valid_images.append(img_name) else: print(f[WARNING] 跳过图片未检测到人脸: {img_name}) print(f[INFO] 成功提取 {len(features_dict)} 张图片的人脸特征。) # 第二步两两计算相似度 results [] image_list list(features_dict.keys()) for i, img_name_a in enumerate(image_list): feature_a features_dict[img_name_a] for img_name_b in image_list[i1:]: # 避免重复组合 (A,B) 和 (B,A) feature_b features_dict[img_name_b] similarity float(np.dot(feature_a, feature_b.T)) is_match YES if similarity threshold else NO result { image_a: img_name_a, image_b: img_name_b, similarity: similarity, is_match: is_match, threshold: threshold } results.append(result) print(f[INFO] N:N 比对完成共进行 {len(results)} 次比对。) save_results(results, output_path) def save_results(results, output_path): 将结果保存为CSV或JSON文件 if not results: print([WARNING] 没有结果可保存。) return output_path Path(output_path) output_path.parent.mkdir(parentsTrue, exist_okTrue) if output_path.suffix.lower() .json: with open(output_path, w, encodingutf-8) as f: json.dump(results, f, indent2, ensure_asciiFalse) print(f[SUCCESS] 结果已保存至 JSON 文件: {output_path}) else: # 默认保存为CSV if output_path.suffix.lower() ! .csv: output_path output_path.with_suffix(.csv) # 动态获取CSV表头 fieldnames results[0].keys() with open(output_path, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(results) print(f[SUCCESS] 结果已保存至 CSV 文件: {output_path}) # 在终端简单统计一下 match_count sum(1 for r in results if r.get(is_match) YES) print(f[STAT] 判定为‘同一人’的匹配对: {match_count} / {len(results)}) def main(): args parse_arguments() # 初始化人脸识别器使用镜像预置的模型 print([INFO] 正在初始化人脸识别模型...) recognizer FaceRecognizer() # 请根据实际 inference_face.py 中的类名调整 # 检查文件夹是否存在 if not os.path.isdir(args.folder): print(f[ERROR] 文件夹不存在: {args.folder}) sys.exit(1) # 加载文件夹内所有图片 image_paths load_images_from_folder(args.folder, args.extensions) if not image_paths: print(f[ERROR] 在文件夹中未找到任何图片: {args.folder}) sys.exit(1) # 根据模式运行 if args.mode one2many: if not args.target or not os.path.isfile(args.target): print(f[ERROR] 在 one2many 模式下必须提供有效的 --target 图片路径。) sys.exit(1) run_one2many_mode(recognizer, args.target, image_paths, args.threshold, args.output) elif args.mode many2many: run_many2many_mode(recognizer, image_paths, args.threshold, args.output) else: print(f[ERROR] 未知模式: {args.mode}) sys.exit(1) print([INFO] 批量人脸比对任务全部完成。) if __name__ __main__: main()代码要点说明参数解析使用argparse模块定义了丰富的命令行参数让工具非常灵活。两种模式核心逻辑封装在run_one2many_mode和run_many2many_mode两个函数中。特征提取复用我们假设原脚本inference_face.py中有一个FaceRecognizer类它有一个extract_feature方法能返回人脸特征向量。这是关键你需要根据镜像内的实际代码调整这个导入和调用方式。批量处理many2many模式先一次性提取所有特征再两两计算比一张张读图计算效率高很多。结果输出支持 CSV 和 JSON 格式便于后续用 Excel 打开或被其他程序读取。4. 实战使用批量工具解决实际问题脚本写好了我们来模拟几个真实的使用场景。4.1 场景一员工打卡照片核验假设你有一个员工“张三”的档案照zhangsan_id.jpg和一个文件夹checkin_photos/里面是本周所有的打卡照片。你想快速找出哪些是张三打的卡。操作步骤将档案照和打卡照片文件夹放到镜像环境能访问的路径比如/root/data/。运行以下命令cd /root/Retinaface_CurricularFace conda activate torch25 python batch_face_match.py \ --mode one2many \ --target /root/data/zhangsan_id.jpg \ --folder /root/data/checkin_photos/ \ --threshold 0.5 \ --output /root/data/zhangsan_checkin_results.csv命令执行后会生成一个zhangsan_checkin_results.csv文件。用文本编辑器或Excel打开你会看到类似下面的内容target_imagequery_imagesimilarityis_matchthresholdzhangsan_id.jpg20240527_080101.jpg0.782YES0.5zhangsan_id.jpg20240527_120202.jpg0.125NO0.5zhangsan_id.jpg20240528_075958.jpg0.801YES0.5一目了然is_match列为YES的就是系统判定为张三的打卡记录。4.2 场景二家庭相册人脸聚类你有一个文件夹family_photos/里面是多年的家庭合影和生活照你想把同一个人的照片自动分组。操作步骤运行以下命令进行两两比对python batch_face_match.py \ --mode many2many \ --folder /root/data/family_photos/ \ --threshold 0.45 \ --output /root/data/family_cluster_pairs.json这次我们输出 JSON 格式信息更结构化。结果文件会包含所有两两比对的结果。你可以写一个简单的后续处理脚本根据is_match: YES的关系将所有图片聚类成不同的人。例如如果 A-B 匹配B-C 匹配那么 A、B、C 很可能就是同一个人。4.3 进阶技巧与参数调优调整阈值--threshold参数是关键。默认 0.4 比较宽松。如果你想要更严格的判定减少误认可以提高到 0.5 或 0.6。如果你在处理质量较差的图片如监控截图可以适当降低到 0.3。建议先用小批量数据测试找到一个适合你场景的阈值。处理无脸图片脚本会自动跳过无法检测到人脸的图片并在结果中标记为NO_FACE。你可以根据这个信息清理你的输入数据。性能考虑many2many模式的计算量是图片数量的组合数。如果文件夹里有 1000 张图片两两比对需要近 50 万次计算这非常耗时。建议先对图片进行预处理比如先用简单的人脸检测器过滤掉明显没有人脸的图片或者先按时间、地点等元信息进行粗分组再在小组内进行精细比对。集成到工作流这个命令行工具可以很容易地被 Shell 脚本、Python 自动化脚本或定时任务如 crontab调用成为你数据处理流水线中的一个可靠环节。5. 总结通过本教程我们完成了一个从单点测试到批量生产的升级。你现在拥有一个功能强大的命令行工具能够快速验证使用预置脚本inference_face.py快速测试单对人脸。批量处理使用新编写的batch_face_match.py脚本高效处理整个文件夹的图片支持两种实用模式。灵活输出结果以结构化的 CSV 或 JSON 格式保存便于后续分析和集成。这个工具的核心价值在于将先进的 AI 模型RetinafaceCurricularFace封装成了简单、可编程的命令行接口让你能够专注于业务逻辑而无需深入模型细节。无论是用于安防、考勤、相册管理还是其他任何需要人脸比对的场景它都能成为一个得力的自动化助手。记住任何工具都需要在实际数据上进行测试和调优。建议你先用一个小规模的数据集跑通整个流程熟悉参数和输出然后再应用到大规模的生产数据中。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。