Node.js 얼굴 이미지 분석 서버

Node.js 와 face-api.js 라이브러리를 이용하여 얼굴 이미지를 분석해주는 서버를 개발합니다.

# Node.js
title

준비

※ Node.js 에 대한 사전 지식이 필요할 수 있습니다.

이 예제는 face-api.js 의 예제 소스를 일부 참고하여 제작되었습니다.

여기서는 이미지 파일을 받아 얼굴의 성별과 나이 분석 기능을 수행하도록 하였습니다.


디렉토리를 하나 생성하고 빠르게 구성합니다.

npm init -y

필요한 패키지들을 설치합니다.

npm i express cors multer face-api.js canvas @tensorflow/tfjs-node@1.7.0
npm i -D nodemon @babel/core @babel/cli @babel/node @babel/preset-env babel-loader @babel/polyfill
  • express - Node.js 프레임워크
  • cors - cors 보안 정책
  • multer - 파일 업로드
  • face-api.js - 얼굴 인식 라이브러리
  • canvas - Node.js canvas
  • @tensorflow/tfjs-node - Node.js tensorflow
  • nodemon - 개발용 서버 재실행 도구
  • babel - 코드 트랜스파일링





개발 환경 구성

package.json 파일의 scripts 를 작성합니다.

"scripts": {
  "start": "node src/index.js",
  "build": "babel src/ -d dist/",
  "dev": "nodemon src/index.js --exec babel-node"
},

명령을 상황에 맞게 사용하실 수 있습니다.


ES6 문법 사용을 위해 .babelrc 파일을 생성하고 작성합니다.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "esmodules": true
        }
      }
    ]
  ]
}

링크에서 필요한 모델을 받아 models 디렉토리에 저장합니다.

이제 필요한 환경 구성은 끝났고 src 디렉토리 내에서 개발을 진행하게 됩니다.





서버 구축

src 디렉토리에 index.js 파일을 생성하고 코드를 작성합니다.

import express from 'express';
import cors from 'cors';

const app = express();
const prod = process.env.NODE_ENV === 'production';

app.set('port', prod ? process.env.PORT : 6060);

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('*', (err, req, res, next) => {
  console.log(err);
  res.status(500).send('error');
});

app.get('/', (req, res, next) => res.status(200).json('Hello, Server.'));

app.listen(app.get('port'), console.log(`Server listening on ${app.get('port')}.`));

그리고 아래 명령어를 통해 서버를 실행합니다.

npm run dev

서버가 실행됬다는 로그가 콘솔에 찍히면 6060 포트에 접속하여 확인이 가능합니다.

서버 실행 확인





라우터 구성

src/routes 디렉토리에 face.js 파일을 생성 및 코드를 작성하고 index.js 에 임포트합니다.

src/routes/face.js

import express from 'express';

const router = express.Router();

router.post('/gender', (req, res, next) => res.status(200).json('gender'));

router.post('/age', (req, res, next) => res.status(200).json('age'));

export default router;

src/index.js

import faceRouter from './routes/face';
app.use('/face', faceRouter);

이제 지정한 경로에 접속이 가능합니다.





기능 구현

서버에 이미지 파일을 업로드할 수 있도록 face.js 에 코드를 작성합니다.

import express from 'express';
import multer from 'multer';

const router = express.Router();

const storage  = multer.diskStorage({
  destination(req, file, cb) {
    cb(null, 'uploads/');
  },
  filename(req, file, cb) {
    cb(null, 'face.jpg');
  },
});
const uploadWithOriginalFilename = multer({ storage });

router.post('/gender', uploadWithOriginalFilename.single('image'), (req, res, next) => {
  res.status(200).json('gender');
});

router.post('/age', uploadWithOriginalFilename.single('image'), async (req, res, next) => {
  res.status(200).json('age');
});

export default router;

이미지 파일을 받아 uploads 디렉토리에 face.jpg 라는 이름으로 저장합니다.

강제로 jpg 확장자 변환이 되니 주의하세요.


저는 테스트를 위해 Postman 이란 도구를 이용하였습니다.

Postman 구성

위처럼 form-data 에 이미지 파일을 담아 요청을 보내면 동작을 확인할 수 있습니다.


다음은 얼굴 이미지 분석 기능을 수행하기 위한 코드를 작성합니다.

src/commons 디렉토리에 3개의 파일을 생성하고 코드를 작성합니다.

  • env.js
  • faceDetection.js
  • index.js

env.js

import '@tensorflow/tfjs-node';

import * as faceapi from 'face-api.js';

const canvas = require('canvas');

const { Canvas, Image, ImageData } = canvas;
faceapi.env.monkeyPatch({ Canvas, Image, ImageData });

export { canvas };

'@tensorflow/tfjs-node' 라이브러리의 버전의 호환성을 주의하세요.

필자의 경우 '1.7.0' 버전에서 잘 동작하는 것을 확인할 수 있었습니다.


faceDetection.js

import { nets, SsdMobilenetv1Options } from 'face-api.js';

const getFaceDetectorOptions = () => (new SsdMobilenetv1Options({ minConfidence: 0.5 }));

export const faceDetectionNet = nets.ssdMobilenetv1;
export const faceDetectionOptions = getFaceDetectorOptions();

index.js

export { canvas } from './env';
export { faceDetectionNet, faceDetectionOptions } from './faceDetection';

작성이 끝나면 src/routes/face.js 파일로 돌아와서 새로 생성한 파일과 필요한 모듈들을 임포트합니다.

import { nets, detectSingleFace } from 'face-api.js';
import { canvas, faceDetectionNet, faceDetectionOptions } from '../commons';

의도한 기능을 수행할 수 있는 함수 run 을 작성합니다.

const run = async (mode) => {
  const MODEL_URL = './models/';

  await faceDetectionNet.loadFromDisk(MODEL_URL);
  await nets.faceLandmark68Net.loadFromDisk(MODEL_URL);
  await nets.ageGenderNet.loadFromDisk(MODEL_URL);

  const img = await canvas.loadImage('./uploads/face.jpg');
  const result = (await detectSingleFace(img, faceDetectionOptions)
    .withFaceLandmarks()
    .withAgeAndGender());

  if (result) return result[mode];

  return 'not found';
};

먼저 필요한 모델들을 loadFromDisk 함수를 이용하여 불러옵니다.

그리고 uploads 디렉토리에 저장된 이미지를 불러와서 face-api.js 라이브러리를 이용해 분석을 진행하고 요청(파라미터)에 맞는 객체를 반환하게 됩니다.


작성이 완료된 run 함수를 라우터로 가져와 이용합니다.

router.post('/gender', uploadWithOriginalFilename.single('image'), async (req, res, next) => {
  try {
    const data = await run('gender');
    res.status(200).json(data);
  } catch (err) {
    next(err);
  }
});

router.post('/age', uploadWithOriginalFilename.single('image'), async (req, res, next) => {
  try {
    const data = await run('age');
    res.status(200).json(data);
  } catch (err) {
    next(err);
  }
});

이제 테스트를 위한 모든 코드 작성이 끝났습니다.





테스트

필자의 경우 요청을 보내면 아래와 같은 응답을 받을 수 있었습니다.

/face/age

16.881759643554688

/face/gender

"male"

제 나이인 17세에 근접한 값이 나오는 것으로 보아 정확도는 높은 것 같습니다.

필자의 경우 휴대폰 셀카를 찍은 사진으로 5번 정도 시도했는데 그 중 4번은 얼굴을 인식할 수 없다는 응답을 받았습니다.

혹시 얼굴 크기나 위치, 용량 등이 영향을 미치는지 알아보기 위해 unsplash 에서 서로 다른 한국인, 외국인의 화보들을 얻어 이용했는데 모두 성공적으로 동작하는 것을 확인할 수 있었습니다.

얼굴이 문제였던 건가...

화질 차이의 영향이 있는 것으로 예상이 됩니다.





저장소

전체 소스코드는 아래 레포지토리에서 확인이 가능합니다.

https://github.com/KHJcode/faceapijs-nodejs-blog-example



읽어주셔서 감사합니다.

😍  댓글