PyTorch训练到部署:树莓派5实现人脸追踪安防闭环


从实验室到客厅——当AI模型走进真实世界

你有没有想过,一个在GPU服务器上跑得飞快的人脸识别模型,能不能“下凡”到一块几十美元的开发板上,真正守在家门口?

这不仅是技术挑战,更是AI落地的关键一跃。过去几年,我们见证了深度学习从云端向边缘迁移的大趋势。而今天,借助 PyTorch 的灵活性与 树莓派5 的性能飞跃,这个设想已经可以轻松实现。

本文将带你走完一条完整的“算法→设备”路径:用PyTorch训练轻量级人脸识别模型,优化并部署到树莓派5,最终构建一个能实时识别人脸、发现陌生人立即报警、甚至自动追踪目标的本地化智能安防系统。

整个过程不依赖云服务,响应更快、隐私更强、成本更低——特别适合家庭、小店、办公室等中小型场景。


训练阶段:用PyTorch打造高效人脸特征提取器

为什么选PyTorch?

在众多深度学习框架中,PyTorch之所以成为研究和原型开发的首选,核心在于它的“像写Python一样自然”。

  • 动态图机制让调试变得直观;
  • torchvision 提供了现成的骨干网络(如MobileNetV2);
  • 支持TorchScript导出,为后续部署铺平道路;
  • 社区活跃,文档齐全,踩坑有人扛。

更重要的是,它足够灵活,让我们能把复杂的度量学习逻辑快速实现出来。

模型设计思路:轻量 ≠ 弱智

要在树莓派这种资源受限的设备上运行,我们必须在精度与效率之间找到平衡。我们的策略是:

  • 主干网络 :选用 MobileNetV2 —— 参数少、推理快、移动端验证过的表现优异;
  • 输出维度 :修改最后分类层,输出512维特征向量(face embedding),用于后续比对;
  • 损失函数 :采用 ArcFace 或 Triplet Loss,进行 度量学习 (Metric Learning),使同类人脸特征更紧凑,异类更分离。

这样训练出来的不是简单的分类器,而是一个“人脸编码器”,能把每张脸压缩成一组数字指纹。

数据准备怎么做?

不需要百万级数据集!对于小范围应用场景(比如只识别家人或员工),自建小型数据集完全够用。

建议做法:
- 每人采集10~30张不同角度、光照下的正脸照片;
- 使用 OpenCV 自动检测并裁剪人脸区域;
- 统一分辨率为 112×112,归一化至 [0,1];
- 划分训练/验证集,避免过拟合。

公开数据集如 LFW、CelebA 可作为预训练使用,提升泛化能力。

关键代码解析:从零开始训练一个嵌入模型

import torch
import torchvision.models as models
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import transforms
from dataset import FaceDataset  # 假设已定义好的Dataset类

# 构建基础模型
model = models.mobilenet_v2(pretrained=True)
# 替换最后一层,输出512维特征
model.classifier[1] = nn.Linear(1280, 512)

# 冻结前面的层(可选),只微调头部
for param in model.features.parameters():
    param.requires_grad = False

# 定义ArcFace损失(简化版)
class ArcFace(nn.Module):
    def __init__(self, embedding_size=512, num_classes=100, s=30., m=0.5):
        super().__init__()
        self.weight = nn.Parameter(torch.randn(embedding_size, num_classes))
        self.s = s
        self.m = m
        self.cos_m = math.cos(m)
        self.sin_m = math.sin(m)
        self.th = math.cos(math.pi - m)
        self.mm = math.sin(math.pi - m) * m

    def forward(self, embeddings, labels):
        cosine = F.linear(F.normalize(embeddings), F.normalize(self.weight))
        sine = torch.sqrt(1.0 - torch.pow(cosine, 2))
        phi = cosine * self.cos_m - sine * self.sin_m
        phi = torch.where(cosine > self.th, phi, cosine - self.mm)
        one_hot = torch.zeros_like(cosine)
        one_hot.scatter_(1, labels.view(-1, 1), 1)
        output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
        output *= self.s
        return output

# 设备选择
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
arcface = ArcFace(num_classes=len(dataset.classes)).to(device)

# 数据加载
transform = transforms.Compose([
    transforms.Resize((112, 112)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

dataset = FaceDataset(root='faces/', transform=transform)
loader = DataLoader(dataset, batch_size=32, shuffle=True)

# 优化器与训练循环
optimizer = torch.optim.AdamW([
    {'params': model.classifier.parameters(), 'lr': 1e-3},
    {'params': arcface.parameters(), 'lr': 1e-3}
], weight_decay=1e-4)

scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)

for epoch in range(10):
    model.train()
    total_loss = 0.
    for img, label in loader:
        img, label = img.to(device), label.to(device)
        embedding = model(img)
        logits = arcface(embedding, label)
        loss = F.cross_entropy(logits, label)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    print(f"Epoch {epoch+1}, Avg Loss: {total_loss/len(loader):.4f}")
    scheduler.step()

# 保存纯推理模型(去掉分类头)
inference_model = torch.nn.Sequential(*list(model.children())[:-1])  # 移除classifier
inference_model.eval()
example_input = torch.rand(1, 3, 112, 112)
traced_script_module = torch.jit.trace(inference_model, example_input)
traced_script_module.save("traced_face_encoder.pt")

🔍 关键点说明 :

  • 我们最终保存的是 traced_face_encoder.pt ,这是一个 仅包含前向推理功能 的 TorchScript 模型,无需依赖原始训练代码即可独立运行。
  • 使用 torch.jit.trace 将动态图固化为静态计算图,极大提升部署时的执行效率。
  • 输入尺寸固定为 (1,3,112,112) ,便于后续在边缘端统一处理。

部署实战:让模型在树莓派5上“活”起来

树莓派5为何值得信赖?

别再拿老眼光看树莓派了。2023年发布的 Raspberry Pi 5 是一次真正的性能跃迁:

特性 参数 实际意义
CPU 四核 Cortex-A76 @ 2.4GHz 浮点性能接近x86低功耗笔记本
GPU VideoCore VII 支持 OpenGL ES 3.1,可用于硬件加速渲染
内存 最高8GB LPDDR4X 足够支撑多线程图像处理+模型推理
PCIe 2.0 接口 M.2 NVMe 扩展 外接SSD,解决SD卡I/O瓶颈
视频编解码 H.264/H.265 硬件支持 解码1080p视频无压力

这意味着:它不仅能跑PyTorch模型,还能同时处理摄像头输入、控制外设、提供Web界面——真正成为一个 独立智能节点 。

软件环境搭建指南

1. 系统选择

务必使用 64位 Raspberry Pi OS(Bookworm) ,否则无法安装现代PyTorch ARM64包。

# 检查系统架构
uname -m  # 应返回 aarch64
2. 安装PyTorch(ARM64版本)

官方不再提供pip直接安装包,需从第三方源获取:

wget https://github.com/peterlee0127/pytorch-rpi/releases/download/v2.0.0/torch-2.0.0a0+gitc74ddd3-cp311-cp311-linux_aarch64.whl
pip install torch-2.0.0a0+gitc74ddd3-cp311-cp311-linux_aarch64.whl

✅ 推荐 Python 3.11,兼容性最佳。

3. 安装其他依赖
pip install opencv-python-headless==4.8.1.78
pip install picamera2
pip install flask flask-cors numpy paho-mqtt

⚠️ 注意:不要安装带GUI的OpenCV( opencv-contrib-python ),会因GTK冲突导致崩溃。


核心推理脚本:实时人脸追踪与报警触发

下面是在树莓派5上运行的核心代码,整合了摄像头采集、人脸检测、模型推理与联动响应。

import torch
import cv2
import numpy as np
from picamera2 import Picamera2
import time
from threading import Thread
import RPi.GPIO as GPIO

# === 配置区 ===
ALERT_PIN = 18  # 连接蜂鸣器或LED
SERVO_X_PIN = 12  # PWM舵机水平控制
KNOWN_EMBEDDINGS_PATH = 'known_embeddings.npy'  # 已注册人员特征库
THRESHOLD = 0.6   # 相似度阈值
FRAME_SKIP = 2    # 每隔几帧做一次完整推理(提速)

# 初始化GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(ALERT_PIN, GPIO.OUT)
GPIO.setup(SERVO_X_PIN, GPIO.OUT)
servo_pwm = GPIO.PWM(SERVO_X_PIN, 50)  # 50Hz PWM
servo_pwm.start(7.5)  # 中间位置

# 加载模型
model = torch.jit.load('traced_face_encoder.pt')
model.eval()

# 加载已知人脸特征库
known_embeddings = np.load(KNOWN_EMBEDDINGS_PATH)  # shape: [N, 512]
known_names = ["Alice", "Bob", "Charlie"]  # 对应名称

# 初始化摄像头
picam2 = Picamera2()
config = picam2.create_preview_configuration(
    main={"size": (640, 480), "format": "RGB888"},
    controls={"FrameRate": 30}
)
picam2.configure(config)
picam2.start()

# 人脸检测器(Haar Cascade)
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

frame_count = 0
last_alert_time = 0
alert_cooldown = 10  # 报警冷却时间(秒)

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

def trigger_alarm():
    global last_alert_time
    curr_time = time.time()
    if curr_time - last_alert_time < alert_cooldown:
        return
    print("[!] 陌生人闯入!正在报警...")
    GPIO.output(ALERT_PIN, GPIO.HIGH)
    time.sleep(1.5)
    GPIO.output(ALERT_PIN, GPIO.LOW)
    last_alert_time = curr_time

def track_face_center(x, y, w, h):
    """根据人脸位置调整云台角度"""
    center_x = x + w // 2
    screen_center = 640 // 2
    diff = (center_x - screen_center) / screen_center  # 归一化偏差 [-1,1]
    duty_cycle = 7.5 + diff * 2.5  # 映射到PWM范围 [5.0, 10.0]
    duty_cycle = np.clip(duty_cycle, 5.0, 10.0)
    servo_pwm.ChangeDutyCycle(duty_cycle)

try:
    while True:
        frame = picam2.capture_array()
        gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(60, 60))

        frame_count += 1
        if len(faces) == 0 or frame_count % FRAME_SKIP != 0:
            # 仅绘制检测框,跳过推理
            for (x, y, w, h) in faces:
                cv2.rectangle(frame, (x,y), (x+w,y+h), (255,0,0), 2)
            continue

        for (x, y, w, h) in faces:
            roi = frame[y:y+h, x:x+w]
            roi = cv2.resize(roi, (112, 112)).astype(np.float32) / 255.0
            roi = np.transpose(roi, (2, 0, 1))  # HWC → CHW
            roi = np.expand_dims(roi, axis=0)  # 添加batch维度
            input_tensor = torch.from_numpy(roi).to('cpu')

            with torch.no_grad():
                embedding = model(input_tensor).numpy().flatten()

            # 计算相似度
            scores = [cosine_similarity(embedding, ke) for ke in known_embeddings]
            max_score = max(scores)
            matched_idx = np.argmax(scores)

            if max_score > THRESHOLD:
                name = known_names[matched_idx]
                color = (0, 255, 0)
                label = f"{name}: {max_score:.2f}"
            else:
                color = (0, 0, 255)
                label = "Unknown"
                Thread(target=trigger_alarm).start()  # 异步报警
                track_face_center(x, y, w, h)  # 启动追踪

            # 绘制结果
            cv2.rectangle(frame, (x,y), (x+w,y+h), color, 2)
            cv2.putText(frame, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)

        # 显示画面(可通过SSH X11转发查看,或关闭以节省资源)
        cv2.imshow('Security Cam', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

except KeyboardInterrupt:
    print("\n[INFO] 系统停止")

finally:
    servo_pwm.stop()
    GPIO.cleanup()
    cv2.destroyAllWindows()
    picam2.stop()

💡 亮点功能说明 :

  • 双线程报警 :避免阻塞主循环;
  • 舵机追踪 :通过PWM调节云台方向,实现物理跟踪;
  • 帧采样推理 :非每一帧都跑模型,兼顾实时性与CPU负载;
  • GPIO联动 :可扩展连接更多报警装置或门锁控制。

如何构建完整的本地化安防闭环?

系统模块拆解与协同流程

[CSI Camera] 
     ↓
[Picamera2 实时采集] 
     ↓
[Haar Cascade 人脸检测] → 是否有人脸?
     ↓ 是
[ROI裁剪 + 预处理]
     ↓
[TorchScript 模型推理] → 得到512维特征
     ↓
[余弦相似度匹配] —— 匹配成功? → 正常通行 & 日志记录
                     ↓ 否
               [连续3帧确认] → 是 → 触发报警 + 云通知
                                 ↓ 否 → 忽略(防误报)
     ↓
[Flask Web Server] ← 可远程访问视频流与状态
     ↓
[M.2 SSD] ← 存储录像片段与日志(异常前后各10秒)

所有组件均运行于同一块树莓派5上,形成 完全离线的智能终端 。


实战技巧:那些手册不会告诉你的坑

🚫 坑1:OpenCV GUI导致程序崩溃
  • ❌ 错误做法: pip install opencv-python
  • ✅ 正确做法: pip install opencv-python-headless

如果需要显示窗口,请通过 SSH X11 转发开启图形界面,而非本地运行。

🚫 坑2:内存不足导致频繁重启
  • 启用 swap 分区:
    bash sudo dphys-swapfile swapoff sudo nano /etc/dphys-swapfile # 修改 CONF_SWAPSIZE=1024 sudo dphys-swapfile setup sudo dphys-swapfile swapon
🚫 坑3:模型加载慢、推理延迟高
  • 使用 FP16 半精度量化:
    python traced_script_module.half() # 转为float16 input_tensor = input_tensor.half()
    内存占用减少近半,速度提升约30%!
🚫 坑4:陌生人频繁误报
  • 加入 时间一致性判断 :
    python unknown_counter = 0 if label == "Unknown": unknown_counter += 1 if unknown_counter >= 3: trigger_alarm() else: unknown_counter = 0

还能怎么升级?这些扩展方向值得一试

1. 添加Web管理后台

使用 Flask + Bootstrap 搭建网页界面,支持:
- 实时查看摄像头画面;
- 注册新用户(拍照→提取特征→存入库);
- 查看报警日志与截图;
- 手动触发OTA模型更新。

2. 接入MQTT实现远程通知

import paho.mqtt.client as mqtt

client = mqtt.Client()
client.connect("broker.hivemq.com", 1883, 60)
client.publish("home/security/alert", "陌生人进入房间!")

手机端订阅主题即可实时接收警报。

3. 结合PIR传感器节能运行

无人时关闭摄像头和模型推理,仅由红外传感器唤醒系统,大幅降低功耗。

4. 多设备组网协同监控

多个树莓派分布在不同房间,通过局域网同步状态,实现全景覆盖。


写在最后:边缘AI的未来就在你手里

这次实践证明了一件事: 强大的AI能力,不必非得依赖昂贵的云服务 。

通过 PyTorch 训练 + 模型优化 + 树莓派5 部署,我们完成了一个真正可用的本地化智能安防系统。它不仅响应快、隐私强,而且成本可控、易于复制。

更重要的是,这套方法论具有普适性——无论是人脸识别、口罩检测、行为分析,还是工业质检、农业监测,都可以沿用“训练→优化→部署”的闭环思路。

随着边缘计算软硬件生态日益成熟,像树莓派这样的微型计算机,正在成为AI普惠化的关键载体。而你我手中的这一块小小开发板,也许就是下一个智能世界的起点。

如果你也在尝试类似的项目,欢迎留言交流经验。毕竟,最好的技术,永远来自实践中的碰撞与分享。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐