Skip to content

Latest commit

Β 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Smart Object Detection & Counting Tool

A full-stack web application that detects objects in an uploaded image with Ultralytics YOLO26, draws bounding boxes over the image, and counts the objects by class.

Python FastAPI React Vite YOLO26 License

πŸ‡ΉπŸ‡· TΓΌrkΓ§e README

Application screenshot

FastAPI backend + React (Vite) frontend. There is no database β€” the app is fully stateless: an uploaded image lives in memory only for the duration of the request, is never written to disk, and no result is ever stored.

The user interface is in Turkish. All code, comments and this README document the behaviour in full.

Features Β· Technical highlights Β· Quick start Β· API Β· Configuration Β· Project structure Β· License


Features

Drag & drop upload File picker, instant preview, client-side type and size validation
Canvas visualisation Boxes are drawn over the image with label score% above each one
Per-class colours Colour is derived from class_id, so a class always keeps the same colour
Confidence slider 0–100 in 1% steps, debounced (400 ms) so a request fires only once the user stops
Per-class counting Total detection count plus one coloured badge per class (⑦ car, β‘‘ person)
Class filter Checkbox list; filtering happens entirely client-side, no extra backend request
Loading & error handling Spinner, human-readable errors, retry action, backend status badge with auto-reconnect
PNG export Downloads the annotated result at full image resolution
Responsive design Single/two-column layout, dark mode support

Technical highlights

The model is loaded exactly once. Weights are loaded into memory inside the FastAPI lifespan; /predict only runs inference. A warm-up pass runs at startup so even the first real request is fast (~100 ms).

The event loop is never blocked. Ultralytics predict is blocking and not thread-safe, so the call runs in a separate thread via asyncio.to_thread, guarded by a lock.

Label collision avoidance. So that labels of nearby boxes never overlap, each label gets a list of candidate positions in order of preference (above the box β†’ inside the top edge β†’ stepping down inside the box β†’ below the box), and the first candidate that does not collide with an already-placed label wins. If every candidate is taken, the one with the smallest overlap area is used. The logic lives in utils/labelLayout.js as a pure, testable function.

Guaranteed coordinate alignment. Boxes are drawn into the same bitmap as the image, using one shared scale factor. In CSS only width is set and the height follows from the aspect ratio, so the browser scales that bitmap by a single uniform factor β€” boxes can never drift away from the image, and no runtime scaleX/scaleY computation is needed.

Stale responses can never win. When the slider is dragged, the in-flight request is cancelled with AbortController, so a late response cannot overwrite a newer one.

Layered structure. On the backend routers only deal with HTTP while services hold the business logic; the frontend separates api / hooks / components / utils.

EXIF orientation is applied, so boxes stay aligned on photos taken with a phone.


Why YOLO26s?

  • Pretrained model: trained on the COCO dataset, recognises 80 classes (person, car, dog, bus, chair …). No training required.
  • The s (small) variant: the sweet spot between accuracy and speed. n is faster but less accurate; m/l are more accurate but noticeably slower on CPU. s runs in roughly 100 ms per image even on CPU.
  • One-line API: YOLO("yolo26s.pt") β€” weights download automatically on first run.
  • To try another variant, just set MODEL_NAME=yolo26m.pt in backend/.env.

Note

The yolo26s.pt weights file (~20 MB) is downloaded automatically by Ultralytics on first startup and placed in backend/models/. The first launch therefore takes a few extra seconds and needs an internet connection. Later launches take about 2 seconds.


Quick start

Requirements: Python 3.10+ Β· Node.js 18+

git clone https://github.com/ismailbilalakbulut/object-detection-counter.git
cd object-detection-counter

You need two terminals: one for the backend, one for the frontend.

1) Backend β€” Terminal 1

# Create and activate the virtual environment (from the project root)
python -m venv .venv
.\.venv\Scripts\Activate.ps1

# Dependencies
cd backend
pip install -r requirements.txt

# Start the server
uvicorn main:app --reload
Linux / macOS
python3 -m venv .venv
source .venv/bin/activate
cd backend && pip install -r requirements.txt
uvicorn main:app --reload

Backend β†’ http://localhost:8000 Β· Interactive API docs β†’ http://localhost:8000/docs

The API is ready once you see Model hazir (... 80 sinif, cihaz=cpu) in the console.

2) Frontend β€” Terminal 2

cd frontend
npm install
copy .env.example .env    # Linux/macOS: cp .env.example .env
npm run dev

Frontend β†’ http://localhost:5173

Open that address in your browser. If the badge in the top right reads "Bağlı" (connected), the backend is reachable.


Usage

  1. Drag an image in, or pick one with Dosya seΓ§ (choose file).
  2. Detection runs automatically at the default 70% threshold and boxes are drawn.
  3. Move the GΓΌven eşiği (confidence threshold) slider β€” a new request fires ~0.4 s after you let go. Lowering it surfaces more detections, raising it keeps only confident ones.
  4. Use the SΔ±nΔ±f filtresi (class filter) checkboxes to choose which classes are drawn β€” instant, with no extra request.
  5. Save the annotated image with PNG indir (download PNG).

API

POST /predict

Body multipart/form-data, field name: file
Query confidence_threshold β€” float, 0.0–1.0, default 0.7
curl -X POST "http://localhost:8000/predict?confidence_threshold=0.7" \
     -F "file=@image.jpg"
{
  "detections": [
    {
      "label": "car",
      "score": 0.9435,
      "box": { "xmin": 667.65, "ymin": 395.18, "xmax": 809.66, "ymax": 880.56 },
      "class_id": 2
    }
  ],
  "counts": { "car": 7, "person": 2, "traffic light": 2 },
  "total": 11,
  "confidence_threshold": 0.7,
  "model_name": "yolo26s.pt",
  "inference_ms": 186.54,
  "image": { "filename": "image.jpg", "width": 740, "height": 493 }
}

Box coordinates are in the original image's pixel space; scaling happens on the frontend.

Error codes

Code Situation
400 Empty file, corrupt or unreadable image
413 File exceeds the 10 MB limit, or the resolution is too high
415 Unsupported file type (JPG, PNG, WEBP, BMP are accepted)
422 confidence_threshold outside the 0–1 range
500 Model inference error
503 Model not loaded yet

Other endpoints

Endpoint Description
GET /health Service and model status (status, device, num_classes)
GET /classes The 80 classes the model recognises
GET /docs Swagger UI

Configuration

Both sides are configured through environment variables β€” no address is hardcoded.

backend/.env (optional β€” see .env.example)

Variable Default Description
MODEL_NAME yolo26s.pt Weights to use (yolo26n/s/m/l)
DEVICE auto cpu, cuda, 0 …
DEFAULT_CONFIDENCE_THRESHOLD 0.7 Default threshold
MAX_UPLOAD_BYTES 10485760 Upload limit (10 MB)
CORS_ORIGINS ["http://localhost:5173", …] Allowed frontend origins

frontend/.env

Variable Default Description
VITE_API_BASE_URL http://localhost:8000 Backend root URL

Tip

If you move the frontend to another port, remember to add that origin to CORS_ORIGINS in backend/.env as well.


Project structure

objectCounter/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ main.py                   # FastAPI entry point, CORS, lifespan
β”‚   β”œβ”€β”€ requirements.txt
β”‚   β”œβ”€β”€ .env.example
β”‚   β”œβ”€β”€ models/                   # Downloaded YOLO weights (created automatically)
β”‚   └── app/
β”‚       β”œβ”€β”€ config.py             # Settings (pydantic-settings, overridable via .env)
β”‚       β”œβ”€β”€ schemas.py            # Request/response models
β”‚       β”œβ”€β”€ routers/
β”‚       β”‚   β”œβ”€β”€ predict.py        # POST /predict
β”‚       β”‚   └── health.py         # GET /health, GET /classes
β”‚       └── services/
β”‚           β”œβ”€β”€ detector.py       # YOLO wrapper (model loaded once)
β”‚           └── image_loader.py   # File validation + conversion to PIL
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ .env.example              # VITE_API_BASE_URL
β”‚   └── src/
β”‚       β”œβ”€β”€ App.jsx               # State management and layout
β”‚       β”œβ”€β”€ config.js             # API address and client settings
β”‚       β”œβ”€β”€ api/detectionApi.js   # fetch wrapper + error types
β”‚       β”œβ”€β”€ hooks/                # useDebouncedValue, useObjectDetection, useBackendHealth
β”‚       β”œβ”€β”€ utils/                # labelLayout (label placement), colour palette, formatting
β”‚       └── components/           # ImageUploader, DetectionCanvas, ConfidenceSlider,
β”‚                                 # CountsPanel, ClassFilter, Spinner, ErrorBanner, Header
└── docs/                         # README assets

Troubleshooting

Symptom Fix
Badge shows "Backend kapalΔ±" Is uvicorn main:app --reload running in Terminal 1, and is port 8000 free? The badge recovers on its own once the backend is up.
CORS error in the browser console The frontend origin must be listed in backend/.env β†’ CORS_ORIGINS
First startup takes very long Weights are downloading (~20 MB); this happens only once
"Nesne bulunamadΔ±" (no objects found) Lower the confidence threshold; the model only knows the 80 COCO classes
Vite opens on 5174 instead of 5173 A previous dev server is still running: find it with Get-NetTCPConnection -State Listen -LocalPort 5173 and stop it
.env is ignored The file must be UTF-8 without BOM; PowerShell's Set-Content -Encoding utf8 adds a BOM

Tech stack

Backend: FastAPI 0.141 Β· Uvicorn Β· Ultralytics 8.4 (YOLO26s) Β· Pillow Β· pydantic-settings Frontend: React 19 Β· Vite 8 Β· CSS Modules Β· oxlint


License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

That is not an arbitrary choice: Ultralytics YOLO and its pretrained weights are themselves AGPL-3.0, a strong copyleft licence whose network clause requires that anyone interacting with the software over a network can obtain its source code. Ultralytics reads this as covering any project that links to their models, so a project built on YOLO is released under AGPL-3.0 as well. Using YOLO inside a closed-source or commercial product instead requires a paid Ultralytics Enterprise License.

About

Real-time object detection & counting web app powered by YOLO26 (Ultralytics), with a FastAPI backend and React frontend. Upload an image, adjust the confidence threshold, and get labeled bounding boxes with per-class counts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages