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

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