• 虹软人脸识别SDK接入Milvus实现海量人脸快速检索


    一、背景

    人脸识别是近年来最热门的计算机视觉领域的应用之一,而且现在已经出现了非常多的人脸识别算法,如:DeepID、FaceNet、DeepFace等等。人脸识别被广泛应用于景区、客运、酒店、办公室、工地、小区等场所,极大的方便了人们的生活。在安防领域,人脸识别也展现出巨大的活力,通过人脸识别对摄像头采集的图像进行处理,可以更快的发现可疑人员。

    1:1人脸核验通常不会过度考虑速度问题,而1:N的人脸识别场景有的时候速度是非常重要的。比如用户想通过人脸识别快速确定图片中的明星是谁,而后台的星数据库中有几百万甚至几千万的数据,一一对比将很难在短时间内返回结果,在高并发的时候更是非常占用资源。所以使用向量近似搜索将在大规模的人脸识别场景中显得非常重要。

    二、虹软SDK及Milvus简介

    虹软人脸识别SDK是一款集人脸检测、人脸跟踪、人脸比对、人脸查找、人脸属性、IR/RGB活体检测多项能力于一身的离线人脸识别SDK。支持Windows,Linux,Android等多个平台。支持离线服务,可在无网络环境下使用,本地化部署。有增值版免费版两个版本。
    20210510081901198.png

    Milvus 是一款开源的向量相似度搜索引擎,提供了Python、Java、Go、C++、RESTful 等API接口,支持针对 TB 级向量的增删改操作和近实时查询,具有高度灵活、稳定可靠以及高速查询等特点。Milvus 集成了 Faiss、NMSLIB、Annoy 等广泛应用的向量索引库,提供了一整套简单直观的 API,让你可以针对不同场景选择不同的索引类型。此外,Milvus 还可以对标量数据进行过滤,进一步提高了召回率,增强了搜索的灵活性。

    三、开发环境

    本文中虹软SDK使用C++调用,Milvus使用Python API。如需使用C++版本的Milvus API 请自行编译。

    本文代码所需环境:

    1. 虹软人脸识别SDK4.0增值版
    2. Milvus 1.0.0
    3. OpenCV 2.4.9
    4. VS 2013
    5. Python 3.6 +(低于3.6可能无法安装pymilvus)

    四、虹软人脸识别SDK使用简介

    虹软人脸识别SDK使用非常简单。对于一般的人脸识别流程:

    1. 调用ASFOnlineActivation在线激活,激活后会生成激活文件,下次再运行就不用再次激活了。
    2. 调用ASFInitEngine初始化引擎,在这里可以选择人脸检测模式或人脸追踪模式(人脸追踪更快)以及传入其他参数。
    3. 调用ASFDetectFaces检测人脸,得到一帧图像里所有的人脸框。
    4. 调用ASFFaceFeatureExtract提取人脸特征
    5. 调用ASFFaceFeatureCompare对两个人脸特征进行对比,返回相似度。

    20210513094841420.png

    • 使用虹软SDK的时候需要注意,每次调用ASFDetectFacesASFFaceFeatureExtract等接口时保存结果的位置是固定的,并且这个位置是在初始化引擎时就确定好了的,返回的结构仅仅是指向这个位置的一个指针,也就是说下一次调用ASFDetectFaces就会覆盖上一次ASFDetectFaces的结果,如果需要保存上一次的结果,请将这部分内存copy出来。 虹软这样做的好处是函数不会因为申请不到内存而失败,并且不会造成内存泄漏。

    这只是最简单的人脸识别流程,除此之外,虹软人脸识别SDK还支持RGB活体识别,IR活体识别,口罩检测,闭眼检测,遮挡检测,图像质量检测等等功能。更多文档参考虹软文档中心

    五、Milvus环境搭建

    Milvus最简单的安装方式是通过docker安装。Milvus有CPU版和GPU版,这里以CPU版为例。可以参考Milvus官方参考文档。 https://milvus.io/cn/docs/v1.0.0/milvus_docker-cpu.md

    1. 安装CentOS或Ubuntu,我使用的这个 https://vault.centos.org/7.4.1708/isos/x86_64/CentOS-7-x86_64-DVD-1708.iso

    2. 安装Docker,使用官方安装脚本自动安装

    curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
    
    1. 拉取 Milvus 镜像
    sudo docker pull milvusdb/milvus:1.0.0-cpu-d030521-1ea92e
    
    1. 下载Milvus配置文件
    mkdir -p /home/$USER/milvus/conf
    cd /home/$USER/milvus/conf
    wget https://raw.githubusercontent.com/milvus-io/milvus/v1.0.0/core/conf/demo/server_config.yaml
    

    如果无法通过 wget 命令下载配置文件,也可以在 /home/$USER/milvus/conf目录下创建 server_config.yaml 文件,然后将 server_config.yaml文件 的内容复制到你创建的配置文件中。

    1. 启动 Milvus Docker 容器
    sudo docker run -d --name milvus_cpu_1.0.0 
    -p 19530:19530 
    -p 19121:19121 
    -v /home/$USER/milvus/db:/var/lib/milvus/db 
    -v /home/$USER/milvus/conf:/var/lib/milvus/conf 
    -v /home/$USER/milvus/logs:/var/lib/milvus/logs 
    -v /home/$USER/milvus/wal:/var/lib/milvus/wal 
    milvusdb/milvus:1.0.0-cpu-d030521-1ea92e
    

    使用sudo docker ps确认 Milvus 运行状态。

    如果Milvus没有正常运行,可以通过sudo docker logs milvus_cpu_1.0.0查看错误日志。
    如果CPU不支持SSE42、AVX、AVX2、AVX512中的一个,可能无法启动Milvus。

    如需安装GPU版,参考GPU版安装

    六、快速检索实现

    人脸识别流程简介

    前面已经介绍了使用虹软SDK人脸识别的基本流程,现在的人脸识别基本上流程都是一样的。这里再简单说明一下一般人脸识别的三个步骤:

    1. 人脸检测

    给出图像,获取图像中人脸的位置。有的也会获取人脸的一些关键点、角度等信息,用来对人脸进行对齐。

    2. 提取特征

    将检测出来的人脸图像截取出来,通过神经网络进行特征提取。提取出来的通常是一个128维或者256维的特征向量(通常还会加入归一化等操作)。

    3. 特征对比

    将上一步提取出来的特征向量进行对比,计算两个向量的距离,再对距离简单的处理就可以得到两个人脸相似度。常见的相似度计算方法有欧氏距离、余弦相似度等。

    快速检索

    通常1:N人脸搜索最常见的办法是直接暴力搜索,对人脸库中全部人脸都进行对比,找出相似度最高的k个。虹软SDK提供了ASFFaceFeatureCompare来对两个人脸特征向量进行对比。如果人脸库过大,搜索的速度无疑会变慢,在一些对实时性要求高的场景下将很难有好的表现。通过一些向量相似度搜索的算法,可以在短的时间内对大量数据进行相似度计算,找出相似度最高的。本文使用虹软人脸识别SDK进行人脸检测和人脸特征提取。提取出来的人脸特征向量使用Milvus进行检索。

    虹软SDK如何获取特征向量

    废话不多说,直接上结果。

    C++版本虹软SDK中,人脸特征使用结构体ASF_FaceFeature结构体存储。

    typedef struct {
    	MByte*		feature;		// 人脸特征信息
    	MInt32		featureSize;	// 人脸特征信息长度	
    }ASF_FaceFeature, *LPASF_FaceFeature;
    

    查看多个feature指向的内存,稍微对机器学习有过了解的人就可以很容易的发现规律,这些数据除了前两个整数,后面的都是的浮点数,明显是经过归一化后的特征向量。

    结论:feature指向的是一个float类型的数组。前8个字节固定是浮点数类型的2004,78(可能用于区别SDK的不同版本) 。后面的2048个字节是512个浮点数。如果ASFFaceFeatureExtract设置的registerOrNot参数为false,那么这512个数据的前256个是0。

    20210510095141569.png

    另外,如果registerOrNot设置为false的话,前256个特征向量全部为0,可以忽略。我们只需要把后256个特征向量复制出来就可以了。

    //registerOrNot设置为false时,只复制后256个向量即可
    	float data[256];
    	memcpy(data, f.feature + 8 + 1024, 1024);
    

    到这里我们已经获取到了虹软SDK提取出来的人脸特征向量。

    批量提取特征向量并插入Milvus

    将CelebA数据集里面的人脸照片提取出特征向量并保存到文件中。如果使用多线程提取的话,最后copy /b *.txt res.txt即可合并成一个。

    #include "FaceEngine.h"
    #include <string>
    #include <atomic>
    #include <fstream>
    #include "TP.cpp"
    #include <windows.h>
    
    atomic_int n = 0;
    
    void task(int start, int end , int index)
    {
    	ofstream save("D:/Face/feature/" + to_string(index) + ".txt");
    	//调用前请先确保已经激活
    	FaceEngine x(ASF_DETECT_MODE_IMAGE, ASF_OP_0_ONLY, 1);
    	char file[50] = { 0 };
    	for (int i = start; i <= end; i++)
    	{
    		sprintf(file, "D:/Face/img_celeba/%06d.jpg", i);
    		Mat img = imread(file);
    		auto faces =  x.DetectFace(img);
    		if (faces.faceNum == 1)
    		{
    			auto face = x.GetSingleFace(faces,0);
    			auto f = x.GetFaceFeature(img,face);
    			float data[256];
    			memcpy(data, f.feature + 8 + 1024, 1024);
    			save << i << "|";
    			for (int u = 0; u < 256; u++)
    				save << data[u] << "|";
    			save << endl;
    		}
    		n++;
    	}
    
    }
    
    int main()
    {
    	ThreadPool pool(2);
    	pool.AddTask(bind(task, 1, 100000, 1));
    	pool.AddTask(bind(task, 100001, 202599, 2));
    	
    	while (n < 202599)
    	{
    		cout << "
    " << n  << "	" <<  n * 100 / 202599 << "	";
    		Sleep(1000);
    	}
    }
    
    

    为了简化虹软SDK的使用,我对SDK简单封装了一下,还有一个简单的线程池实现,可以在文末链接下载。

    上面已经将特征向量保存到txt中了,接下来将20万特征向量插入Milvus(20万数据量有点少,不过由于我的电脑配置较低,提取20万个人脸的特征向量花了接近2个小时,这里就不添加过多数据了。有条件的话可以添加更多数据。)。也可以通过编译Milvus的C++ SDK,提取插入一步到位。 首先安装一下pymilvus pip3 install pymilvus==1.0.1

    from milvus import Milvus, IndexType, MetricType, Status
    import numpy as np
    
    m = Milvus(host='IP', port='19530')
    
    # 创建collection
    param = {
        'collection_name':'face',
        'dimension':256,
        'index_file_size':256,
        'metric_type':MetricType.IP #相似度计算方式使用內积
    }
    print(m.create_collection(param))
    
    
    num = 200000
    step = 5000
    now = 0
    
    def GetBatch(data):
        global now
    
        ids = np.zeros(step,dtype=np.int32)
        vects = np.zeros((step,256),dtype=np.float32)
    
        for i in range(step):
            tmp = data[i+now].split("|")
            ids[i] = int(tmp[0])
            for u in range(256):
                vects[i][u] = float(tmp[u+1])
        now += step
        return ids.tolist() , vects.tolist()
    
    # 将所以人脸向量插入Milvus
    data = open("G:\feature\res.txt").readlines()
    for i in range(int(num / step)):
        ids , vs = GetBatch(data)
        res = m.insert(collection_name='face', records=vs, ids=ids)
        print(i)
    
    

    这里要注意的是,Milvus的Python SDK插入时使用的是list,numpy创建的数据需要使用tolist()来转成list

    默认插入后是使用的FLAT索引(暴力搜索),暴力搜索的速度最慢,但召回率为100%,如果数据量很大,可以通过建立其他的索引来加快检索速度。在CPU上查询常见的索引有:

    20210512152535316.png
    了解更多索引,参考Milvus官方文档

    创建索引:

    # `ivf_param` 是创建索引的参数,`IVF_FLAT`是索引类型。
    ivf_param = {'nlist': 16384}
    print(m.create_index('face', IndexType.IVF_FLAT, ivf_param))
    

    查询

    data = open("G:\feature\res.txt").readlines()
    ids , vs = GetBatch(data)
    
    idx = int(input("index:")) # 输入一个下标,从Batch中取出第idx个进行查询
    print("id:" , ids[idx]) # 输出下标为idx的特征向量的id,这里的id就是文件名。14就是CelebA数据集中的000014.jpg
    search_param = {'nprobe': 16}
    res = m.search(collection_name='face', query_records=[vs[idx]], top_k=3, params=search_param)
    print(res)
    

    查询batch里面随便一项得到结果:

    index:12
    id: 14
    (Status(code=0, message='Search vectors successfully!'), [
    	[
    		(id:14, distance:1.0000004768371582),
    		(id:39306, distance:0.8084499835968018),
    		(id:109420, distance:0.776871919631958)
    	]
    ])
    

    Milvus搜索到的tok3个相似度最高的id是14、39306、109420(id就是文件名编号,14就是000014.jpg)。14就是这个文件本身,所以计算出来內积为1,39306和109420的內积分别是0.8084、0.7769。这三个id对应的图片分别是:

    2021051310122962.png

    可见,Top3的人脸确实为同一个值。可以根据计算出来的distance设置一个阈值来判断是否为同一个人。阈值可以设置为0.55-0.6左右,有需要的话可以自行测试确定一个更合适的阈值。

    七、性能说明

    使用虹软SDK的ASFFaceFeatureCompare接口单线程检索20万人脸需要156ms。Milvus(运行在虚拟机中)使用默认FLAT索引,检索20万人脸需要168ms,建立IVF_FLAT 索引并且搜索nprobe设置为16时耗时70ms。

    在高并发场景下,使用GPU版的Milvus可以很大程度的减少搜索时间,并且可以通过设置参数获得一个理想的召回率。但是在低并发且数据量少的时候,推荐之间使用ASFFaceFeatureCompare接口。

    八、补充

    关于相似度计算方式,Milvus中常用的有两种:

    • 欧氏距离(L2)

    20210510112602262.png

    • 内积 (IP)

    20210510112642814.png

    当向量归一化后,这两种计算方式是等价的。虹软SDK提取的人脸特征是经过归一化的,所以选择这两种计算方式都是可以的。

    全部代码已经上传github https://github.com/Memory2414/milvus-arcface

    如果你连OpenCV环境也懒得配置,也可以在这里下载已经配置好的虹软SDK和OpenCV的环境(VS2013),提取码:atkw。

    了解更多人脸识别产品相关内容请到虹软视觉开放平台

  • 相关阅读:
    【CITE】C# 如何 实现一个窗体和另一个窗体始终保持相对的位置
    【CITE】5个最优秀的Java和C#代码转换工具
    【EasyX】RGB to Gray
    【Problem solved】发现输入法都是仅桌面使用,无法输入中文时
    【Problem solved】 error C2665: “loadimage”: 2 个重载中没有一个可以转换所有参数类型
    Software Engineering Training in 2016 summer: snake
    hello world!
    数组名和指针
    取得一个给定的地址所存放的值,将给定值存放在给定地址
    搭建hadoop集群 单机版
  • 原文地址:https://www.cnblogs.com/feishixin123/p/14874641.html
Copyright © 2020-2023  润新知