Real-time bottle liquid level detection and quality inspection powered by YOLOv8 + ByteTrack.
BottleVision is a production-ready computer vision system that automatically inspects
bottles on a conveyor (or in any video source) and determines whether the liquid fill level
meets your quality threshold — all in real time.

Video / Camera feed
│
▼
YOLOv8 ──► Bottle detected ──► Crop ROI
│
▼
Gaussian Blur ──► Gradient Analysis ──► Liquid level Y
│
▼
Compare to Virtual Line ──► PASS ✅ / REJECT ❌
| Feature | Detail |
|---|---|
| Detection | YOLOv8s fine-tuned on custom bottle dataset |
| Tracking | ByteTrack multi-object tracking (each bottle inspected once) |
| Liquid sensing | Gaussian blur + steepest gradient detection of liquid/air interface |
| Calibration UI | Move the virtual acceptance line with ↑ ↓ arrow keys (1 px steps) |
| All-in-one app | Single PyQt5 window — no separate calibration script needed |
| Live preview | Annotated feed shows bounding boxes, liquid level line, and stats overlay |
| Event log | Scrollable timestamped log of every pass/reject decision |
pip install -r requirements.txtPython 3.9+ recommended.
python app.py- Choose Video File or Camera from the source dropdown.
- Click Browse (for video) or set the camera index.
- Select your YOLO model (
.ptfile). The defaultbest.ptis pre-filled. - Click ▶ Start.
The Calibration panel (left sidebar) lets you position the virtual acceptance line while the video is running, with changes reflected in the preview and detection logic instantly.
| Action | Result |
|---|---|
| ↑ / ↓ arrow keys | Move line ±1 px (or ± step-size px) |
| Shift + ↑ / ↓ | Move line ±10 × step-size |
| Step size spinbox | Set the movement granularity |
| Jump to field | Type a pixel value and click ↳ Set |
| 💾 Save Calibration | Writes calibration.txt with the TARGET_LINE_Y value |
After calibration, paste the saved TARGET_LINE_Y value into any standalone script or
CI/CD pipeline config.
A YOLOv8s model is fine-tuned on a bottle dataset. During inference each frame is
passed through the model at 640 × 640. Detections with confidence ≥ CONF are kept.
ByteTrack assigns a persistent track_id to each bottle across frames. Once a bottle's
bounding-box center crosses the detection line (vertical cyan line) it is processed
exactly once — preventing double-counting on fast conveyors.
The YOLO bounding box is cropped from the frame. Inside the crop:
- Convert to grayscale.
- Apply Gaussian blur (kernel size configurable, odd number).
- Compute the vertical intensity profile (column-averaged).
- Find the steepest intensity gradient — this is the liquid/air interface.
- Fallback: if gradient < threshold, look for a consistently dark region.
The detected interface Y coordinate is converted back to the full-frame coordinate system.
liquid_level_Y ≤ TARGET_LINE_Y + tolerance → PASS ✅
liquid_level_Y > TARGET_LINE_Y + tolerance → REJECT (low fill) ❌
liquid_level_Y not detected → UNKNOWN ⚠
The green horizontal line in the preview is TARGET_LINE_Y. Red dashed lines
show the ± tolerance band.
- Collect images of your bottles (filled and unfilled).
- Label bounding boxes using Label Studio or Roboflow — export in YOLO format.
- Recommended: ≥ 200 images covering lighting/angle/background variation.
yolo train \
model=yolov8s.pt \
data=dataset/data.yaml \
epochs=60 \
imgsz=640 \
batch=16The best checkpoint will be saved at runs/detect/train/weights/best.pt.
Copy it to your project folder and select it in the Model file picker.
path: ./dataset
train: images/train
val: images/val
names:
0: bottle| Parameter | Default | Description |
|---|---|---|
TARGET_LINE_Y |
400 | Virtual acceptance line (px from top) |
TOLERANCE |
5 | ± px band around target line |
CONF_THRESHOLD |
0.30 | YOLO minimum detection confidence |
IOU_THRESHOLD |
0.45 | ByteTrack IoU threshold |
GAUSSIAN_KERNEL_SIZE |
5 | Blur kernel (must be odd) |
MIN_GRADIENT_THRESHOLD |
10 | Minimum gradient to accept as liquid edge |
DETECTION_LINE_POSITION |
0.50 | Fraction of frame width for the trigger line |
All parameters are adjustable live in the Detection Settings panel — no restart needed.
bottle_liquid_estimation/
├── app.py ← Main PyQt5 application (this file runs everything)
├── best.pt ← Pre-trained YOLO model checkpoint
├── requirements.txt ← Python dependencies
├── calibration.txt ← Auto-generated after saving calibration
├── claude_final.py ← Original headless detection script (reference)
└── calibrate_liquid_level.py ← Original CV2 calibration tool (reference)
MIT — free to use, modify, and distribute.
Built with Ultralytics YOLOv8, OpenCV, and PyQt5.