UNPKG

opencv-facecrop

Version:

Npm package to autodetect and crop faces using opencv

141 lines (119 loc) 5.42 kB
const { Canvas, createCanvas, Image, ImageData, loadImage } = require('canvas'); const { JSDOM } = require('jsdom'); const { writeFileSync, existsSync, statSync } = require('fs'); module.exports = async (file, name = "output.jpg", type = "image/jpeg", quality = 0.95, factor = 0, trainingSet = "./node_modules/opencv-facecrop/resources/haarcascade_frontalface_default.xml") => { let image, src, gray, faces, faceCascade; try { await loadOpenCV().catch((e) => { throw new Error("Error: Loading OpenCV failed.\n" + e.message) }); console.log("Loading file..."); image = await loadImage(file) .catch((e) => { throw new Error("Error: Loading input image failed.\n" + e.message) }); if (image != null) src = cv.imread(image); gray = new cv.Mat(); cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); faces = new cv.RectVector(); faceCascade = new cv.CascadeClassifier(); console.log("Loading pre-trained classifier files..."); try { statSync(trainingSet); } catch (err) { throw new Error("Error: Pre-Trained Classifier file failed to load.\n" + err.message); } faceCascade.load(trainingSet); console.log("Processing...") let mSize = new cv.Size(0, 0); faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, mSize, mSize); let point1, point2; for (let i = 0; i < faces.size(); ++i) { point1 = new cv.Point(faces.get(i).x, faces.get(i).y); point2 = new cv.Point(faces.get(i).x + faces.get(i).width, faces.get(i).y + faces.get(i).height); point1.x = point1.x - factor; point1.y = point1.y - factor; point2.x = point2.x + factor; point2.y = point2.y + factor; if (point1.x < 0 || point1.y < 0 || point2.x < 0 || point2.y < 0) throw new Error('Error: Factor passed is too high/low.'); const canvas = createCanvas(point2.x - point1.x, point2.y - point1.y); let rect = new cv.Rect(point1.x, point1.y, point2.x - point1.x, point2.y - point1.y); console.log('Rendering output image...') let dst = src.roi(rect); console.log("Source File dimension: " + src.size().width + "x" + src.size().height); console.log("Destination File dimension: " + dst.size().width + "x" + dst.size().height); cv.imshow(canvas, dst); let outputFilename = name.toString(); if (faces.size() > 1) { if (outputFilename.charAt(outputFilename.length - 4) == '.') outputFilename = outputFilename.substr(0, (outputFilename.length - 4)) + `-${i + 1}` + outputFilename.substr((outputFilename.length - 4), 4); else throw new Error('Error: File extension should be 3 characters only.'); } writeFileSync(outputFilename, canvas.toBuffer(type, { quality: quality })); console.log(outputFilename + " created successfully."); } return "Success"; } catch (e) { console.error(e.message); return e.message; } finally { if (src) src.delete(); if (gray) gray.delete(); if (faceCascade) faceCascade.delete(); if (faces) faces.delete(); } }; /** * Loads opencv.js. * * Installs HTML Canvas emulation to support `cv.imread()` and `cv.imshow` * * Mounts given local folder `localRootDir` in emscripten filesystem folder `rootDir`. By default it will mount the local current directory in emscripten `/work` directory. This means that `/work/foo.txt` will be resolved to the local file `./foo.txt` * @param {string} rootDir The directory in emscripten filesystem in which the local filesystem will be mount. * @param {string} localRootDir The local directory to mount in emscripten filesystem. * @returns {Promise} resolved when the library is ready to use. */ function loadOpenCV(rootDir = '/work', localRootDir = process.cwd()) { if (global.Module && global.Module.onRuntimeInitialized && global.cv && global.cv.imread) { return Promise.resolve() } return new Promise(resolve => { installDOM() global.Module = { onRuntimeInitialized() { // We change emscripten current work directory to 'rootDir' so relative paths are resolved // relative to the current local folder, as expected cv.FS.chdir(rootDir) resolve() }, preRun() { // preRun() is another callback like onRuntimeInitialized() but is called just before the // library code runs. Here we mount a local folder in emscripten filesystem and we want to // do this before the library is executed so the filesystem is accessible from the start const FS = global.Module.FS // create rootDir if it doesn't exists if (!FS.analyzePath(rootDir).exists) { FS.mkdir(rootDir); } // create localRootFolder if it doesn't exists if (!existsSync(localRootDir)) { mkdirSync(localRootDir, { recursive: true }); } // FS.mount() is similar to Linux/POSIX mount operation. It basically mounts an external // filesystem with given format, in given current filesystem directory. FS.mount(FS.filesystems.NODEFS, { root: localRootDir }, rootDir); } }; global.cv = require('opencv4js') }); } function installDOM() { const dom = new JSDOM(); global.document = dom.window.document; global.Image = Image; global.HTMLCanvasElement = Canvas; global.ImageData = ImageData; global.HTMLImageElement = Image; }