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 对比约 40 MB;2048 维输出对比参考版本的 128–512 维)。需要直白地说:部署检测和嵌入向量模型本身并不构成识别证据。许多应用在设备端运行人脸检测仅用于构图或自动对焦。
一个余弦相似度人脸索引,其维度与设备端指纹提取器的输出精确匹配
实际运行并读取该数据库的识别流水线:
/data/user/0/com.facebook.stella/files/rldrive/person_profiles/objects.db
这位于 Meta 的跨设备同步框架 RLDrive 之下,位于一个名为 person_profiles 的命名空间中,该命名空间设计为可远程填充。我没有直接观察到 Meta 专门向我的测试账户上的 person_profiles 推送数据。我想明确说明,我描述的是该通道的存在,而非观察到的实际传输。
数据模式:
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 嵌入器输出的精确形状。
- 余弦度量是比较人脸嵌入向量的标准选择。
该数据模式允许每个 personUri 对应多个人脸行(无 UNIQUE 约束),但生产部署是采用一对一还是一对多关系,从非注册设备上无法观察到。
端到端测试确认了两个分支,并隔离了写入操作的位置。我对数据库进行了 SHA-256 快照和行数统计,然后运行了完整的识别流程两次:一次针对空索引(无匹配),一次针对预加载了单个嵌入向量的索引(有匹配):
- 无匹配(空的 face_mediaPath_vec):一个 (uuid.jpg, uuid.emb) 文件对被写入 NameTagsPending/ 目录。无通知。
- 匹配:通过生产环境的 nametags_recognition 通道触发了一条 Android 通知——标题为“人物已识别”,内容为“识别出米歇尔·福柯”。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 数据库中的 person 表:
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 需要回答的问题。
这项研究与《连线》杂志的报道同时发布。