宇树 R1 迎宾机器人(一):整体架构与人脸识别引擎

张 张站长 · 2026-09-14 17:47 · 技术-机器人 · 47 次浏览 · 0 条评论

给公司年会做的迎宾机器人:感知/决策/表达三层架构、本地与机器人双模式,用 InsightFace 提取人脸特征、余弦相似度向量化匹配,识别部分一台带摄像头的电脑就能复现。

这段时间给公司年会做了个迎宾机器人:宇树 R1 站在门口,认出来的人它就喊名字打招呼、挥手致意,没人的时候还会自己左右张望。这一篇先讲整体怎么搭的、以及最核心的人脸识别部分——识别这块不用机器人,一台带摄像头的电脑就能复现。

它到底在做什么

一句话:摄像头看到人脸 → 在特征库里比对认出是谁 → 用语音喊出名字、挥手、亮灯。做成年会迎宾就是——熟人一进门,机器人喊"张三先生你好,欢迎光临",同时抬手挥两下。

拆开看是三层,每一层我都留了"退路",这样没机器人时也能开发调试:

整体架构

  • ① 感知:拿到画面 + 找出人脸特征。机器人上用 R1 头部相机的视频流,本地开发用电脑摄像头。
  • ② 决策:把人脸特征丢进特征库做比对,认出是谁;再处理"同一个人别反复喊""多个人合并成一句问候"。
  • ③ 表达:语音问候 + 手臂挥手 + 头部张望 + LED 灯。机器人上用 R1 原生能力,本地用 edge-tts 发声。

两种运行模式一个参数切换:--mode local(电脑摄像头 + edge-tts,测识别用)和 --mode r1(R1 视频流 + 原生 TTS/关节控制,部署用)。这一篇聚焦感知和决策里的人脸识别,机器人相关的相机、语音、动作放到后面两篇。

人脸识别用 InsightFace,别自己造轮子

人脸识别我没有自己训模型,直接用开源的 InsightFace——它一步到位:给它一张图,它把图里每张脸检测出来、并为每张脸算一个 512 维的特征向量(embedding)。认人的本质就是:同一个人的两张脸,特征向量很接近;不同人差得远。 比对就是算两个向量的"像不像"。

先装环境(这几样在普通电脑上就能跑,用 CPU 即可):

pip install insightface onnxruntime opencv-python numpy

加载模型、提取特征,就这么点代码:

from insightface.app import FaceAnalysis
import numpy as np, cv2

_app = None
def get_app():
    global _app
    if _app is None:
        _app = FaceAnalysis(providers=["CPUExecutionProvider"])   # 用 CPU
        _app.prepare(ctx_id=-1, det_size=(640, 640))              # ctx_id=-1 即 CPU
    return _app

def extract_embedding(image_bgr):
    """检测人脸并提取特征,返回最大那张脸的 embedding"""
    faces = get_app().get(image_bgr)          # 一次返回图中所有人脸
    if not faces:
        return None
    face = max(faces, key=lambda f: (f.bbox[2]-f.bbox[0])*(f.bbox[3]-f.bbox[1]))  # 取最大的脸
    return face.embedding                     # 512 维向量

app.get(frame) 返回图里每张脸,每张脸带 bbox(人脸框)、kps(关键点)、embedding(特征向量)。录入时一般取画面里最大的那张脸(离镜头最近的),用人脸框面积挑。

判断"像不像":余弦相似度

两个向量像不像,用余弦相似度:把两个向量都归一化成长度 1,再做点积,结果在 -1 到 1 之间,越接近 1 越像。

def compare_embeddings(emb1, emb2):
    emb1 = emb1 / np.linalg.norm(emb1)        # 归一化
    emb2 = emb2 / np.linalg.norm(emb2)
    return float(np.dot(emb1, emb2))          # 点积 = 余弦相似度

实际用一个阈值来判"是不是同一个人"。我用的是 0.4:相似度 ≥ 0.4 就认为匹配上了。这个值可以调——调高更严格(宁可认不出也不认错),调低更宽松(容易认出但可能张冠李戴)。年会场景我宁愿偶尔认不出,也不想喊错名字,所以没敢调太低。

一次和整库比:向量化匹配

年会几百号人,来一张新脸要和库里每个人都比一遍。一个个 for 循环太慢,用 numpy 一次算完:把库里所有特征堆成一个矩阵,新脸向量和这个矩阵做一次矩阵乘法,就得到它和每个人的相似度,再取最大的那个。

class FaceMatcher:
    def __init__(self, threshold=0.4):
        self.threshold = threshold
        self.ids = []
        self.embeddings = None                 # 形状 (人数, 512) 的矩阵

    def load_from_db(self, db):
        rows = db.get_all_embeddings()         # [(person_id, 向量字节), ...]
        self.ids = [r[0] for r in rows]
        mat = np.array([np.frombuffer(r[1], dtype=np.float32) for r in rows])
        self.embeddings = mat / np.linalg.norm(mat, axis=1, keepdims=True)  # 每行归一化

    def match(self, embedding):
        if self.embeddings is None:
            return None, 0.0
        emb = embedding / np.linalg.norm(embedding)
        sims = self.embeddings @ emb           # 一次矩阵乘 → 和每个人的相似度
        idx = int(np.argmax(sims))             # 最像的那个
        sim = float(sims[idx])
        return (self.ids[idx], sim) if sim >= self.threshold else (None, sim)

关键就是 self.embeddings @ emb 这一行:库里 500 人就是一个 500×512 的矩阵,乘一个 512 的向量,一步得到 500 个相似度。几百人的库,匹配一张脸是毫秒级,实时视频完全跟得上。

特征库怎么存

人脸特征存在 SQLite 里,每个人一条记录:姓名、部门、职位等信息,加上那串 512 维向量(float32 直接存成二进制 blob)。录入的时候上传一张照片,提取 embedding 存库;识别的时候把整库的 embedding 读出来给 FaceMatcher。

# 向量存库:numpy 数组转字节
emb_bytes = embedding.astype(np.float32).tobytes()
# 读出来:字节转回 numpy
emb = np.frombuffer(emb_bytes, dtype=np.float32)

录入界面我用 Gradio 搭了个网页(pip install gradio),打开浏览器就能上传照片、填信息、录入人员,不用手写 SQL。这块是纯 Web,跟机器人无关,就不展开了。

先在电脑上把识别跑起来

到这一步,不用机器人你就能验证识别管不管用:装好上面的库,用电脑摄像头读一帧、app.get(frame) 拿到人脸、和你录入的几张脸比一比。核心循环就是"读帧 → 提特征 → 匹配 → 画框显示名字和相似度"。识别这块跑通了,剩下的就是把它接到机器人身上——怎么接 R1 的相机和语音、怎么让它挥手,是接下来两篇的事。

下一篇:接住 R1 的头部相机视频流(GStreamer)、让它用原生 TTS 说话、以及用遥控器一键启停。

评论 0

还没有评论
← 全部文章

找站长

站长微信二维码 扫码加微信
售后群二维码 扫码进售后群
站长收款码 建站不易,感谢打赏
微信 18581469390
本站不支持自助充值;需要充值或开通额度,请直接联系站长。
加微信可以找我重置密码、充值、问工具怎么用