UNPKG

pmr-core

Version:

Core library of Poor Man's Rekognition project under CCExtractor Development

208 lines (184 loc) 7.77 kB
import { VideoCapture, ImageCapture, Image, SequenceCapture, showImage, waitKey } from "nodoface"; import { Facenet, utils } from "../"; import { FaceDetectorConfig, FaceDetector } from "../face/detection/main"; import { FaceRecogConfig } from "../face/recognition/recogniser-base"; import { ObjectBlob } from "./object-blob"; import { FaceBlob } from "../face"; import { Tensor3D, util } from "@tensorflow/tfjs-node"; import { IoUMap, IOU } from "./tracker-utils"; import { VisTracking } from './visualise'; import { FaceMatcher } from "../face/recognition/macther"; export interface TrackerConfig { capture: VideoCapture; detectorConfig?: FaceDetectorConfig; recogniserConfig?: FaceRecogConfig; inputFPS?: number; detectorFPS?: number; recogFPS?: number; checkInterval?: number; blobDelInterval?: number; iouThreshold?: number; } export class FaceTracker { capture: VideoCapture; detector: FaceDetector; recogniser: Facenet; inputFPS: number; detectorFPS: number; recogFPS: number; checkInterval: number; blobDelInterval: number; iouThreshold: number; blobs: ObjectBlob[]; recogConfig: FaceRecogConfig; detectorConfig: FaceDetectorConfig; private currFrame: Image; private lastFrame; private faceMatcher: FaceMatcher; private resultArray; private frameNo: number; constructor(config: TrackerConfig, detector: FaceDetector, recogniser: Facenet, faceMatcher: FaceMatcher) { this.capture = config.capture; this.detector = detector; this.recogniser = recogniser; this.inputFPS = config.inputFPS ? config.inputFPS : 30; this.detectorFPS = config.detectorFPS ? config.detectorFPS : 25; this.recogFPS = config.recogFPS ? config.recogFPS : 15; this.checkInterval = config.checkInterval ? config.checkInterval : 5; this.blobDelInterval = config.blobDelInterval ? config.blobDelInterval : 2; this.iouThreshold = config.iouThreshold ? config.iouThreshold : 0.75; this.recogConfig = config.recogniserConfig; this.detectorConfig = this.detectorConfig; this.faceMatcher = faceMatcher; this.resultArray = Array(); this.frameNo = 0; } async track() { while (this.capture.isOpened()) { await this.next(); VisTracking.annotate(this.currFrame, this.blobs) } } async next() { this.currFrame = this.nextFrame(); let faces = await this.detector.detect(this.currFrame); console.log('Detections: ', faces.length); await this.assignBlobs(faces); console.log('Frame no.', this.frameNo); // this.lastFrame = this.currFrame; return await this.blobs; } nextFrame() { let frame: Image; try { if (this.capture instanceof VideoCapture) { frame = this.capture.read() this.frameNo++; } } catch (error) { console.log('Error loading next frame'); } return frame; } /** * Findinding which new face belongs to which old face * @param faces */ async assignBlobs(faces: FaceBlob[]) { console.log('in assignBlobs') if (!this.blobs) { return await this.initBlobs(faces); } let faceAssigned = Array<boolean>(faces.length).fill(false); let blobAssigned = Array<boolean>(this.blobs.length).fill(false); // pass 1: Match detections by iou for (let i = 0; i < this.blobs.length; i++) { let closestIndex = -1; let closestIou = 0; for (let j = 0; j < faces.length; j++) { let iou = IOU(this.blobs[i].lastRect, faces[j].bbox); if ((iou > this.iouThreshold) && (iou > closestIou) && (this.frameNo - this.blobs[i].lastFrameNo < this.blobDelInterval) && (faceAssigned[j] == false)) { closestIndex = j; closestIou = iou; } } // update properties of blob with its matched detection if (closestIndex != -1) { this.updateBlob(faces[closestIndex], i); // update using IOU measure faceAssigned[closestIndex] = true; blobAssigned[i] = true; } } // pass 2: Match remaining blobs using distance between their face descriptors // Should be executed at recogFPS for (let i = 0; i < faceAssigned.length; i++) { if (!faceAssigned[i]) { // TODO: compute descriptors in parallel if (faces[i].descriptor == undefined) { faces[i].descriptor = await this.recogniser.embedding(utils.imageToTensor(faces[i].faceImage) as Tensor3D); } let minDist = 1000000000000; let index = -1; for (let j = 0; j < this.blobs.length; j++) { if (!blobAssigned[j]) { let d = this.recogniser.distance(faces[i].descriptor, this.blobs[j].attrs['descriptor'] as Float32Array); if (d < minDist) { minDist = d; index = j; } } } if (index != -1) { this.updateBlob(faces[i], index); // assign `i`th face of faces to blob `index`th blob of blobs faceAssigned[i] = true; blobAssigned[index] = true; } } } // Create new blob for faces that cannot be identified for (let i = 0; i < faceAssigned.length; i++) { if (!faceAssigned[i]) { this.blobs.push( new ObjectBlob('face', faces[i].bbox, this.frameNo, faces[i].confidence, { descriptor: faces[i].descriptor }) ); } } this.deleteOldBlobs(); // correct the rcognitions at every recogFPS interval if (this.frameNo % this.recogFPS == 0) { this.faceMatcher.match(this.blobs, this.recogConfig.distanceThreshold); // assign names from known names } } async initBlobs(faces: FaceBlob[]) { console.log('in initBlobs'); this.blobs = new Array<ObjectBlob>(); for (let i = 0; i < faces.length; i++) { let blob = new ObjectBlob('face', faces[i].bbox, this.frameNo, faces[i].confidence); faces[i].descriptor = await this.recogniser.embedding(utils.imageToTensor(faces[i].faceImage) as Tensor3D) blob.attrs['descriptor'] = faces[i].descriptor; this.blobs.push(blob); } await this.faceMatcher.match(this.blobs, this.recogConfig.distanceThreshold); // assign names from known names } updateBlob(face: FaceBlob, targetIndex: number) { console.log('in updateBlob: updated blob no.', targetIndex); this.blobs[targetIndex].lastRect = face.bbox; this.blobs[targetIndex].lastFrameNo = this.frameNo; this.blobs[targetIndex].confidence = face.confidence; this.blobs[targetIndex].lastImage = face.faceImage; this.blobs[targetIndex].attrs['descriptor'] = face.descriptor; } // delete blobs which are not seen since past blobDelInterval number of frames deleteOldBlobs() { let newBlobs = Array<ObjectBlob>(); this.blobs.forEach((blob, i) => { if (blob.lastFrameNo - this.frameNo < this.blobDelInterval) { newBlobs.push(blob); } }); this.blobs = newBlobs; } }