Page Summary
-
ML Kit can be used to recognize and label entities in images using custom models.
-
You can bundle the model with your app or host it on Firebase, with trade-offs in app size, availability, and update flexibility.
-
Before using the model, you need to load it either locally or from Firebase, configure an image labeler, and prepare the input image.
-
After processing the image with the labeler, you can retrieve information about the identified entities and their confidence scores.
-
For real-time performance, use the synchronous API and render results in a single step.
You can use ML Kit to recognize entities in an image and label them. This API supports a wide range of custom image classification models. Refer to Custom models with ML Kit for guidance on model compatibility requirements, where to find pre-trained models, and how to train your own models.
There are two ways to integrate a custom model. You can bundle the model by putting it inside your app’s asset folder, or you can dynamically download it from Cloud Storage. The following table compares the two options.
| Bundled Model | Hosted Model |
|---|---|
| The model is part of your app's APK, which increases its size. | The model is not part your APK. It is hosted by uploading to Cloud Storage. We recommend using Cloud Storage for Firebase. |
| The model is available immediately, even when the Android device is offline | Your app must include code to download the model on demand |
| No need for a Firebase project | Requires a Firebase project (if using Cloud Storage for Firebase). |
| You must republish your app to update the model | Push model updates without republishing your app |
| No built-in A/B testing | A/B testing with Firebase Remote Config |
Try it out
- See the vision quickstart app for an example usage of the bundled model and the automl quickstart app for an example usage of the hosted model.
Before you begin
Include the ML Kit libraries in your Podfile:
pod 'GoogleMLKit/ImageLabelingCustom', '8.0.0'After you install or update your project's Pods, open your Xcode project using its
.xcworkspace. ML Kit is supported in Xcode version 13.2.1 or higher.If you want to download a model using Cloud Storage for Firebase, make sure you add Firebase to your iOS project, if you have not already done so. This is not required when you bundle the model.
1. Load the model
Configure a local model source
To bundle the model with your app:
Copy the model file (usually ending in
.tfliteor.lite) to your Xcode project, taking care to selectCopy bundle resourceswhen you do so. The model file will be included in the app bundle and available to ML Kit.Create
LocalModelobject, specifying the path to the model file:Swift
let localModel = LocalModel(path: localModelFilePath)
Objective-C
MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:localModelFilePath];
Configure a remotely-hosted model source
To use the remotely-hosted model, you must download the model file to the device's local storage using your own app logic, and then load it as a local model. We recommend using Cloud Storage for Firebase to host a model. For implementation details, see the Firebase ML to Cloud Storage migration guide.
Configure the image labeler
After you configure your model sources, create an ImageLabeler object from one
of them.
The following options are available:
| Options | |
|---|---|
confidenceThreshold
|
Minimum confidence score of detected labels. If not set, any classifier threshold specified by the model’s metadata will be used. If the model does not contain any metadata or the metadata does not specify a classifier threshold, a default threshold of 0.0 will be used. |
maxResultCount
|
Maximum number of labels to return. If not set, the default value of 10 will be used. |
If you only have a locally-bundled model, just create a labeler from your
LocalModel object:
Swift
let options = CustomImageLabelerOptions(localModel: localModel) options.confidenceThreshold = NSNumber(value: 0.0) let imageLabeler = ImageLabeler.imageLabeler(options: options)
Objective-C
MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel]; options.confidenceThreshold = @(0.0); MLKImageLabeler *imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];
If you have a remotely-hosted model, you will have to check that it has been downloaded before you run it.
Although you only have to confirm this before running the labeler, if you
have both a remotely-hosted model and a locally-bundled model, it might make
sense to perform this check when instantiating the ImageLabeler: create a
labeler from the remote model if it's been downloaded, and from the local model
otherwise.
Swift
// Path where your download logic saves the model let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite") let model: LocalModel if FileManager.default.fileExists(atPath: localModelURL.path) { // Use the downloaded model model = LocalModel(path: localModelURL.path) } else { // Fall back to bundled model guard let bundledModelPath = Bundle.main.path(forResource: "model", ofType: "tflite") else { return } model = LocalModel(path: bundledModelPath) } let options = CustomImageLabelerOptions(localModel: model) let imageLabeler = ImageLabeler.imageLabeler(options: options)
Objective-C
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"]; MLKLocalModel *model; if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) { // Use the downloaded model model = [[MLKLocalModel alloc] initWithPath:localModelPath]; } else { // Fall back to bundled model NSString *bundledModelPath = [NSBundle.mainBundle pathForResource:@"model" ofType:@"tflite"]; model = [[MLKLocalModel alloc] initWithPath:bundledModelPath]; } MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:model]; MLKImageLabeler *imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];
If you only have a remotely-hosted model, you should disable model-related functionality—for example, gray-out or hide part of your UI—until you confirm the model has been downloaded.
Swift
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite") if FileManager.default.fileExists(atPath: localModelURL.path) { // Model is already cached, initialize immediately self.initializeLabeler(with: localModelURL) } else { // Model is not yet available, show loading UI and start download self.showLoadingUI() let storage = Storage.storage() let modelRef = storage.reference(forURL: "gs://YOUR_BUCKET/path/to/model.tflite") modelRef.write(toFile: localModelURL) { url, error in self.hideLoadingUI() if let error = error { // Handle download error self.showErrorUI() } else if let modelURL = url { // Download success, initialize labeler self.initializeLabeler(with: modelURL) } } } func initializeLabeler(with modelURL: URL) { let localModel = LocalModel(path: modelURL.path) let options = CustomImageLabelerOptions(localModel: localModel) self.imageLabeler = ImageLabeler.imageLabeler(options: options) // Enable ML-related UI features here self.enableMLFeatures() }
Objective-C
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"]; NSURL *localModelURL = [NSURL fileURLWithPath:localModelPath]; if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) { // Model is already cached, initialize immediately [self initializeLabelerWithURL:localModelURL]; } else { // Model is not yet available, show loading UI and start download [self showLoadingUI]; FIRStorage *storage = [FIRStorage storage]; FIRStorageReference *modelRef = [storage referenceForURL:@"gs://YOUR_BUCKET/path/to/model.tflite"]; [modelRef writeToFile:localModelURL completion:^(NSURL * _Nullable URL, NSError * _Nullable error) { [self hideLoadingUI]; if (error != nil) { // Handle download error [self showErrorUI]; } else { // Download success, initialize labeler [self initializeLabelerWithURL:URL]; } }]; } - (void)initializeLabelerWithURL:(NSURL *)modelURL { MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path]; MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel]; self.imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options]; // Enable ML-related UI features here [self enableMLFeatures]; }
2. Prepare the input image
Create a VisionImage object using a UIImage or a
CMSampleBuffer.
If you use a UIImage, follow these steps:
- Create a
VisionImageobject with theUIImage. Make sure to specify the correct.orientation.Swift
let image = VisionImage(image: UIImage) visionImage.orientation = image.imageOrientation
Objective-C
MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image]; visionImage.orientation = image.imageOrientation;
If you use a
CMSampleBuffer, follow these steps:-
Specify the orientation of the image data contained in the
CMSampleBuffer.To get the image orientation:
Swift
func imageOrientation( deviceOrientation: UIDeviceOrientation, cameraPosition: AVCaptureDevice.Position ) -> UIImage.Orientation { switch deviceOrientation { case .portrait: return cameraPosition == .front ? .leftMirrored : .right case .landscapeLeft: return cameraPosition == .front ? .downMirrored : .up case .portraitUpsideDown: return cameraPosition == .front ? .rightMirrored : .left case .landscapeRight: return cameraPosition == .front ? .upMirrored : .down case .faceDown, .faceUp, .unknown: return .up } }
Objective-C
- (UIImageOrientation) imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation cameraPosition:(AVCaptureDevicePosition)cameraPosition { switch (deviceOrientation) { case UIDeviceOrientationPortrait: return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored : UIImageOrientationRight; case UIDeviceOrientationLandscapeLeft: return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored : UIImageOrientationUp; case UIDeviceOrientationPortraitUpsideDown: return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored : UIImageOrientationLeft; case UIDeviceOrientationLandscapeRight: return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored : UIImageOrientationDown; case UIDeviceOrientationUnknown: case UIDeviceOrientationFaceUp: case UIDeviceOrientationFaceDown: return UIImageOrientationUp; } }
- Create a
VisionImageobject using theCMSampleBufferobject and orientation:Swift
let image = VisionImage(buffer: sampleBuffer) image.orientation = imageOrientation( deviceOrientation: UIDevice.current.orientation, cameraPosition: cameraPosition)
Objective-C
MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer]; image.orientation = [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation cameraPosition:cameraPosition];
3. Run the image labeler
To label objects in an image, pass the
imageobject to theImageLabeler'sprocess()method.Asynchronously:
Swift
imageLabeler.process(image) { labels, error in guard error == nil, let labels = labels, !labels.isEmpty else { // Handle the error. return } // Show results. }
Objective-C
[imageLabeler processImage:image completion:^(NSArray
*_Nullable labels, NSError *_Nullable error) { if (label.count == 0) { // Handle the error. return; } // Show results. }]; Synchronously:
Swift
var labels: [ImageLabel] do { labels = try imageLabeler.results(in: image) } catch let error { // Handle the error. return } // Show results.
Objective-C
NSError *error; NSArray
*labels = [imageLabeler resultsInImage:image error:&error]; // Show results or handle the error. 4. Get information about labeled entities
If the image labeling operation succeeds, it returns an array ofImageLabel. EachImageLabelrepresents something that was labeled in the image. You can get each label's text description (if available in the metadata of the LiteRT model file), confidence score, and index. For example:Swift
for label in labels { let labelText = label.text let confidence = label.confidence let index = label.index }
Objective-C
for (MLKImageLabel *label in labels) { NSString *labelText = label.text; float confidence = label.confidence; NSInteger index = label.index; }
Tips to improve real-time performance
If you want to label images in a real-time application, follow these guidelines to achieve the best framerates:
- For processing video frames, use the
results(in:)synchronous API of the detector. Call this method from theAVCaptureVideoDataOutputSampleBufferDelegate'scaptureOutput(_, didOutput:from:)function to synchronously get results from the given video frame. KeepAVCaptureVideoDataOutput'salwaysDiscardsLateVideoFramesastrueto throttle calls to the detector. If a new video frame becomes available while the detector is running, it will be dropped. - If you use the output of the detector to overlay graphics on the input image, first get the result from ML Kit, then render the image and overlay in a single step. By doing so, you render to the display surface only once for each processed input frame. See the updatePreviewOverlayViewWithLastFrame in the ML Kit quickstart sample for an example.
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-06-16 UTC.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2026-06-16 UTC."],[],[]] - For processing video frames, use the
-