Meta 在智能眼镜上搭载人脸识别功能
阅读原文· buchodi.comMeta 把完整的人脸识别栈塞进了智能眼镜 App,这事一旦激活,公共场所的匿名性将被重新定义。作者的技术取证链条扎实,迫使 Meta 必须正面回应。
Meta 为其智能眼镜产品推出了人脸识别功能,用户可通过眼镜识别他人身份,并获取相关信息。该功能目前正通过早期测试版本向部分用户开放,旨在增强增强现实设备的社交与信息交互能力。
Stella 是 Meta 智能眼镜的配套应用。在检查 Android 构建版本 273.0.0.21(com.facebook.stella)时,我发现了完整的设备端人脸识别计算与存储堆栈:三个人脸模型、一个本地数据库模式、一个与模型匹配的余弦相似度向量索引、一个将生物特征记录分阶段写入磁盘的写入路径、一个完全连通的通知界面,以及一个面向用户的“联系人”小组件。
我想精确说明这代表什么和不代表什么,因为两者之间的差距很重要。
我能证明的是:相关机制已经存在,并且是相互连通的。设备上存在多个面部提取和面部指纹提取模型,我能够在测试图像上完整运行识别流水线,它检测到了人脸,生成了一个 2048 维的生物特征嵌入向量,搜索了本地索引,并在匹配成功后触发了一条 Android 通知,告知用户“已识别此人”。为了使流水线运行,我直接调用其现有的处理程序并传入了一张测试照片。
我无法证明的是:这些功能对普通用户是激活状态。在未注册的标准账户上,面向用户的界面并不显示,而识别通知深度链接到的屏幕在构建中也缺失。此外,我也没有观察到 Meta 服务器将身份数据推送到我测试账户的相应数据库中。
所以这并非“Meta 在秘密识别你注视的人”。而是:完整实现这一功能的全部装置已存在于设备上,组装完毕且功能完备,只是受 Meta 控制是否开启。
以下所有发现均可针对 com.facebook.stella v273.0.0.21 重现。
设备内置了三个面部识别模型(约 100 MB)
三个 ExecuTorch (.pte) 模型通过 Meta 的资产交付系统 NMLML 从 Meta 下载到设备上。
| 资产名称(Meta 命名) | 文件 | 大小 | 功能 |
|---|---|---|---|
| android_facerec_scrfd | SCRFD.pte | 3.4 MB | 检测图像中的人脸 |
| android_facerec_kps_aligner | KPSAligner.pte | 117 KB | 裁剪并对齐每个检测到的人脸 |
| android_facerec_sface | SFace.pte | 96 MB | 将人脸转换为 2048 个数字的嵌入向量(生物特征指纹) |
这些映射到开源架构上,也就是其他应用和学术项目已经在使用的同一模型族。
- SCRFD——面向高效人脸检测的样本与计算再分配(InsightFace,ICLR 2022)。参考实现:github.com/deepinsight/insightface。
- SFace——基于Sigmoid约束超球面损失的人脸识别(Zhong 等人,2021年)。参考:github.com/zhongyy/SFace
- KPSAligner——基于关键点的对齐,自2015年以来的标准做法(MTCNN、dlib、InsightFace)。
Meta的SFace变体似乎比公开参考版本规模更大(96 MB vs. ~40 MB;2048维输出 vs. 参考版本的128–512维)。有必要直白地说:部署检测和嵌入模型本身并不等同于识别。很多应用在设备端运行人脸检测只是为了取景或自动对焦。
一个余弦相似度的人脸索引,维度与设备端的指纹提取器完全一致。
实际运行并读取该数据库的识别流水线:
/data/user/0/com.facebook.stella/files/rldrive/person_profiles/objects.db
这部分位于Meta的跨设备同步框架RLDrive下,位于一个旨在远程填充的person_profiles命名空间中。我没有直接观察到Meta专门向我的测试账户上的person_profiles推送数据。我想明确说明:我描述的是这个通道的存在,而非一次被观察到的传输。
其架构(schema)如下:
CREATE TABLE person (
nodeid INTEGER PRIMARY KEY,
name TEXT,
uri TEXT,
blob BLOB,
deleted INTEGER,
version BLOB
);
CREATE TABLE face (
nodeid INTEGER PRIMARY KEY,
mediaPath TEXT, -- the face_id used in the deep link
personUri TEXT, -- soft reference back to person.uri
blob BLOB,
deleted INTEGER,
uri TEXT,
version BLOB
);
CREATE VIRTUAL TABLE face_mediaPath_vec
USING vec0(mediaPath float[2048] distance_metric=cosine);
-- 2048-float biometric fingerprint per face, cosine-distance search
-- (uses the sqlite-vec extension)
每个人脸行通过personUri指向一个人。每个face.mediaPath是face_mediaPath_vec的主键,该表存储了2048个数字的嵌入向量。识别操作是针对该索引进行余弦相似度查询,然后与person.name进行连接以获得通知文本。
有几处相互吻合:
- vec0是开源sqlite-vec扩展,它将SQLite变成了一个向量相似度引擎。
- float[2048]这个维度正好是应用中内置的SFace嵌入器的输出形状。
- 余弦度量是比较人脸嵌入的标准选择。
该架构(schema)允许一个personUri对应多个人脸行(没有UNIQUE约束),但生产部署是采用一对一还是一对多,从非注册设备上是看不到的。
端到端测试确认了两个分支,并隔离了写入位置。我对数据库进行了 SHA-256 快照和行数统计,然后完整运行了两次识别流程:一次针对空索引(无匹配),一次针对预加载了单个嵌入向量的索引(有匹配):
- 无匹配(空 face_mediaPath_vec):一个 (uuid.jpg, uuid.emb) 文件对被写入 NameTagsPending/。无通知。
- 有匹配:一条 Android 通知通过生产环境的 nametags_recognition 通道触发——标题为“人物已识别”,正文为“识别出米歇尔·福柯”。NameTagsPending/ 中未添加任何内容。
未识别的人脸被暂存到磁盘:裁剪图像加上特征指纹,存放在 NameTagsPending/ 中。
当设备看到本地索引无法匹配的人脸时,Stella 将其写入:
/data/user/0/com.facebook.stella/files/NameTagsPending/
每张未识别的人脸会生成一对文件,以新的 UUID 命名:
- 一个 .jpg 文件——裁剪并对齐的人脸,即 SCRFD + KPSAligner 的输出;以及
- 一个 .emb 文件——2048 维的 SFace 特征指纹。
该目录权限为 0700,且在重启后仍然保留。写入操作仅在无匹配分支上发生;匹配的人脸会触发通知,且不会在磁盘上留下任何痕迹。
我直接验证了嵌入向量的结构:
File: NameTagsPending/1566ab46-[...].emb
Size: 8,192 bytes (2048 × float32, big-endian)
L2 norm: 0.999999 ← canonical L2-normalized face embedding
Min/max: −0.092110 / +0.098950
Mean: +0.000292
综上,(uuid.jpg, uuid.emb) 构成了一张人脸的完整、可索引的生物特征记录——其形态和编码与 person_profiles/objects.db 中用于匹配的余弦索引相同。
“NameTagsPending”这个名称最直接的理解是“待命名的人脸”——已完成生物特征编码,等待一个标签。我注意到这一结构事实,让它自己说明问题:一张人脸图像及其特征指纹,以明文形式并排存储,权限 0700,重启后依然保留——这恰恰是当你打算在标签到来后追溯识别这些人脸时所需要组装的数据集。


通知界面已完全连接就绪。
Stella 定义了一个专用的 Android 通知通道。
NotificationChannel{
id = "nametags_recognition"
name = "NameTags recognition"
description = "Notifications for recognized NameTags connections"
importance = IMPORTANCE_HIGH (heads-up + sound + badge)
sound = system notification sound
}
通知模板是硬编码在识别处理器中的。标题始终是“已识别到人员”;正文始终是“已识别到 ”+ 姓名,其中姓名来自 person_profiles/objects.db 数据库中的人脸表。
NotificationCompat.Builder(ctx, "nametags_recognition")
.setContentTitle("Person recognized")
.setContentText("Recognized " + matched_name)
.setAutoCancel(true)
.setContentIntent(
PendingIntent.getActivity(
ctx,
matched_name.hashCode(),
Intent.ACTION_VIEW with
Uri "fb-viewapp://name_tags?face_id=" + face_id,
FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT))
.build()
NotificationManagerCompat.notify(matched_name.hashCode(), notification)
通知是可点击的:其 contentIntent 是一个深层链接,格式为 fb-viewapp://name_tags?face_id=<face_id>,这是 Meta 编写的 URL 方案,用于在 Stella 内部打开人物资料页面。
坦率地说一点:在 v273 版本中,我未能找到那个目标页面。点击通知后,Stella 会跳转到其默认标签页,因为目标 Compose 目标在导航图中不存在。通知会触发,但它指向的页面并未集成在此版本中。

APK 中存在一个面向用户的“连接”入口点。
Stella v273 包含一个小组件,该组件在一个标题为“连接”的章节标题下渲染一张卡片,卡片上显示文字“查看你的连接”/“记住你遇到的人,并建立新的连接”。这两个字符串都是 APK 中的硬编码文字,并非由服务器推送。
在普通的、未注册的账号上,该卡片根本不会出现在 Glasses 标签页中。它在测试期间才变得可见。在正常使用中,用户不会看到它。

综上所述
- 完整的本地人脸识别技术栈:检测、对齐、嵌入向量、向量索引、存储、写入路径以及通知界面,在 Stella v273 中均已存在并整合在一起。
- 它是可运行的。端到端执行后,它能识别出已知人脸并在通知中显示其姓名,同时将未知人脸(裁剪图 + 指纹)暂存到磁盘。
- 索引维度、嵌入向量形状和存储模式彼此一致,这是一个协调一致的系统,而非零散的废弃代码。
- 用户实际会接触到的部分:“连接”卡片以及通知打开的档案页面,要么没有包含在此构建版本中,要么埋藏得更深。
- 实时流水线所使用的数据库位于 Meta 服务端填充的一个同步命名空间中,与它已经填充的其他命名空间并列,但我没有观察到我的账号上有任何针对人脸命名空间的推送。
我并未声称:Meta 现在正在为用户识别陌生人、注册数据正在流动、或者这些功能中的任何一个已在生产环境中启用。
难以忽视的是:构建、部署并连接如此庞大的设备,最终形成2048维面部指纹识别和一条硬编码的“已识别人员”通知——这本身就是一项工程投入。这种能力绝非偶然推出。是否以及何时投入生产,将由Meta来决定。
这项研究与《连线》杂志的报道同时发布。