Finds apples, bananas and oranges in a webcam feed and says whether each one is fresh or rotten. YOLOv12n trained on 8,162 photos merged from six Roboflow datasets, exported to OpenVINO and run on the NPU of an Intel Core Ultra laptop.
Test split: 593 photos with 870 fruit (170 of the photos have no fruit), used neither for training nor for picking the best epoch.
| Model | Classes | mAP@50 | mAP@50-95 | Precision | Recall |
|---|---|---|---|---|---|
| 6-class (demo) | fresh / rotten apple, banana, orange | 0.986 | 0.885 | 0.966 | 0.972 |
| 3-class | apple, banana, orange | 0.989 | 0.880 | 0.975 | 0.978 |
Of the 870 test fruit, the 6-class model finds 846 with the right class, gives 10 the wrong class and misses 14. It puts 5 boxes on the 170 photos without fruit. The weakest class is rotten banana (0.775 mAP@50-95), mostly tight bunches where the boxes overlap.
Speed of the 6-class model on an Intel Core Ultra 5 226V laptop (OpenVINO, 640x640, batch 1, average of 300 runs):
| Device | Inference | Inference FPS | FPS with pre- and post-processing |
|---|---|---|---|
| NPU (Intel AI Boost) | 6.35 ms | 157.5 | 105.7 |
| iGPU (Arc 130V) | 5.98 ms | 167.2 | 96.5 |
| CPU | 77.2 ms | 13.0 | 12.4 |
The NPU and the integrated GPU are about equally fast, and both are 12 to 13 times faster than the CPU. The webcam demo runs on the NPU. Measured with python demo.py --benchmark 300 on a still frame, so camera capture and display aren't included.
Per-class scores, confusion matrix, sample predictions and an error breakdown are in notebooks/03_evaluation.ipynb.
Two fruit datasets relabelled to one 6-class scheme, plus two face datasets and two datasets of everyday scenes with their labels emptied. The face and scene photos teach the model that people and their surroundings, which fill most webcam frames, aren't fruit. merge_datasets.py combines the six folders and rebuilds the dataset exactly.
| Split | Photos | Fruit | Photos without fruit |
|---|---|---|---|
| train | 6,578 | 8,545 | 2,291 |
| validation | 991 | 1,758 | 144 |
| test | 593 | 870 | 170 |
No invalid boxes, and only one test photo is a copy of a training photo. prepare_data.py --classes 3 merges fresh and rotten for the 3-class model. Sources and samples: notebooks/01_dataset.ipynb.
An earlier dataset had 51,161 studio photos, one fruit per photo on a plain background, with boxes generated by OpenCV. Checking it turned up two problems (notebooks/04_studio_dataset.ipynb):
- Every box was wrong. The labelling code unpacked
cv2.boundingRectas(xmin, ymin, xmax, ymax), but it returns(x, y, width, height). 30% of the boxes had a negative size. The conversion is invertible, soprepare_data.py --fix-boxesrebuilds all of them. - About 1 in 5 validation and test photos were copies of training photos. They are left out.
A model trained on the fixed data scores 0.995 mAP@50 on that dataset's own test split, but 0.094 on the test split above, because it only ever saw one fruit on a plain background. The demo uses the model trained on the merged real-world photos.
Fine-tuned from COCO-pretrained YOLOv12n for 50 epochs at 640 px, batch 8, on an RTX 3090. Augmentation is Ultralytics' default: HSV colour jitter, translation, scaling, horizontal flips and mosaic. Curves are in notebooks/02_training.ipynb; the unedited notebook the 6-class model was trained in is notebooks/original_6class_training_run.ipynb.
pip install -r requirements.txt
python prepare_data.py --classes 3 # 3-class copy of datasets/combined
python train.py # 6 classes
python evaluate.py # test split, writes results/eval_*.json
python export.py # .pt to OpenVINO
python demo.py # webcam, NPU
python demo.py --device intel:gpu # or intel:cpu
python demo.py --model models/fruit-6class.pt --device cpu # no Intel hardware
python demo.py --benchmark 300 # inference timing, writes results/benchmark_*.json
Datasets go in datasets/ and are only ever read. The NPU needs Intel's NPU driver. demo.py stops if the requested OpenVINO device is missing, because Ultralytics would otherwise fall back to AUTO with only a warning and you'd end up timing the CPU.
notebooks/ dataset, training, evaluation, the studio-photo experiment, original training run
models/ weights (.pt) and OpenVINO exports
results/ dataset stats, training logs, eval and benchmark output
merge_datasets.py combines fruit datasets and no-fruit datasets into one
prepare_data.py relabels to 3 or 6 classes (and fixes the studio boxes)
dataset_stats.py counts per split and class, invalid boxes, duplicate check
train.py training
evaluate.py mAP on the val or test split
export.py .pt to OpenVINO
demo.py webcam demo and benchmark
| Dataset | Used for |
|---|---|
| Rotten Fruit Detector v3 (CC BY 4.0) | fruit |
| Fresh and Rotten Fruits v3 | fruit |
| Face Detection v27, Face Detection v4 | no-fruit photos |
| Room v7, Blind Assistant v9 | no-fruit photos |