VNCoreMLRequest — what it is, Vision request for Core ML in iOS

Author: IT Sectr Published: 2026-07-20 Reading time: 8 min

VNCoreMLRequest — is a subclass of VNRequest that allows running Core ML models inside the Vision pipeline, combining the advantages of ready-made Vision detectors (faces, text, objects) with custom ML models for classification and regression. According to Apple Machine Learning Documentation (2024), VNCoreMLRequest automatically handles image preprocessing — scaling, cropping and color space conversion — according to the Core ML model requirements.

Key Takeaways

  • VNCoreMLRequest — bridge between Core ML and Vision: ML model works as a Vision request.
  • Automatic image preprocessing: scaling, crop and color correction to meet the model requirements.
  • Combines with other VNRequest in a single perform() for building pipelines.
  • Supports VNCoreMLModel — a wrapper over MLModel for working with images and FeatureValue.
  • Runs on Neural Engine and GPU for maximum performance.

What is VNCoreMLRequest?

VNCoreMLRequest is a subclass of VNRequest, added in iOS 11 along with Vision, which allows running Core ML models in the context of Vision. It handles all image preprocessing required by the Core ML model: resizing, cropping, normalization and color space conversion.

Without VNCoreMLRequest, a developer would have to manually convert UIImage/CGImage to MultiArray (MLMultiArray) or PixelBuffer (CVPixelBuffer) of the required size. VNCoreMLRequest automates this process: you pass a CGImage through VNImageRequestHandler, and VNCoreMLRequest scales it to the model input itself.

VNCoreMLRequest inherits all VNRequest capabilities: completion handler, regionOfInterest, ability to execute multiple requests simultaneously, support for VNImageRequestHandler and VNSequenceRequestHandler.

swift
import Vision
import CoreML

// 1. Load and wrap model
guard let model = try VNCoreMLModel(
    for: MobileNetV2().model)
else { return }

// 2. Create VNCoreMLRequest
let request = VNCoreMLRequest(model: model) { req, _ in
    guard let results = req.results
        as? [VNClassificationObservation]
    else { return }
    for r in results.prefix(3) {
        print("\\(r.identifier): \\(r.confidence)")
    }
}

// 3. Execute via handler
let handler = VNImageRequestHandler(cgImage: image, options: [:])
try handler.perform([request])

VNCoreMLRequest supports models with different input types: images (Image Feature), MultiArray and Double. For images, Vision automatically converts CGImage to CVPixelBuffer of the required size and color space. For other input data types, Core ML should be used directly without Vision.

According to Apple WWDC 2023, VNCoreMLRequest is used in 40% of all iOS applications that use Core ML for image processing. This is the most popular way to integrate ML models into iOS applications.

VNCoreMLModel: a wrapper over Core ML model

VNCoreMLModel is a wrapper that adapts the Core ML model (MLModel) for use in Vision. It converts the model input and output data into a format understandable by Vision: image → CVPixelBuffer, result → VNObservation.

VNCoreMLModel initialization checks model compatibility with Vision: the model must accept an image as input (Image Feature) and return classification (MLMultiArray or Dictionary). If the model is incompatible, the initializer throws an error.

swift
import Vision
import CoreML

// Option 1: From .mlmodel (compiled at build time)
let model1 = try VNCoreMLModel(
    for: MyVisionModel().model)

// Option 2: From .mlmodelc (compiled on device)
let compiledURL = Bundle.main.url(
    forResource: "MyVisionModel",
    withExtension: "mlmodelc")!
let model2 = try VNCoreMLModel(
    for: MLModel(contentsOf: compiledURL))

VNCoreMLModel caches the model in memory after the first load. Reloading the same model returns the cached instance, which speeds up subsequent requests. However, if the model weighs more than 100 MB, iOS may unload it from memory when resources are low — in this case, VNCoreMLModel will reload the model automatically.

For models trained with Create ML, VNCoreMLModel works without additional configuration. Create ML exports models with the correct metadata that Vision recognizes automatically — just pass the model to VNCoreMLModel(model:).

Configuring imageCropAndScaleOption

imageCropAndScaleOption is a key property of VNCoreMLRequest that determines how Vision transforms the source image to match the Core ML model input size. Choosing the correct option directly affects classification accuracy.

.centerCrop crops the image from the center to a square, then scales it to the model input size. Suitable for models trained on centered objects (most ImageNet classifiers). .scaleFill stretches the image to the input size without preserving proportions. Fast, but distorts geometry. .scaleFit scales while preserving proportions, adding letterbox (black bars) along the edges.

swift
import Vision

let request = VNCoreMLRequest(model: model)

// .centerCrop — for centered objects (default)
request.imageCropAndScaleOption = .centerCrop

// .scaleFill — for uniform textures (no distortion)
request.imageCropAndScaleOption = .scaleFill

// .scaleFit — when object proportions matter
request.imageCropAndScaleOption = .scaleFit

Recommendations: for most classification models, use .centerCrop — it gives the best balance of accuracy and performance. If the model was trained on images with preserved proportions (e.g., anomaly detection on document photos), choose .scaleFit with letterbox.

According to Apple Developer Documentation 2024, incorrect choice of imageCropAndScaleOption can reduce model accuracy by 15–25%. For example, .scaleFill for a face located at the edge of the frame may cut off part of it with .centerCrop or distort proportions with .scaleFill.

Combining with other VNRequest

The main strength of VNCoreMLRequest is the ability to combine it with other VNRequest in a single perform() call. This allows building pipelines: first detect faces (VNDetectFaceRectanglesRequest), then classify each face through a custom Core ML model (VNCoreMLRequest).

VNCoreMLRequest also supports regionOfInterest — if you set this area, Vision will crop the image to the specified rectangle before passing it to the Core ML model. This is critical for pipelines: after face detection, you pass its bounding box as regionOfInterest for VNCoreMLRequest.

swift
import Vision

// 1. Face detection
let faceRequest = VNDetectFaceRectanglesRequest()

// 2. Emotion classification via Core ML
guard let emotionModel = try VNCoreMLModel(
    for: EmotionClassifier().model)
else { return }

let emotionRequest = VNCoreMLRequest(model: emotionModel)
try handler.perform([faceRequest, emotionRequest])

// 3. Set regionOfInterest for each face
for face in faceRequest.results as? [VNFaceObservation] ?? [] {
    emotionRequest.regionOfInterest = face.boundingBox
    try handler.perform([emotionRequest])
    // Process emotion classification result
}

Limitation: regionOfInterest for VNCoreMLRequest makes sense when the model is trained on images of the same size and proportion. If the model expects a strictly square input (224x224), .centerCrop with regionOfInterest will give the best result.

According to Apple ML Research, the “detection → classification” pipeline through regionOfInterest provides a 20–30% accuracy improvement compared to classifying the whole image, because the ML model receives only the relevant area without background noise.

Pipeline typeRequest 1 (detection)Request 2 (ML)Example
Face → emotionVNDetectFaceRectanglesRequestVNCoreMLRequestMood detection
Object → brandVNDetectObjectAtPointRequestVNCoreMLRequestLogo recognition
Text → languageVNRecognizeTextRequestVNCoreMLRequestText language classification
Scene → descriptionVNClassifyImageRequestVNCoreMLRequestTag generation

Processing VNCoreMLRequest results

VNCoreMLRequest returns results as VNClassificationObservation (for classification models) or VNCoreMLFeatureValueObservation (for regression and other types). The result type depends on the output data of the Core ML model.

VNClassificationObservation contains identifier (class name) and confidence. Models with softmax output return an array of such observations sorted by confidence in descending order. VNCoreMLFeatureValueObservation contains an arbitrary MLFeatureValue — can be MultiArray, Double, String or Dictionary.

swift
// For classification models
if let classificationResults = request.results
    as? [VNClassificationObservation] {
    for result in classificationResults
        where result.confidence > 0.5 {
        print("\\(result.identifier): \\(result.confidence)")
    }
}

// For regression models (feature values)
if let featureResults = request.results
    as? [VNCoreMLFeatureValueObservation] {
    for result in featureResults {
        let value = result.featureValue
        print("\\(result.featureName): \\(value)")
    }
}

Confidence filtering: Apple recommends discarding results with confidence < 0.3 for general classifiers and < 0.7 for critical applications. For models trained on balanced datasets, confidence correlates with the probability of correct answer but does not guarantee it.

VNCoreMLFeatureValueObservation.featureName corresponds to the model output layer name (e.g., “classLabel” or “features”). This allows handling models with multiple outputs — each output is represented by a separate observation with a unique featureName.

Best practices and performance

VNCoreMLRequest is optimized to work on Neural Engine (A12+), GPU and CPU. Vision automatically selects the best device for model execution depending on its type and size. However, performance can be further improved with proper configuration.

Memory management

Core ML models are loaded into memory on the first VNCoreMLRequest and remain there until the application is unloaded. For models larger than 200 MB, Apple recommends loading them on demand and unloading via MLModel.release(). VNCoreMLModel manages caching itself, but you can control this through autoreleasepool.

Batch processing

For batch processing of images, create one VNCoreMLRequest and reuse it with different VNImageRequestHandler. Do not create a new VNCoreMLRequest for each image — this will slow down processing due to repeated model loading. Reusing the request provides a performance gain of up to 40% when processing 10+ images.

swift
// Correct: single request for all images
let batchSize = 20
let batchRequest = VNCoreMLRequest(model: model)

for i in 0..<batchSize {
    let handler = VNImageRequestHandler(
        cgImage: images[i],
        options: [:])
    try handler.perform([batchRequest])
    // batchRequest.results update on each call
}

Device selection: by default, Vision selects Neural Engine for compatible models on devices A12+. If the model does not support Neural Engine, Vision uses GPU or CPU. You can force specify the device through MLModelConfiguration.computeUnits, but Apple recommends leaving automatic selection.

According to Apple Performance Benchmarks 2024, VNCoreMLRequest on Neural Engine (iPhone 15 Pro) processes MobileNetV2 classification in 3–5 ms, on GPU — 8–12 ms, on CPU — 20–30 ms. The difference becomes critical for real-time applications processing 30+ frames per second.

Frequently Asked Questions

Can VNCoreMLRequest be used without Vision — directly with Core ML?

Yes, Core ML can be used directly through MLModel.prediction() without Vision. However, VNCoreMLRequest automates image preprocessing (scaling, cropping, conversion to CVPixelBuffer). If the model accepts not an image but MultiArray or Double — use Core ML directly. VNCoreMLRequest is only for models with Image Feature as input.

How to update VNCoreMLRequest when a new model version is released?

Create a new VNCoreMLModel from the updated MLModel and a new VNCoreMLRequest. The old request will continue using the old model version. For remote model updates, use MLModel.compileModel(at:) to compile the model on device from a .mlmodelc file downloaded from the server.

Does VNCoreMLRequest support models with multiple inputs?

VNCoreMLRequest only supports models with a single Image Feature input. If the model has multiple inputs (e.g., image + text), use Core ML directly through MLModel. Vision cannot pass additional parameters beyond the image.

What is the maximum image size for VNCoreMLRequest?

The limit is 8192 x 8192 pixels for CGImage passed to VNImageRequestHandler. However, Core ML models typically expect input of 224x224, 299x299 or 512x512. Vision automatically scales large images to the input size. If the original image is too large, reduce it beforehand through CGImage to save memory.

Can VNCoreMLRequest be run in the background?

Yes, set preferBackgroundProcessing = true on VNRequest. This allows Vision to defer request execution if the system is in a resource-intensive mode (e.g., content loading). Also, be sure to use DispatchQueue.global(qos: .background) for calling handler.perform().

Summary

  • VNCoreMLRequest — a subclass of VNRequest for running Core ML models in the Vision pipeline with automatic image preprocessing.
  • VNCoreMLModel wraps MLModel for Vision, automatically converting CGImage to CVPixelBuffer of the required size and color space.
  • imageCropAndScaleOption (centerCrop, scaleFill, scaleFit) determines the scaling strategy and affects model accuracy by 15–25%.
  • Combining with VNDetectFaceRectanglesRequest and other VNRequest allows building “detection → classification” pipelines with regionOfInterest.
  • Results are returned as VNClassificationObservation (for classification) or VNCoreMLFeatureValueObservation (for regression/other types).
  • Reusing a single VNCoreMLRequest for batch processing gives up to 40% performance gain compared to creating a new one for each image.
  • Performance on Neural Engine (3–5 ms on MobileNetV2) is 4–6 times higher than on CPU, which is critical for real-time applications.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also