Retinaface+CurricularFace实战:基于爬虫的人脸数据自动采集系统
每次想搞点人脸识别的小项目,最头疼的就是数据从哪来。网上公开的数据集要么太旧,要么场景单一,自己拍吧,费时费力还不一定能覆盖所有情况。最近在做一个社区门禁识别的原型,就卡在了数据收集这一步。
后来琢磨了一下,能不能用爬虫技术,从一些公开的图片网站上,自动抓取带有人脸的图片,然后用Retinaface检测出来,再用CurricularFace提取特征,最后整理成一个干净、标注好的人脸数据集呢?说干就干,折腾了几天,还真跑通了一套自动化流程。今天就把这个“懒人”数据采集方案分享出来,如果你也在为人脸数据发愁,这套系统或许能帮你省下不少功夫。
1. 系统整体思路:从网页到特征向量
整个系统的目标很明确:自动化、批量化地构建人脸图像数据集。它的工作流程可以概括为四个核心步骤,就像一条流水线。
第一步,定向抓取。我们不是漫无目的地爬取整个互联网,而是针对特定的、图片质量较高的公开网站(比如一些摄影社区、人物图库),编写爬虫脚本,定向下载包含人物的原始图片。
第二步,智能筛选。下载下来的图片五花八门,可能包含风景、物品或者多人场景。这时就需要Retinaface出场了。它是一个非常强大的人脸检测器,能快速、准确地在图片中定位出人脸区域。我们用它来过滤掉没有人脸的图片,并初步框出人脸位置。
第三步,特征提取。对于检测到的人脸,我们将其裁剪并对齐(通常基于眼睛位置),得到标准尺寸(如112x112)的人脸区域图。然后,使用CurricularFace模型为每一张对齐后的人脸图片计算一个512维的特征向量。这个向量就像是这张人脸的“数字身份证”,浓缩了其核心特征。
第四步,结构化存储。最后,我们把原始图片路径、人脸框坐标、对齐后的人脸小图以及对应的特征向量,以一种结构化的方式(比如存到数据库里,或者按文件夹分类保存)组织起来。这样一个带有丰富信息的人脸数据集就初步建成了。
整个过程完全自动化,你只需要设定好目标网站和爬取规则,泡杯茶的功夫,数据就源源不断地来了。
2. 核心工具选型:为什么是它们?
工欲善其事,必先利其器。这套系统主要依赖三个核心组件:爬虫框架、人脸检测模型和人脸识别模型。
爬虫框架:Scrapy vs. Requests+BeautifulSoup对于大规模、结构复杂的网站,Scrapy是工业级的选择,它自带异步处理、中间件、管道等强大功能,适合构建健壮的爬虫项目。但对于我们这种相对简单、目标明确的图片抓取任务,Requests库配合BeautifulSoup或lxml进行HTML解析,更加轻量、灵活,上手也快。本文的示例将采用后者,因为它更贴近“快速实现”的思路。
人脸检测:为什么选择Retinaface?在人脸检测领域,Retinaface是绕不开的一个名字。它属于“单阶段”检测器,速度很快,同时精度又非常高,尤其是在处理小脸、侧脸和遮挡脸时表现优异。它不仅能给出人脸的边界框,还能预测5个关键点(双眼、鼻尖、嘴角),这对后续的人脸对齐至关重要。相比一些更早的模型,Retinaface在复杂场景下的鲁棒性要好得多,能确保我们采集到的数据质量更高。
人脸识别:CurricularFace的过人之处提取人脸特征,我们选择了CurricularFace。它是ArcFace系列的一个改进版本。简单来说,CurricularFace在模型训练时引入了一个“课程学习”的机制。就像学生先学加减法再学乘除法一样,这个机制让模型先学习区分容易的样本(差异大的人脸),再逐步攻克困难的样本(长得像的人脸)。这样训练出来的模型,提取的特征判别力更强,更能抓住人脸的细微差异。用它的特征来构建数据集,未来在做识别、聚类任务时,效果会更好。
3. 动手搭建:分步实现自动化流水线
理论说再多,不如代码跑一遍。我们一步步来把这条流水线搭起来。
3.1 环境准备与依赖安装
首先,确保你的Python环境在3.7以上。然后,我们通过pip安装必要的库。建议创建一个虚拟环境来管理依赖。
# 基础HTTP请求和解析库 pip install requests beautifulsoup4 lxml # 图像处理核心库 pip install opencv-python pillow # 深度学习框架及模型工具 # PyTorch安装请根据你的CUDA版本前往官网获取对应命令,例如: # pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install torch torchvision # 安装InsightFace库,它封装了Retinaface和CurricularFace等模型 pip install insightfaceinsightface这个库非常方便,它把模型加载、推理这些繁琐的步骤都封装好了,我们直接调用就行。
3.2 第一步:编写图片爬虫脚本
假设我们的目标是一个虚构的图片分享网站example-photos.com,它的列表页图片链接藏在img标签的data-src属性里。下面是一个简单的爬虫示例。
import os import requests from bs4 import BeautifulSoup import time import random def download_images_from_url(base_url, page_num, save_dir='downloaded_images'): """ 从目标网站下载图片 :param base_url: 网站基础URL,例如 'https://example-photos.com/page/' :param page_num: 要爬取的页数 :param save_dir: 图片保存目录 """ if not os.path.exists(save_dir): os.makedirs(save_dir) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } for page in range(1, page_num + 1): print(f'正在爬取第 {page} 页...') try: # 构造每一页的URL url = f'{base_url}{page}' response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 检查请求是否成功 soup = BeautifulSoup(response.content, 'lxml') # 假设图片链接在img标签的data-src属性中 img_tags = soup.find_all('img', {'data-src': True}) for idx, img_tag in enumerate(img_tags): img_url = img_tag['data-src'] # 确保是完整的URL if not img_url.startswith('http'): img_url = 'https:' + img_url # 下载图片 try: img_data = requests.get(img_url, headers=headers, timeout=15).content # 生成文件名 file_name = f"page{page}_{idx}_{int(time.time())}.jpg" file_path = os.path.join(save_dir, file_name) with open(file_path, 'wb') as f: f.write(img_data) print(f' 已下载: {file_name}') # 礼貌性延迟,避免请求过快 time.sleep(random.uniform(0.5, 1.5)) except Exception as e: print(f' 下载失败 {img_url}: {e}') continue except Exception as e: print(f'爬取第 {page} 页失败: {e}') # 每爬完一页稍作休息 time.sleep(random.uniform(2, 4)) print('图片爬取任务完成!') # 使用示例:爬取前3页图片 if __name__ == '__main__': download_images_from_url('https://example-photos.com/page/', 3)重要提醒:在实际操作前,务必检查目标网站的robots.txt文件,尊重网站的爬虫协议,并控制请求频率,避免对对方服务器造成压力。
3.3 第二步:用Retinaface进行人脸检测与裁剪
图片下载好了,接下来就是“淘金”环节——用Retinaface把里面的人脸找出来并抠出来。
import cv2 import insightface from insightface.app import FaceAnalysis import os import glob def detect_and_crop_faces(input_dir='downloaded_images', output_dir='cropped_faces'): """ 检测图片中的人脸,并裁剪对齐保存 :param input_dir: 原始图片目录 :param output_dir: 裁剪后的人脸保存目录 """ # 初始化InsightFace人脸分析应用,指定使用retinaface_mobile(轻量版)进行检测 app = FaceAnalysis(providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) # 优先用GPU app.prepare(ctx_id=0, det_size=(640, 640)) if not os.path.exists(output_dir): os.makedirs(output_dir) image_paths = glob.glob(os.path.join(input_dir, '*.jpg')) total_faces = 0 for img_path in image_paths: print(f'处理: {os.path.basename(img_path)}') img = cv2.imread(img_path) if img is None: continue # 人脸检测 faces = app.get(img) if len(faces) == 0: print(' 未检测到人脸') continue print(f' 检测到 {len(faces)} 张人脸') for i, face in enumerate(faces): # face.bbox 是边界框 [x1, y1, x2, y2] x1, y1, x2, y2 = face.bbox.astype(int) # 稍微扩大一点区域,确保人脸完整 margin = 20 h, w = img.shape[:2] x1 = max(0, x1 - margin) y1 = max(0, y1 - margin) x2 = min(w, x2 + margin) y2 = min(h, y2 + margin) # 裁剪人脸 cropped_face = img[y1:y2, x1:x2] # 保存裁剪后的人脸图片 base_name = os.path.splitext(os.path.basename(img_path))[0] save_name = f"{base_name}_face{i}.jpg" save_path = os.path.join(output_dir, save_name) cv2.imwrite(save_path, cropped_face) total_faces += 1 print(f'人脸裁剪完成!共保存 {total_faces} 张人脸图片至 {output_dir}') if __name__ == '__main__': detect_and_crop_faces()这段代码会读取下载的所有图片,用Retinaface找出每张脸的位置,然后根据边界框把脸的部分单独裁剪出来,保存到新的文件夹。你会发现,文件夹里可能一下子多了很多“大头照”。
3.4 第三步:使用CurricularFace提取人脸特征
现在,我们有了干净的人脸图片,是时候给它们制作“数字身份证”了。
import numpy as np import pickle from insightface.model_zoo import get_model def extract_face_features(cropped_faces_dir='cropped_faces', feature_save_path='face_features.pkl'): """ 为裁剪后的人脸图片提取特征向量 :param cropped_faces_dir: 裁剪后的人脸图片目录 :param feature_save_path: 特征保存路径(pickle格式) """ # 加载CurricularFace模型(InsightFace中通常表示为‘arcface’或具体名称,需根据版本确认) # 这里使用一个通用的识别模型,InsightFace会自动加载性能较好的预训练模型。 model = get_model('buffalo_l') # 'buffalo_l' 是InsightFace提供的一个包含CurricularFace的模型包 model.prepare(ctx_id=0) face_files = glob.glob(os.path.join(cropped_faces_dir, '*.jpg')) feature_dict = {} # 用字典保存,键为文件名,值为特征向量 for face_file in face_files: img = cv2.imread(face_file) if img is None: continue # 将图片调整为模型输入所需尺寸(通常是112x112) # 注意:get_model返回的模型通常已经封装了检测和识别,这里我们直接输入对齐后的人脸。 # 我们需要先进行人脸检测和对齐(虽然图片已经是人脸,但为了绝对精确,可再做一次) # 简化流程:假设输入已经是112x112对齐人脸,这里我们直接调整尺寸。 img_aligned = cv2.resize(img, (112, 112)) # 提取特征向量 # 注意:模型通常需要BGR格式和标准化。InsightFace模型内部一般会处理。 face_embedding = model.get_embedding(img_aligned) if face_embedding is not None and len(face_embedding) > 0: feature_dict[os.path.basename(face_file)] = face_embedding[0] # 取第一个也是唯一一个特征 print(f'已提取特征: {os.path.basename(face_file)}') else: print(f'特征提取失败: {os.path.basename(face_file)}') # 将特征字典保存到文件 with open(feature_save_path, 'wb') as f: pickle.dump(feature_dict, f) print(f'特征提取完成!共提取 {len(feature_dict)} 个特征,已保存至 {feature_save_path}') if __name__ == '__main__': extract_face_features()运行后,你会得到一个.pkl文件,里面存储了每张人脸图片文件名与其对应的512维特征向量的映射关系。这个文件很小,但信息量极大,是你数据集的精华所在。
3.5 第四步:构建结构化人脸数据集
最后一步,我们把所有信息整合起来,形成一个方便查询和使用的数据集。这里我们用SQLite数据库来管理。
import sqlite3 import json def build_face_database(cropped_faces_dir, feature_pkl_path, db_path='face_database.db'): """ 将人脸图片、路径、特征等信息存入SQLite数据库 """ # 加载特征向量 with open(feature_pkl_path, 'rb') as f: feature_dict = pickle.load(f) conn = sqlite3.connect(db_path) cursor = conn.cursor() # 创建表 cursor.execute(''' CREATE TABLE IF NOT EXISTS faces ( id INTEGER PRIMARY KEY AUTOINCREMENT, original_image_path TEXT, cropped_face_path TEXT, face_bbox TEXT, -- 存储为JSON字符串,如'[x1, y1, x2, y2]' feature_vector BLOB -- 存储512维向量的二进制数据 ) ''') # 这里为了简化,假设bbox信息已丢失。实际项目中,应在检测步骤就保存bbox。 # 我们这里只插入裁剪后路径和特征。 for face_file in glob.glob(os.path.join(cropped_faces_dir, '*.jpg')): base_name = os.path.basename(face_file) if base_name in feature_dict: feature_vec = feature_dict[base_name].tobytes() # 将numpy数组转为二进制 # 原始图片路径可以根据文件名规则推断,这里用占位符 original_path = f"downloaded_images/{base_name.split('_face')[0]}.jpg" cursor.execute(''' INSERT INTO faces (original_image_path, cropped_face_path, feature_vector) VALUES (?, ?, ?) ''', (original_path, face_file, feature_vec)) conn.commit() conn.close() print(f'数据库构建完成!数据已存入 {db_path}') # 验证一下 conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM faces') count = cursor.fetchone()[0] print(f'数据库中共有 {count} 条人脸记录。') conn.close() if __name__ == '__main__': build_face_database('cropped_faces', 'face_features.pkl')现在,你有了一个face_database.db文件。你可以用数据库工具查看,或者用Python轻松查询:“给我找出与某张新人脸最相似的10张已有人脸”。一个属于你自己的、可扩展的人脸数据仓库就建好了。
4. 实际应用与效果思考
这套系统跑起来后,我大概用了一个周末的时间,就从几个摄影论坛爬取并构建了一个包含近万张亚洲人脸的初始数据集。效果怎么样呢?
数据多样性:由于源网站图片风格多样(室内、室外、不同光照、不同姿势),得到的数据集比很多单一的公开数据集要丰富,这大大提升了后续训练模型的泛化能力。
效率对比:如果手动从网上搜索、下载、裁剪,处理1000张图片可能就需要好几天。而这个系统一旦搭建好,全程自动化,除了电费和一点时间,几乎没有额外成本。
需要注意的地方:
- 数据版权:这是最重要的一点!爬取的数据绝不能用于商业用途,仅限个人学习、研究或测试。最好选择明确标注了“免费素材”或采用宽松许可证(如CC0)的网站。
- 数据清洗:自动化系统难免会有“漏网之鱼”或“误伤”。Retinaface虽然强,但偶尔也会把雕塑、画报上的人脸检测出来,或者漏掉特别模糊的脸。所以,在关键项目中使用前,最好能对生成的数据集进行一轮快速的人工抽检和清洗。
- 模型选择:本文用的
buffalo_l是综合包。如果你对精度要求极高,可以专门寻找并加载独立的、最新的CurricularFace预训练模型文件。
5. 总结
回过头来看,用爬虫+Retinaface+CurricularFace搭建自动化人脸采集系统,其实是一个典型的“用工程化思维解决数据问题”的案例。它把繁琐重复的劳动交给代码,让我们能更专注于算法和模型本身。
这套方案的代码并不复杂,但串联起来却能产生很大的实用价值。它特别适合那些需要特定领域人脸数据(比如某种民族、年龄阶段、装饰特征)但又找不到现成数据集的场景。你可以通过调整爬虫的目标网站,轻松地让系统为你采集特定类型的人脸。
当然,它只是一个起点。在此基础上,你可以增加去重模块(利用特征向量计算相似度,去除重复人脸)、质量评分模块(根据清晰度、遮挡程度过滤)、甚至自动聚类和标注模块。数据的道路永无止境,但有了这条自动化流水线,至少第一步会走得轻松许多。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。