Skip to content

Instantly share code, notes, and snippets.

@heaversm
Last active January 20, 2023 00:00
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heaversm/d04e4c9c578b12d8861d1eefb36060a1 to your computer and use it in GitHub Desktop.
Save heaversm/d04e4c9c578b12d8861d1eefb36060a1 to your computer and use it in GitHub Desktop.
Face Detection Automatic Selfie Cam using React Native and Expo
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Camera, Permissions, FaceDetector, DangerZone } from 'expo';
export default class CameraExample extends React.Component {
static defaultProps = {
countDownSeconds: 5,
motionInterval: 500, //ms between each device motion reading
motionTolerance: 1, //allowed variance in acceleration
cameraType: Camera.Constants.Type.front, //front vs rear facing camera
}
state = {
hasCameraPermission: null,
faceDetecting: false, //when true, we look for faces
faceDetected: false, //when true, we've found a face
countDownSeconds: 5, //current available seconds before photo is taken
countDownStarted: false, //starts when face detected
pictureTaken: false, //true when photo has been taken
motion: null, //captures the device motion object
detectMotion: false, //when true we attempt to determine if device is still
};
countDownTimer = null;
async componentWillMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
componentDidMount(){
this.motionListener = DangerZone.DeviceMotion.addListener(this.onDeviceMotion);
setTimeout(()=>{ //MH - tempm - wait 5 seconds for now before detecting motion
this.detectMotion(true);
},5000);
}
componentWillUpdate(nextProps, nextState) {
if (this.state.detectMotion && nextState.motion && this.state.motion){
if (
Math.abs(nextState.motion.x - this.state.motion.x) < this.props.motionTolerance
&& Math.abs(nextState.motion.y - this.state.motion.y) < this.props.motionTolerance
&& Math.abs(nextState.motion.z - this.state.motion.z) < this.props.motionTolerance
){
//still
this.detectFaces(true);
this.detectMotion(false);
} else {
//moving
}
}
}
detectMotion =(doDetect)=> {
this.setState({
detectMotion: doDetect,
});
if (doDetect){
DangerZone.DeviceMotion.setUpdateInterval(this.props.motionInterval);
} else if (!doDetect && this.state.faceDetecting) {
this.motionListener.remove();
}
}
onDeviceMotion = (rotation)=>{
this.setState({
motion: rotation.accelerationIncludingGravity
});
}
detectFaces(doDetect){
this.setState({
faceDetecting: doDetect,
});
}
render() {
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<Camera
style={{ flex: 1 }}
type={this.props.cameraType}
onFacesDetected={this.state.faceDetecting ? this.handleFacesDetected : undefined }
onFaceDetectionError={this.handleFaceDetectionError}
faceDetectorSettings={{
mode: FaceDetector.Constants.Mode.fast,
detectLandmarks: FaceDetector.Constants.Mode.none,
runClassifications: FaceDetector.Constants.Mode.none,
}}
ref={ref => {
this.camera = ref;
}}
>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
position: 'absolute',
bottom: 0,
}}>
<Text
style={styles.textStandard}>
{this.state.faceDetected ? 'Face Detected' : 'No Face Detected'}
</Text>
</View>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
display: this.state.faceDetected && !this.state.pictureTaken ? 'flex' : 'none',
}}>
<Text
style={styles.countdown}
>
{this.state.countDownSeconds}
</Text>
</View>
</Camera>
</View>
);
}
}
handleFaceDetectionError = ()=>{
//
}
handleFacesDetected = ({ faces }) => {
if (faces.length === 1){
this.setState({
faceDetected: true,
});
if (!this.state.faceDetected && !this.state.countDownStarted){
this.initCountDown();
}
} else {
this.setState({faceDetected: false });
this.cancelCountDown();
}
}
initCountDown = ()=>{
this.setState({
countDownStarted: true,
});
this.countDownTimer = setInterval(this.handleCountDownTime, 1000);
}
cancelCountDown = ()=>{
clearInterval(this.countDownTimer);
this.setState({
countDownSeconds: this.props.countDownSeconds,
countDownStarted: false,
});
}
handleCountDownTime = ()=>{
if (this.state.countDownSeconds > 0){
let newSeconds = this.state.countDownSeconds-1;
this.setState({
countDownSeconds: newSeconds,
});
} else {
this.cancelCountDown();
this.takePicture();
}
}
takePicture = ()=>{
this.setState({
pictureTaken: true,
});
if (this.camera) {
console.log('take picture');
this.camera.takePictureAsync({ onPictureSaved: this.onPictureSaved });
}
}
onPictureSaved = ()=>{
this.detectFaces(false);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
textStandard: {
fontSize: 18,
marginBottom: 10,
color: 'white'
},
countdown: {
fontSize: 40,
color: 'white'
}
});
@seanAkhtar
Copy link

Nicely done man! i borrowed this for something im working on.

@msvargas
Copy link

Update code to Expo SDK36:

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { DeviceMotion } from 'expo-sensors';
import { Camera } from 'expo-camera';

import * as FaceDetector from 'expo-face-detector';
import * as Permissions from 'expo-permissions';

export default class CameraExample extends React.Component {
  static defaultProps = {
    countDownSeconds: 5,
    motionInterval: 500, //ms between each device motion reading
    motionTolerance: 1, //allowed variance in acceleration
    cameraType: Camera.Constants.Type.front, //front vs rear facing camera
  }
  
  state = {
    hasCameraPermission: null,
    faceDetecting: false, //when true, we look for faces
    faceDetected: false, //when true, we've found a face
    countDownSeconds: 5, //current available seconds before photo is taken
    countDownStarted: false, //starts when face detected
    pictureTaken: false, //true when photo has been taken
    motion: null, //captures the device motion object 
    detectMotion: false, //when true we attempt to determine if device is still
  };

  countDownTimer = null;
 
  constructor(props){
    super(props)
    this.checkPermissions();
  }
  async checkPermissions(){
     const { status } = await Permissions.askAsync(Permissions.CAMERA);
    this.setState({ hasCameraPermission: status === 'granted' });
  }
  componentDidMount(){
    this.motionListener = DeviceMotion.addListener(this.onDeviceMotion);
    setTimeout(()=>{ //MH - tempm - wait 5 seconds for now before detecting motion
      this.detectMotion(true);
    },5000);
  }

  componentWillUpdate(nextProps, nextState) {
    if (this.state.detectMotion && nextState.motion && this.state.motion){
      if (
        Math.abs(nextState.motion.x - this.state.motion.x) < this.props.motionTolerance
        && Math.abs(nextState.motion.y - this.state.motion.y) < this.props.motionTolerance
        && Math.abs(nextState.motion.z - this.state.motion.z) < this.props.motionTolerance
      ){
        //still
        this.detectFaces(true);
        this.detectMotion(false);
      } else {
        //moving
      }
    }
    
  }

  detectMotion =(doDetect)=> {
    this.setState({
      detectMotion: doDetect,
    });
    if (doDetect){
      DeviceMotion.setUpdateInterval(this.props.motionInterval);
    } else if (!doDetect && this.state.faceDetecting) {
      this.motionListener.remove();
    }
    
  }

  onDeviceMotion = (rotation)=>{
    this.setState({
      motion: rotation.accelerationIncludingGravity
    });
  }


  detectFaces(doDetect){
    this.setState({
      faceDetecting: doDetect,
    });
  }


  render() {
    const { hasCameraPermission } = this.state;
    if (hasCameraPermission === null) {
      return <View />;
    } else if (hasCameraPermission === false) {
      return <Text>No access to camera</Text>;
    } else {
      return (
        <View style={{ flex: 1 }}>
          <Camera 
            style={{ flex: 1 }} 
            type={this.props.cameraType} 
            onFacesDetected={this.state.faceDetecting ? this.handleFacesDetected : undefined }
            onFaceDetectionError={this.handleFaceDetectionError}
            faceDetectorSettings={{
              mode: FaceDetector.Constants.Mode.fast,
              detectLandmarks: FaceDetector.Constants.Mode.none,
              runClassifications: FaceDetector.Constants.Mode.none,
            }}
            ref={ref => {
              this.camera = ref;
            }}
          >
            <View
              style={{
                flex: 1,
                backgroundColor: 'transparent',
                flexDirection: 'row',
                position: 'absolute',
                bottom: 0,
              }}>
                <Text
                  style={styles.textStandard}>
                  {this.state.faceDetected ? 'Face Detected' : 'No Face Detected'}
                </Text>
            </View>
            <View
              style={{
                flex: 1,
                backgroundColor: 'transparent',
                flexDirection: 'row',
                width: '100%',
                height: '100%',
                justifyContent: 'center',
                alignItems: 'center',
                display: this.state.faceDetected && !this.state.pictureTaken ? 'flex' : 'none',
              }}>
                <Text
                  style={styles.countdown}
                >
                  {this.state.countDownSeconds}  
                </Text>
            </View>

          </Camera>
        </View>
      );
    }
  }

  handleFaceDetectionError = ()=>{
    //
  }
  handleFacesDetected = ({ faces }) => {
    if (faces.length === 1){
      this.setState({
        faceDetected: true,
      });
      if (!this.state.faceDetected && !this.state.countDownStarted){
        this.initCountDown();
      }
    } else {
      this.setState({faceDetected: false });
      this.cancelCountDown();
    }
  }
  initCountDown = ()=>{
    this.setState({ 
      countDownStarted: true,
    });
    this.countDownTimer = setInterval(this.handleCountDownTime, 1000);
  }
  cancelCountDown = ()=>{
    clearInterval(this.countDownTimer);
    this.setState({ 
      countDownSeconds: this.props.countDownSeconds,
      countDownStarted: false,
    });
  }
  handleCountDownTime = ()=>{
    if (this.state.countDownSeconds > 0){
      let newSeconds = this.state.countDownSeconds-1;
      this.setState({
        countDownSeconds: newSeconds,
      });
    } else {
      this.cancelCountDown();
      this.takePicture();
    }
  }
  takePicture = ()=>{
    this.setState({
      pictureTaken: true,
    });
    if (this.camera) {
      console.log('take picture');
      this.camera.takePictureAsync({ onPictureSaved: this.onPictureSaved });
    }
  }
  onPictureSaved = ()=>{
    this.detectFaces(false);
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  textStandard: {
    fontSize: 18, 
    marginBottom: 10, 
    color: 'white'
  },
  countdown: {
    fontSize: 40,
    color: 'white'
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment