发帖
8 1 0

【安信可小安派BW21-CBV-Kit】实现类似Windows Hello 人脸解锁功能

WT_0213
论坛元老

100

主题

1397

回帖

1万

积分

论坛元老

积分
12780
小安派·BW21-CBV-KIt 60 8 昨天 15:34
[i=s] 本帖最后由 WT_0213 于 2025-3-14 09:34 编辑 [/i]

使用小安派BW21-CBV-Kit开发板实现丐版 Windows Hello 人脸功能。

一、什么是 Windows Hello?

Windows Hello是登录 Windows 设备的一种更个性化、更安全的方式。 使用Windows Hello可以使用面部识别、指纹或 PIN 登录,而不是使用密码。

这些选项有助于更轻松地登录电脑,因为你的 PIN 仅与一台设备相关联,并且使用 Microsoft 帐户备份用于恢复。

可以使用“设置”应用配置和管理Windows Hello。

设置 Windows Hello

请按照以下步骤设置 Windows Hello。

  1. 选择**“开始” >“设置” >“帐户”>“登录”选项**。
  2. 在**“登录方式下”**,你将看到使用 Windows Hello 登录的三个选项:
  • 选择**“面部识别 (Windows Hello)”**以使用电脑的红外摄像头或外部红外摄像头设置通过面部识别登录。
  • 选择**“指纹识别 (Windows Hello)”**以设置使用指纹读取器登录。
  • 选择**“PIN (Windows Hello)”**以设置使用 PIN 登录。

1.JPG

Windows Hello技术简析

3D活体检测

==‌硬件组成‌==:

  • 近红外(NIR)摄像头
  • 泛光照明器(Flood Illuminator)
  • 点阵投影器(Dot Projector)

==‌工作原理‌==:

def 活体验证():

第一阶段:深度图构建

红外图像 = 捕捉940nm波长图像()
点阵图案 = 投射30000个不可见光点()
深度图 = 计算点阵形变(点阵图案)

第二阶段:生理特征验证

脉搏检测 = 分析皮下血液流动(红外序列图像)
眼球运动 = 追踪虹膜微震颤()
return 深度图 and 脉搏检测 and 眼球运动

非对称加密体系

==‌密钥生成流程‌==:

sequenceDiagram
用户->>TPM: 生物特征数据
TPM->>TPM: 生成RSA 2048密钥对
TPM->>系统: 公钥
TPM->>TPM: 私钥永久锁定
系统->>AD: 注册公钥

==‌认证过程‌==:

  1. 系统发送随机挑战码(Challenge)
  2. TPM用私钥签名挑战码
  3. 服务器用公钥验证签名

安全防护机制

1. 分层防御体系
层级 防护措施 破解难度
L1 防照片/视频回放 ★☆☆☆☆
L2 防3D打印面具 ★★☆☆☆
L3 防热成像复制 ★★★☆☆
L4 防物理攻击TPM ★★★★☆
2. 数据存储规范

struct BiometricData {
uint8_t 加密模板[512]; // AES-GCM加密
uint32_t 版本号;
uint64_t 时间戳; // 防止重放攻击
uint8_t HMAC签名; // SHA-256校验
};


与传统方案的对比

1. 认证流程差异
步骤 传统密码方案 Windows Hello
身份验证 字符串比对 生物特征+密码学证明
数据传输 密码明文传输 数字签名挑战响应
存储方式 密码哈希存储 非对称密钥对存储
防重放攻击 时间戳+随机数

2. 安全性能指标

指标 传统PIN码 Windows Hello
暴力破解时间 2小时 10^18年
中间人攻击成功率 63% 0.0001%
凭证泄露影响范围 所有系统 单设备

二、 HID设备模拟原理

HID设备模拟原理是通过软件或硬件手段生成符合HID协议规范的数据流,使计算机系统将其识别为真实物理输入设备并响应操作指令,其核心实现机制包含以下要点:

协议与驱动交互原理
  1. ==‌协议规范匹配‌==
    模拟设备需遵循HID协议定义的数据格式和通信规则,包括设备描述符、接口描述符、报告描述符等元数据配置例如,设备类(bDeviceClass)需设置为0x03以标识为HID设备,并通过报告描述符定义数据用途与格式。
  2. ==‌操作系统驱动模型‌==
    操作系统通过硬件抽象层(HAL)和内置的HID类驱动直接解析标准HID设备数据,无需额外驱动。模拟设备通过注册为系统级输入设备,与原生驱动交互完成指令映射。
实现方式分类
  1. ==‌硬件模拟‌==
    • 使用微控制器(如Arduino)或专用芯片(如USB Rubber Ducky)模拟USB HID设备,通过固件生成符合协议的电气信号和数据包
    • 蓝牙HID模拟通过协议栈封装输入指令,如蓝牙低功耗(BLE)设备伪装成键盘/鼠标,通过GATT服务传输操作指令‌
  2. ==‌软件模拟‌==
  • 虚拟驱动层拦截系统输入事件,构造虚拟HID设备并注入输入信号,例如通过Windows的HID API动态创建虚拟设备节点‌
  • 远程控制场景中,通过传输协议(如USB over IP)转发外设操作数据至受控端,在系统层面重放为本地输入事件‌
数据映射与传输机制
  1. ==‌输入信号编码‌==
    用户操作(如按键、移动)需转换为HID报告描述符定义的结构化数据。例如,键盘按键对应特定Usage Page和Usage ID,通过中断传输模式发送至主机‌
  2. ==传输通道控制‌==
    HID设备仅支持控制传输(设备枚举、配置)和中断传输(实时输入),模拟设备需实现端点管理及带宽分配策略以保证低延迟‌
典型应用与限制
  • ==‌正向应用‌==:自动化测试脚本执行、无障碍辅助设备开发‌
  • ==安全风险‌==:恶意设备模拟可绕过系统安全机制执行隐蔽操作(如BadUSB攻击)
  • ==性能瓶颈‌==:高频率操作(如游戏手柄)需优化中断传输速率与数据包压缩算法以避免丢帧‌

HID设备模拟本质是通过协议逆向与系统交互机制的深度适配,结合软硬件协同实现输入信号的精准复现与控制‌

三、BW21-CBV-Kit实现Windows Hello 人脸

1. 整体设计思路

摄像头->>BW21-CBV-Kit: 捕获RGB图像
BW21-CBV-Kit->>BW21-CBV-Kit: 运行人脸识别算法
    识别成功
    BW21-CBV-Kit->>PC: HID发送Esc进入密码解锁界面
    BW21-CBV-Kit->>PC: 模拟键盘输入预设密码完成解锁

WX20250314-004428.png

2.基础设置

面容解锁前置条件。

2.JPG

上传好项目代码后,打开windows 设置。搜索蓝牙,打开蓝牙、然后点击“添加蓝牙或其他设备”

3.JPG

点击“蓝牙”,找到名称为AMEBA_HELLO的蓝牙设备并连接
4.JPG

系统配置就完成了。接下来回到Arduino IDE 或者打开串口工具。

波特率设置115200,发送 REG=XXX 。其中XXX是你要设置面容名称

5.JPG

将BW21-CBV-Kit对准自己的脸。或者图片【为什么图片呢,因为它目前还没有活体检测】录入人脸信息。录入人脸后会输出

13:57:01.881 -> 
>>> MBFACENET FPS = 2.54
13:57:01.881 -> 
Total number of faces detected = 1
13:57:01.881 -> 
Face 0 name xxx(人脸名称):	798 1169 292 786

人脸信息录入以后只要检测到录入过的人脸。就会触发。 Esc -> 输入密码 -> 回车的操作。 解锁速度还挺快。

WX20250314-004350.png

WX20250314-004646.png

3. 完整代码

#include "BLEHIDDevice.h"
#include "BLEHIDKeyboard.h"
#include "BLEDevice.h"
#include "WiFi.h"
#include "StreamIO.h"
#include "VideoStream.h"
#include "RTSP.h"
#include "NNFaceDetectionRecognition.h"
#include "VideoStreamOverlay.h"
#include <AmebaServo.h>
#include "AmebaFatFS.h"

char filename[] = "pw.txt";
String password = "";

#define CHANNELVID  0    // Channel for RTSP streaming
#define CHANNELJPEG 1    // Channel for taking snapshots
#define CHANNELNN   3    // RGB format video for NN only available on channel 3

// Customised resolution for NN
#define NNWIDTH  576
#define NNHEIGHT 320

VideoSetting configVID(VIDEO_FHD, 30, VIDEO_H264, 0);
VideoSetting configJPEG(VIDEO_FHD, CAM_FPS, VIDEO_JPEG, 1);
VideoSetting configNN(NNWIDTH, NNHEIGHT, 10, VIDEO_RGB, 0);
NNFaceDetectionRecognition facerecog;
RTSP rtsp;
StreamIO videoStreamer(1, 1);
StreamIO videoStreamerFDFR(1, 1);
StreamIO videoStreamerRGBFD(1, 1);

char ssid[] = "";    // your network SSID (name)
char pass[] = "";        // your network password
int status = WL_IDLE_STATUS;

bool unLock = false;
uint32_t img_addr = 0;
uint32_t img_len = 0;
long counter = 0;

// File Initialization
AmebaFatFS fs;

char buf[128];
char path[128];

BLEHIDKeyboard keyboardDev;
BLEAdvertData advdata;

void setup() {

  fs.begin();
  printf("write something to \"%s\"\r\n", filename);
  sprintf(path, "%s%s", fs.getRootPath(), filename);
  File file = fs.open(path);

  password = file.readString();

  file.close();
  printf("==== content ====\r\n");
  printf("%s", buf);
  printf("====   end   ====\r\n");
  fs.end();

  Serial.begin(115200);
  advdata.addFlags();
  advdata.addCompleteName("AMEBA_HELLO");
  advdata.addAppearance(GAP_GATT_APPEARANCE_HUMAN_INTERFACE_DEVICE);
  advdata.addCompleteServices(BLEUUID(HID_SERVICE_UUID));

  BLEHIDDev.init();

  BLE.init();
  BLE.configAdvert()->setAdvData(advdata);
  BLE.setDeviceName("AMEBA_HELLO");
  BLE.setDeviceAppearance(GAP_GATT_APPEARANCE_HUMAN_INTERFACE_DEVICE);
  BLE.configSecurity()->setPairable(true);
  BLE.configSecurity()->setAuthFlags(GAP_AUTHEN_BIT_BONDING_FLAG);
  BLE.configServer(3);
  BLE.addService(BLEHIDDev.hidService());
  BLE.addService(BLEHIDDev.battService());
  BLE.addService(BLEHIDDev.devInfoService());
  BLE.beginPeripheral();

  // Attempt to connect to Wifi network:
  while (status != WL_CONNECTED) {
      Serial.print("Attempting to connect to WPA SSID: ");
      Serial.println(ssid);
      status = WiFi.begin(ssid, pass);

      // wait 2 seconds for connection:
      delay(2000);
  }

  // Configure camera video channels with video format information
  Camera.configVideoChannel(CHANNELVID, configVID);
  Camera.configVideoChannel(CHANNELJPEG, configJPEG);
  Camera.configVideoChannel(CHANNELNN, configNN);
  Camera.videoInit();

  // Configure RTSP with corresponding video format information
  rtsp.configVideo(configVID);
  rtsp.begin();

  // Configure Face Recognition model
  facerecog.configVideo(configNN);
  facerecog.modelSelect(FACE_RECOGNITION, NA_MODEL, DEFAULT_SCRFD, DEFAULT_MOBILEFACENET);
  facerecog.begin();
  facerecog.setResultCallback(FRPostProcess);

  // Configure StreamIO object to stream data from video channel to RTSP
  videoStreamer.registerInput(Camera.getStream(CHANNELVID));
  videoStreamer.registerOutput(rtsp);
  if (videoStreamer.begin() != 0) {
      Serial.println("StreamIO link start failed");
  }

  // Start data stream from video channel
  Camera.channelBegin(CHANNELVID);
  Camera.channelBegin(CHANNELJPEG);

  // Configure StreamIO object to stream data from RGB video channel to face detection
  videoStreamerRGBFD.registerInput(Camera.getStream(CHANNELNN));
  videoStreamerRGBFD.setStackSize();
  videoStreamerRGBFD.setTaskPriority();
  videoStreamerRGBFD.registerOutput(facerecog);
  if (videoStreamerRGBFD.begin() != 0) {
      Serial.println("StreamIO link start failed");
  }

  // Start video channel for NN
  Camera.channelBegin(CHANNELNN);

  // Start OSD drawing on RTSP video channel
  OSD.configVideo(CHANNELVID, configVID);
  OSD.begin();

  // Restore any registered faces saved in flash
  facerecog.restoreRegisteredFace();
}

void loop() {

  if (Serial.available() > 0) {
    String input = Serial.readString();
    input.trim();

    if (input.startsWith(String("REG="))) {
        String name = input.substring(4);
        facerecog.registerFace(name);
    } else if (input.startsWith(String("DEL="))) {
        String name = input.substring(4);
        facerecog.removeFace(name);
    } else if (input.startsWith(String("RESET"))) {
        facerecog.resetRegisteredFace();
    } else if (input.startsWith(String("BACKUP"))) {
        facerecog.backupRegisteredFace();
    } else if (input.startsWith(String("RESTORE"))) {
        facerecog.restoreRegisteredFace();
    } else if(input.startsWith(String("PW="))){
      password = input.substring(3);
      File file = fs.open(path);
      file.println(password);
      file.close();
      printf("write finish\r\n\r\n");
    }
  }

  if ((unLock == true) && BLE.connected()) {
      unLock = false;
      Serial.println("Sending keystrokes");
      keyboardDev.keyReleaseAll();
      delay(100);
      keyboardDev.keyPress(HID_KEY_ESCAPE);
      delay(300);
      keyboardDev.keyReleaseAll();
      delay(300);
      keyboardDev.keySequence("123456");
      delay(100);
      keyboardDev.keyPress(HID_KEY_ENTER);
      delay(100);
      keyboardDev.keyReleaseAll();
  }

  delay(1000);
  OSD.createBitmap(CHANNELVID);
  OSD.update(CHANNELVID);

}


// User callback function for post processing of face recognition results
void FRPostProcess(std::vector<FaceRecognitionResult> results)
{
    uint16_t im_h = configVID.height();
    uint16_t im_w = configVID.width();

    printf("Total number of faces detected = %d\r\n", facerecog.getResultCount());
    OSD.createBitmap(CHANNELVID);

    if (facerecog.getResultCount() > 0 && !unLock) {
   
        if (facerecog.getResultCount() > 1) {    // Door remain close when more than one face detected
            unLock = false;
        } else {
            FaceRecognitionResult face = results[0];
            if (String(face.name()) == String("unknown")) {    // Door remain close when unknown face detected
                unLock = false;
            } else {    // Door open when a single registered face detected
                unLock = true;
            }
        }
  
    }

    for (int i = 0; i < facerecog.getResultCount(); i++) {
        FaceRecognitionResult item = results[i];
        // Result coordinates are floats ranging from 0.00 to 1.00
        // Multiply with RTSP resolution to get coordinates in pixels
        int xmin = (int)(item.xMin() * im_w);
        int xmax = (int)(item.xMax() * im_w);
        int ymin = (int)(item.yMin() * im_h);
        int ymax = (int)(item.yMax() * im_h);

        uint32_t osd_color;

        // Draw boundary box
        if (String(item.name()) == String("unknown")) {
            osd_color = OSD_COLOR_RED;
        } else {
            osd_color = OSD_COLOR_GREEN;
        }
        printf("Face %d name %s:\t%d %d %d %d\n\r", i, item.name(), xmin, xmax, ymin, ymax);
        OSD.drawRect(CHANNELVID, xmin, ymin, xmax, ymax, 3, osd_color);

        // Print identification text above boundary box
        char text_str[40];
        snprintf(text_str, sizeof(text_str), "Face:%s", item.name());
        OSD.drawText(CHANNELVID, xmin, ymin - OSD.getTextHeight(CHANNELVID), text_str, osd_color);
    }
    OSD.update(CHANNELVID);
}

目前实现的功能比较简单。电脑密码和WIFI信息都保存在了代码中这样不够灵活。

代码中也尝试将密码写到tf卡中。不过没有卡暂时没办法测试。

项目参考:

【教程】小安派BW21-CBV-Kit——人脸识别门锁

【教程】小安派BW21-CBV-Kit——BLE HID 键盘

基本上官方提供的案例很多都功能窦涵盖了。

四、问题整理

关于开发板

1、BW21-CBV-Kit开发板发热量较大

2、麦克风做成插座(会不会更好一些)

3、TF卡槽和PCB对齐做外壳方便插拔卡

目前这个项目程序试玩可以真正用起来还有很多优化的地方。

1、密码、WIFI信息、人脸信息等持久化保存

2、注册方式需要优化

3、PC与开发板状态同步,解锁后通知开发板已解锁。锁定后通知开发板已锁定。

小安派BW21-CBV-Kit:实现丐版Windows Hello

──── 1人觉得很赞 ────

使用道具 举报

棒~已把问题反馈给了莫哥
昨天 19:47
优秀
当做台式机的摄像头,用howdy直接调
好创意,整活儿又有灵感了
优秀
2 小时前
爱笑 发表于 2025-3-13 16:59
棒~已把问题反馈给了莫哥

补充了一下视频和图片
2 小时前
WildboarG 发表于 2025-3-13 22:41
当做台式机的摄像头,用howdy直接调

刚刚搜了一下Howdy :https://github.com/boltgolt/howdy 是这个吧
是不是这个只支持 Linux呀。如果跨平台得话也挺好得。
不过这个开发板,如果只是单纯当个摄像头有些可惜。 我这个就是一个可玩性方向。真要做起来的话会涉及到很多问题。
2 小时前
无垠的广袤 发表于 2025-3-14 08:01
好创意,整活儿又有灵感了

期待大佬作品。我负责给大佬们抛砖。
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 28073 个
  • 话题数: 39658 篇