image.png

🧠 项目概述

在人脸识别遍地走的今天,拿张照片或放段视频就能骗过普通摄像头?为了治治这种“二维伪装”,我们基于 OpenCV 3D视觉实验箱 和 Orbbec Gemini 2 深度相机,设计出了这套面部真伪判定系统。

本项目由华南理工大学汪思佟、董兴舒、孙奥博同学联手打造,运行在 Khadas 平台上。系统不仅能在复杂背景下精准框出人脸,更核心的是——它利用深度信息(Depth)对活体进行“降维打击”。通过计算面部关键点的深度方差,结合彩色视觉提示,让照片和屏幕伪装无所遁形。

🔗 源码传送门:GitHub – [Facial-authenticity-judgment]

📖 教程链接:Jetson Orin Nano + Orbbec Femto Mega 复现 RGB-D 人脸活体检测项目手把手指南

🏗️ 系统架构

系统采用“感知-计算-反馈”的极简三段式架构,充分发挥 OpenCV + Orbbec SDK 的软硬协同能力:

层级 核心组件 关键技术/依赖 职责
感知层 Orbbec Gemini 2 cv.CAP_OBSENSOR 同步获取对齐的 RGB 与 Depth 数据
计算层 Khadas 平台 YuNet (ONNX) + NPU (TIM-VX) 人脸检测、关键点提取、深度方差计算
应用层 交互逻辑 OpenCV GUI, 深度二值化 活体判定可视化、距离分割、轮廓绘制

数据流向:

_cgi-bin_mmwebwx-bin_webwxgetmsgimg__&MsgID=4186302587159272690&skey=@crypt_b87d6e1d_c90f37bdd5f351f8b742a5b875219473&mmweb_appid=wx_webfilehelper.jpg

🧩 核心模块说明

下面我们重点聊聊这几个模块背后的设计取舍。

1️⃣ 数据采集与可视化 (video_capture.py)

* 为什么深度图要手动归一化?

Gemini 2 输出的深度图是以毫米为单位的 16 位数据,直接 imshow 只能看到一片黑。我们采用线性映射 (depth * 255.0 / 5000) 并截断到 8 位,再套上 COLORMAP_JET 伪彩色。这不仅是视觉友好,更是为了在调试时快速判断深度数据是否跑飞。

* 设计取舍:这里没有用 cv.normalize 做全局自适应归一化,而是硬编码了 5000mm 的缩放因子。因为全局归一化会导致近处物体的深度对比度被压缩,而固定因子在常规交互距离(0.5m-2m)内表现更稳定。

# 获取深度数据
 ret_depth, depth_map = orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_DEPTH_MAP)
 if ret_depth:
 # 为了在屏幕上显示深度图,需要额外做一些处理
    depth_map_8U = depth_map * 255.0 / 5000 # 将以毫米度量的距离归一化到0-255之间
    depth_map_8U = np.clip(depth_map_8U, 0, 255) # 超过255的值置为255
    depth_map_8U = np.uint8(depth_map_8U) # 转成uint8格式
    cv.imshow("Depth: Gray", depth_map_8U) # 以灰度图方式显示深度

2️⃣ 核心活体判定 (anti_spoofing.py)

这是项目的灵魂,为什么只用 5 个关键点的深度方差就能防伪?

* 物理直觉:真人的脸是立体的(鼻尖凸起、眼窝凹陷),而照片/屏幕是平面的。如果将 YuNet 检测出的 5 个关键点(双眼、鼻尖、嘴角)投影到深度图上,真人这 5 个点的深度值波动(方差 std)一定会大于平面屏幕。

* 边界保护:在提取深度前,我们严格校验了坐标 0 <= coords[2*i] < w。因为关键点有时会落在画面边缘,此时深度图越界会读到脏数据,导致方差计算直接翻车。

def anti_spoofing(faces, depth_map):
    face_flags = []

    if faces[1] is not None:
        thickness = 2
        #每一张脸
        for idx, face in enumerate(faces[1]):
            # 人脸框和5点关键点数据
            coords = face[:-1].astype(np.int32)

            std = -1
            dist = []
            h, w = depth_map.shape
            # 5个点关键点到相机的距离(单位mm)
            for i in range(2, 7):
                if 0 <= coords[2*i] < w and 0 <= coords[2*i+1] < h:
                    dist.append(depth_map[coords[2*i+1], coords[2*i]])
            if len(dist) == 5: # 计算全部5个关键点到相机的距离的均方差
                std = np.std(dist)
            print("std: {:.2f}".format(std))
        
            # 使用限制条件:人脸正对相机
            if std < 0: # 未判断
                face_flags.append(0)
            elif std < 5.: # 伪造
                face_flags.append(-1)
            else: # 真人
                face_flags.append(1)

    return face_flags

* 算力考量:YuNet 本身极其轻量,配合 Khadas 的 NPU 加速(代码中预留了 DNN_BACKEND_TIMVX 和 DNN_TARGET_NPU),将推理耗时压到了极低,为后续的深度计算留足了时间窗。

3️⃣ 距离分割与轮廓提取 (body_segmentation.py)

* 为什么加 Trackbar?

深度阈值极度依赖现场环境。与其在代码里反复改阈值重启,不如直接用滑动条(700mm-1000mm)做运行时调参。这是一种非常实用的工程习惯。

# 创建窗口
    cv.namedWindow(window_name)
    # 创建滚动条以便手动调节距离阈值
    cv.createTrackbar("dist1", window_name, 700, 1000, set_thre1)
    cv.createTrackbar("dist2", window_name, 900, 1000, set_thre2)

* 分割逻辑:通过 segment_body 将深度图二值化,提取特定距离范围内的物体轮廓并映射回 RGB 图。这不仅是人体分割的基础,也为未来“只对特定距离内的人做活体检测”做了技术储备。

# 定义函数,用于根据设定的距离阈值对深度图进行分割
def segment_body(depth_image, thres):
    # 人体与相机的距离在thres[0]和thres[1]之间
    output = cv.inRange(depth_image, thres[0], thres[1])
    output = cv.dilate(output, None)
    output = cv.dilate(output, None)

    # 寻找连通区域
    contours, _ = cv.findContours(output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    max_area = 0
    max_idx = -1
    # 取面积最大的连通区域为人体
    for idx, contour in enumerate(contours):
        area = cv.contourArea(contour)
        if area > max_area:
            max_area = area
            max_idx = idx    
    body_contour = None
    if max_idx > -1:
        body_contour = (contours[max_idx],) # 分割出的人体的轮廓
        output = np.zeros(depth_image.shape, dtype=np.uint8) # 分割出的人体mask图像
        cv.fillPoly(output, body_contour, 255)

    return output, body_contour

🚀 使用示例

跑起来非常简单,只需确保你的 Gemini 2 已经正确连接,且 OpenCV 编译时包含了 Obsensor 支持:

# 1. 克隆项目
git clone https://github.com/3DCVdeveloper/Facial-authenticity-judgment.git

# 2. 运行基础视频流测试(确认相机正常)
python code/video_capture.py

# 3. 运行人体距离分割(调节滑动条体验不同距离的分割效果)
python code/body_segmentation.py
contour.jpg
# 4. 运行核心:面部真伪判定
python code/anti_spoofing.py
anti-spoofing.jpg

🧭 后续规划

虽然目前基于深度方差的防伪逻辑简单有效,但距离商用级应用还有打磨空间:

1. 🎭 对抗3D面具/头模:目前的方差法对高仿3D头模可能失效。后续计划引入红外通道(Gemini 2 支持)或结合局部纹理分析(如 rPPG 心率检测),构建多模态防伪。

2. 🧠 端到端轻量化部署:目前防伪逻辑与检测逻辑分离,未来考虑将深度特征输入到轻量级分类网络(如MobileNet),实现“检测+防伪”的端到端推理,进一步降低延迟。

3. 🗣️ 语音交互融合:结合 Khadas 的音频模块,在判定伪造时增加语音播报警告,让具身智能的交互更自然。

4. 🔧 自适应深度阈值:在 body_segmentation 中加入环境光/深度直方图自适应算法,省去手动调 Trackbar 的步骤,实现真正的 Plug & Play。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注