YOLO26 TFLite — Manual inference gives different results than Ultralytics' predict() #24229
Replies: 4 comments 5 replies
|
👋 Hello @TheRealGreatFAS, thank you for your detailed report and for sharing a minimal reproduction script for YOLO26 TFLite 🚀 This is an automated response to help get your discussion triaged quickly, and an Ultralytics engineer will also assist soon. We recommend a visit to the Docs for new users where you can find many Python and CLI usage examples and where many of the most common questions may already be answered. Since this appears to be a 🐛 bug report or inference discrepancy, thank you for already providing a minimum reproducible example 🙌 That will help the team investigate the difference between your manual TFLite pipeline and the Ultralytics If this is a custom training ❓ question, please provide as much information as possible, including dataset image examples and training logs, and verify you are following our Tips for Best Training Results. Join the Ultralytics community where it suits you best. For real-time chat, head to Discord 🎧. Prefer in-depth discussions? Check out Discourse. Or dive into threads on our Subreddit to share knowledge with the community. UpgradeUpgrade to the latest pip install -U ultralyticsEnvironmentsYOLO may be run in any of the following up-to-date verified environments (with all dependencies including CUDA/CUDNN, Python and PyTorch preinstalled):
StatusIf this badge is green, all Ultralytics CI tests are currently passing. CI tests verify correct operation of all YOLO Modes and Tasks on macOS, Windows, and Ubuntu every 24 hours and on every commit. |
|
You can check the code here for preprocessing and postprocessing reference: https://github.com/ultralytics/ultralytics/blob/main/examples/YOLOv8-TFLite-Python/main.py |
|
The most common cause of this is letterboxing. The Ultralytics pipeline resizes the image to fit within the target size while preserving aspect ratio and pads the rest with gray (114, 114, 114), which is called letterboxing. If your manual pipeline does a straight resize to the input dims instead, the spatial layout seen by the model is completely different and detections will be wrong or missing. The other thing to double-check for quantized TFLite models: dequantization. The output tensors have a scale and zero_point per tensor that you need to apply correctly. The reference script linked above handles both of these correctly, so comparing your preprocessing step by step against that should show where the divergence is. |
|
"Wrong places" + "too few" together almost always means two specific mismatches with what Ultralytics does internally: 1. Letterbox, not a plain resize. Ultralytics doesn't stretch to 2. Reverse the letterbox in post-processing. Because the model sees the padded image, its boxes are in letterboxed space. To map back to the original you must subtract the pad and divide by gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
pad_x = round((img1_shape[1] - img0_shape[1] * gain) / 2 - 0.1)
pad_y = round((img1_shape[0] - img0_shape[0] * gain) / 2 - 0.1)
boxes[..., [0, 2]] -= pad_x
boxes[..., [1, 3]] -= pad_y
boxes[..., :4] /= gainA naive Then the usual TFLite checklist:
Diff your script against the |
Uh oh!
There was an error while loading. Please reload this page.
Hi all,
I exported a YOLO26n pretrained model to TFLite and I'm seeing different results depending on how I run inference.
Here's a minimal reproduction script.
What works:
Using the Ultralytics API to run inference on a frame (auto method) — detections look correct.
What doesn't:
Running inference manually by:
The manual method produces incorrect results (wrong and too few detections in wrong places compare to the Ultralytics tools) even though I believe the pre and postprocessing should be equivalent.
My question: What steps does the Ultralytics pipeline apply under the hood that I might be missing or getting wrong in my manual pipeline? Are there additional transforms (resizing, padding/letterboxing, specific normalization values, channel ordering, etc.) that could account for the difference?
I've attached a Python script that reproduces the issue with both methods side by side.
Any pointers would be appreciated — thanks!
All reactions