«تشخیص چهره» (Face Detection) و «بازشناسی چهره» (Face Recognition)، امروزه از جمله مباحث مهم و داغی هستند که توجه بسیاری از پژوهشگران و کسب و کارهای بزرگ، متوسط و کوچک را به خود جلب کرده‌اند. در این مطلب، روش بازشناسی چهره با استفاده از «ری‌اکت» (React) و «face-api.js» مورد بررسی قرار گرفته است. در مطلب تشخیص چهره با جاوا اسکریپت و ری اکت از تصاویر اعضای یک گروه موسیقی برای انجام کار تشخیص چهره استفاده شده است که احتمالا، تشخیص چهره آن‌ها حتی با چشم انسانی هم کار دشواری است.

تشخیص چهره با جاوا اسکریپت و ری اکت

در ادامه، مبحث تشخیص چهره با جاوا اسکریپت و ری اکت مورد بررسی قرار گرفته است. «face-api.js» یک «رابط برنامه‌نویسی کاربردی» (Application Programming Interface) برای تشخیص چهره و بازشناسی چهره است که با «تنسورفلو دات جی‌اس» (TensorFlow.js) کار می‌کند. اکنون و با استفاده از این API، امکان آن وجود دارد که همه فرایندهای «یادگیری عمیق» (Deep Learning) روی مرورگر و بدون نیاز به کد بک‌اند لازم برای این کار، انجام شوند.

اکنون، بدون هرگونه کد بک-اند یا تنظیمات محیطی، تنها با داشتن یک میزبان وب استاتیک، می‌توان امکان بازشناسی چهره با ری‌اکت و تنسورفلو را داشت و آن را روی هر دستگاه یا مرورگری اجرا کرد. البته، مرورگر باید امکان اجرای TensorFlow.js را داشته باشد. در پایان این مطلب، چگونگی استقرار این برنامه کاربردی ری‌اکت در صفحه گیت‌هاب، آموزش داده شده است.

همانطور که پیش‌تر نیز بیان شد، در اینجا پیاده‌سازی یک برنامه کاربردی تک صفحه‌ای برای تشخیص و بازشناسی چهره، با استفاده از ری‌اکت و کتابخانه تشخیص چهره ace-api.js انجام می‌شود. این API با یک سیستم تشخیص چهره از پیش آموزش دیده شده، نقاط برحسته چهره (Face-Landmarks) و «تراز چهره» (Face-Alignment) را شناسایی می‌کند. بنابراین، نیازی به نوشتن مدل یادگیری عمیق در «تنسورفلو» (TensorFlow) نیست.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

در واقع، کاربر حقیقتا نیازی به آن ندارد که با عملکرد یادگیری عمیق یا «شبکه‌های عصبی پیچشی» (Convolutional Neural Networks) آشنا باشد. تنها چیزی که کاربر نیاز به دانستن آن دارد، مفاهیم پایه‌ای جاوا اسکریپت و ری‌اکت است. برای فراگیری جاوااسکریپت مطالعه مطلب «آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس» توصیه می‌شود. همچنین، برای فراگیری ری‌اکت، مطالعه مطالب زیر توصیه می‌شود.

کدی که در این مطلب پیاده‌سازی شده، بسیار ساده است و جای نگرانی پیرامون یادگیری آن وجود ندارد. همچنین، توضیحات لازم پیرامون بخش‌های مختلف کد، به طور کامل ارائه شده است.

توضیحات کوتاهی پیرامون تشخیص چهره

در این بخش از مطلب تشخیص چهره با جاوا اسکریپت و ری اکت توضیحاتی اجمالی پیرامون تشخیص چهره ارائه شده است. حالتی مفروض است که فردی به یک اداره دولتی مراجعه می‌کند. مسئول مربوطه، یک کپی از مستندات فرد را دریافت می‌کند. او از فرد می‌خواهد که اثبات کند خودش است. فرد برای این منظور، کارت هویتی خود مثلا شناسنامه یا کارت ملی را ارائه می‌کند. مسئول، نام فرد و تصویری او را در کارت شناسایی مشاهده و با فردی که در مقابل خودش نشسته مقایسه می‌کند. همچنین، در حالت پیشرفته‌تری، اسم نوشته شده در کارت هویتی را وارد سیستم می‌کند تا از صحت کارت هویتی مطمئن شود و سپس، چهره فرد را با تصویر موجود روی کارت هویتی مقایسه می‌کند.

به همین شکل، در سیستم تشخیص چهره نیز اسم فرد به همراه اطلاعات چهره او ذخیره شده است. بنابراین، هنگامی که تصویر دیگری از فرد به سیستم داده می‌شود، سیستم تلاش می‌کند تا تشخیص دهد که آیا اطلاعات فرد صاحب تصویر در پایگاه داده خود دارد یا خیر و اگر دارد، اسم فرد و یا مشخصات کامل او را باز می‌گراند. این کار، توسط شبکه تشخیص چهره (Face Detection Network) انجام می‌شود. مدلی که در این پروژه برای کار تشخیص چهره استفاده شده، Tiny Face Detector نام دارد. دلیل این نام‌گذاری، سایز کوچک و موبایل‌پسند بودن آن است. API مورد استفاده در این پروژه، یک SSD mobileNet و MTCNN نیز برای تشخیص‌دهنده چهره استفاده می‌کند؛ اما در حال حاضر به ان پرداخته نخواهد شد.

هنگامی که چهره یا چهره‌ها توسط سیستم شناخته شدند، مدل تشخیص‌دهنده چهره یک جعبه محصورکننده را دور هر چهره ترسیم می‌کند و می‌گوید که چهره‌های موجود در تصویر، در کدام محل قرار گرفته‌اند. سپس، از شبکه علامت‌گذاری چهره (Face Landmark Network) برای علامت‌گذاری ۶۸ نقطه تصویر و استفاده از مدل تراز برای حصول اطمینان از اینکه چهره در مرکز قرار گرفته استفاده می‌شود. اینکار پیش از ارائه خوراک به شبکه تشخیص چهره (Face Recognition Network) انجام می‌شود.

Face Recognition Network یک شبکه عصبی از نوع RestNet-34 است که شناساگر چهره یا Face Descriptor را بازمی‌گرداند که یک بردار ویژگی شامل ۱۲۸ مقدار است. از این ۱۲۸ مقدار می‌توان برای مقایسه چهره‌ها و تشخیص چهره در تصاویر استفاده کرد.

درست مانند یک اثر انگشت، شناساگر چهره یا Face Descriptor یک مقدار یکتا برای هر چهره است. Face Descriptor برای یک فرد خاص در تصاویر گوناگون، دارای مقادیر بسیار بسیار نزدیک و مشابهی است. در این پروژه، از فاصله اقلیدسی (Euclidean Distance) برای مقایسه استفاده شده است. اگر فاصله کم‌تر از آستانه تنظیم شده باشد، می‌توان فهمید که دو تصویر متعلق به یک فرد هستند. باید توجه داشت که هر چه فاصله کم‌تر باشد، اطمینان بالاتر است.

معمولا، سیستم Face Descriptor برای هر فرد را به عنوان منبعی در کنار برچسب نام ان فرد نگهداری می‌کند. هنگامی که یک تصویر جدید به سیتسم داده می‌شود، سیستم Face Descriptor آن تصویر را با همه Face Descriptor‌های تصایر موجود مقایسه می‌کند. اگر فاصله Face Descriptor تصویر جدید با هیچ یک از تصاویر موجود در پایگاه داده کمتر از آستانه نبود، تصویر به عنوان «ناشناس» (Unknown) تعیین می‌شود.

شایان توجه است که پیش از این، در مجله فرادرس طی مطالب گوناگونی به تشخیص چهره با استفاده از روش‌ها و ابزارهای گوناگون پرداخته شد. برخی از این مطالب، در ادامه و برای مطالعه علاقه‌مندان، ارائه شده است.

در ادامه مطلب تشخیص چهره با جاوا اسکریپت و ری اکت کار پیاده‌سازی سیستم تشخیص چهره انجام می‌شود.

پیاده‌سازی سیستم تشخیص چهره با جاوا اسکریپت و ری اکت

در این بخش از مطلب تشخیص چهره با جاوا اسکریپت و ری اکت کار پیاده‌سازی سیستم تشخیص چهره با جاوا اسکریپت و ری اکت مورد بررسی قرار می‌گیرد. دو تابع وجود دارد که هدف، پیاده‌سازی آن‌ها در این برنامه است. یکی از این توابع برای شناسایی چهره از تصویر ورودی استفاده می‌شود و دیگری، چهره فرد را از یک ویدئو زنده ورودی (Live Video) تشخیص می‌دهد. کار با create-react-app، نصب react-router-dom و آغاز برنامه، شروع می‌شود.

npx create-react-app react-face-recognition
cd react-face-recognition
npm i react-router-dom
npm start

اکنون، باید مرورگر را باز کرد و به مسیر http://localhost:3000/‎ رفت. در صورتی که هنگام باز شدن صفحه، کاربر لوگو ری‌اکت را مشاهده کرد، یعنی شرایط برای ادامه کار مهیا است. اکنون، کاربر باید پوشه پروژه را با هر ویرایشگری که به آن علاقه دارد، باز کند. خروجی، باید چیزی شبیه به ساختار درختی زیر باشد.

react-face-recognition 
├── README.md 
├── node_modules 
├── package.json 
├── .gitignore 
├── public 
│   ├── favicon.ico 
│   ├── index.html 
│   └── manifest.json 
└── src 
    ├── App.css 
    ├── App.js 
    ├── App.test.js 
    ├── index.css 
    ├── index.js 
    ├── logo.svg 
    └── serviceWorker.js

اکنون، باید به src/App.js رفت و کد موجود را با کد زیر جایگزین کرد.

import React, { Component } from 'react';
import { Route, Router } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import './App.css';

import Home from './views/Home';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Router history={createHistory()}>
          <div className="route">
            <Route exact path="/" component={Home} />
          </div>
        </Router>
      </div>
    );
  }
}

export default App;

آنچه در اینجا آمده، تنها وارد کردن مولفه Home و ساخت یک «مسیر» (Route) به «/» به عنوان صفحه لندینگ است. این مولفه به سرعت ساخته می‌شود. کار با ساخت یک پوشه جدید src/views و ساخت یک فایل جدید Home.js درون این پوشه، انجام می‌شود. سپس، کد زیر را در فایل قرار داده و فایل، ذخیره می‌شود.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';

export default class Home extends Component {
  render() {
    return (
      <div>
        <h2>BNK48 Facial Recognition App</h2>
        <li>
          <Link to="/photo">Photo Input</Link>
        </li>
        <li>
          <Link to="/camera">Video Camera</Link>
        </li>
      </div>
    );
  }
}

در اینجا، دو لینک برای Photo Input ساخته شده است که لینک به “localhost:3000/photo” و Video Camera برای لینک به “localhost:3000/camera” هستند. اگر همه چیز به خوبی پیش برود، کاربر باید صفحه‌ای مانند آنچه در زیر آمده است را مشاهده کند.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

رابط برنامه‌نویسی کاربردی جاوا اسکریپت برای تشخیص چهره یا همان Face API

در این بخش از مطلب تشخیص چهره با جاوا اسکریپت و ری اکت و پیش از آنکه کار ساخت صفحه جدید ادامه پیدا کند، باید face-api.js را نصب کرد و فایل API را برای متصل شدن به ری‌اکت با استفاده از API ساخت. اکنون، به کنسول بازگشته و کتابخانه نصب می‌شود.

npm i face-api.js

کتابخانه با TensorFlow.jsو همه مولفه‌هایی که لازم است، به جز وزن‌های مدل، ارائه شده است. برای کاربرانی که با مفهوم وز‌های مدل آشنایی ندارند، در ادامه توضیحاتی پیرامون این موضوع داده می‌شود. وزن‌های مدل، در واقع وزن‌های «شبکه عصبی» (Neural Network) است که با یک مجموعه داده بزرگ آموزش داده شده است. در بررسی موردی که در این مطلب در حال انجام است، مجموعه داده در واقع هزاران تصویر از چهره انسان است.

با توجه به آنکه افراد زیادی تاکنون مدل را آموزش داده‌اند، تنها چیزی که در اینجا نیاز است، به دست آوردن وزن‌های لازم برای استفاده و قرار دادن آن‌ها در پروژه به صورت دستی است. همه وزن‌های این رابط برنامه‌نویسی کاربردی را می‌توان از اینجا [+] به دست آورد.

اکنون، پوشه جدید public/models برای قرار دادن همه وزن‌های مدل در آن ساخته می‌شود. سپس، همه وزن‌های لازم از پوشه دانلود می‌شود. همانطور که پیش از این بیان شد، در اینجا از مدل «Tiny Face Detector» استفاده می‌شود. بنابراین نیازی به مدل‌های SSD MobileNet و MTCNN نیست. باید اطمینان حاصل کرد که همه وزن‌ها در پوشه public/models به صورت زیر قرار دارند. در غیر این صورت، مدل‌ها بدون وزن‌های مناسب به درستی کار نخواهند کرد.

react-face-recognition 
├── README.md 
├── node_modules 
├── package.json 
├── .gitignore 
├── public 
│   ├── models
│   │   ├── face_landmark_68_tiny_model-shard1
│   │   ├── face_landmark_68_tiny_model-weights_manifest.json
│   │   ├── face_recognition_model-shard1
│   │   ├── face_recognition_model-shard2
│   │   ├── face_recognition_model-weights_manifest.json
│   │   ├── tiny_face_detector_model-shard1
│   │   └── tiny_face_detector_model-weights_manifest.json
│   ├── favicon.ico 
│   ├── index.html 
│   └── manifest.json

اکنون به عقب بازگشته و یک پوشه جدید برای API به صورت src/api و یک فایل جدید face.js درون این پوشه، ساخته می‌شود. آنچه در اینجا انجام خواهد شد، بارگذاری مدل و ساخت تابعی برای خوراک دادن به رابط برنامه‌نویسی کاربردی و بازگرداندن همه توصیفات چهره و همچنین، مقایسه توصیف‌گرها برای شناسایی چهره است. این توابع اکسپورت می‌شوند و بعدا در مولفه‌های ری‌اکت (React Components) مورد استفاده قرار می‌گیرند.

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

// Load models and weights
export async function loadModels() {
  const MODEL_URL = process.env.PUBLIC_URL + '/models';
  await faceapi.loadTinyFaceDetectorModel(MODEL_URL);
  await faceapi.loadFaceLandmarkTinyModel(MODEL_URL);
  await faceapi.loadFaceRecognitionModel(MODEL_URL);
}

export async function getFullFaceDescription(blob, inputSize = 512) {
  // tiny_face_detector options
  let scoreThreshold = 0.5;
  const OPTION = new faceapi.TinyFaceDetectorOptions({
    inputSize,
    scoreThreshold
  });
  const useTinyModel = true;

  // fetch image to api
  let img = await faceapi.fetchImage(blob);

  // detect all faces and generate full description from image
  // including landmark and descriptor of each face
  let fullDesc = await faceapi
    .detectAllFaces(img, OPTION)
    .withFaceLandmarks(useTinyModel)
    .withFaceDescriptors();
  return fullDesc;
}

دو بخش مهم در این فایل API در اینجا وجود دارد. اولین مورد، بارگذاری مدل‌ها و وزن‌ها با تابع loadModels()‎ است. در اینجا، تنها مدل‌های Face Landmark Tiny Model ،Tiny Face Detector و Face Recognition بارگذاری می‌شوند.

بخش دیگر، تابع getFullFaceDescription()‎ است که حباب تصویر را به عنوان ورودی دریافت کرده و توصیف کامل چهره را باز می‌گرداند. این تابع از تابع رابط برنامه‌نویسی کاربردی faceapi.fetchImage()‎ برای «واکشی» (Fetch) حباب تصویر به API استفاده می‌کند.

سپس، faceapi.detectAllFaces()‎ این تصویر را دریافت کرده و همه چهره‌ها را در آن پیدا می‌کند. در ادامه، withFaceLandmarks()‎ تعداد ۶۸ نقطه برجسته چهره را پیش از استفاده از withFaceDescriptors()‎ برای بازگرداندن ویژگی چهره از ۱۲۸ مقدار، به صورت Float32Array ترسیم می‌کند.

شایان ذکر است که از تصویر ۵۱۲ پیکسلی برای تصویر ورودی استفاده خواهد شد و از ۱۶۰ پیکسل بعدا برای ورودی ویدئو استفاده می‌شود. این کار، توسط API توصیه شده است. اکنون، باید تصویر زیر در پوشه جدید src/img ذخیره و test.jpg نام‌گذاری شود. این تصویر، در واقع تصویر تست، برای  تست برنامه کاربردی است.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

در ادامه مطلب تشخیص چهره با جاوا اسکریپت و ری اکت فایل جدید src/views/ImageInput.js ساخته می‌شود. این فایل، مولفه را به ورودی نمایش و فایل تصویر را نشان می‌دهد.

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { loadModels, getFullFaceDescription } from '../api/face';

// Import image to test API
const testImg = require('../img/test.jpg');

// Initial State
const INIT_STATE = {
  imageURL: testImg,
  fullDesc: null
};

class ImageInput extends Component {
  constructor(props) {
    super(props);
    this.state = { ...INIT_STATE };
  }

  componentWillMount = async () => {
    await loadModels();
    await this.handleImage(this.state.imageURL);
  };

  handleImage = async (image = this.state.imageURL) => {
    await getFullFaceDescription(image).then(fullDesc => {
      console.log(fullDesc);
      this.setState({ fullDesc });
    });
  };

  render() {
    const { imageURL } = this.state;
    return (
      <div>
        <img src={imageURL} alt="imageURL" />
      </div>
    );
  }
}

export default withRouter(ImageInput);

این مولفه، در این لحظه، تنها تصویر تست src/img/test.jpg را نمایش داده و شروع به بارگذاری مدل‌های API به مرورگر می‌کند که چند ثانیه زمان می‌برد. پس از آن، تصویر به API خوراک داده می‌شود که توصیف کامل چهره را دریافت کند. می‌توان fullDesc بازگردانده شده در state را برای استفاده آتی ذخیره کرد و جزئیات آن را در console.log مشاهده کرد. اما پیش از آن، نیاز به ایمپورت کردن مولفه ImageInput در فایل src/App.js است. همچنین، باید یک Route برای photo/ ساخت. با استفاده از قطعه کد زیر، می‌توان این کار را انجام داد.

import React, { Component } from 'react';
import { Route, Router } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import './App.css';

import Home from './views/Home';
import ImageInput from './views/ImageInput';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Router history={createHistory()}>
          <div className="route">
            <Route exact path="/" component={Home} />
            <Route exact path="/photo" component={ImageInput} />
          </div>
        </Router>
      </div>
    );
  }
}

export default App;

اکنون، اگر کاربر به صفحه http://localhost:3000 مراجعه و روی Photo Input کلید کند، باید نمایش تصویر را ببینید. اگر کنسول مروگر بررسی شود، باید Full Face Description از این تصویر به صورت زیر مشاهده شود.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

در ادامه مطلب تشخیص چهره با جاوا اسکریپت و ری اکت بحث ترسیم جعبه تشخیص چهره مورد بررسی قرار گرفته است.

جعبه تشخیص چهره

همانطور که می‌توان مشاهده کرد، توصیف حاوی همه اطلاعات تشخیص چهره مورد نیاز در این پروژه، شامل descriptor و detection است. درون detection، اطلاعات جعبه مانند مختصات شامل x y top bottom left right height width قرار می‌گیرد.

کتابخانه face-api.js با تابعی برای ترسیم جعبه تشخیص تصویر با استفاده از بوم HTML ارائه می‌شود که واقعا مناسب است. اما با توجه به اینکه از ری‌اکت استفاده می‌شود، چرا جعبه تشخیص تصویر با CSS ترسیم نمی‌شود؟ اکنون، می‌توان جعبه و نمایش تشخیص را با بهره‌گیری از ری‌اکت انجام داد.

آنچه اکنون کاربر قصد دارد انجام دهد، استفاده از اطلاعات box تشخیص برای جای‌گذاری جعبه چهره روی تصویر است. همچنین، می‌توان نام هر چهره تشخیص داده شده را با برنامه‌کاربردی، روی آن نمایش داد. این همان روشی است که با استفاده از آن، drawBox به مولفه ImageInput اضافه شده است. در ادامه، تگ input اضافه می‌شود، بنابراین می‌توان تصویر ورودی را تغییر داد.

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { loadModels, getFullFaceDescription } from '../api/face';

// Import image to test API
const testImg = require('../img/test.jpg');

// Initial State
const INIT_STATE = {
  imageURL: testImg,
  fullDesc: null,
  detections: null
};

class ImageInput extends Component {
  constructor(props) {
    super(props);
    this.state = { ...INIT_STATE };
  }

  componentWillMount = async () => {
    await loadModels();
    await this.handleImage(this.state.imageURL);
  };

  handleImage = async (image = this.state.imageURL) => {
    await getFullFaceDescription(image).then(fullDesc => {
      if (!!fullDesc) {
        this.setState({
          fullDesc,
          detections: fullDesc.map(fd => fd.detection)
        });
      }
    });
  };

  handleFileChange = async event => {
    this.resetState();
    await this.setState({
      imageURL: URL.createObjectURL(event.target.files[0]),
      loading: true
    });
    this.handleImage();
  };

  resetState = () => {
    this.setState({ ...INIT_STATE });
  };

  render() {
    const { imageURL, detections } = this.state;

    let drawBox = null;
    if (!!detections) {
      drawBox = detections.map((detection, i) => {
        let _H = detection.box.height;
        let _W = detection.box.width;
        let _X = detection.box._x;
        let _Y = detection.box._y;
        return (
          <div key={i}>
            <div
              style={{
                position: 'absolute',
                border: 'solid',
                borderColor: 'blue',
                height: _H,
                width: _W,
                transform: `translate(${_X}px,${_Y}px)`
              }}
            />
          </div>
        );
      });
    }

    return (
      <div>
        <input
          id="myFileUpload"
          type="file"
          onChange={this.handleFileChange}
          accept=".jpg, .jpeg, .png"
        />
        <div style={{ position: 'relative' }}>
          <div style={{ position: 'absolute' }}>
            <img src={imageURL} alt="imageURL" />
          </div>
          {!!drawBox ? drawBox : null}
        </div>
      </div>
    );
  }
}

export default withRouter(ImageInput);

با استفاده از CSS در ری‌اکت، می‌توان همه جعبه‌های تصویر را روی تصویری مانند این قرار داد. اگر تصویری با تعداد چهره‌های بیشتر استفاده شود، جعبه‌های بیشتری نیز روی تصویر مشاهده می‌شود.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

بازشناسی چهره

اکنون، در این بخش از مطلب تشخیص چهره با جاوا اسکریپت و ری اکت بخش ساده کار از راه رسید. برای شناسایی یک فرد، نیاز به حداقل یک تصویر منبع برای استخراج ۱۲۸ مقدار از بردار ویژگی‌ها یا descriptor از تصویر است. رابط برنامه‌نویسی کاربردی از تابع LabeledFaceDescriptors برای استخراج برچسب از توصیف‌گرها و نام برای هر فردی که قصد آن شناسایی است، بهره می‌برد. این برچسب، به رابط برنامه‌نویسی کاربردی همراه با پرس و جوی descriptor برای تطبیق فرد، خوراک می‌دهد. اما پیش از آن، نیاز به فراهم کردن یک پروفایل از نام و توصیف‌گرها است.

پروفایل چهره

در حال حاضر، یک مرجع تصویری برای شخصیت Cherprang وجود دارد. بنابراین، از descriptor آن می‌توان برای ساخت یک پروفایل استفاده کرد. آنچه در این وهله قصد انجام آن  وجود دارد، ساخت یک فایل JSON و پوشه src/descriptors/bnk48.json است. این فایل حاوی اسم عضو گروه و توصیف‌گرها از تصویر مرجع آن‌ها است. در ادامه، اولین فایل نمونه با تنها یک descriptor از تصویر، ارائه شده است.

{
  "Cherprang": {
    "name": "เฌอปราง",
    "descriptors": [
      [-0.08113786578178406,0.05256778374314308,0.009473100304603577,-0.10561250895261765,-0.1783013790845871,-0.04459364339709282,-0.09838695079088211,-0.11281408369541168,0.15290355682373047,-0.12802188098430634,0.22490034997463226,-0.0876401960849762,-0.20344389975070953,-0.06546909362077713,-0.09518688917160034,0.21771210432052612,-0.1978156566619873,-0.20811164379119873,-0.005263347178697586,0.022171149030327797,0.08166252076625824,-0.024958046153187752,-0.008690833114087582,0.07264935970306396,-0.09877212345600128,-0.32639357447624207,-0.07010464370250702,-0.05077863112092018,-0.06450583040714264,-0.08223631232976913,0.03569770231842995,0.0591503381729126,-0.19877424836158752,0.005495572462677956,0.032966941595077515,0.10699400305747986,0.00043915724381804466,-0.10360816866159439,0.17986394464969635,-0.040484149008989334,-0.30697202682495117,0.03211132436990738,0.08842744678258896,0.19950658082962036,0.15933744609355927,-0.03205663338303566,0.0013319365680217743,-0.16289959847927094,0.14904619753360748,-0.15430817008018494,0.0096973218023777,0.14715969562530518,0.08346439152956009,0.031750328838825226,-0.016189731657505035,-0.1357622593641281,0.03173140063881874,0.1332130879163742,-0.1280241757631302,-0.03812456876039505,0.10302945971488953,-0.06710688769817352,-0.0045883068814873695,-0.18414227664470673,0.19671869277954102,0.05899167060852051,-0.13076898455619812,-0.2127394676208496,0.09676400572061539,-0.15055659413337708,-0.1203978881239891,0.0671667754650116,-0.15655417740345,-0.21337451040744781,-0.33455413579940796,0.0025228180456906557,0.32778817415237427,0.12706099450588226,-0.18636029958724976,0.077443927526474,0.04969285428524017,0.0199972465634346,0.04955039545893669,0.18967756628990173,-0.0021310225129127502,0.10583700984716415,-0.09680759161710739,-0.0061607323586940765,0.237107053399086,-0.07061111927032471,-0.0200827457010746,0.20259839296340942,-0.025431372225284576,0.097686767578125,0.024423770606517792,-0.0027051493525505066,-0.05065762251615524,0.05029483884572983,-0.18550457060337067,0.04197317361831665,-0.06792200356721878,0.0047242967411875725,-0.020428016781806946,0.061017416417598724,-0.0777672529220581,0.09362616389989853,-0.018073776736855507,0.00793382991105318,-0.11228518187999725,0.011685803532600403,-0.09283971041440964,-0.05933531001210213,0.12439746409654617,-0.177815243601799,0.20367340743541718,0.12916350364685059,0.16568998992443085,0.14472319185733795,0.18697629868984222,0.07876216620206833,-0.025242770090699196,-0.02333880588412285,-0.17500267922878265,0.011992575600743294,0.09926775842905045,-0.05089148133993149,0.1372077465057373,0.017258809879422188]
    ]
  }
}

اگر همه تصاویر از همه اعضا موجود باشد، می‌توان descriptor را اضافه و یک به یک نام‌گذاری کرد تا پروفایل چهره کامل شود. در اینجا از ۵ تا ۱۰ تصویر از هر عضو برای ساخت این پروفایل کامل تصویر استفاده شده است. بنابراین، مخاطبان می‌توانند این فایل را به سادگی از اینجا [+] دانلود و src/descriptors/bnk48.json را جایگزین کند.

اندازه فایل در حدود یک مگابایت برای کل اعضا است که برای ارزیابی برنامه کاربردی مناسب به نظر می‌رسد. اما در دنیای واقعی، ممکن است نیاز به ذخیره‌سازی همه پروفایل‌های تصویر در پایگاه داده باشد، بنابراین نیازی به نگرانی پیرامون سایز فایل نیست. اما در عوض باید از سمت سرور برای اجرای فرایند بازشناسی تصویر استفاده کرد.

تطبیق‌دهنده چهره

در گام بعدی، نیاز به ساخت labeledDescriptors و faceMatcher برای وظیفه بازشناسی تصویر است. اکنون، به src/api/face.js بازگشته و سپس، تابع زیر به فایل اضافه می‌شود.

// face.js code...

const maxDescriptorDistance = 0.5;
export async function createMatcher(faceProfile) {
  // Create labeled descriptors of member from profile
  let members = Object.keys(faceProfile);
  let labeledDescriptors = members.map(
    member =>
      new faceapi.LabeledFaceDescriptors(
        faceProfile[member].name,
        faceProfile[member].descriptors.map(
          descriptor => new Float32Array(descriptor)
        )
      )
  );

  // Create face matcher (maximum descriptor distance is 0.5)
  let faceMatcher = new faceapi.FaceMatcher(
    labeledDescriptors,
    maxDescriptorDistance
  );
  return faceMatcher;
}

این تابع، یک پروفایل چهره (فایل JSON) را به عنوان ورودی دریافت و labeledDescriptors را از توصیف‌گر هر عضو با نام آن به عنوان برچسب می‌سازد. سپس، می‌توان faceMatcher را با برچسب ساخت و خروجی گرفت (Export). ممکن است این موضوع توجه مخاطبان را به خود جلب کرده باشد که maxDescriptorDistance برابر با 0.5 پیکربندی شده است. این آستانه «فاصله اقلیدسی» (Euclidean Distance)‌ برای تعیین آن است که توصیف‌گر مرجع و توصیف‌گر کوئری به اندازه کافی به یکدیگر نزدیک هستند که مفهومی داشته باشند. مقدار پیش‌فرض رابط برنامه‌نویسی کاربردی برابر با 0.6 است که برای موارد عمومی، به اندازه کافی خوب محسوب می‌شود. اما ۰.۵ دقیق‌تر است و خطای کمتری نیز در این مثال دارد، زیرا چهره اشخاصی که تصاویر آن‌ها در در این مثال مورد استفاده قرار گرفته است، بسیار شبیه به هم هستند. با توجه به آنکه تابع آماده است، برای پایان دادن به کد، به src/views/ImageInput.js بازگشته می‌شود. نسخه نهایی به صورت زیر است.

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { loadModels, getFullFaceDescription, createMatcher } from '../api/face';

// Import image to test API
const testImg = require('../img/test.jpg');

// Import face profile
const JSON_PROFILE = require('../descriptors/bnk48.json');

// Initial State
const INIT_STATE = {
  imageURL: testImg,
  fullDesc: null,
  detections: null,
  descriptors: null,
  match: null
};

class ImageInput extends Component {
  constructor(props) {
    super(props);
    this.state = { ...INIT_STATE, faceMatcher: null };
  }

  componentWillMount = async () => {
    await loadModels();
    this.setState({ faceMatcher: await createMatcher(JSON_PROFILE) });
    await this.handleImage(this.state.imageURL);
  };

  handleImage = async (image = this.state.imageURL) => {
    await getFullFaceDescription(image).then(fullDesc => {
      if (!!fullDesc) {
        this.setState({
          fullDesc,
          detections: fullDesc.map(fd => fd.detection),
          descriptors: fullDesc.map(fd => fd.descriptor)
        });
      }
    });

    if (!!this.state.descriptors && !!this.state.faceMatcher) {
      let match = await this.state.descriptors.map(descriptor =>
        this.state.faceMatcher.findBestMatch(descriptor)
      );
      this.setState({ match });
    }
  };

  handleFileChange = async event => {
    this.resetState();
    await this.setState({
      imageURL: URL.createObjectURL(event.target.files[0]),
      loading: true
    });
    this.handleImage();
  };

  resetState = () => {
    this.setState({ ...INIT_STATE });
  };

  render() {
    const { imageURL, detections, match } = this.state;

    let drawBox = null;
    if (!!detections) {
      drawBox = detections.map((detection, i) => {
        let _H = detection.box.height;
        let _W = detection.box.width;
        let _X = detection.box._x;
        let _Y = detection.box._y;
        return (
          <div key={i}>
            <div
              style={{
                position: 'absolute',
                border: 'solid',
                borderColor: 'blue',
                height: _H,
                width: _W,
                transform: `translate(${_X}px,${_Y}px)`
              }}
            >
              {!!match && !!match[i] ? (
                <p
                  style={{
                    backgroundColor: 'blue',
                    border: 'solid',
                    borderColor: 'blue',
                    width: _W,
                    marginTop: 0,
                    color: '#fff',
                    transform: `translate(-3px,${_H}px)`
                  }}
                >
                  {match[i]._label}
                </p>
              ) : null}
            </div>
          </div>
        );
      });
    }

    return (
      <div>
        <input
          id="myFileUpload"
          type="file"
          onChange={this.handleFileChange}
          accept=".jpg, .jpeg, .png"
        />
        <div style={{ position: 'relative' }}>
          <div style={{ position: 'absolute' }}>
            <img src={imageURL} alt="imageURL" />
          </div>
          {!!drawBox ? drawBox : null}
        </div>
      </div>
    );
  }
}

export default withRouter(ImageInput);

در این کد نهایی، تابع createMatcher از face.js بازیابی می‌شود و faceMatcher با پروفایل چهره‌ی که آماده شده، ساخته می‌شود. درون تابع handleImage()‎، پس از آنکه fullDesc از تصویر گرفته شد، descriptors نگاشت و بهترین match برای هر تصویر یافت می‌شود. سپس، از تگ p و CSS برای نمایش بهترین تطبیق زیر هر جعبه تشخیص چهره، استفاده می‌شود. نمایی از این مورد در تصویر زیر ارائه شده است.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

افرادی که پروفایل چهره کامل را دانلود کرده‌اند [+] می‌توانند تصویر را با تصویر زیر جایگزین و نتیجه سیستم تشخیص چهره را مشاهده کنند.

تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

در ادامه مطلب تشخیص چهره با جاوا اسکریپت و ری اکت کار تشخیص چهره روی ورودی ویدئوی زنده انجام می‌شود.

ورودی ویدئو زنده

در این بخش از مطلب تشخیص چهره با جاوا اسکریپت و ری اکت روش تشخیص چهره با ویدئو زنده، آموزش داده شده است. در اینجا، ویدئوی زنده با استفاده از React-Webcam به face-api.js داده می‌شود. در ادامه، کار با نصب کتابخانه لازم، انجام می‌شود.

npm i react-webcam

دوباره، پیش از ساخت یک مولفه جدید، نیاز به اضافه کردن یک مسیر «Route» برای ویدئوی ورودی در /src/App.js است. مولفه VideoInput خیلی زود ساخته خواهد شد.

import React, { Component } from 'react';
import { Route, Router } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import './App.css';

import Home from './views/Home';
import ImageInput from './views/ImageInput';
import VideoInput from './views/VideoInput';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Router history={createHistory()}>
          <div className="route">
            <Route exact path="/" component={Home} />
            <Route exact path="/photo" component={ImageInput} />
            <Route exact path="/camera" component={VideoInput} />
          </div>
        </Router>
      </div>
    );
  }
}

export default App;

مولفه ویدئوی ورودی

اکنون، فایل جدید src/views/VideoInput.js ساخته می‌شود. سپس، کد زیر در آن کپی و فایل ذخیره می‌شود. کد کامل برای این مولفه، در ادامه آمده است.

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import Webcam from 'react-webcam';
import { loadModels, getFullFaceDescription, createMatcher } from '../api/face';

// Import face profile
const JSON_PROFILE = require('../descriptors/bnk48.json');

const WIDTH = 420;
const HEIGHT = 420;
const inputSize = 160;

class VideoInput extends Component {
  constructor(props) {
    super(props);
    this.webcam = React.createRef();
    this.state = {
      fullDesc: null,
      detections: null,
      descriptors: null,
      faceMatcher: null,
      match: null,
      facingMode: null
    };
  }

  componentWillMount = async () => {
    await loadModels();
    this.setState({ faceMatcher: await createMatcher(JSON_PROFILE) });
    this.setInputDevice();
  };

  setInputDevice = () => {
    navigator.mediaDevices.enumerateDevices().then(async devices => {
      let inputDevice = await devices.filter(
        device => device.kind === 'videoinput'
      );
      if (inputDevice.length < 2) {
        await this.setState({
          facingMode: 'user'
        });
      } else {
        await this.setState({
          facingMode: { exact: 'environment' }
        });
      }
      this.startCapture();
    });
  };

  startCapture = () => {
    this.interval = setInterval(() => {
      this.capture();
    }, 1500);
  };

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  capture = async () => {
    if (!!this.webcam.current) {
      await getFullFaceDescription(
        this.webcam.current.getScreenshot(),
        inputSize
      ).then(fullDesc => {
        if (!!fullDesc) {
          this.setState({
            detections: fullDesc.map(fd => fd.detection),
            descriptors: fullDesc.map(fd => fd.descriptor)
          });
        }
      });

      if (!!this.state.descriptors && !!this.state.faceMatcher) {
        let match = await this.state.descriptors.map(descriptor =>
          this.state.faceMatcher.findBestMatch(descriptor)
        );
        this.setState({ match });
      }
    }
  };

  render() {
    const { detections, match, facingMode } = this.state;
    let videoConstraints = null;
    let camera = '';
    if (!!facingMode) {
      videoConstraints = {
        width: WIDTH,
        height: HEIGHT,
        facingMode: facingMode
      };
      if (facingMode === 'user') {
        camera = 'Front';
      } else {
        camera = 'Back';
      }
    }

    let drawBox = null;
    if (!!detections) {
      drawBox = detections.map((detection, i) => {
        let _H = detection.box.height;
        let _W = detection.box.width;
        let _X = detection.box._x;
        let _Y = detection.box._y;
        return (
          <div key={i}>
            <div
              style={{
                position: 'absolute',
                border: 'solid',
                borderColor: 'blue',
                height: _H,
                width: _W,
                transform: `translate(${_X}px,${_Y}px)`
              }}
            >
              {!!match && !!match[i] ? (
                <p
                  style={{
                    backgroundColor: 'blue',
                    border: 'solid',
                    borderColor: 'blue',
                    width: _W,
                    marginTop: 0,
                    color: '#fff',
                    transform: `translate(-3px,${_H}px)`
                  }}
                >
                  {match[i]._label}
                </p>
              ) : null}
            </div>
          </div>
        );
      });
    }

    return (
      <div
        className="Camera"
        style={{
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center'
        }}
      >
        <p>Camera: {camera}</p>
        <div
          style={{
            width: WIDTH,
            height: HEIGHT
          }}
        >
          <div style={{ position: 'relative', width: WIDTH }}>
            {!!videoConstraints ? (
              <div style={{ position: 'absolute' }}>
                <Webcam
                  audio={false}
                  width={WIDTH}
                  height={HEIGHT}
                  ref={this.webcam}
                  screenshotFormat="image/jpeg"
                  videoConstraints={videoConstraints}
                />
              </div>
            ) : null}
            {!!drawBox ? drawBox : null}
          </div>
        </div>
      </div>
    );
  }
}

export default withRouter(VideoInput);

همه مکانیزم‌های تشخیص و بازشناسی جهره مانند مولفه ImageInput هستند، به جز آنکه ورودی، یک اسکرین‌شات ثبت شده از وب‌کم در هر ۱۵۰۰ میلی ثانیه یک بار است. سایز تصویر روی 420×420 پیکسل تنظیم شده است، اما می‌تواند تصاویر با ابعاد کوچک‌تر یا بزرگ‌تر رت میز مورد بررسی قرار داد. البته، باید توجه داشت که استفاده از تصاویر بزرگ‌تر، منجر به زمان پردازش بالا می‌شود. درون تابع setInputDevice، به سادگی بررسی می‌شود که آیا دستگاه ۱ یا ۲ دوربین (یا بیشتر) دارد. اگر تنها یک دوربین وجود داشته باشد، برنامه فرض می‌کند که یک کامپیوتر شخصی است، سپس، از دوربین facingMode: user ثبت می‌شود، اما اگر دو یا تعداد بیشتری بود، ممکن است گوشی هوشمند باشد. در این صورت، می‌توان با دوربین از بک‌اند facingMode: { exact: ‘environment’ }‎ دریافت کرد.

از تابع مشابهی برای ترسیم جعبه تشخیص تصویر مانند مولفه ImageInput استفاده می‌شود. در واقع، می‌توان آن را به عنوان مولفه دیگری آماده کرد. بنابراین، نیازی به تکرار دوباره آن نیست. اکنون، برنامه کاربردی آماده است. کاربران می‌توانند حالت VideoInput برنامه کاربردی را با چهره خودشان تست کنند. اما احتمالا کاربر با به عنوان unknown و یا به اشتباه، یکی از افرادی که مدل با تصاویر آن آموزش دیده است، تشخیص دهد. دلیل این امر آن است که سیستم تلاش می‌کند که همه تصاویر با فاصله اقلیدسی کمتر از ۰.۵ را تشخیص دهد.

استقرار پروژه در صفحه گیت‌هاب

در این بخش از مطلب تشخیص چهره با جاوا اسکریپت و ری اکت روش استقرار پروژه در گیت‌هاب مورد بررسی قرار می‌گیرد. این برنامه کاربردی را می‌توان در هر میزبان استاتیکی مستقر کرد. اما در این بخش، روش استقرار این برنامه ری‌اکت در صفحات گیت‌هاب با استفاده از چند ترفند آموزش داده شده است. برای انجام این مراحل، نیاز به داشتن اکانت گیت‌هاب است. افرادی که گیت‌هاب ندارند، برای انجام ادامه مراحل موجود در این بخش، یک اکانت گیت‌هاب بسازند. در ابتدا، باید کتابخانه gh-pages نصب شود. برای انجام این کار می‌توان از قطعه کد زیر استفاده کرد.

npm i gh-pages

سپس، نیاز به اضافه کردن { basename: process.env.PUBLIC_URL } درون createHistory()‎ در src/App.js به صورت زیر است.

import React, { Component } from 'react';
import { Route, Router } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import './App.css';

import Home from './views/Home';
import ImageInput from './views/ImageInput';
import VideoInput from './views/VideoInput';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Router history={createHistory({ basename: process.env.PUBLIC_URL })}>
          <div className="route">
            <Route exact path="/" component={Home} />
            <Route exact path="/photo" component={ImageInput} />
            <Route exact path="/camera" component={VideoInput} />
          </div>
        </Router>
      </div>
    );
  }
}

export default App;

اکنون، کاربر می‌تواند به صفحه گیت‌هاب خود مراجعه کرده و یک مخزن با نام برنامه کاربردی بسازد. در اینجا، نام برنامه کاربردی react-face-recognition است. سپس، می‌توان URL گیت را کپی کرد تا بتوان آن را بعدا به پروژه اضافه کرد. سپس، باید package.json را باز کرده و homepage را با اکانت گیت‌هاب و نام برنامه مانند زیر، اضافه کرد:

"homepage": "http://YOUR_GITHUB_ACCOUNT.github.io/react-face-recognition"

نباید فایل package.json را هنوز بست، زیرا نیاز به اضافه کردن predeploy و دستور deploy خط فرمان تحت scripts به صورت زیر است.

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject",
  "predeploy": "npm run build",
  "deploy": "gh-pages -d build"
}

اکنون، می‌توان فایل را ذخیره کرد و به کنسول ترمینال بازگشت و سپس، دستور گیت را برای بارگذاری کد در مخزن گیت‌هاب و اجرای npm run deploy، اجرا کرد. صفحه باید با URL منتشر شود که به صورت http://YOUR_GITHUB_ACCOUNT.github.io/react-face-recognition تنظیم شده است.

git add .
git commit -m "make something good"
git remote add origin https://github.com/YOUR_GITHUB_ACCOUNT/react-face-recognition.git
git push -u origin master

npm run deploy

اگر این مطلب برای شما مفید بوده است، آموزش‌ها و مطالب زیر نیز به شما پیشنهاد می‌شوند:

بر اساس رای 2 نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«الهام حصارکی»، فارغ‌التحصیل مقطع کارشناسی ارشد مهندسی فناوری اطلاعات، گرایش سیستم‌های اطلاعات مدیریت است. او در زمینه هوش مصنوعی و داده‌کاوی، به ویژه تحلیل شبکه‌های اجتماعی، فعالیت می‌کند.

یک نظر ثبت شده در “تشخیص چهره با جاوا اسکریپت و ری اکت — راهنمای کاربردی

  • مهدی سازگار — says: ۳ بهمن، ۱۳۹۹ در ۹:۳۸ ب٫ظ

    خانم حصارکی خسته نباشید. مهدی سازگار هستم دانش آموز از بوشهر.
    خیلی وقته به دنبال راه ارتباط با شما هستم ولی راه ارتباطی پیدا نکردم. مجذوب مطالب عالی و تخصص نهفته درشون هستم.
    من شرایط زندگیم و شرایط سنی نمیزاره که با افراد فعال در حوزه برنامه نویسی کانکت باشم و بتونم اعتماد کنم. به همین دلیل به تنهایی بر روی
    پروژه هام کار می کنم.
    به شدت علاقه مندم که فردی با تخصص شما راهنمام باشه و لایق باشم از نظراتش بیشترین استفاده رو ببرم.
    روزشمار منتظر پاسخ شما هستم

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *