Two new tasks, batch inference, in-memory model loading, and two parity fixes that
move output values. Everything in the high-level API is additive — v1.0.0 code
compiles unchanged.
Features
Monocular metric depth estimation (YOLO26)
A new task returning a dense per-pixel depth map in meters, not a normalized
disparity. Unlike the other tasks it takes no labels file and no confidence/IoU
thresholds, because the model outputs one dense map rather than instances to filter.
yolos::depth::YOLODepthEstimator estimator("yolo26n-depth.onnx", /*gpu=*/true);
cv::Mat depth = estimator.estimate(frame); // CV_32FC1, meters
estimator.drawDepth(frame, depth);./build/image_depth_inference models/yolo26n-depth.onnx data/dog.jpgAvailable for yolo26{n,s,m,l,x}-depth. Parity against Ultralytics on the real
yolo26n-depth export measures mean AbsRel ~1.8e-06, max relative error ~5.2e-06,
with δ1 saturated at 1.0. Image, video and camera examples ship alongside. See
Depth Estimation.
Batch inference
batchDetect / batchSegment / batchClassify push many images through a single
ONNX Runtime call — the throughput win on GPU.
std::vector<cv::Mat> images = {a, b, c};
auto results = detector.batchDetect(images, /*conf=*/0.25f, /*iou=*/0.45f);
// one result vector per input image, in input orderFixed-batch exports fall back to a per-image loop automatically, so the call works
against any model. The fallback also catches Ort::Exception at run time, because
real Ultralytics static exports bake batch=1 into their DFL Reshape nodes and so
advertise a dynamic batch they cannot actually execute.
In-memory model loading
Every task class gained a (const void* data, size_t size, ...) constructor, for
encrypted stores, network streams and resources embedded in the binary. Class names
are passed as a vector, so no labels file is needed either.
std::vector<uint8_t> bytes = yolos::utils::readFileBytes("yolo11n.onnx");
yolos::det::YOLODetector detector(bytes.data(), bytes.size(), {"person", "car"});
// ONNX Runtime copies the buffer during construction — safe to wipe it nowGrayscale input support
Single-channel models are now detected from the input tensor's channel dimension and
preprocessed accordingly, instead of assuming three channels. Thanks @choyy (#139).
Build as a subdirectory
add_subdirectory(YOLOs-CPP) now propagates the project's own include directories, so
consuming projects build without manual include paths. Thanks @Ferdi0412 (#142).
Documentation site
The docs moved into docs/ and now build with MkDocs and deploy to GitHub Pages, with
the API reference rewritten around worked examples. Thanks @imessam (#133).
YOLOE open-vocabulary detection and segmentation
Landed after v1.0.0 and shipping in a tagged release for the first time. Runs ONNX
exported after set_classes() or from prompt-free *-pf checkpoints, via
scripts/export_yoloe_onnx.py and the image_yoloe_seg / video_yoloe_seg demos.
Behavior changes
These move output values without breaking compilation — if you have thresholds or
golden files tuned against v1.0.0, recheck them.
Classification preprocessing now matches Ultralytics (#145, fixes #137).
Ultralytics resizes classification inputs through PIL, whose bilinear filter is
antialiased; cv::resize(INTER_LINEAR) is not. On a 768×576 image that shifted the
input tensor by up to 76/255 and moved confidences by 0.12–0.22 — enough to flip
yolo26n-cls from husky to Alaskan Malamute. A second, smaller mismatch:
torchvision's CenterCrop rounds the crop offset where this code floored it.
YOLOs-CPP now ports Pillow's resampling (preprocessing::resizeAntialiasBilinear) and
crops with nearbyint. Tensors agree with Pillow to within 1/255, max confidence delta
0.0034 across 11 image shapes × 3 models, zero class flips. Expect your classification
confidences to drop slightly — the old values were inflated by aliasing.
Detection, segmentation, pose and OBB are deliberately untouched: Ultralytics' LetterBox
really does use cv2.INTER_LINEAR, so those paths already matched.
Letterbox descaling matches the padding actually applied (#147). getScalePad()
returned the unrounded half-difference while letterBoxToBlob() pads by the integer
nearbyint(dw - 0.1). Whenever (target - resized) is odd the two disagree by half a
letterbox pixel, and descaling by the gain magnifies it — up to 3.3 original pixels on
a 1600×2128 image at 320. Tie-rounding now also follows Python's round-half-to-even.
Measured against Ultralytics with YOLOv8n on that shape, x-edge error drops from
3.589 / 3.726 px to 0.589 / 0.726 px; across the detection parity set the worst
coordinate goes from 5 px to exactly 0, over 35 boxes and 7 models.
This shifts box, mask, keypoint and OBB coordinates by up to a few pixels for
detection, segmentation, pose and OBB — toward Ultralytics.
Breaking changes
One low-level signature changed. The task classes (YOLODetector, YOLOSegDetector,
YOLOPoseDetector, YOLOOBBDetector, YOLOClassifier) are unaffected.
// before
preprocessing::letterBoxToBlob(image, blob, targetSize, actualSize, padColor);
// after — targetChannels is required, and comes from the model's input tensor
preprocessing::letterBoxToBlob(image, blob, targetChannels, targetSize, actualSize, padColor);Both the std::vector<float>& and InferenceBuffer& overloads take the new
parameter. It arrived with grayscale support (#139).
Testing
107 tests across 8 suites — 50 Ultralytics-parity and 57 self-contained. The
self-contained tests run against synthetic ONNX models or fixed reference values: no
downloaded weights and no Ultralytics reference run. 27 of them need nothing but the
compiler; the other 30 use Python only to generate a synthetic model.
| Task | Parity | Self-contained | Total |
|---|---|---|---|
| Detection | 7 | 3 | 10 |
| Segmentation | 8 | — | 8 |
| Pose | 7 | — | 7 |
| OBB | 7 | — | 7 |
| Classification | 6 | 7 | 13 |
| Depth | 7 | 25 | 32 |
| YOLOE | 8 | — | 8 |
| API (batch + in-memory) | — | 22 | 22 |
| Total | 50 | 57 | 107 |
Each suite runs as its own CI job. The classification parity harness previously
re-implemented the C++ preprocessing in Python — comparing YOLOs-CPP against a
transcription of itself, which is why #137 went undetected. It now calls Ultralytics'
classify_transforms() directly, and the tolerance tightened from 0.1 to 0.02. The
detection bbox margin tightened from 50 px to 3 px.
tests/*/results/ is no longer version-controlled. Every suite regenerates it from
scratch on each run, so the committed copy only ever went stale against whatever
Ultralytics version was installed.
Supported models
| Version | Detection | Segmentation | Pose | OBB | Classification | Depth |
|---|---|---|---|---|---|---|
| YOLOv5 | ✅ | — | — | — | — | — |
| YOLOv6 | ✅ | — | — | — | — | — |
| YOLOv7 | ✅ | — | — | — | — | — |
| YOLOv8 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| YOLOv9 | ✅ | — | — | — | — | — |
| YOLOv10 | ✅ | — | — | — | — | — |
| YOLOv11 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| YOLOv12 | ✅ | — | — | — | — | — |
| YOLO26 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| YOLOE (open-vocab) | ✅ | ✅ | — | — | — | — |
Requirements
Unchanged from v1.0.0 — CMake 3.16+, a C++17 compiler, OpenCV 4.5+, ONNX Runtime 1.16+.
Contributors
@Geekgineer, @imessam, @choyy (#139, first contribution), @Ferdi0412 (#142, first
contribution).
Full changelog: v1.0.0...v1.1.0