/* * Face recognition: * 1. For each photo, extract all faces. Store face rectangles. * face_id unique * photo_id foreign key * top left bottom right * identity_id * distance (0 == truth; manually assigned identity) * 2. For each face_id, create: * /${picturesPath}face-data/${face_id % 100}/ * ${face_id}-normalized * ${face_id}-original * ${face_id}-data */ "use strict"; process.env.TZ = "Etc/GMT"; console.log("Loading face-recognizer"); const config = require("config"), Promise = require("bluebird"), { mkdir, unlink } = require("./lib/util"), fr = require("face-recognition"); require("./console-line.js"); /* Monkey-patch console.log with line numbers */ const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/", faceData = picturesPath + "face-data/"; let photoDB = null, faceDataExists = false; console.log("Loading pictures out of: " + picturesPath); require("./db/photos").then(function(db) { photoDB = db; }).then(() => { console.log("DB connected."); }).then(() => { console.log("Beginning face detection scanning."); return photoDB.sequelize.query("SELECT photos.id,photos.filename,photos.width,photos.height,albums.path " + "FROM photos " + "LEFT JOIN albums ON (albums.id=photos.albumId) " + "WHERE faces=-1 ORDER BY albums.path,photos.filename", { type: photoDB.sequelize.QueryTypes.SELECT } ).then((results) => { console.log(`${results.length} photos have not had faces scanned.`); return Promise.map(results, (photo) => { const filePath = photo.path + photo.filename; console.log(`Processing ${filePath}...`); return photoDB.sequelize.transaction(function(transaction) { /* Remove any existing face data for this photo */ return photoDB.sequelize.query("SELECT id FROM faces WHERE photoId=:id", { transaction: transaction, replacements: photo, }).then((faces) => { /* For each face-id, remove any face-data files, and then remove all the entries * from the DB */ return Promise.map(faces, (id) => { return Promise.mapSeries(["-normalized.png", "-data.json" ], (fileSuffix) => { const filePath = faceData + "/" + (id % 100) + "/" + id + fileSuffix; return exists(filePath).then((result) => { console.log(`...removing ${filePath}`); return unlink(filePath); }); }); }).then(() => { return photoDB.sequelize.query("DELETE FROM faces WHERE photoId=:id", { transaction: transaction, replacements: photo, }); }).then(() => { const image = fr.loadImage(filePath), detector = fr.FaceDetector(); console.log("...detecting faces."); const faceRectangles = detector.locateFaces(image) if (faceRectangles.length == 0) { console.log("...no faces found in image."); return; } /* Create a face entry in photos for each face found. */ const faceImages = detector.detectFaces(image, 200) console.log(`...saving ${faceImages.length} faces.`); return Promise.map(faceRectangles, (face, index) => { return photoDB.sequelize.query("INSERT INTO faces (photoId,top,left,bottom,right,faceConfidence) " + "VALUES (:id,:top,:left,:bottom,:right,:faceConfidence)", { replacements: { id: photo.id, top: face.top / photo.height, left: face.left / photo.width, bottom: face.top / photo.height, right: face.right / photo.width, faceConfidence: face.confidence }, transaction: transaction }).spread((results, metadata) => { return metadata.lastID; }).then((id) => { console.log(`...DB id ${id}. Writing data and images...`); const filePathPrefix = faceData + "/" + (id % 100) + "/" + id; /* https://medium.com/@ageitgey/machine-learning-is-fun-part-4-modern-face-recognition-with-deep-learning-c3cffc121d78 */ const data = []; for (let i = 0; i < 128; i++) { data.push(Math.random() - 0.5); } fs.writeFileSync(filePathPrefix + "-data.json", JSON.stringify(data)); fr.saveImage(filePathPrefix + "-normalized.png", faceImages[index]); }); }); }); }); }); }); }); }).then(() => { console.log("Face detection scanning completed."); }).catch((error) => { console.error(error); process.exit(-1); });