Wie man sich an jeden persönlich erinnert oder wie man effektiv nach Gesichtern in einer großen Datenbank sucht

Über mich


Hallo Habr! Mein Name ist Pavel, ich arbeite als technischer Direktor in einem Unternehmen, das sich mit der Herstellung von IoT-GerÀten beschÀftigt. Mit unserem patentierten Sensornetzwerkprotokoll produzieren wir viele Dinge - von Smart-Home-Controllern bis hin zu Smart-Metering-GerÀten.


Sie fungieren auch als Generaldirektor des IT-Unternehmens. In der Vergangenheit war ACM ICPC-Halbfinalist der Weltcup-Programmierung.


Motivation


Ich schreibe diesen Artikel, weil unser Team ungefĂ€hr einen Monat getötet hat, um eine Lösung zu finden (weitere zwei Wochen, um Tests zu implementieren und zu schreiben), um erkannte Personen in der Datenbank zu speichern und effizient zu finden, um Zeit fĂŒr Sie in Ihren Projekten zu sparen. Spoiler: Sie fanden nichts Fertiges wie ein cooles Plug-In fĂŒr das vorhandene DBMS, aber die Fristen waren hoch. Deshalb haben wir unser eigenes DBMS fĂŒr genau diese Aufgabe geschrieben (Speichern einer großen Anzahl von Gesichtseinbettungen). Mein Artikel behauptet in keiner Weise, ein erschöpfender Leitfaden zu sein, aber ich hoffe, dass er einen Ausgangspunkt fĂŒr das weitere Studium und die Entwicklung unserer Gedanken bietet.


Das Einbetten ist eine Abbildung von einem diskreten Vektor kategorialer Merkmale in einen kontinuierlichen Vektor mit einer vorbestimmten Dimension.

Wo hat alles angefangen?


- , , , / , . , , , , . "" 87%, , , . , 3 . . 2-3 . — 
 , , - "" ( ) 6 . .



, , : ( ) , , , , , , . 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 , .


Bild


, . , , . .


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. , .


Wenn es andere Möglichkeiten gibt, dieses Problem zu lösen, werde ich gerne in den Kommentaren darĂŒber lesen.


Und die Moral dieser Fabel lautet: Die Menge ist sogar gefallen der LöweOpen-Source-Lösungen nach Zeit getestet. Lerne Algorithmen, Pasans :)


All Articles