FIG. 01
RECORD DATA
Category
Software
TARİH
Training finishes and the terminal shows an mAP50 (mean average precision at an IoU threshold of 0.50) north of 0.9. The next day you go out to the field: the model calls a tree shadow a target, and the mannequin it spotted at 20 meters in one frame disappears in the next. The first reflex is almost always the same — bigger model, more epochs, more aggressive augmentation.
In our experience the problem was almost never there. In the first version of the dataset we built for SUAS 2026, the overwhelming majority of frames were ground-level photos scraped from the web; aerial frames made up less than 15% of the set, and the two classes were imbalanced roughly 7:1. A validation split carved randomly out of that set produced a high number, but that number told us next to nothing about flight performance.
In this post we treat the dataset like a software component: it has a contract (the labeling rules), it has test coverage (the validation set), and it can break silently (leakage — information bleeding from training into test). The numeric targets come from the Ultralytics documentation; the structural decisions come from our own time in the field.
Anatomy of a good dataset
Ultralytics' "Tips for Best Training Results" guide recommends at least 1500 images per class and at least 10,000 labeled instances per class for production quality. For a student team those numbers are usually out of reach; what matters is knowing which axis you are closing the gap on.
Component | Target | Why it matters |
|---|---|---|
Images per class | ≥ 1500 (Ultralytics recommendation) | Baseline visual diversity |
Instances per class | ≥ 10,000 (Ultralytics recommendation) | Scale and pose variation |
Background images with no objects | 0-10% of the total | Drives down false positives (FP) |
Source of the validation set | 100% flight-like imagery | Makes the metric meaningful |
Corrupt or duplicate labels | 0 | Silent loss of metric quality |
Priority matters too: for us, 500 well-labeled aerial frames turned out to be worth more than 5000 badly labeled web photos.
Labeling: tight boxes, written rules
Ultralytics states three ground rules plainly: every instance of every class in an image must be labeled, boxes must wrap the object with no slack, and no object may be left unlabeled. Partial labeling does not work, because an unlabeled object tells the model "there is nothing here."
The label file is a .txt file carrying the same name as the image; each line is one object, and the coordinates are x_center y_center width height normalized to the 0-1 range. Class indices start at zero.
The format is not the hard part — consistency is. If two people label the same frame and draw different boxes, the model learns noise. So we collected our decisions on a single written rules page:
Case | Our rule | Rationale |
|---|---|---|
Object cut off by the frame edge | Label the visible part | Edge detections are a real scenario |
More than 50% of the object is occluded | Don't label it, and don't count the frame as background | It produces an ambiguous signal |
Include the shadow? | No, the object only | Shadows change with altitude |
Very small object (< 8 px) | Label it, but track it on a separate list | It distorts how you read the metrics |
Ambiguous frame | Goes to the "review" folder; one person makes the call | Consistency > speed |
Every week of labeling you spend without a rules page comes back later as a week of relabeling.
Class balance: 7:1 punishes you quietly
Our class ratio was roughly 7:1. When that happens, the overall mAP is dominated by the performance of the majority class and you never notice that the minority class is doing badly. The simplest diagnostic: always read your metrics per class, and look at the per-class rows in the val output.
Method | When to prefer it | Risk |
|---|---|---|
Targeted data collection | If you have the time — the best fix | Slow; requires field flights |
Oversampling the minority class | When collecting more data isn't an option | Memorizing the same frames |
Merging classes | If the mission doesn't need the distinction | May violate a mission requirement |
Copy-paste augmentation | When objects are small and the background is uniform | Unrealistic placement |
We went with targeted collection first and moderate oversampling second; merging classes was off the table because of the mission requirements.
Hard negatives: why you need frames with nothing in them
A hard negative is a frame the model mistakes for an object but that contains no target at all — a trash bag on grass, an awning that looks like a tent, a road sign shaped like a human silhouette. You add these frames to the dataset unlabeled: background images need no label file.
Ultralytics recommends background images at 0-10% of the total dataset; for reference, COCO contains roughly 1000 background frames, which is about 1% of the set. Higher ratios can cut false positives noticeably in some domains, but that is entirely domain-specific and only works with carefully chosen frames — piling on background images blindly will cost you recall.
The most valuable backgrounds are not collected at random; they are mined from the model's own mistakes. The workflow:
Background ratio | Expected effect | When |
|---|---|---|
0% | High FP, needless releases in the field | Never |
1-5% | Balanced — our starting point | General use |
5-10% | Lower FP, but watch recall | When false positives are expensive |
Above 10% | Higher risk of missed detections | Only with measurements to back it |
The key point: change this ratio by measurement too. If adding backgrounds doesn't reduce the FP count on your validation set, the frames you added weren't "hard" in the first place.
Splitting properly: a random split is a trap
The usual advice is 70/20/10 or 70/15/15 (train/validation/test). The ratio itself is secondary; what really matters is what you treat as the unit of the split.
Consecutive frames pulled out of a video are near-copies of one another. Split them randomly at the image level and frame 187 lands in training while frame 188 lands in validation. The model has already seen your validation frame; what you are measuring is memorization, not generalization. The literature describes this as leakage arising from spatio-temporal correlation in video-derived datasets, and it inflates metrics systematically.
The fix is to split groups, not frames: the same flight, the same video, the same scene all go to one side.
If you also have class imbalance, StratifiedGroupKFold preserves the class ratios while keeping the groups disjoint.
The validation set has to represent the environment you'll work in
A metric is only as meaningful as the distribution it measures. A high score on a validation set full of web photos tells you nothing about the scene an 81° HFOV gimbal camera sees from 100 meters of altitude.
Once we rebuilt the validation set from aerial frames only, the value we measured was mAP50 0.950. What makes that number valuable isn't its size but the fact that we know what it measures: the same model detected two targets from roughly 20 meters of altitude in a field test and completed an autonomous release. With a web-heavy validation set we could never have drawn that connection.
Practical rule: the validation and test sets must represent flight conditions — altitude range, time of day, ground texture, camera. Lock the test set the day you build it. The moment you look at it once to make a decision, it has become training data.
Data hygiene: duplicates and corrupt labels
When training starts, Ultralytics scans the dataset and prints a summary in the form ... images, ... backgrounds, ... corrupt; it also warns you about non-normalized or out-of-bounds coordinates and duplicate labels. Don't skip past that line — it can be hiding hundreds of silently dropped frames.
Our checklist:
A file hash is enough for byte-identical frames. For near-duplicates, FiftyOne Brain's compute_exact_duplicates() and compute_near_duplicates() functions make the job easy. When one copy of a scene sits in training and another sits in validation, your measurement loses its credibility.
Common traps
Trap | Symptom | Fix |
|---|---|---|
Random frame-level split | Val mAP very high, field performance poor | Group-based split |
Partial labeling | Low recall, unstable loss | Label every instance |
Loose boxes | Localization drift, low mAP50-95 | Snap the box to the object |
No background images at all | Produces detections over an empty field | Add 1-5% hard negatives |
Unrepresentative val set | Metrics don't match the field | Build val from flight imagery |
Constantly peeking at the test set | The final number comes out optimistic | Lock the test set |
Augmenting before splitting | Variants of the same frame on both sides | Split first, then augment |
Practical summary
Pick the unit of the split first, the ratio second. Group by flight/video; use
GroupShuffleSplitorStratifiedGroupKFold. 70/15/15 is a reasonable starting point.Build the validation and test sets from flight imagery only. Web photos can stay in training; they can't stay in your measurement.
Put the labeling rules on a single page and validate them by having two people label the same frame and comparing the result. Boxes should wrap the object with no slack.
Add hard negatives at 1-5% of the total, mine them from the model's own false positives, and raise the ratio only on measured evidence (ceiling around 10%).
Run a cleanup pass before every training run: duplicate hash check, out-of-bounds coordinate scan, and a
corruptcount of zero in the Ultralytics scan summary.
Sources: Ultralytics — Tips for Best Training Results, Ultralytics — Detection Datasets, Ultralytics — Data Collection and Annotation, Ultralytics — Preprocessing Annotated Data, scikit-learn — Cross-validation, FiftyOne Brain
