从零到一:基于Facenet-PyTorch与自定义数据集构建人脸识别系统
1. 环境准备与工具安装
第一次接触人脸识别系统开发时,我对着各种框架和库的文档看得头晕眼花。后来发现用Facenet-PyTorch这个开源项目入门特别友好,它把复杂的人脸检测和特征提取功能都封装好了,我们只需要关注自己的业务逻辑就行。
Facenet-PyTorch主要包含两个核心组件:MTCNN人脸检测器和InceptionResnetV1特征提取网络。MTCNN就像个智能摄像头,能自动找到图片中的人脸位置;而InceptionResnetV1则像人脑的视觉皮层,能把人脸图像转换成512维的特征向量。这两个组件配合使用,就能实现端到端的人脸识别。
安装方式有三种,我推荐新手先用pip安装试试水:
pip install facenet-pytorch
如果遇到网络问题,可以尝试清华源:
pip install facenet-pytorch -i https://pypi.tuna.tsinghua.edu.cn/simple
进阶用户可以直接克隆GitHub仓库,这样能随时查看和修改源码:
git clone https://github.com/timesler/facenet-pytorch.git
cd facenet-pytorch
pip install -e .
我最近在帮学校实验室搭建门禁系统时,发现Colab环境特别适合做原型验证。在Notebook里运行前记得加上感叹号:
!pip install facenet-pytorch
安装完成后,建议先跑个简单的测试脚本验证环境是否正常:
from facenet_pytorch import MTCNN
mtcnn = MTCNN()
print("MTCNN加载成功!")
2. 数据准备与预处理
去年给小区物业做人脸门禁时,最头疼的就是数据收集。后来发现用手机拍视频再抽帧是个好办法:让被采集者在镜头前缓慢转头,然后用OpenCV每10帧提取一张图片,这样能获得不同角度的面部数据。
数据目录建议按这个结构组织:
data/
├── train/
│ ├── person1/
│ │ ├── img1.jpg
│ │ └── img2.jpg
│ └── person2/
│ ├── img1.jpg
│ └── img2.jpg
└── val/
├── person1/
└── person2/
用MTCNN处理原始图片时,我总结出几个实用参数:
image_size=160:输出人脸图像尺寸margin=20:在人脸周围多保留20像素背景keep_all=False:只保留检测概率最高的人脸
from facenet_pytorch import MTCNN
import os
mtcnn = MTCNN(
image_size=160,
margin=20,
min_face_size=40,
thresholds=[0.6, 0.7, 0.7],
device='cuda' if torch.cuda.is_available() else 'cpu'
)
def process_images(input_dir, output_dir):
for person in os.listdir(input_dir):
person_dir = os.path.join(input_dir, person)
save_dir = os.path.join(output_dir, person)
os.makedirs(save_dir, exist_ok=True)
for img_name in os.listdir(person_dir):
img_path = os.path.join(person_dir, img_name)
img = Image.open(img_path)
img_cropped = mtcnn(img, save_path=os.path.join(save_dir, img_name))
数据增强我常用这些组合:
from torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.3, contrast=0.3),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
3. 模型训练与调优
第一次训练时我直接用了默认参数,结果准确率只有70%左右。后来发现学习率和批大小对结果影响很大,经过多次实验找到一组黄金参数:
optimizer = optim.Adam(model.parameters(), lr=0.0005, weight_decay=1e-4)
scheduler = MultiStepLR(optimizer, milestones=[10, 20], gamma=0.1)
在训练过程中,我发现这几个技巧很实用:
- 使用TensorBoard监控训练过程:
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
for epoch in range(epochs):
writer.add_scalar('Loss/train', train_loss, epoch)
writer.add_scalar('Accuracy/val', val_acc, epoch)
- 早停机制防止过拟合:
best_acc = 0
for epoch in range(30):
train(...)
val_acc = validate(...)
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), 'best_model.pth')
patience = 3
else:
patience -= 1
if patience == 0:
break
- 混合精度训练加速:
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
完整训练代码示例:
from facenet_pytorch import InceptionResnetV1
import torch.nn as nn
class FaceNet(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.backbone = InceptionResnetV1(pretrained='vggface2')
self.classifier = nn.Linear(512, num_classes)
def forward(self, x):
features = self.backbone(x)
return self.classifier(features)
model = FaceNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()
4. 模型部署与优化
在树莓派上部署时,我发现原始模型太大,于是用这个技巧压缩模型:
# 模型量化
quantized_model = torch.quantization.quantize_dynamic(
model, {torch.nn.Linear}, dtype=torch.qint8
)
torch.jit.save(torch.jit.script(quantized_model), 'quantized.pt')
实时检测的优化技巧:
- 使用多线程处理视频流:
from threading import Thread
import queue
frame_queue = queue.Queue(maxsize=10)
def capture_thread(camera):
while True:
ret, frame = camera.read()
frame_queue.put(frame)
Thread(target=capture_thread, args=(camera,)).start()
- 人脸跟踪减少计算量:
tracker = None
for frame in video_stream:
if tracker is None:
faces = detect_faces(frame)
if faces:
tracker = create_tracker(faces[0])
else:
success, box = tracker.update(frame)
- ONNX格式导出提升推理速度:
dummy_input = torch.randn(1, 3, 160, 160).to(device)
torch.onnx.export(
model, dummy_input, "model.onnx",
input_names=["input"], output_names=["output"],
dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}
)
完整的推理代码示例:
def predict(image_path):
img = Image.open(image_path).convert('RGB')
img_tensor = transform(img).unsqueeze(0).to(device)
with torch.no_grad():
features = model(img_tensor)
_, pred = torch.max(features, 1)
return class_names[pred.item()]
# 测试单张图片
result = predict("test.jpg")
print(f"识别结果: {result}")
5. 常见问题排查
在开发过程中我踩过不少坑,这里分享几个典型问题的解决方法:
- CUDA内存不足:
- 减小批大小(batch_size=8或16)
- 使用梯度累积:
optimizer.zero_grad()
for i, (inputs, labels) in enumerate(train_loader):
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
if (i+1) % 4 == 0:
optimizer.step()
optimizer.zero_grad()
- 人脸检测不准:
- 调整MTCNN阈值:
mtcnn = MTCNN(thresholds=[0.4, 0.5, 0.5]) # 降低阈值
- 对视频使用跟踪算法减少抖动
- 类别不平衡问题:
from torch.utils.data import WeightedRandomSampler
class_counts = [...] # 每个类别的样本数
weights = 1. / torch.tensor(class_counts, dtype=torch.float)
samples_weights = weights[labels]
sampler = WeightedRandomSampler(samples_weights, len(samples_weights))
- 模型不收敛检查清单:
- 检查数据预处理是否与预训练模型匹配
- 尝试更小的学习率(如0.0001)
- 添加梯度裁剪:
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
- 部署时的版本兼容问题:
# 创建兼容性环境
conda create -n deploy python=3.8
pip install torch==1.8.0+cu111 -f https://download.pytorch.org/whl/torch_stable.html
pip install facenet-pytorch==2.5.2
6. 进阶优化技巧
当基本模型跑通后,我通常会做这些优化来提升性能:
- 特征融合技术:
class EnhancedFaceNet(nn.Module):
def __init__(self):
super().__init__()
self.backbone = InceptionResnetV1(pretrained=False)
self.attention = nn.Sequential(
nn.Linear(512, 128),
nn.ReLU(),
nn.Linear(128, 512),
nn.Sigmoid()
)
def forward(self, x):
features = self.backbone(x)
attention = self.attention(features)
return features * attention
- 难样本挖掘:
def hard_example_mining(features, labels):
distance_matrix = pairwise_distance(features)
positive_mask = labels.unsqueeze(0) == labels.unsqueeze(1)
hardest_positive = (distance_matrix * positive_mask.float()).max(1)[0]
negative_mask = ~positive_mask
hardest_negative = (distance_matrix + 1e6 * positive_mask.float()).min(1)[0]
return hardest_positive, hardest_negative
- 知识蒸馏:
teacher_model = InceptionResnetV1(pretrained='vggface2').eval()
student_model = SmallFaceNet()
for inputs, labels in train_loader:
with torch.no_grad():
teacher_logits = teacher_model(inputs)
student_logits = student_model(inputs)
loss = 0.3 * criterion(student_logits, labels) + 0.7 * mse_loss(student_logits, teacher_logits)
- 多任务学习:
class MultiTaskModel(nn.Module):
def __init__(self):
super().__init__()
self.backbone = InceptionResnetV1(pretrained=True)
self.classifier = nn.Linear(512, num_classes)
self.attribute = nn.Linear(512, 5) # 性别、年龄等属性
def forward(self, x):
features = self.backbone(x)
return self.classifier(features), self.attribute(features)
- 模型剪枝:
from torch.nn.utils import prune
parameters_to_prune = [
(module, 'weight') for module in filter(
lambda m: isinstance(m, nn.Conv2d),
model.modules()
)
]
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.2
)
7. 实际应用案例
去年为某企业开发的考勤系统中,我们遇到了光照条件复杂的问题。最终解决方案是:
- 数据层面:
- 添加随机光照增强:
transforms.ColorJitter(
brightness=0.5,
contrast=0.3,
saturation=0.2,
hue=0.1
)
- 使用Gamma校正预处理:
def adjust_gamma(image, gamma=1.0):
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255
for i in np.arange(0, 256)]).astype("uint8")
return cv2.LUT(image, table)
- 模型层面:
- 添加光照鲁棒性损失:
class IlluminationLoss(nn.Module):
def forward(self, features1, features2):
return 1 - torch.cosine_similarity(features1, features2, dim=1).mean()
- 系统层面:
- 动态质量评估:
def image_quality_score(img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
fm = cv2.Laplacian(gray, cv2.CV_64F).var()
return fm > 50 # 阈值根据实际情况调整
另一个有意思的应用是相册自动分类。我们开发了这样的处理流程:
- 使用MTCNN检测相册中所有人脸
- 对每张人脸提取512维特征
- 用层次聚类算法自动分组
- 人工核对后生成标签
核心聚类代码:
from sklearn.cluster import DBSCAN
features = [...] # 所有人脸特征
clustering = DBSCAN(eps=0.5, min_samples=3).fit(features)
for label in set(clustering.labels_):
if label == -1: continue # 噪声点
print(f"类别{label}包含{sum(clustering.labels_==label)}张人脸")
8. 持续学习与模型更新
线上系统运行一段时间后,我发现模型性能会逐渐下降。后来设计了这个增量学习方案:
- 新数据收集接口:
@app.route('/feedback', methods=['POST'])
def feedback():
img = request.files['image']
label = request.form['label']
save_to_training_set(img, label)
return "反馈已接收"
- 增量训练脚本:
def incremental_train(new_data_dir):
new_dataset = datasets.ImageFolder(new_data_dir)
combined_dataset = ConcatDataset([original_dataset, new_dataset])
# 只训练最后一层
for param in model.backbone.parameters():
param.requires_grad = False
train_loader = DataLoader(combined_dataset, batch_size=32)
optimizer = optim.SGD(model.classifier.parameters(), lr=0.001)
for epoch in range(5):
train_one_epoch(model, train_loader, optimizer)
- 模型版本管理:
# 保存带时间戳的模型版本
import datetime
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M")
torch.save(model.state_dict(), f"model_{timestamp}.pth")
# 模型回滚机制
if new_model_performance < threshold:
load_previous_model()
- 性能监控面板:
# 使用Prometheus记录关键指标
from prometheus_client import Summary, Gauge
REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
ACCURACY = Gauge('model_accuracy', 'Current model accuracy')
@REQUEST_TIME.time()
def process_request(input):
result = model(input)
ACCURACY.set(calculate_accuracy())
return result
最近在开发边缘设备部署方案时,发现TensorRT能显著提升推理速度。这是我们的优化流程:
- 转换模型:
import tensorrt as trt
logger = trt.Logger(trt.Logger.INFO)
builder = trt.Builder(logger)
network = builder.create_network()
parser = trt.OnnxParser(network, logger)
with open("model.onnx", "rb") as f:
parser.parse(f.read())
config = builder.create_builder_config()
config.max_workspace_size = 1 << 30 # 1GB
engine = builder.build_engine(network, config)
- 部署优化:
# 使用内存映射加速加载
with open("model.engine", "wb") as f:
f.write(engine.serialize())
# 运行时加载
runtime = trt.Runtime(logger)
with open("model.engine", "rb") as f:
engine = runtime.deserialize_cuda_engine(f.read())
- 性能对比:
- 原始PyTorch模型:45ms/帧
- ONNX Runtime:28ms/帧
- TensorRT优化后:12ms/帧
更多推荐


所有评论(0)