| name | tpl-ai-ml-computer-vision-yolo |
| description | Template do pack (ai-ml/10-computer-vision-yolo.md). Orienta o agente em integracao de IA/ML, LLM e pipelines de dados alinhado a esse contexto. |
| metadata | {"version":"1.0.0","source_template":"ai-ml/10-computer-vision-yolo.md","generated_by":"install_pack_templates_as_claude_skills"} |
PROJECT: Computer Vision with YOLOv8/v9 + FastAPI + ByteTrack
Skill gerado a partir do pack templates-claude-code. Arquivo de origem: ai-ml/10-computer-vision-yolo.md. Use como baseline e adapte ao projeto antes de mudancas grandes.
Conteudo do template
STACK
- Detection: Ultralytics YOLOv8/v9 (ultralytics >= 8.1)
- Tracking: ByteTrack (via ultralytics built-in)
- Language: Python 3.12
- Video Processing: OpenCV 4.9+
- API: FastAPI + WebSocket (real-time) + REST (batch)
- GPU: CUDA 12.1 + TensorRT 8.6 (inference optimization)
- Export: ONNX (CPU), TensorRT (NVIDIA GPU), CoreML (Apple)
PROJECT STRUCTURE
yolo-api/
├── app/
│ ├── main.py # FastAPI app
│ ├── detector.py # YOLO detector singleton
│ ├── tracker.py # ByteTrack wrapper
│ ├── routes/
│ │ ├── detect.py # POST /v1/detect
│ │ ├── track.py # POST /v1/track (video)
│ │ └── websocket.py # WS /v1/stream
│ └── schemas.py # Pydantic request/response
├── training/
│ ├── train.py # Training script
│ ├── validate.py # Evaluation metrics
│ └── datasets/ # YOLO format datasets
├── models/
│ ├── yolov8n.pt # Nano (fastest)
│ ├── yolov8m.pt # Medium (balanced)
│ └── yolov9c.pt # YOLOv9 custom
├── export/
│ ├── export_onnx.py
│ └── export_tensorrt.py
├── tests/
│ └── test_detector.py
└── Dockerfile.gpu
ARCHITECTURE RULES
- Model loads at startup — YOLOv8 model loads once; never reload per request (warmup takes 2-5s).
- GPU inference exclusively — CPU fallback only for dev/testing; production always GPU + CUDA.
- Confidence threshold ≥ 0.4 — never use default 0.25 in production; tune per class.
- NMS IOU threshold = 0.45 — default Ultralytics setting; only change with data evidence.
- TensorRT in production — convert to
.engine for 2-5x speedup; ONNX for portability.
- Batch size = 1 for REST API, 4-8 for video processing — streaming APIs always batch_size=1.
- Dataset follows YOLO format strictly — never convert mid-experiment; commit yaml + images together.
- Evaluation metrics before deploy — mAP50 > 0.7 AND mAP50-95 > 0.5 minimum quality gate.
DATASET STRUCTURE (YOLO FORMAT)
datasets/
└── my_dataset/
├── dataset.yaml # Dataset config
├── images/
│ ├── train/ # 70% of data
│ │ ├── img001.jpg
│ │ └── ...
│ ├── val/ # 20% of data
│ └── test/ # 10% of data
└── labels/
├── train/
│ ├── img001.txt # One file per image
│ └── ...
├── val/
└── test/
path: /data/my_dataset
train: images/train
val: images/val
test: images/test
nc: 3
names:
0: person
1: car
2: truck
TRAINING CONFIG + SCRIPT
from ultralytics import YOLO
import mlflow
import os
def train(
model_size: str = "n",
epochs: int = 100,
imgsz: int = 640,
batch: int = 16,
dataset_yaml: str = "datasets/my_dataset/dataset.yaml",
):
mlflow.set_experiment("yolo-detection")
run_name = f"yolov8{model_size}_{epochs}ep_{imgsz}px"
model = YOLO(f"yolov8{model_size}.pt")
results = model.train(
data=dataset_yaml,
epochs=epochs,
imgsz=imgsz,
batch=batch,
device=0,
patience=20,
optimizer="AdamW",
lr0=0.001,
lrf=0.01,
momentum=0.937,
weight_decay=0.0005,
hsv_h=0.015,
hsv_s=0.7,
hsv_v=0.4,
flipud=0.0,
fliplr=0.5,
mosaic=1.0,
mixup=0.0,
copy_paste=0.0,
project="runs/train",
name=run_name,
save=True,
save_period=10,
plots=True,
)
with mlflow.start_run(run_name=run_name):
mlflow.log_params({
"model": f"yolov8{model_size}",
"epochs": epochs, "imgsz": imgsz, "batch": batch,
})
mlflow.log_metrics({
"mAP50": results.results_dict["metrics/mAP50(B)"],
"mAP50-95": results.results_dict["metrics/mAP50-95(B)"],
"precision": results.results_dict["metrics/precision(B)"],
"recall": results.results_dict["metrics/recall(B)"],
})
mlflow.log_artifact(str(results.save_dir / "weights/best.pt"))
return results
VALIDATION + METRICS
from ultralytics import YOLO
QUALITY_GATES = {
"mAP50": 0.70,
"mAP50-95": 0.50,
"precision": 0.75,
"recall": 0.65,
}
def validate_model(weights_path: str, dataset_yaml: str) -> dict:
model = YOLO(weights_path)
metrics = model.val(data=dataset_yaml, imgsz=640, conf=0.001, iou=0.7)
results = {
"mAP50": metrics.box.map50,
"mAP50-95": metrics.box.map,
"precision": metrics.box.mp,
"recall": metrics.box.mr,
"per_class": {
model.names[i]: {"ap50": ap}
for i, ap in enumerate(metrics.box.ap50)
}
}
failures = [
f"{k}: {v:.3f} < {QUALITY_GATES[k]}"
for k, v in results.items()
if k in QUALITY_GATES and v < QUALITY_GATES[k]
]
if failures:
raise ValueError(f"Quality gates failed:\n" + "\n".join(failures))
return results
MODEL LOADING SINGLETON
from ultralytics import YOLO
from ultralytics.engine.results import Results
import torch
import numpy as np
from pathlib import Path
class YOLODetector:
_instance: "YOLODetector | None" = None
_model: YOLO | None = None
@classmethod
def get_instance(cls) -> "YOLODetector":
if cls._instance is None:
cls._instance = cls()
return cls._instance
def load(
self,
weights: str = "models/best.pt",
device: str = "0",
half: bool = True,
) -> None:
self._model = YOLO(weights)
self._model.to(device)
if half and torch.cuda.is_available():
self._model.model.half()
dummy = np.zeros((640, 640, 3), dtype=np.uint8)
self._model(dummy, verbose=False)
print(f"Model loaded and warmed up. Device: {device}")
def detect(
self,
image: np.ndarray,
conf: float = 0.4,
iou: float = 0.45,
classes: list[int] | None = None,
max_det: int = 300,
) -> Results:
if self._model is None:
raise RuntimeError("Model not loaded. Call load() first.")
with torch.inference_mode():
results = self._model(
image,
conf=conf,
iou=iou,
classes=classes,
max_det=max_det,
verbose=False,
)
return results[0]
FASTAPI DETECT ENDPOINT
from fastapi import APIRouter, UploadFile, File, HTTPException
from app.detector import YOLODetector
from app.schemas import DetectionResponse, Detection
import numpy as np
import cv2
router = APIRouter(prefix="/v1")
@router.post("/detect", response_model=DetectionResponse)
async def detect_objects(
file: UploadFile = File(...),
conf: float = 0.4,
classes: str | None = None,
):
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=422, detail="File must be an image")
content = await file.read()
nparr = np.frombuffer(content, np.uint8)
image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if image is None:
raise HTTPException(status_code=422, detail="Could not decode image")
class_filter = [int(c) for c in classes.split(",")] if classes else None
detector = YOLODetector.get_instance()
results = detector.detect(image, conf=conf, classes=class_filter)
detections = []
for box in results.boxes:
detections.append(Detection(
class_id=int(box.cls),
class_name=results.names[int(box.cls)],
confidence=float(box.conf),
bbox_xyxy=[float(x) for x in box.xyxy[0].tolist()],
))
return DetectionResponse(
image_shape={"width": image.shape[1], "height": image.shape[0]},
detections=detections,
inference_time_ms=results.speed["inference"],
)
TENSORRT EXPORT + BENCHMARK
from ultralytics import YOLO
import time
import numpy as np
def export_tensorrt(weights: str = "models/best.pt"):
model = YOLO(weights)
model.export(
format="engine",
device=0,
half=True,
imgsz=640,
workspace=4,
simplify=True,
dynamic=False,
)
print("Exported to models/best.engine")
def benchmark():
"""Compare PyTorch vs ONNX vs TensorRT speed."""
dummy = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
N = 100
for fmt, path in [("pytorch", "models/best.pt"),
("onnx", "models/best.onnx"),
("tensorrt","models/best.engine")]:
model = YOLO(path)
model(dummy, verbose=False)
t0 = time.perf_counter()
for _ in range(N):
model(dummy, verbose=False)
elapsed = (time.perf_counter() - t0) / N * 1000
print(f"{fmt:10}: {elapsed:.1f} ms/image | {1000/elapsed:.0f} FPS")
BYTETRACK OBJECT TRACKING
from ultralytics import YOLO
import cv2
def track_video(video_path: str, output_path: str, weights: str = "models/best.pt"):
model = YOLO(weights)
results = model.track(
source=video_path,
save=True,
project="runs/track",
tracker="bytetrack.yaml",
conf=0.4,
iou=0.5,
persist=True,
stream=True,
)
for frame_result in results:
if frame_result.boxes.id is None:
continue
for i, box in enumerate(frame_result.boxes):
track_id = int(box.id)
class_id = int(box.cls)
confidence = float(box.conf)
x1, y1, x2, y2 = [int(v) for v in box.xyxy[0].tolist()]
print(f"Frame: track_id={track_id}, class={class_id}, conf={confidence:.2f}")
DOCKERFILE (GPU)
FROM nvcr.io/nvidia/pytorch:24.01-py3 AS base
WORKDIR /app
# OpenCV dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1-mesa-glx \
libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Pre-download model weights (baked into image for fast cold start)
RUN python -c "from ultralytics import YOLO; YOLO('yolov8n.pt')"
ENV PYTHONUNBUFFERED=1 PORT=8000
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
# Note: Use --workers=1 with GPU. Multiple workers fight over GPU memory.
# Scale horizontally (multiple containers) instead of multiple workers.