This project focuses on single-image apple counting in real orchard environments using a regression-based deep learning approach.
Instead of object detection, the model directly predicts the total apple count from an input image.
- Task: Apple counting from RGB orchard images
- Approach: Regression-based YOLOv8
- Backbone: Residual-enhanced MobileViT-v3
- Output: A single scalar value (apple count)
The method is designed to handle:
- Occlusion by leaves
- Complex backgrounds
- Variable lighting conditions
- Different apple colours and densities
Unlike detection-based approaches, this method does not require bounding-box annotations.
It directly predicts the total apple count from a single image using an end-to-end regression framework.
- YOLOv8 backbone with detection head removed
- Standard CSP blocks replaced by an improved MobileViT-v3
- Added MV2 residual blocks for better local feature preservation
- Transformer layers capture global context
- Final fully connected regression head
Only lightweight preprocessing is used:
- Gaussian blur
- Image resizing
Traditional handcrafted features (e.g. LBP, colour segmentation) were tested but excluded due to performance degradation.
Two public apple-counting datasets are merged and re-split to construct a unified regression dataset:
- ACFR Apple Dataset
- MinneApple Dataset
- Training set: 80%
- Validation set: 10%
- Testing set: 10%
Total images: 65,715
The task is formulated as image-level regression.
Annotations are stored in a .txt file, where each line corresponds to one image and its ground-truth apple count:
images_00000.png,0
images_00001.png,3
images_00002.png,15
images_00003.png,7
Each entry follows the format:
<image_filename>,<apple_count>
This annotation scheme does not require bounding-box or instance-level labels, enabling efficient end-to-end regression training.
- Loss: Mean Squared Error (MSE)
- Optimizer: Adam
- Learning rate: 1e-4
- Epochs: 5 (Google Colab constraint)
This document describes how to integrate a custom MobileViT-v3 backbone into the Ultralytics YOLOv8 framework, including model configuration and required code modifications.
Create a custom model configuration file, for example:
yolov8_mobilevitv3.yaml
with the following content:
# Ultralytics YOLO 🚀, GPL-3.0 license
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33
width_multiple: 0.50
# YOLOv8 backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, MobileViTBv3, [256]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, MobileViTBv3, [512]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [1024, True]]
- [-1, 1, SPPF, [1024, 5]]
# YOLOv8 head
head:
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 6], 1, Concat, [1]]
- [-1, 3, C2f, [512]]
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 4], 1, Concat, [1]]
- [-1, 3, C2f, [256]]
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]]
- [-1, 3, C2f, [512]]
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]]
- [-1, 3, C2f, [1024]]
- [[15, 18, 21], 1, Detect, [nc]]Place your MobileViT-v3 implementation in the Ultralytics codebase:
ultralytics/
└── nn/
├── mobilevitv3.py # contains class MobileViTBv3
├── tasks.py
└── modules/
Ensure that mobilevitv3.py defines:
class MobileViTBv3(nn.Module):
...Open:
ultralytics/nn/tasks.py
Add the following import near the top of the file:
from ultralytics.nn.mobilevitv3 import MobileViTBv3In tasks.py, locate the following code:
elif m is nn.BatchNorm2d:
args = [ch[f]]Insert the following block immediately above it:
elif m is MobileViTBv3:
c1, c2 = ch[f], args[0]
if c2 != nc: # not a classification head output
c2 = make_divisible(min(c2, max_channels) * width, 8)
args = [c1, c2, *args[1:]]
args.insert(2, n) # number of repeats
n = 1This step is required for correct channel scaling and repeat handling when parsing the custom backbone.


