An unofficial Swift Package Manager distribution of Google MediaPipe Tasks Vision for iOS and macOS.
The package provides:
MediaPipeTasksVision- MediaPipe Tasks Vision XCFramework
MediaPipeTasksVisionHandLandmarker- The standard Hand Landmarker model and helpers for creating
HandLandmarkerOptions
- The standard Hand Landmarker model and helpers for creating
MediaPipeTasksVisionPoseLandmarker- The Pose Landmarker (lite) model and helpers for creating
PoseLandmarkerOptions
- The Pose Landmarker (lite) model and helpers for creating
MediaPipeTasksVisionFaceLandmarker- The Face Landmarker model and helpers for creating
FaceLandmarkerOptions
- The Face Landmarker model and helpers for creating
- iOS 17 or later / macOS 14 or later
- arm64 iOS devices/simulators
- macOS: universal (arm64 + x86_64) linking, but MediaPipe inference runs only when executing natively on Apple Silicon — see Intel Macs and Rosetta
- Xcode 26.x or later
You can install this package with Swift Package Manager.
Select one of the following products:
| Product | Description |
|---|---|
MediaPipeTasksVision |
MediaPipe Tasks Vision APIs only |
MediaPipeTasksVisionHandLandmarker |
APIs and the bundled Hand Landmarker model |
MediaPipeTasksVisionPoseLandmarker |
APIs and the bundled Pose Landmarker model |
MediaPipeTasksVisionFaceLandmarker |
APIs and the bundled Face Landmarker model |
Select a landmarker product when using the model it bundles; the products can be used together.
The bundled product includes hand_landmarker.task. Applications do not need
to copy the model, locate it with Bundle.module, or manage its checksum.
import MediaPipeTasksVision
import MediaPipeTasksVisionHandLandmarker
let options = try HandLandmarkerModel.makeOptions(
runningMode: .image,
numberOfHands: 2
)
let handLandmarker = try HandLandmarker(options: options)import MediaPipeTasksVision
import MediaPipeTasksVisionHandLandmarker
import UIKit
func detectHands(in image: UIImage) throws -> HandLandmarkerResult {
let options = try HandLandmarkerModel.makeOptions(
runningMode: .image
)
let handLandmarker = try HandLandmarker(options: options)
let mpImage = try MPImage(uiImage: image)
return try handLandmarker.detect(image: mpImage)
}Each detected hand contains 21 normalized landmarks, world landmarks, and handedness information.
On macOS there is no UIImage; wrap a CVPixelBuffer (for example the output
of a camera capture or an NSImage rendered into a BGRA buffer) instead:
import MediaPipeTasksVision
import MediaPipeTasksVisionHandLandmarker
func detectHands(in pixelBuffer: CVPixelBuffer) throws -> HandLandmarkerResult {
let options = try HandLandmarkerModel.makeOptions(
runningMode: .image
)
let handLandmarker = try HandLandmarker(options: options)
let mpImage = try MPImage(pixelBuffer: pixelBuffer)
return try handLandmarker.detect(image: mpImage)
}Set the delegate before creating the HandLandmarker.
let options = try HandLandmarkerModel.makeOptions(
runningMode: .liveStream,
numberOfHands: 2
)
options.handLandmarkerLiveStreamDelegate = delegate
let handLandmarker = try HandLandmarker(options: options)Send frames with monotonically increasing timestamps:
try handLandmarker.detectAsync(
image: mpImage,
timestampInMilliseconds: timestamp
)When using CVPixelBuffer or CMSampleBuffer, the underlying pixel format must be kCVPixelFormatType_32BGRA.
The model URL can be obtained directly when custom options are needed:
let modelURL = try HandLandmarkerModel.url
let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelURL.path
options.runningMode = .video
options.numHands = 2Model metadata is also available:
let metadata = try HandLandmarkerModel.metadata()
print(metadata.modelVersion)
print(metadata.testedMediaPipeVersion)
print(metadata.sha256)The bundled product includes pose_landmarker_lite.task (33 landmarks,
BlazePose GHUM).
import MediaPipeTasksVision
import MediaPipeTasksVisionPoseLandmarker
let options = try PoseLandmarkerModel.makeOptions(
runningMode: .video,
numberOfPoses: 1
)
let poseLandmarker = try PoseLandmarker(options: options)Frames are supplied exactly like the Hand Landmarker: MPImage(pixelBuffer:)
on both platforms, MPImage(uiImage:) on iOS, and detectAsync with a
delegate in live stream mode.
import MediaPipeTasksVisionPoseLandmarker
guard MediaPipePoseTrackingSupport.isAvailable else {
// Fall back to another implementation (e.g. Vision).
return
}
let tracker = try MediaPipePoseTrackingFactory.makeTracker(
configuration: PoseLandmarkTrackingConfiguration(numberOfPoses: 1)
)
let result = try tracker.detect(in: pixelBuffer, timestampInMilliseconds: timestamp)
for pose in result.poses {
print(pose.landmarks.count, pose.worldLandmarks.count, pose.visibilities.count)
}The tracker keeps the CPU delegate in full precision and leaves segmentation masks off — see macOS notes for why both matter there.
The bundled product includes face_landmarker.task (Face Mesh V2): 478
landmarks, the 52 blend shape scores, and the facial transformation matrix.
import MediaPipeTasksVision
import MediaPipeTasksVisionFaceLandmarker
let options = try FaceLandmarkerModel.makeOptions(
runningMode: .video,
numberOfFaces: 1
)
let faceLandmarker = try FaceLandmarker(options: options)Blend shapes are requested by default because they are what most callers are
after; the transformation matrix is opt-in (outputsTransform: true). Frames
are supplied exactly like the Hand Landmarker.
import MediaPipeTasksVisionFaceLandmarker
guard MediaPipeFaceTrackingSupport.isAvailable else {
// Fall back to another implementation (e.g. Vision).
return
}
let tracker = try MediaPipeFaceTrackingFactory.makeTracker(
configuration: FaceLandmarkTrackingConfiguration(numberOfFaces: 1)
)
let result = try tracker.detect(in: pixelBuffer, timestampInMilliseconds: timestamp)
for face in result.faces {
print(face.landmarks.count, face.blendShapes[.jawOpen] ?? 0)
}blendShapes is keyed by FaceBlendShape, whose 52 cases are named after the
ARKit blend shape locations (plus neutral), so the scores reach an
ARKit-shaped rig without a lookup table of category names.
Use the MediaPipeTasksVision product and provide an absolute path to your
model file:
import MediaPipeTasksVision
let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelURL.path
options.runningMode = .image
options.numHands = 2
let handLandmarker = try HandLandmarker(options: options)MediaPipeTasksVisionHandLandmarker also provides a tracker API that hides
every MediaPipe type behind package-defined value types, which keeps app
binaries free of MediaPipe symbol references outside this package:
import MediaPipeTasksVisionHandLandmarker
guard MediaPipeHandTrackingSupport.isAvailable else {
// Fall back to another implementation (e.g. Vision).
return
}
let tracker = try MediaPipeHandTrackingFactory.makeTracker(
configuration: HandLandmarkTrackingConfiguration(numberOfHands: 2)
)
let result = try tracker.detect(in: pixelBuffer, timestampInMilliseconds: timestamp)
for hand in result.hands {
print(hand.handedness, hand.landmarks.count)
}The macOS slice is built from upstream MediaPipe sources plus the patches in
macos/ (Google does not ship macOS binaries):
- Inference runs on the CPU (XNNPACK) by default; the Metal GPU delegate is opt-in per tracker.
- The CPU delegate runs models in full precision unless a task opts into FP16
through
MEDIAPIPE_XNNPACK_FORCE_FP16=1. The bundled hand tracker opts in (~1.8x faster, ~1% landmark drift); the pose tracker must not, because its detector stops producing detections in FP16; the face tracker gains neither speed nor drift from it and stays in full precision. - Pose segmentation masks are unavailable: the mask branch of the graph uses
shaders that the macOS OpenGL context (2.1) rejects, so
PoseLandmarkeris only created withshouldOutputSegmentationMasks = false.
The macOS framework is universal so that universal apps can link and launch
everywhere, but only the arm64 slice contains MediaPipe. The x86_64 slice is
a link-only stub: every class exists for the linker and dyld, task
initializers report an error, and any other use raises
MPPUnsupportedArchitectureException.
Check MediaPipeHandTrackingSupport.isAvailable (or, when using the raw
APIs, guard with #if arch(arm64)) before creating MediaPipe objects. The
check reflects the executing binary slice, so it is also false on Apple
Silicon Macs when the app runs under Rosetta.
This project is licensed under the Apache License 2.0.
MediaPipe, the bundled models, and bundled third-party dependencies remain subject to their respective licenses and notices.
See: