react-native-face-match
v0.7.2
Published
Expo module for on-device face matching across a device photo library using FaceNet embeddings and ObjectBox vector storage.
Downloads
145
Maintainers
Readme
react-native-face-match
On-device face similarity search for React Native. Give it a reference photo and it scans the device gallery to find matching faces — fully offline, no cloud API needed.
Built as an Expo module using FaceNet 512 (TensorFlow Lite) for face embeddings and ObjectBox as the on-device vector store.
How it works
- Extracts a 512-dimensional face embedding from your reference image using FaceNet.
- Scans the device photo library (up to 3,000 photos), detects faces, and stores their embeddings in ObjectBox.
- Finds photos whose embeddings are within a cosine-distance threshold of the reference.
- Emits real-time progress events so you can show a live progress bar.
Results from previous runs are cached — subsequent searches only process new photos.
Installation
npm install react-native-face-match
# or
yarn add react-native-face-matchiOS
Run pod install after adding the package:
cd ios && pod installAdd the following keys to your Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library to find matching faces.</string>The FaceNet model (facenet_512.tflite, ~23 MB) is bundled inside the iOS pod — no download step needed on iOS.
Android
Add the following permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- For Android 12 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.INTERNET" />Unlike iOS, the Android model is not bundled — you must host facenet_512.tflite yourself and call downloadModel() before the first use.
Quick start
import FaceMatch from 'react-native-face-match';
// Android only: download the model once before first use
if (Platform.OS === 'android') {
await FaceMatch.downloadModel('https://your-cdn.example.com/facenet_512.tflite', false);
}
// Check if the reference image contains a face
const hasFace = await FaceMatch.isFaceAvailable(referenceImageUri);
// Subscribe to live progress
const sub = FaceMatch.addListener('FaceComparisonProgress', (data) => {
console.log(`${data.progressPercent}% — ${data.totalProcessed}/${data.totalImages}`);
console.log('Current matches:', data.data?.results);
});
// Run the gallery search
const result = await FaceMatch.getPhotos(referenceImageUri);
console.log('Matches:', result.data);
sub.remove();API
downloadModel(modelUrl, forceDownload) — Android only
Downloads the FaceNet TFLite model from your CDN. Must be called before getPhotos() or isFaceAvailable() on Android.
FaceMatch.downloadModel(modelUrl: string, forceDownload: boolean): Promise<{
success: boolean;
modelPath: string;
fileSize: number;
exists: boolean;
}>| Parameter | Type | Description |
|-----------|------|-------------|
| modelUrl | string | HTTPS URL to your hosted facenet_512.tflite |
| forceDownload | boolean | Re-download even if the model already exists locally |
isFaceAvailable(imageUri)
Returns true if a face is detected in the given image. Use this to validate the reference photo before starting a search.
FaceMatch.isFaceAvailable(imageUri: string): Promise<boolean>Accepts file://, ph:// (iOS Photo Library), or base64 data URIs.
getPhotos(referenceImageUri)
Scans the device photo library and returns photos that match the reference face. This is the main method.
FaceMatch.getPhotos(referenceImageUri: string): Promise<{
data: PhotoMatch[];
time: string;
totalImagesProcessed: string;
timeTakenToScanGalleryInSec: string;
timeTakenToGetDataFromDbInSec: string;
failedProcessed: number;
finalProcessedImages: number;
finalAvailableMemoryMB: string;
finalMemoryUsage: string;
}>PhotoMatch object:
| Field | Type | Description |
|-------|------|-------------|
| matchedImagePath | string | URI of the matched gallery photo |
| score | string | Similarity score (lower = more similar; threshold is 0.35) |
| photoTimestamp | number | Unix timestamp (ms) of when the photo was taken |
Requires photo library permission. Automatically skips screenshots, WhatsApp media, and cache files.
compareSinglePhoto(imageUri, referenceImageUri)
Compares two specific images and returns a similarity score. Useful for one-off comparisons without scanning the gallery.
FaceMatch.compareSinglePhoto(imageUri: string, referenceImageUri: string): Promise<{
score: string;
photoTimestamp: string;
}>addListener('FaceComparisonProgress', callback)
Subscribe to real-time progress updates during getPhotos(). Events fire approximately every 2% of processing progress.
const subscription = FaceMatch.addListener('FaceComparisonProgress', (data) => {
// data.progressPercent — 0–100
// data.totalImages — total gallery images to scan
// data.totalProcessed — images processed so far
// data.successfulProcessed
// data.failedProcessed — images with no face or load error
// data.remainingImages
// data.data?.results — current top matches (updated in real time)
});
// Always remove the listener when done
subscription.remove();expirePhotoPickerCache()
Clears the list of already-processed images. The next getPhotos() call will re-process the entire gallery.
FaceMatch.expirePhotoPickerCache(): voidclearObjectBoxDatabase()
Deletes all stored face embeddings from ObjectBox. Combined with expirePhotoPickerCache(), this gives you a full reset.
FaceMatch.clearObjectBoxDatabase(): voidEvents
| Event name | Payload |
|------------|---------|
| FaceComparisonProgress | { progressPercent, totalImages, totalProcessed, successfulProcessed, failedProcessed, remainingImages, data: { results: PhotoMatch[] } } |
Accepted image formats
| Format | iOS | Android |
|--------|-----|---------|
| file:// path | ✅ | ✅ |
| ph:// Photo Library URI | ✅ | ❌ |
| Base64 data URI (data:image/...) | ✅ | ✅ |
| JPEG / HEIC / PNG | ✅ | ✅ |
Performance notes
- Caching: embeddings computed in previous runs are stored in ObjectBox. Repeat searches only process new photos — no redundant work.
- Concurrency: uses a producer-consumer pipeline with up to 4 producers and 8 consumers running in parallel (scaled to available CPU cores).
- Gallery limit: scans up to 3,000 of the most recent photos.
- Model size: the FaceNet 512 model is ~23 MB. On iOS it is bundled with the pod. On Android it is downloaded on first use.
- Similarity threshold: the default match threshold is a cosine distance of 0.35. Scores below this threshold are considered a match.
Example
A full working example is in example/App.tsx. It demonstrates:
- Downloading the model (Android)
- Picking a reference photo and validating it has a face
- Running a gallery search with a live progress bar
- Comparing a single photo
- Clearing the cache
To run the example, install expo-image-picker alongside this package:
npx expo install expo-image-pickerPlatform requirements
| | Minimum | |-|---------| | iOS | 15.1 | | Android | API 21 (Android 5.0) | | Expo SDK | 49+ |
License
MIT
