人脸检测与识别-InsightFace:向量相似性搜索Faiss

1.什么是Faiss?

Faiss(Facebook AI Similarity Search)是由Facebook(现Meta)AI Research团队开发并开源的一个高效向量相似性搜索和聚类库。它专门用于在高维空间中对大规模向量数据进行快速的相似性检索(即近似最近邻搜索,ANN),支持十亿级向量的毫秒级检索,广泛应用于推荐系统、图像检索、文本检索、人脸识别、自然语言处理等领域。

能够对稠密向量进行高效的相似性搜索和聚类,支持多种距离度量(如欧氏距离L2、内积IP、余弦相似度等),并能灵活地在搜索速度、精度和内存占用之间做权衡。

  • 主要功能:
    • 向量索引:支持多种索引结构(如Flat、IVF、HNSW、PQ等),可针对不同规模和精度需求选择最优索引。
    • 相似性搜索:输入一个或多个查询向量,快速返回最相似的若干向量(TopK)。
    • 聚类:支持k-means等聚类算法。
    • 支持CPU和GPU加速,适合大规模数据。
  • 常见应用场景
    • 推荐系统:根据用户兴趣向量推荐相似商品或内容。
    • 图像检索:将图片转为向量,实现"以图搜图"。
    • 文本检索:对文本向量化,快速查找相似文档或句子。
    • 人脸识别:根据人脸特征向量快速匹配身份。
    • 知识库问答(LLM相关):在知识库中检索与问题最相关的文本片段。

2.安装

根据自己的情况选择安装 cpu版本还是gpu版本

shell 复制代码
# cpu版本
pip install faiss-cpu

# GPU版本
pip install faiss-gpu

3.索引

索引(Index) 是 Faiss 用于加速向量检索的核心结构。它通过预处理和压缩向量,实现以下功能:

  • 快速检索:避免暴力遍历所有向量,提升查询效率。
  • 内存优化:通过量化等技术减少内存占用。
  • 灵活度量:支持多种距离/相似度度量(如 L2、内积、余弦相似度等)。
  • 可扩展性:支持 GPU 加速和分布式部署。

3.1 索引类型

Faiss 提供多种索引类型,以适应不同数据规模、精度、速度和内存需求。主要分为精确索引和近似索引:

  • Flat: 精确的暴力搜索(适合小规模数据)。
  • IVF (Inverted File): 更高效的近似搜索,适合中到大规模数据。
  • HNSW (Hierarchical Navigable Small World): 高效的近似搜索,适合大规模数据。

3.1.1 精确索引(Exact Search)

IndexFlatL2 / IndexFlatIP ,

  • 原理:暴力检索,计算查询向量与所有向量的距离(L2 或内积)。
  • 特点:结果精确,但速度随数据量线性增长,适合小规模数据。
python 复制代码
import faiss
import numpy as np

# 创建一个随机的 10000 条 128 维向量
d = 128  # 向量维度
nb = 10000  # 向量数量
np.random.seed(1234)
# 生成一个形状为 (10000, 128) 的二维数组。数组中的每个元素都是一个在 [0.0, 1.0) 区间内均匀分布的随机浮点数。
#  Faiss 库要求输入的向量数据必须是 float32 类型的 NumPy 数组。如果使用 Python 默认的 float64 类型,Faiss 会报错。
xb = np.random.random((nb, d)).astype('float32')

# 创建一个 Faiss 索引
index = faiss.IndexFlatL2(d)  # 使用 L2 距离度量创建 Flat 索引

# 向索引中添加向量.一旦 xb 被添加到索引中,这个索引就变成了一个"向量数据库"。
index.add(xb)

print("Number of vectors in the index:", index.ntotal)
  • IndexIVFFlat(倒排索引)
    • 原理:先用 K-means 将向量空间划分为 nlist 个簇,查询时只在最近的 nprobe 个簇内精确检索。
    • 特点:速度快于 Flat,但需训练,精度略低于精确索引。
python 复制代码
    # 创建一个 IVF 索引(适用于更大规模数据集)
    d = 128  # 向量维度
    nb = 10000  # 向量数量
    nlist = 100  # 索引分桶的数量
    quantizer = faiss.IndexFlatL2(d)  # 使用 Flat 索引作为量化器
    index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)

    xb = np.random.random((nb, d)).astype('float32')
    # 训练索引(需要一个大部分数据的子集来训练索引)
    index_ivf.train(xb)

    # 向索引中添加数据
    index_ivf.add(xb)

    print("Number of vectors in IVF index:", index_ivf.ntotal)
  • HNSW 索引

HNSW(Hierarchical Navigable Small World,分层可导航小世界) 是一种基于图的近似最近邻搜索(ANN)算法。它通过构建多层图结构,在高维向量空间中实现对数级复杂度的快速搜索,并保持高召回率。非常适合大规模、高维向量检索场景(如 RAG、推荐、图像/文本检索等)。

python 复制代码
d = 128  # 向量维度
nb = 10000  # 向量数量

# 创建一个 HNSW 索引
index_hnsw = faiss.IndexHNSWFlat(d, 32)  # 32 是 HNSW 图的连接数
xb = np.random.random((nb, d)).astype('float32')
# 向索引中添加向量
index_hnsw.add(xb)

print("Number of vectors in HNSW index:", index_hnsw.ntotal)

3.1.2 近似索引(Approximate Search)

有一个查询向量,然后找出与之最相似的向量。

  • 搜索最近邻向量
python 复制代码
# 创建一个随机查询向量
xq = np.random.random((5, d)).astype('float32')  # 5 个查询向量

# 执行最近邻搜索,返回最相似的 5 个向量
k = 5  # 查找最相似的 5 个向量
D, I = index.search(xq, k)  # D 是距离,I 是索引

print("Distances of the nearest neighbors:\n", D)
print("Indices of the nearest neighbors:\n", I)

D 是距离矩阵,I 是索引矩阵,分别表示每个查询向量与最相似的 k 个向量之间的距离和对应的索引。

  • 搜索时的优化(IVF 或 HNSW)
python 复制代码
# 在 IVF 索引中执行搜索
index_ivf.nprobe = 10  # 搜索时考虑的桶的数量
D_ivf, I_ivf = index_ivf.search(xq, k)

print("Distances of the nearest neighbors (IVF):\n", D_ivf)
print("Indices of the nearest neighbors (IVF):\n", I_ivf)
python 复制代码
# 在 HNSW 索引中执行搜索
index_hnsw.hnsw_efSearch = 32  # 增加搜索时的图搜索精度
D_hnsw, I_hnsw = index_hnsw.search(xq, k)

print("Distances of the nearest neighbors (HNSW):\n", D_hnsw)
print("Indices of the nearest neighbors (HNSW):\n", I_hnsw)

3.2 保存和加载索引

索引可以保存到磁盘上:

python 复制代码
faiss.write_index(index, "index_flat.index")

从文件中加载索引:

python 复制代码
index_loaded = faiss.read_index("index_flat.index")
print("Loaded index has", index_loaded.ntotal, "vectors.")

使用GPU加速(可选):

如果你有 GPU,Faiss 支持在 GPU 上加速向量搜索。首先,你需要安装 GPU 版本的 Faiss,然后将索引移动到 GPU:

python 复制代码
import faiss
res = faiss.StandardGpuResources()  # GPU 资源管理
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)  # 将索引移动到 GPU

# 搜索时使用 GPU 加速
D_gpu, I_gpu = gpu_index.search(xq, k)

3.3 小结

  • 创建索引 :使用不同的索引类型(IndexFlatL2, IndexIVFFlat, IndexHNSWFlat 等)来创建 Faiss 索引。
  • 插入数据 :使用 .add() 方法将向量插入到索引中。
  • 执行搜索 :使用 .search() 方法进行最近邻搜索。
  • GPU 加速:如果需要更高的性能,可以使用 Faiss 的 GPU 版本来加速搜索。

4.使用Faiss存储和检索人脸特征

shell 复制代码
# GPU版本
pip install faiss-gpu
python 复制代码
from insightface.app import FaceAnalysis
import os
import cv2
import pickle
import numpy as np

4.1 初始化 FaceAnalysis 模型

python 复制代码
# 初始化 FaceAnalysis 模型
def init_model():
    app = FaceAnalysis(name='buffalo_l', 
                       providers=['CUDAExecutionProvider','CPUExecutionProvider'], # 优先使用GPU
                       allowed_modules=['detection', 'recognition'],
                       # 项目根目录作为模型目录,它会自动创建models文件夹
                       root='.',
          )  # 只加载检测和识别模块
    app.prepare(ctx_id=0, det_thresh=0.5, det_size=(640, 640))  # ctx_id=-1 表示使用 CPU
    print(f"模型初始化完成,模型文件路径 {os.path.abspath(app.model_dir)}")
    return app

4.2 初始化索引

python 复制代码
# 初始化 Faiss 索引
def init_faiss_index(dim=512, index_type="HNSW"):
    """
    初始化 Faiss 索引
    dim: 特征向量维度
    index_type: 索引类型,支持 HNSW、IVF、Flat
    """
    if index_type == "HNSW":
        # HNSW 索引,适合高维向量,查询速度快
        index = faiss.IndexHNSWFlat(dim, 32)  # 32 是 HNSW 的参数
        index.hnsw.efConstruction = 40  # 构建索引时的参数
        index.hnsw.efSearch = 16  # 查询时的参数
    elif index_type == "IVF":
        # IVF 索引,适合大规模数据
        nlist = 100  # 聚类中心数量
        index = faiss.IndexIVFFlat(faiss.IndexFlatIP(dim), dim, nlist, faiss.METRIC_INNER_PRODUCT)
    else:
        # Flat 索引,暴力搜索,适合小规模数据
        index = faiss.IndexFlatIP(dim)  # IP 表示内积(余弦相似度)
    
    return index

4.3 提取人脸特征并添加到Faiss索引

python 复制代码
# 提取人脸特征并添加到 Faiss 索引
def extract_and_add_to_index(app, image_path, index, face_ids, prefix="face"):
    """
    提取图像中所有人脸特征并添加到 Faiss 索引
    app: FaceAnalysis 模型
    image_path: 图像路径
    index: Faiss 索引
    face_ids: 人脸 ID 列表
    prefix: 人脸 ID 前缀
    """
    # 读取图像
    img = cv2.imread(image_path)
    if img is None:
        print(f"无法读取图像: {image_path}")
        return
    
    # 检测人脸并提取特征
    faces = app.get(img)
    if len(faces) == 0:
        print(f"在图像中未检测到人脸: {image_path}")
        return
    
    # 处理所有检测到的人脸
    print(f"在图像中检测到 {len(faces)} 张人脸")
    for i, face in enumerate(faces):
        feature = face.normed_embedding
        
        # 生成唯一的人脸 ID
        face_id = f"{prefix}_{len(face_ids)}"
        
        # 将特征添加到索引
        feature_np = np.array([feature], dtype=np.float32)
        index.add(feature_np)
        
        # 添加人脸 ID 到列表
        face_ids.append(face_id)
        
        print(f"人脸 {i+1} 已添加到索引")
        print(f"人脸 ID: {face_id}")
        print(f"人脸位置: {face.bbox}")

4.4 保存 Faiss 索引和人脸 ID

python 复制代码
def save_index_and_ids(index, face_ids, save_dir):
    """
    保存 Faiss 索引和人脸 ID
    index: Faiss 索引
    face_ids: 人脸 ID 列表
    save_dir: 保存目录
    """
    # 确保保存目录存在
    os.makedirs(save_dir, exist_ok=True)
    
    # 保存 Faiss 索引
    index_path = os.path.join(save_dir, "face_index.faiss")
    faiss.write_index(index, index_path)
    
    # 保存人脸 ID 列表
    ids_path = os.path.join(save_dir, "face_ids.txt")
    with open(ids_path, "w") as f:
        for face_id in face_ids:
            f.write(f"{face_id}\n")
    
    print(f"索引已保存到: {index_path}")
    print(f"人脸 ID 列表已保存到: {ids_path}")

4.5 从文件中加载Faiss索引和ID

python 复制代码
def load_index_and_ids(save_dir):
    """
    加载 Faiss 索引和人脸 ID
    save_dir: 保存目录
    返回: (Faiss 索引, 人脸 ID 列表)
    """
    # 加载 Faiss 索引
    index_path = os.path.join(save_dir, "face_index.faiss")
    if not os.path.exists(index_path):
        print(f"索引文件不存在: {index_path}")
        return None, None
    index = faiss.read_index(index_path)
    
    # 加载人脸 ID 列表
    ids_path = os.path.join(save_dir, "face_ids.txt")
    if not os.path.exists(ids_path):
        print(f"人脸 ID 文件不存在: {ids_path}")
        return None, None
    with open(ids_path, "r") as f:
        face_ids = [line.strip() for line in f]
    
    print(f"成功加载索引,包含 {len(face_ids)} 张人脸特征")
    return index, face_ids

4.6 构建新索引主程序

python 复制代码
if __name__ == "__main__":
    # 初始化模型
    app = init_model()
    source_path = 'face_database/wangbaoqiang.png'
    database_path = 'face_database/faiss'
    # 创建保存特征索引目录(如果不存在)
    os.makedirs(database_path, exist_ok=True)
    # 先提取一个特征获取维度
    img = cv2.imread(source_path)
    if img is None:
        print(f"无法读取图像: {source_path}")
        exit(1)
    faces = app.get(img)
    if len(faces) == 0:
        print(f"在图像中未检测到人脸: {source_path}")
        exit(1)
    dim = len(faces[0].normed_embedding)  
    # 初始化索引
    index = init_faiss_index(dim)
    face_ids = []
    
    # 提取特征并添加到索引
    extract_and_add_to_index(app, source_path, index, face_ids)
    
    # 保存索引
    save_index_and_ids(index, face_ids, database_path)
latex 复制代码
模型初始化完成,模型文件路径 /home/qy/insightface_demo/models/buffalo_l
在图像中检测到 1 张人脸
人脸 1 已添加到索引
人脸 ID: face_0
人脸位置: [121.27753  84.08319 284.78592 294.70786]
索引已保存到: face_database/faiss/face_index.faiss
人脸 ID 列表已保存到: face_database/faiss/face_ids.txt

注意 numpy 如果是 2.0 以上版本,需要降级到 2.0以下,因为 Faiss 当前的版本 1.7.2 不支持 numpy 2.0以上版本

4.7 向现有索引添加特征

python 复制代码
if __name__ == "__main__":
    # 初始化模型
    app = init_model()
    print("向现有索引添加特征...")
        
    # 加载现有索引
    database_path = 'face_database/faiss'
    source_path = 'face_database/wuqilong.png'
    
    index, face_ids = load_index_and_ids(database_path)
    if index is None or face_ids is None:
        print("无法加载现有索引,正在创建新索引...")
        # 初始化新索引
        img = cv2.imread(source_path)
        if img is None:
            print(f"无法读取图像: {source_path}")
            exit(1)
        faces = app.get(img)
        if len(faces) == 0:
            print(f"在图像中未检测到人脸: {source_path}")
            exit(1)
        dim = len(faces[0].normed_embedding)
        index = init_faiss_index(dim, "HNSW")
        face_ids = []
    
    # 提取特征并添加到索引
    extract_and_add_to_index(app, source_path, index, face_ids)
    
    # 保存更新后的索引
    save_index_and_ids(index, face_ids,database_path)
latex 复制代码
模型初始化完成,模型文件路径 /home/qy/insightface_demo/models/buffalo_l
向现有索引添加特征...
成功加载索引,包含 1 张人脸特征
在图像中检测到 1 张人脸
人脸 1 已添加到索引
人脸 ID: face_1
人脸位置: [339.06497 103.46757 658.17676 490.30322]
索引已保存到: face_database/faiss/face_index.faiss
人脸 ID 列表已保存到: face_database/faiss/face_ids.txt

4.8 识别图像中的人脸

python 复制代码
# 识别图像中的人脸
def recognize_faces(app, image_path, index, face_ids, k=3, threshold=0.5):
    """
    识别图像中的人脸
    app: FaceAnalysis 模型
    image_path: 图像路径
    index: Faiss 索引
    face_ids: 人脸 ID 列表
    k: 返回前 k 个最相似的结果
    threshold: 相似度阈值
    """
    # 读取图像
    img = cv2.imread(image_path)
    if img is None:
        print(f"无法读取图像: {image_path}")
        return
    
    # 检测人脸并提取特征
    faces = app.get(img)
    if len(faces) == 0:
        print(f"在图像中未检测到人脸: {image_path}")
        return
    
    print(f"在图像中检测到 {len(faces)} 张人脸")
    print("=" * 60)
    
    # 处理所有检测到的人脸
    for i, face in enumerate(faces):
        feature = face.normed_embedding
        
        # 使用 Faiss 查询最相似的人脸
        query = np.array([feature], dtype=np.float32)
        distances, indices = index.search(query, k)
        
        print(f"处理人脸 {i+1}:")
        print(f"人脸位置: {face.bbox}")
        
        # 输出匹配结果
        found_match = False
        for j in range(k):
            idx = indices[0][j]
            sim = distances[0][j]
            if sim > threshold:
                found_match = True
                print(f"  匹配 #{j+1}:")
                print(f"    人脸 ID: {face_ids[idx]}")
                print(f"    相似度: {sim:.4f}")
                print(f"    判定结果: 匹配成功")
        
        if not found_match:
            print(f"  未找到匹配的人脸(相似度 < {threshold})")
        
        print("=" * 60)
python 复制代码
if __name__ == "__main__":
    # 初始化模型
    app = init_model()
    
    # 加载现有索引
    database_path = 'face_database/faiss'
    source_path = 'face_database/wbq.png' # 要识别的照片
    
    # 识别人脸
    print("识别人脸...")
    
    # 加载索引
    index, face_ids = load_index_and_ids(database_path)
    if index is None or face_ids is None:
        print("无法加载索引,识别失败")
        exit(1)
    # 识别人脸
    recognize_faces(app, source_path, index, face_ids)
latex 复制代码
模型初始化完成,模型文件路径 /home/qy/insightface_demo/models/buffalo_l
识别人脸...
成功加载索引,包含 2 张人脸特征
在图像中检测到 1 张人脸
============================================================
处理人脸 1:
人脸位置: [317.3244  244.75237 814.38354 867.85034]
  匹配 #1:
    人脸 ID: face_0
    相似度: 0.7359
    判定结果: 匹配成功
  匹配 #2:
    人脸 ID: face_1
    相似度: 2.0006
    判定结果: 匹配成功
  匹配 #3:
    人脸 ID: face_1
    相似度: 340282346638528859811704183484516925440.0000
    判定结果: 匹配成功
============================================================
相关推荐
颜颜yan_1 小时前
CANN异构计算架构深度解析:打造高效AI开发利器
人工智能·架构·昇腾·cann
GISer_Jing1 小时前
SSE Conf大会分享——UTOO WASM:AI时代的浏览器原生极速研发套件
前端·人工智能·架构·wasm
i02081 小时前
向量数据库 FAISS
faiss
海边夕阳20061 小时前
【每天一个AI小知识】:什么是Prompt?
人工智能·prompt
KKKlucifer1 小时前
数据分类分级为基的跨域流通权限动态管控技术:构建安全可控的跨域数据流通体系
大数据·数据库·人工智能
机器之心1 小时前
NeurIPS 2025 | DePass:通过单次前向传播分解实现统一的特征归因
人工智能·openai
机器之心1 小时前
NeurIPS 2025 | 英伟达发布Nemotron-Flash:以GPU延迟为核心重塑小模型架构
人工智能·openai
sali-tec1 小时前
C# 基于halcon的视觉工作流-章65 点云匹配-基于形状
开发语言·人工智能·算法·计算机视觉·c#
科学最TOP1 小时前
时间序列的“语言”:从语言模型视角理解时序基础模型
人工智能·深度学习·机器学习·时间序列