1. 程式人生 > 其它 >虹軟人臉識別SDK接入Milvus實現海量人臉快速檢索

虹軟人臉識別SDK接入Milvus實現海量人臉快速檢索

一、背景

人臉識別是近年來最熱門的計算機視覺領域的應用之一,而且現在已經出現了非常多的人臉識別演算法,如:DeepID、FaceNet、DeepFace等等。人臉識別被廣泛應用於景區、客運、酒店、辦公室、工地、小區等場所,極大的方便了人們的生活。在安防領域,人臉識別也展現出巨大的活力,通過人臉識別對攝像頭採集的影象進行處理,可以更快的發現可疑人員。

1:1人臉核驗通常不會過度考慮速度問題,而1:N的人臉識別場景有的時候速度是非常重要的。比如使用者想通過人臉識別快速確定圖片中的明星是誰,而後臺的星資料庫中有幾百萬甚至幾千萬的資料,一一對比將很難在短時間內返回結果,在高併發的時候更是非常佔用資源。所以使用向量近似搜尋將在大規模的人臉識別場景中顯得非常重要。

二、虹軟SDK及Milvus簡介

虹軟人臉識別SDK是一款集人臉檢測、人臉跟蹤、人臉比對、人臉查詢、人臉屬性、IR/RGB活體檢測多項能力於一身的離線人臉識別SDK。支援Windows,Linux,Android等多個平臺。支援離線服務,可在無網路環境下使用,本地化部署。有增值版免費版兩個版本。

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對兩個人臉特徵進行對比,返回相似度。
  • 使用虹軟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。

另外,如果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 << "\r" << n  << "\t" <<  n * 100 / 202599 << "\t";
		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上查詢常見的索引有:


瞭解更多索引,參考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對應的圖片分別是:

可見,Top3的人臉確實為同一個值。可以根據計算出來的distance設定一個閾值來判斷是否為同一個人。閾值可以設定為0.55-0.6左右,有需要的話可以自行測試確定一個更合適的閾值。

七、效能說明

使用虹軟SDK的ASFFaceFeatureCompare介面單執行緒檢索20萬人臉需要156ms。Milvus(執行在虛擬機器中)使用預設FLAT索引,檢索20萬人臉需要168ms,建立IVF_FLAT 索引並且搜尋nprobe設定為16時耗時70ms。

在高併發場景下,使用GPU版的Milvus可以很大程度的減少搜尋時間,並且可以通過設定引數獲得一個理想的召回率。但是在低併發且資料量少的時候,推薦之間使用ASFFaceFeatureCompare介面。

八、補充

關於相似度計算方式,Milvus中常用的有兩種:

  • 歐氏距離(L2)
  • 內積 (IP)

當向量歸一化後,這兩種計算方式是等價的。虹軟SDK提取的人臉特徵是經過歸一化的,所以選擇這兩種計算方式都是可以的。

全部程式碼已經上傳github https://github.com/Memory2414/milvus-arcface

如果你連OpenCV環境也懶得配置,也可以在這裡下載已經配置好的虹軟SDK和OpenCV的環境(VS2013),提取碼:atkw。

瞭解更多人臉識別產品相關內容請到虹軟視覺開放平臺