如何亲自记住每个人,或者如何有效地在大型数据库中搜索人脸

关于我自己


哈Ha!我叫帕维尔(Pavel),我在一家从事IoT设备生产的公司中担任技术总监。我们生产很多产品-从我们专利的传感器网络协议中的智能家居控制器到智能计量设备。


他们还担任IT公司的总经理。过去,ACM是ICPC世界杯的半决赛选手。


动机


我写这篇文章是因为我们的团队花了大约一个月的时间才能找到解决方案(另外两周来实施和编写测试),以在数据库中存储并有效地找到公认的人员,以便为您节省时间。剧透:他们找不到现成的DBMS的现成产品,例如酷炫的插件,但是截止日期非常快,所以我们为此任务编写了自己的DBMS(存储了大量的面部嵌入物)。我的文章绝不声称是详尽的指南,但我希望它可以为进一步研究和发展我们的思想提供一个起点。


嵌入是从分类特征的离散向量到具有预定维度的连续向量的映射。

那一切从哪里开始


不知何故,我们面临的任务是开发一个系统,为具有许多不同特征的购物中心精品店的唯一,固定和稀有访客提供记账系统,例如识别访客的年龄,精品店出入口的情绪,人​​们在购物中心的时间以及对出租人和其他重要的其他方面。租户的东西。我们毫不犹豫地开始执行,编写了一个结构,训练了级联的神经网络来解决这些问题。那时,我们以87%的输出获得了“诚实”的准确性,对于合成生成的和肿的数据来说,这似乎是一个不错的结果。随着时间的流逝,负责某些特征的图层被重新训练,标记几乎以3个班次工作。一年后,该解决方案在郊区的大约十个中小型购物中心进行了测试。最初,这是一个云解决方案,我们与2-3个购物中心网络的所有者合作。一旦其他城市开始扩展和连接,一个真正的地狱就开始了……重点不是神经元服务的负荷不断增长,而是“唯一”(就神经元的准确性而言)的数量超过了600万。然后,我们开始寻找其他解决方案来解决人员存放问题。



, , : ( ) , , , , , , . 10 200 , . , 50 . 10.


, +- , .


, , , dlib, . C++, BLAS, Python CPU. .


, dlib , 0.6, . 128.


, . , , , , , . , , k , k , , - -. , -, -.


.



. dlib .


def get_img_vector(img):
    dets = detector(img, 1)
    for k, d in enumerate(dets):
        shape = sp(img, d)
        return facerec.compute_face_descriptor(img, shape)
    return None

def prepare_database():
    database = {}

    for file in glob.glob("_images/*"):
        identity = os.path.splitext(os.path.basename(file))[0]
        img = cv2.imread(file, 1)

        database[identity] = get_img_vector(img)

    return database

def who_is_it(img, shape, database):
    face_descriptor1 = facerec.compute_face_descriptor(img, shape)

    min_dist = 100
    identity = None

    for (name, db_enc) in database.items():
        dist = distance.euclidean(db_enc, face_descriptor1)

        if dist < min_dist:
            min_dist = dist
            identity = name

    print(min_dist)
    if min_dist > 0.57:
        return None
    else:
        return str(identity)

if __name__ == "__main__":
    global sp
    sp = dlib.shape_predictor('weights/shape_predictor_face_landmarks.dat')
    global facerec
    facerec = dlib.face_recognition_model_v1('weights/dlib_face_recognition_resnet_model_v1.dat')
    global detector
    detector = dlib.get_frontal_face_detector()

    database = prepare_database()
    webcam_face_recognizer(database)

webcam_face_recognizer ( cv2- ) . who_is_it, . , , , , !


, 1 . (N*k), N — , k — . , , . , , - . .



, ?


— , , . — . , , , L2 .



scores = np.linalg.norm(face_descriptor1 - np.asarray(database.items()), axis=1)
min_el_ind = scores.argmax()

, , , .


, , nmslib. HNSW k . , . :


import nmslib

index = nmslib.init(method='hnsw', space='l2', data_type=nmslib.DataType.DENSE_VECTOR)

for idx, emb in enumerate(database.items()):
    index.addDataPoint(idx, emb)

index_params = {
    'indexThreadQty': 5,
    'skip_optimized_index': 0,
    'post': 3,
    'delaunay_type': 2,
    'M': 100,
    'efConstruction': 2000
}

index.createIndex(index_params, print_progress=True)
index.saveIndex('./db/database.bin')

HNSW .


"" . ?



, 4 . dlib , .


图片


, . , , . .


postgresql


- , , (, . ) .


:


import postgresql

def setup_db():
    db = postgresql.open('pq://user:pass@localhost:5434/db')
    db.execute("create extension if not exists cube;")
    db.execute("drop table if exists vectors")
    db.execute("create table vectors (id serial, file varchar, vec_low cube, vec_high cube);")
    db.execute("create index vectors_vec_idx on vectors (vec_low, vec_high);")

:


query = "INSERT INTO vectors (file, vec_low, vec_high) VALUES ('{}', CUBE(array[{}]), CUBE(array[{}]))".format(
            file_name,
            ','.join(str(s) for s in encodings[0][0:64]),
            ','.join(str(s) for s in encodings[0][64:128]),
        )
db.execute(query)

:


import time
import postgresql
import random

db = postgresql.open('pq://user:pass@localhost:5434/db')

for i in range(100):
    t = time.time()
    encodings = [random.random() for i in range(128)]

    threshold = 0.6
    query = "SELECT file FROM vectors WHERE sqrt(power(CUBE(array[{}]) <-> vec_low, 2) + power(CUBE(array[{}]) <-> vec_high, 2)) <= {} ".format(
        ','.join(str(s) for s in encodings[0:64]),
        ','.join(str(s) for s in encodings[64:128]),
        threshold,
    ) + \
            "ORDER BY sqrt(power(CUBE(array[{}]) <-> vec_low, 2) + power(CUBE(array[{}]) <-> vec_high, 2)) ASC LIMIT 1".format(
                ','.join(str(s) for s in encodings[0:64]),
                ','.join(str(s) for s in encodings[64:128]),
            )
    print(db.query(query))
    print('inset time', time.time() - t, 'ind', i)

10^5 (4- i5, 2,33 GHz) 0.034 .
? +- ( ). , , … , .


K-d


- , , " ", . , .


, , , .


K-d — k- . , ( , . .), .


, :


.


(N) , , , (N). , !


"" , NDA.


. , .



15 . , . . .


— . , , :)


k-tree , ( ), , ( -), 4-6 . .



, . — 1,5 , , , , , , . , , .


, , , . , mail cloud. , .


如果还有其他解决方案,我会很乐意在评论中阅读。


这个寓言的寓意是这样-人群甚至被击倒 狮子经过时间测试的开源解决方案。学习算法,pasans :)


All Articles