기본 콘텐츠로 건너뛰기

React Native | 이미지에서 얼굴인식(Face Detection) 하기


React Native 이미지에서 얼굴인식 하기

Face Detection, Face Recognition




React Native를 이용하여 앱 개발하면서

사진(이미지)에서 얼굴 위치(바운더리)를 추출해야 하는

기능이 필요했습니다.


tensorflow 등 AI를 이용하여

얼굴을 추출할 수 있는 다양한 방법이 있지만

심플하고, 가벼운 앱을 생산하는게 목표기 때문에

제일 사용하기 쉽고, 간단한 얼굴인식 라이브러리를 사용하였습니다.




1. react-native-face-detection 설치(Install)


Google ML Kit(Machine learning for mobile developers)에서 얼굴인식 부분만 추출한 라이브러리
https://github.com/nonth/react-native-face-detection

$ npm install --save react-native-face-detection




2. 예제 소스(Example Source)


1. IMAGE_PATH

예제에서는 로컬에 저장된 이미지 파일을 대상으로 얼굴 바운더리를 추출합니다.
얼굴을 추출할 이미지 파일을 안드로이드 디바이스나 에뮬레이터에 전송하는 방법은 아래 링크를 참고하세요.
adb를 사용하여 안드로이드 디바이스(device)나 에뮬레이터(emulator)에 파일 전송(https://itdevlog.blogspot.com/2021/12/android-debug-bridge.html)


2. getFaces()

로컬에 저장된 사진으로부터 얼굴인식 후 결과 리턴
저는 바운더리만 결과만 가져왔지만 아래와 같은 정보를 추출할 수 있다.
- 머리 바운더리(Head boundary)
- 머리 방향(Head rotation)
- 왼쪽, 오른쪽 눈 감은 확률(Left, Right eye open probability)
- 왼쪽, 오른쪽 눈 외곽선 포인트(Left, Right eye outline points)
- 스마일 확률(Smiling probability)
- 얼굴 외곽선 포인트(Face outline points)


3. getImageSize()

getFaces()로 추출한 얼굴 바운러디는 사진좌표값이기 때문에
이미지 파일의 실제 사이즈를 추출


4. renderFaces()

로컬 이미지 파일의 실제 사이즈와, 추출된 얼굴 좌표를
화면에 표시되는 이미지의 크기와 비율을 계산하여
화면에 빨간 박스로 결과 표시


import React, { Component } from 'react';
import { Text, View, Button, Image, StyleSheet } from 'react-native';
import FaceDetection, { FaceDetectorContourMode, FaceDetectorLandmarkMode } from "react-native-face-detection";
 
 
// ------------------------------
// variable
// ------------------------------
let _self;
let _data = {};
 
// local path to file on the device
const IMAGE_PATH = 'file:///data/data/com.facedetection/files/photo.png';
 
 
// ------------------------------
// function
// ------------------------------
// get face list
function getFaces(imagePath) {
 
  return new Promise(function(resolve, reject) {
 
    const options = {
      landmarkMode: FaceDetectorLandmarkMode.ALL,
      contourMode: FaceDetectorContourMode.ALL
    };
  
    FaceDetection.processImage(imagePath, options).then((faces) => {
      console.log('faces length : ' + faces.length);
      resolve(faces);
    });
 
  });
 
}
 
// get image size from image file
function getImageSize(imagePath) {
 
  return new Promise(function(resolve, reject) {
    
    Image.getSize(imagePath, (width, height) => {
      console.log("image width: " + width + ", image height: " + height);
      resolve({width, height});
    });
 
  });
 
}
 
// execute face detection
async function execute() {
 
  if (_data.imageViewInfo == null) {
    alert("Image is not loaded.");
    return;
  }
  console.log("image view width: " + _data.imageViewInfo.width + ", image view height: " + _data.imageViewInfo.height);
 
  var {width, height} = await getImageSize(IMAGE_PATH);
  console.log("image width: " + width + ", image height: " + height);
 
  _data.imageInfo = {
    'width': width,
    'height': height,
  };
 
  var faces = await getFaces(IMAGE_PATH);
 
  var faceBBoxList = [];
  faces.forEach(face => {
    faceBBoxList.push([face.boundingBox[0], face.boundingBox[1], face.boundingBox[2], face.boundingBox[3]]);
  });
  
  // refresh view for face detection results
  _self.setState({
    faceAreaList: faceBBoxList
  });
  
}
 
 
// ------------------------------
// event
// ------------------------------
const onImageLayout = event => {
 
  var {x, y, width, height, size} = event.nativeEvent.layout;
 
  _data.imageViewInfo = {
    'width': width,
    'height': height,
  };
  
};
 
const onButtonPress = () => {
  execute();
}
 
 
// ------------------------------
// style
// ------------------------------
const styles = StyleSheet.create({
  wrapper: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  image: {
    width: '100%',
    aspectRatio: 1,
    resizeMode: 'contain',
    backgroundColor: '#00ff00',
  },
  overlay: {
    position: 'absolute',
    borderColor: '#ff0000',
    borderWidth: 3,
  },
});
 
 
// ------------------------------
// view
// ------------------------------
export default class App extends Component{
 
  constructor() {
    super();
    this.state = {
      faceAreaList: []
    }
    _self = this;
  }
 
  // render face bbox
  renderFaces() {
 
    // image view info
    if (_data.imageViewInfo == null) {
      return;
    }
 
    var viewWidth = _data.imageViewInfo.width;
    var viewHeight = _data.imageViewInfo.height;
 
    // image info
    if (_data.imageInfo == null) {
      return;
    }
    
    var imageWidth = _data.imageInfo.width;
    var imageHeight = _data.imageInfo.height;
    
    // calculate ratio, margin
    var ratio;
    var margin;   // grean area
    if (imageWidth > imageHeight) {
      ratio = (viewWidth / imageWidth);
      margin = (imageWidth - imageHeight) / 2;
    } else {
      ratio = (viewHeight / imageHeight);
      margin = (imageHeight - imageWidth) / 2;
    }
    
    // list of face bbox
    var viewList = [];
    this.state.faceAreaList.forEach(faceBBox => {
 
      if (imageWidth > imageHeight) {
        faceBBox[1+= margin;
        faceBBox[3+= margin;
      } else {
        faceBBox[0+= margin;
        faceBBox[2+= margin;
      }
  
      console.log("--");
      console.log("left: " + (faceBBox[0* ratio));
      console.log("bottom: " + (faceBBox[1]) * ratio);
      console.log("width: " + ((faceBBox[2- faceBBox[0]) * ratio));
      console.log("height: " + ((faceBBox[3- faceBBox[1]) * ratio));
 
      viewList.push( <View style={[styles.overlay, {
          top: (faceBBox[1* ratio),
          left: (faceBBox[0* ratio),
          width: ((faceBBox[2- faceBBox[0]) * ratio),
          height: ((faceBBox[3- faceBBox[1]) * ratio),
        }]} />);
 
    });
 
    return viewList;
 
  };
 
  render(){
    return (
      <View
        style={styles.wrapper}
      >
        <View>
          <Image
            onLayout={onImageLayout}
            style={styles.image}
            source={{uri: IMAGE_PATH}}
          />
          {
            this.renderFaces()
          }
        </View>
        <Button
          onPress={onButtonPress}
          title='detect face'
        />
      </View>
    )
  }
}




3. 결과(Result)


빨간색 박스가 이미지에서 얼굴을 찾아 표시한 결과입니다.
얼굴은 캡쳐 후 blur 처리했습니다.

초록색 영역은 View 태그 안에 Image 태그가 들어가면서
스타일을 resizeMode: 'contain'로 넣으며 남은 영역입니다.
초록색 영역을 없애고 싶은데 아직 잘 모르겠네요.



- TEST 1

[ 4개 탐색 / 얼굴 4개 ]



- TEST 2

[ 4개 탐색 / 얼굴 4개, 1개 오탐색 ]

댓글