react-native-mlkit icon indicating copy to clipboard operation
react-native-mlkit copied to clipboard

Can't detect faces in an image on iOS device

Open mgulfam0722 opened this issue 7 months ago • 7 comments

Hi there, So I have been using this library for Android with no issues, it detects face with ease, however, on iOS device, the faces property is just an empty array, its not able to detect any face whatsoever.

Here's the screen file

//facial-scan.tsx
import React, { useRef, useState } from 'react';
import { Button, Image, View, StyleSheet, Dimensions, Text } from 'react-native';
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
import Svg, { Rect, Mask } from 'react-native-svg';
import { useRouter } from 'expo-router';
import { layout } from '@/styles/common';
import { Button as ButtonComponent, Header } from '@/components';
import { typography } from '@/styles/typography';
import { useIsFocused } from '@react-navigation/native';
import { useFacesInPhoto, useFaceDetection } from '@infinitered/react-native-mlkit-face-detection';

const { width, height } = Dimensions.get('window');
const horizontalPadding = 20;
const maskMarginTop = 30;
const frameWidth = width - horizontalPadding * 2;
const frameHeight = frameWidth;
const frameX = (width - width * 0.9) / 2;
const frameY = maskMarginTop;

export default function FaceRecognitionScreen() {
    const model = useFaceDetection();
    const device = useCameraDevice('front');
    const router = useRouter();

    const [photoUri, setPhotoUri] = useState<string | null>(null);
    const { hasPermission, requestPermission } = useCameraPermission();
    // This hook returns `true` if the screen is focused, `false` otherwise
    const isFocused = useIsFocused();
    // const appState = useAppState()
    const isActive = isFocused;

    const cameraRef = useRef<Camera>(null);

    const { faces, error } = useFacesInPhoto(photoUri ?? undefined);

    const takePicture = async () => {
        if (!cameraRef.current) return;
        try {
            const photo = await cameraRef.current?.takePhoto({ flash: 'off' });
            try {
                const result = await model.detectFaces(`file://${photo.path}`);
                // Comment below line prior to submitting new release APK
                // router.replace('/(auth)/review-info');

                console.log('result: ', result)
                // Uncomment below line prior during development with physical device
                if (!result || !faces.length) {
                    // Handle undefined result case
                    alert('No faces detected!');
                    return;
                }
                else if(faces.length === 1) {
                    router.navigate('/(auth)/review-info');
                }
                alert(`Faces detected!, ${faces.length}, ${JSON.stringify(faces)}`);
            } catch (error) {
                // Handle error case
                console.error('Face detection failed:', error);
            }
        } catch (err) {
            console.error('Error taking photo:', err);
        }
    };

    return (
        <View style={layout.fill}>
            <Header onPressCallback={() => {
                router.back();
            }} />
            <View
                style={styles.cameraContainer}
            >
                {/* Camera */}
                {device && (
                    <View style={styles.roundedCameraWrapper}>
                        {!hasPermission ? (
                            <View>
                                <Text style={styles.permissionPromptText}>
                                    We need your permission to show the camera
                                </Text>
                                <Button onPress={requestPermission} title="grant permission" />
                            </View>
                        ) : <Camera
                            style={StyleSheet.absoluteFill}
                            isActive={isActive}
                            device={device}
                            resizeMode="cover"
                            ref={cameraRef}
                            photo={true}
                        />}
                    </View>
                )}

                {/* Masked Overlay */}
                <Svg style={StyleSheet.absoluteFill}>
                    <Mask id="mask">
                        <Rect x="0" y="0" width={width} height={height} fill="white" />
                        <Rect
                            x={frameX}
                            y={frameY}
                            width={'90%'}
                            height={frameHeight}
                            rx={10}
                            ry={10}
                            fill="black"
                        />
                    </Mask>

                    <Rect
                        y="0"
                        width={'100%'}
                        height={height * 0.75}
                        fill="#000000C7"
                        mask="url(#mask)"
                        rx={10}
                        ry={10}
                    />
                </Svg>

                {/* Text inside frame */}
                <View style={styles.textInFrameContainer}>
                    <Image source={require('@/assets/images/facial.png')} />
                    <Text style={styles.title}>Face Recognition</Text>
                    <Text style={styles.subtitle}>Try to keep your face within the frame</Text>
                </View>
            </View>

            <View style={layout.buttonContainer}>
                <ButtonComponent title="Submit" onPressCallback={takePicture} />
            </View>
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        borderWidth: 3,
        borderColor: 'red'
    },
    backButton: {},
    title: {
        fontSize: 18,
        fontWeight: 'bold',
        color: '#00C2CB',
    },
    subtitle: {
        fontSize: 14,
        color: '#fff',
        marginTop: 5,
        borderWidth: 2,
        borderColor: 'red'
    },
    submitButton: {
        position: 'absolute',
        bottom: 40,
        alignSelf: 'center',
        backgroundColor: '#00C2CB',
        // borderRadius: 999,
        paddingVertical: 12,
        paddingHorizontal: 40,
    },
    submitText: {
        color: '#fff',
        fontSize: 16,
        fontWeight: 'bold',
    },
    textInFrameContainer: {
        position: 'absolute',
        alignItems: 'center',
        justifyContent: 'space-evenly',
        top: frameHeight * 0.65,
        left: frameX - 20,
        width: frameWidth,
        height: 150
    },
    camera: {
        width: '99.9%',
        height: height * 0.75,
        overflow: 'hidden',
        borderColor: 'green',
        borderWidth: 2
    },
    cameraContainer: {
        marginTop: 20,
        overflow: 'hidden',
        flex: 1,
    },
    roundedCameraWrapper: {
        width: '99.9%',
        height: height * 0.75,
        borderRadius: 10, // whatever radius you want
        overflow: 'hidden',
        alignSelf: 'center', // optional, if needed
        borderWidth: 2,
        borderColor: 'green', // optional
    },
    permissionPromptText: {
        ...typography.body,
        color: 'white',
        marginBottom: 10
    },
});

and here is output from

await model.detectFaces(`file://${photo.path}`):

{"faces": [], "imagePath": "file://file///private/var/mobile/Containers/Data/Application/AD8AF7CF-043A-4125-970F-5ECB662C9016/tmp/9B9AEF52-9647-4259-9A3F-AA3BAFC6EB6B.jpg"}

mgulfam0722 avatar Apr 24 '25 07:04 mgulfam0722

Though it doesn't mention anything about real iOS physical device, it only says that it won't work on simulators. Mine is a physical iOS device.

mgulfam0722 avatar Apr 24 '25 07:04 mgulfam0722

Versions installed

"@infinitered/react-native-mlkit-core": "^3.1.0",
"@infinitered/react-native-mlkit-face-detection": "^3.1.0"

and I am using Expo.

mgulfam0722 avatar Apr 24 '25 07:04 mgulfam0722

Tried on a different iOS device, same issue, face property is an empty array.

Here is the reproducible code:

import React, { useEffect, useRef, useState } from 'react';
import { Alert, Button, StyleSheet, View, Text } from 'react-native';
import { Camera, useCameraDevices, useCameraPermission, PhotoFile } from 'react-native-vision-camera';
import { useFaceDetection } from '@infinitered/react-native-mlkit-face-detection';

export default function FullScreenCamera() {
  const { hasPermission, requestPermission } = useCameraPermission();
  const devices = useCameraDevices();
  const device = devices[1];
  const camera = useRef<Camera>(null);
  const [photoPath, setPhotoPath] = useState<string | null>(null);

  const model = useFaceDetection();

  useEffect(() => {
    if (!hasPermission) {
      Alert.alert(
        'Camera Permission Required',
        'We need access to your camera to continue.',
        [
          {
            text: 'Okay',
            onPress: requestPermission,
            style: 'default',
          },
        ],
        { cancelable: false }
      );
    }
  }, [hasPermission]);

  const takePhoto = async () => {
    if (camera.current == null) return;

    try {
      const photo: PhotoFile = await camera.current.takePhoto({
        // qualityPrioritization: 'speed',
      });

      const res = await model.detectFaces(photo.path);
      console.log('Face detection result:', res);
      // if (!res) {
      //   console.error('Error detecting faces:', res);
      //   return;
      // }
      // const faces = res.faces;
      // const hasFaces = faces.length > 0;
      // console.log('Faces detected:', faces);
      // console.log('Has faces:', hasFaces);

      console.log('Photo taken at path:', photo.path);
      setPhotoPath(photo.path); // This will trigger useFaceDetection automatically
    } catch (error) {
      console.error('Error taking photo:', error);
    }
  };

  if (!device || !hasPermission) {
    return <View style={styles.blackScreen} />;
  }

  return (
    <View style={styles.container}>
      <Camera
        ref={camera}
        style={StyleSheet.absoluteFill}
        device={device}
        isActive={true}
        photo={true}
      />
      <View style={styles.captureButton}>
        <Button title="Capture" onPress={takePhoto} />
      </View>

      {/* {photoPath && (
        <View style={styles.faceResult}>
          <Text style={{ color: 'white' }}>
          </Text>
          <Button title="Reset" onPress={takePhoto} />
        </View>
      )} */}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  blackScreen: {
    flex: 1,
    backgroundColor: 'black',
  },
  captureButton: {
    position: 'absolute',
    bottom: 40,
    alignSelf: 'center',
    backgroundColor: 'white',
    borderRadius: 8,
    paddingHorizontal: 20,
    paddingVertical: 10,
  },
  faceResult: {
    position: 'absolute',
    top: 60,
    alignSelf: 'center',
    backgroundColor: 'rgba(0,0,0,0.6)',
    padding: 10,
    borderRadius: 10,
  },
});

musabgulfam avatar Apr 27 '25 09:04 musabgulfam

Any updates?

mgulfam0722 avatar Apr 28 '25 04:04 mgulfam0722

Hey @mgulfam0722 - sorry for the delay here, no updates as of yet but we will take a look!

coolsoftwaretyler avatar Nov 11 '25 22:11 coolsoftwaretyler

@mgulfam0722 can you just let us know your Expo and RN version please?

frankcalise avatar Nov 13 '25 21:11 frankcalise

Hi, quick update: the last time I tested this issue was on April 24th, 2025. I’ve since removed the reproducible test project I had set up, so I currently don't have an environment to re-verify the behaviour.

mgulfam0722 avatar Nov 14 '25 10:11 mgulfam0722