Usage Guide
September 6, 2026 ยท View on GitHub
This guide shows the common ways to use the plugin in real apps without reintroducing model-management logic in your own code.
๐ฏ Single-Image Inference
Use YOLO when you already have image bytes:
import 'dart:io';
import 'package:ultralytics_yolo/ultralytics_yolo.dart';
class ObjectDetector {
late final YOLO yolo;
Future<void> initialize() async {
yolo = YOLO(modelPath: 'yolo26n');
await yolo.loadModel();
}
Future<List<dynamic>> detect(File imageFile) async {
final imageBytes = await imageFile.readAsBytes();
final results = await yolo.predict(imageBytes);
return results['boxes'] ?? [];
}
}
๐ท Real-Time Camera Inference
Use YOLOView for camera-based inference:
final controller = YOLOViewController();
YOLOView(
modelPath: 'yolo26n',
controller: controller,
onResult: (results) {
print('Detections: ${results.length}');
},
onPerformanceMetrics: (metrics) {
print('FPS: ${metrics.fps.toStringAsFixed(1)}');
},
)
Use YOLOShowcase when you want the complete Ultralytics camera UI with model/task controls, thresholds, lens controls, capture, and performance labels:
YOLOShowcase(
initialTask: YOLOTask.detect,
initialModelSize: 'n',
onCapture: (bytes) {},
)
๐ Migrating From 0.3.x Overlay/UI APIs
Version 0.4.0 removes the old Dart-side overlay/control layer. YOLOView is now the camera + native detection-rendering surface. The full app UI lives in YOLOShowcase, and reusable controls are exported as normal Flutter widgets for custom layouts.
| Removed 0.3.x API | 0.4.0 replacement |
|---|---|
YOLOOverlay, YOLOOverlayTheme | Remove these widgets. Use native YOLOView overlays, or consume onResult/YOLO.predict() data. |
YOLOControls | Use YOLOShowcase for the full UI, or compose TaskSegmentedControl, ThresholdSliderRow, etc. |
YOLOView.showNativeUI | Use YOLOShowcase for built-in controls; use bare YOLOView when building your own UI. |
YOLOView.showOverlays, YOLOView.overlayTheme | No constructor replacement. Camera overlay drawing is native and not themed from Dart. |
YOLOViewController.setShowUIControls() | Show/hide your own Flutter controls around YOLOView. |
YOLOViewController.setShowOverlays() | Still available: toggles native overlay rendering. capturePhoto(withOverlays: false) only affects captured JPEG output. |
Typical upgrade:
// 0.3.x: YOLOView plus package-provided Dart controls/overlays.
YOLOView(
modelPath: 'yolo26n',
onResult: (results) {},
)
// 0.4.0: full built-in experience.
YOLOShowcase()
// 0.4.0: custom experience.
Stack(
children: [
YOLOView(modelPath: 'yolo26n', controller: controller),
Align(
alignment: Alignment.bottomCenter,
child: ThresholdSliderRow(
label: 'Confidence',
value: confidence,
min: 0.0,
max: 1.0,
onChanged: controller.setConfidenceThreshold,
),
),
],
)
๐ง Using Custom Models
Custom models work the same way:
final yolo = YOLO(modelPath: 'assets/models/custom.tflite');
Task and labels are auto-detected from the model's embedded metadata (Ultralytics appended-ZIP metadata, with a TFLite FlatBuffers fallback). If metadata is missing, pass task explicitly:
final yolo = YOLO(
modelPath: 'assets/models/custom.tflite',
task: YOLOTask.detect,
);
On Android, inference runs on LiteRT 2.x with an automatic GPU โ CPU accelerator ladder. Official int8 YOLO26 TFLite assets can compile on the LiteRT GPU path on supported devices, but int8 GPU coverage depends on the device driver and graph; graphs the GPU cannot compile fall back to CPU. For GPU benchmarking, non-end-to-end exports are also useful (the GPU delegate runs them in FP16):
# Requires ultralytics>=8.4.142
from ultralytics import YOLO
YOLO("yolo26n.pt").export(format="litert", nms=None, imgsz=640)
# Classification models use imgsz=224.
Keep useGpu: true for automatic GPU -> CPU fallback and verify actual delegate placement on target devices.
๐ Task-Specific Result Access
Detection
final results = await yolo.predict(imageBytes);
for (final box in results['boxes'] ?? <dynamic>[]) {
print('${box['class']}: ${box['confidence']}');
}
Semantic Segmentation
final segmenter = YOLO(
modelPath: 'yolo26n-sem',
task: YOLOTask.semantic,
);
await segmenter.loadModel();
final results = await segmenter.predict(imageBytes);
final semanticMask = results['semanticMask'] as Map?;
print('Mask: ${semanticMask?['width']}x${semanticMask?['height']}');
Depth Estimation
Official depth model IDs are available on Android and iOS, and both platforms return the same typed metric-depth result.
final estimator = YOLO(
modelPath: 'yolo26n-depth',
task: YOLOTask.depth,
);
await estimator.loadModel();
final results = await estimator.predict(imageBytes);
final depth = results['depthMap'] as Map?;
print('Depth: ${depth?['width']}x${depth?['height']} ${depth?['minDepth']}-${depth?['maxDepth']} m');
Classification
final classifier = YOLO(
modelPath: 'assets/models/custom-cls.tflite',
task: YOLOTask.classify,
);
await classifier.loadModel();
final results = await classifier.predict(imageBytes);
for (final item in results['detections'] ?? <dynamic>[]) {
print('${item['className']}: ${item['confidence']}');
}
Pose
final poseModel = YOLO(
modelPath: 'assets/models/custom-pose.tflite',
task: YOLOTask.pose,
);
await poseModel.loadModel();
final results = await poseModel.predict(imageBytes);
for (final pose in results['detections'] ?? <dynamic>[]) {
print('Keypoints: ${(pose['keypoints'] as List?)?.length ?? 0}');
}
OBB
final obbModel = YOLO(
modelPath: 'assets/models/custom-obb.tflite',
task: YOLOTask.obb,
);
await obbModel.loadModel();
final results = await obbModel.predict(imageBytes);
for (final detection in results['detections'] ?? <dynamic>[]) {
final result = YOLOResult.fromMap(Map<String, dynamic>.from(detection));
print('${result.className}: angle=${result.angle}');
}
๐ Switching Models
Camera model switching uses the same resolver as normal loading:
final controller = YOLOViewController();
await controller.switchModel('yolo26n');
await controller.switchModel('assets/models/custom.tflite', YOLOTask.detect);
That means switching supports:
- official model IDs
- asset paths
- local file paths
- remote URLs
- metadata-inferred tasks
๐ก Streaming Configuration
Use a throttled config when you want steadier battery usage:
YOLOView(
modelPath: 'yolo26n',
streamingConfig: YOLOStreamingConfig.throttled(
maxFPS: 15,
includeMasks: false,
includeOriginalImage: false,
),
onStreamingData: (data) {
final detections = data['detections'] as List? ?? [];
print('Streaming detections: ${detections.length}');
},
)
๐งฉ Multi-Instance
Use multiple YOLO instances when you actually need more than one model loaded at once:
final detector = YOLO(
modelPath: 'yolo26n',
useMultiInstance: true,
);
final classifier = YOLO(
modelPath: 'assets/models/custom-cls.tflite',
task: YOLOTask.classify,
useMultiInstance: true,
);
await Future.wait([
detector.loadModel(),
classifier.loadModel(),
]);
Keep this for real cases such as:
- running detection and classification side by side
- A/B model comparisons
- benchmark tooling
If you only need one active model, keep a single instance.
๐งฑ End-To-End Examples
1. Camera-first app with the default official model
class LiveDetectionScreen extends StatelessWidget {
final controller = YOLOViewController();
@override
Widget build(BuildContext context) {
return YOLOView(
modelPath: 'yolo26n',
controller: controller,
onResult: (results) {
if (results.isNotEmpty) {
print(results.first.className);
}
},
);
}
}
2. Photo picker flow with a custom bundled model
final yolo = YOLO(
modelPath: 'assets/models/custom-seg.tflite',
task: YOLOTask.segment,
);
await yolo.loadModel();
final results = await yolo.predict(imageBytes);
final masks = results['masks'] ?? [];
3. Runtime switching between an official model and a custom export
final controller = YOLOViewController();
await controller.switchModel('yolo26n');
await controller.switchModel('assets/models/custom-pose.tflite', YOLOTask.pose);
โ Practical Guidance
- Start with an official ID when you want the fewest moving parts.
- Use custom asset paths when shipping your own model with the app.
- Use
YOLO.inspectModel()when you need to see task or labels before loading. - Let metadata drive
taskwhenever possible. - Use
YOLOViewController.switchModel()instead of rebuilding the whole camera view just to swap models.