bayramsn
commited on
Commit
·
8c738c4
1
Parent(s):
4839c43
feat(tools): add pt_bundle to bundle multiple .pt files without modifying originals\nfix(apps): webcam_app device auto-resolution and session_state safety
Browse files- apps/README.md +29 -0
- apps/webcam_app.py +155 -0
- tools/pt_bundle.py +159 -0
apps/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# YOLO11 Webcam App
|
2 |
+
|
3 |
+
Basit bir Streamlit arayüzü ile webcam üzerinden YOLO11 gerçek zamanlı tespit.
|
4 |
+
|
5 |
+
## Gereksinimler
|
6 |
+
- Python 3.9+
|
7 |
+
- Paketler: `ultralytics`, `streamlit`, `streamlit-webrtc`, `av`, (opsiyonel) `huggingface_hub`
|
8 |
+
|
9 |
+
## Kurulum (PowerShell)
|
10 |
+
```powershell
|
11 |
+
cd C:\yolov11\YOLO11
|
12 |
+
python -m venv .venv
|
13 |
+
. .venv\Scripts\Activate.ps1
|
14 |
+
pip install -U pip
|
15 |
+
pip install ultralytics streamlit streamlit-webrtc av huggingface_hub
|
16 |
+
```
|
17 |
+
|
18 |
+
## Çalıştırma
|
19 |
+
```powershell
|
20 |
+
cd C:\yolov11\YOLO11
|
21 |
+
. .venv\Scripts\Activate.ps1
|
22 |
+
streamlit run apps/webcam_app.py
|
23 |
+
```
|
24 |
+
|
25 |
+
Sol menüden modeli yükleyin (örn. `yolo11n.pt`) ve kameraya izin verin.
|
26 |
+
|
27 |
+
Notlar:
|
28 |
+
- HF Hub’dan özel model çekmek için `HF_TOKEN` ortam değişkeni gerekebilir.
|
29 |
+
- CUDA yoksa cihazı `cpu` seçin.
|
apps/webcam_app.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
from typing import Optional
|
4 |
+
|
5 |
+
import av
|
6 |
+
import torch
|
7 |
+
import streamlit as st
|
8 |
+
from streamlit_webrtc import webrtc_streamer, VideoProcessorBase, RTCConfiguration
|
9 |
+
|
10 |
+
try:
|
11 |
+
from ultralytics import YOLO
|
12 |
+
except Exception as e:
|
13 |
+
st.error(f"Ultralytics import failed: {e}")
|
14 |
+
raise
|
15 |
+
|
16 |
+
|
17 |
+
st.set_page_config(page_title="YOLO11 Webcam", layout="wide")
|
18 |
+
st.title("YOLO11 Webcam Demo")
|
19 |
+
st.caption("Live object detection with YOLO11 using your webcam. Use the sidebar to configure the model and thresholds.")
|
20 |
+
|
21 |
+
|
22 |
+
def _resolve_device(device: str) -> str:
|
23 |
+
"""Map UI device option to a valid torch/Ultralytics device string."""
|
24 |
+
d = (device or "").lower().strip()
|
25 |
+
if d in {"cpu", "cuda", "mps", "xpu"}:
|
26 |
+
return d
|
27 |
+
if d == "auto":
|
28 |
+
if torch.cuda.is_available():
|
29 |
+
return "cuda"
|
30 |
+
# Apple MPS (macOS). Kept for completeness even if not typical on Windows.
|
31 |
+
if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
32 |
+
return "mps"
|
33 |
+
return "cpu"
|
34 |
+
# Fallback to CPU for any unknown value
|
35 |
+
return "cpu"
|
36 |
+
|
37 |
+
|
38 |
+
@st.cache_resource(show_spinner=True)
|
39 |
+
def load_model(model_source: str, device: str = "auto"):
|
40 |
+
"""
|
41 |
+
Load an Ultralytics model from a local path, built-in alias, or Hugging Face Hub path.
|
42 |
+
Examples for model_source:
|
43 |
+
- "yolo11n.pt" (auto-downloads if available)
|
44 |
+
- "C:/path/to/your_model.pt"
|
45 |
+
- "hf://Ultralytics/YOLO11/yolo11n.pt" (HF Hub file)
|
46 |
+
"""
|
47 |
+
# Resolve device (maps 'auto' to an actual device)
|
48 |
+
resolved_device = _resolve_device(device)
|
49 |
+
|
50 |
+
# Hugging Face hub-style path
|
51 |
+
if model_source.startswith("hf://"):
|
52 |
+
try:
|
53 |
+
from huggingface_hub import hf_hub_download
|
54 |
+
except Exception as e:
|
55 |
+
st.error("huggingface_hub not installed. Install it or use a local/builtin model.")
|
56 |
+
raise
|
57 |
+
|
58 |
+
# Parse hf path: hf://<repo_id>/<filename>
|
59 |
+
path = model_source.replace("hf://", "", 1)
|
60 |
+
if "/" not in path:
|
61 |
+
raise ValueError("For hf://, use hf://<repo_id>/<filename>")
|
62 |
+
repo_id, filename = path.split("/", 1)
|
63 |
+
st.info(f"Downloading {filename} from {repo_id}...")
|
64 |
+
local_path = hf_hub_download(repo_id=repo_id, filename=filename, token=os.getenv("HF_TOKEN"))
|
65 |
+
try:
|
66 |
+
return YOLO(local_path).to(resolved_device)
|
67 |
+
except Exception as e:
|
68 |
+
st.warning(f"{resolved_device} cihaza taşınamadı, CPU'ya düşülüyor. Detay: {e}")
|
69 |
+
return YOLO(local_path).to("cpu")
|
70 |
+
|
71 |
+
# Local file or alias handled directly by Ultralytics
|
72 |
+
try:
|
73 |
+
return YOLO(model_source).to(resolved_device)
|
74 |
+
except Exception as e:
|
75 |
+
st.warning(f"{resolved_device} cihaza taşınamadı, CPU'ya düşülüyor. Detay: {e}")
|
76 |
+
return YOLO(model_source).to("cpu")
|
77 |
+
|
78 |
+
|
79 |
+
class YOLOProcessor(VideoProcessorBase):
|
80 |
+
def __init__(self, model: YOLO, conf: float, iou: float):
|
81 |
+
self.model = model
|
82 |
+
self.conf = conf
|
83 |
+
self.iou = iou
|
84 |
+
|
85 |
+
def recv(self, frame: av.VideoFrame) -> av.VideoFrame:
|
86 |
+
img = frame.to_ndarray(format="bgr24")
|
87 |
+
results = self.model.predict(img, conf=self.conf, iou=self.iou, verbose=False)
|
88 |
+
plotted = results[0].plot()
|
89 |
+
return av.VideoFrame.from_ndarray(plotted, format="bgr24")
|
90 |
+
|
91 |
+
|
92 |
+
with st.sidebar:
|
93 |
+
st.header("Ayarlar")
|
94 |
+
default_model = "yolo11n.pt" # Ultralytics dağıtımından otomatik indirilmeye çalışılır
|
95 |
+
model_path = st.text_input("Model yolu veya alias", value=default_model, help="Yerel .pt yolu, yerleşik alias (örn. yolo11n.pt) veya hf://Ultralytics/YOLO11/yolo11n.pt")
|
96 |
+
device = st.selectbox("Cihaz", options=["auto", "cpu", "cuda"], index=0)
|
97 |
+
conf = st.slider("Confidence", min_value=0.1, max_value=0.9, value=0.25, step=0.05)
|
98 |
+
iou = st.slider("IoU", min_value=0.1, max_value=0.9, value=0.45, step=0.05)
|
99 |
+
load_btn = st.button("Modeli Yükle")
|
100 |
+
|
101 |
+
|
102 |
+
st.session_state.setdefault("model", None)
|
103 |
+
|
104 |
+
|
105 |
+
if load_btn:
|
106 |
+
try:
|
107 |
+
st.session_state.model = load_model(model_path, device=device)
|
108 |
+
names = getattr(st.session_state.model, "names", None)
|
109 |
+
if isinstance(names, dict):
|
110 |
+
st.success(f"Model yüklendi. Sınıflar: {len(names)}")
|
111 |
+
else:
|
112 |
+
st.success("Model yüklendi.")
|
113 |
+
except Exception as e:
|
114 |
+
st.exception(e)
|
115 |
+
|
116 |
+
|
117 |
+
rtc_config = RTCConfiguration({
|
118 |
+
"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}],
|
119 |
+
})
|
120 |
+
|
121 |
+
|
122 |
+
col1, col2 = st.columns([2, 1])
|
123 |
+
with col1:
|
124 |
+
st.subheader("Webcam")
|
125 |
+
if st.session_state.get("model") is None:
|
126 |
+
st.info("Önce soldan bir model yükleyin.")
|
127 |
+
else:
|
128 |
+
# Capture current values to avoid session_state race during worker creation
|
129 |
+
_model = st.session_state.get("model")
|
130 |
+
_conf = float(conf)
|
131 |
+
_iou = float(iou)
|
132 |
+
|
133 |
+
def video_processor_factory(model=_model, conf_val=_conf, iou_val=_iou):
|
134 |
+
return YOLOProcessor(model, conf=conf_val, iou=iou_val)
|
135 |
+
|
136 |
+
webrtc_streamer(
|
137 |
+
key="yolo11-webcam",
|
138 |
+
video_processor_factory=video_processor_factory,
|
139 |
+
rtc_configuration=rtc_config,
|
140 |
+
media_stream_constraints={"video": True, "audio": False},
|
141 |
+
)
|
142 |
+
|
143 |
+
with col2:
|
144 |
+
st.subheader("İpuçları")
|
145 |
+
st.markdown(
|
146 |
+
"""
|
147 |
+
- Model alanına şunlardan birini girebilirsiniz:
|
148 |
+
- Yerleşik: `yolo11n.pt` (veya sizde olan başka bir .pt)
|
149 |
+
- Yerel dosya: `C:/yolov11/weights/custom.pt`
|
150 |
+
- HF Hub: `hf://Ultralytics/YOLO11/yolo11n.pt`
|
151 |
+
- Hugging Face özel/korumalı dosyalar için `HF_TOKEN` ortam değişkenini ayarlayabilirsiniz.
|
152 |
+
- GPU (CUDA) yoksa cihazı `cpu` bırakabilirsiniz.
|
153 |
+
- Stream durmuyorsa tarayıcı izinlerini kontrol edin ve sayfayı yenileyin.
|
154 |
+
"""
|
155 |
+
)
|
tools/pt_bundle.py
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python
|
2 |
+
"""
|
3 |
+
PT bundler: Bundle multiple .pt files into a single archive without modifying originals.
|
4 |
+
|
5 |
+
Supports two formats:
|
6 |
+
1) ZIP archive (recommended) – exact bytes of each .pt preserved.
|
7 |
+
2) PT container – a single .pt (pickle) file containing a dict {relative_path: bytes}.
|
8 |
+
|
9 |
+
CLI examples (PowerShell):
|
10 |
+
# Create ZIP bundle from current repo
|
11 |
+
python tools/pt_bundle.py zip --source . --out models_bundle.zip
|
12 |
+
|
13 |
+
# Create PT container bundle
|
14 |
+
python tools/pt_bundle.py pt --source . --out models_multi.pt
|
15 |
+
|
16 |
+
# List contents
|
17 |
+
python tools/pt_bundle.py list --bundle models_bundle.zip
|
18 |
+
python tools/pt_bundle.py list --bundle models_multi.pt
|
19 |
+
|
20 |
+
# Extract a single model from bundle to a path
|
21 |
+
python tools/pt_bundle.py extract --bundle models_multi.pt --member path/to/model.pt --out C:/tmp/model.pt
|
22 |
+
"""
|
23 |
+
|
24 |
+
from __future__ import annotations
|
25 |
+
|
26 |
+
import argparse
|
27 |
+
import io
|
28 |
+
import os
|
29 |
+
import sys
|
30 |
+
from pathlib import Path
|
31 |
+
from typing import Iterable, List
|
32 |
+
|
33 |
+
try:
|
34 |
+
import torch # Only needed for PT container
|
35 |
+
except Exception: # pragma: no cover - optional for ZIP-only usage
|
36 |
+
torch = None # type: ignore
|
37 |
+
|
38 |
+
import zipfile
|
39 |
+
|
40 |
+
|
41 |
+
def find_pt_files(source: Path, include: Iterable[str] | None = None, exclude: Iterable[str] | None = None) -> List[Path]:
|
42 |
+
include = list(include or ["*.pt"]) # default include all .pt
|
43 |
+
exclude = list(exclude or [])
|
44 |
+
files: List[Path] = []
|
45 |
+
for p in source.rglob("*.pt"):
|
46 |
+
rel = p.relative_to(source)
|
47 |
+
rel_str = str(rel).replace("\\", "/")
|
48 |
+
if include and not any(Path(rel_str).match(pat) for pat in include):
|
49 |
+
continue
|
50 |
+
if exclude and any(Path(rel_str).match(pat) for pat in exclude):
|
51 |
+
continue
|
52 |
+
files.append(p)
|
53 |
+
return files
|
54 |
+
|
55 |
+
|
56 |
+
def create_zip_bundle(source: Path, out_path: Path, includes: Iterable[str] | None = None, excludes: Iterable[str] | None = None) -> int:
|
57 |
+
files = find_pt_files(source, includes, excludes)
|
58 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
59 |
+
with zipfile.ZipFile(out_path, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
|
60 |
+
for f in files:
|
61 |
+
zf.write(f, f.relative_to(source))
|
62 |
+
return len(files)
|
63 |
+
|
64 |
+
|
65 |
+
def create_pt_container(source: Path, out_path: Path, includes: Iterable[str] | None = None, excludes: Iterable[str] | None = None) -> int:
|
66 |
+
if torch is None:
|
67 |
+
raise RuntimeError("torch is required for PT container mode. Install torch and retry.")
|
68 |
+
files = find_pt_files(source, includes, excludes)
|
69 |
+
payload = {}
|
70 |
+
for f in files:
|
71 |
+
rel = str(f.relative_to(source)).replace("\\", "/")
|
72 |
+
with open(f, "rb") as fh:
|
73 |
+
payload[rel] = fh.read() # store exact bytes (no mutation)
|
74 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
75 |
+
torch.save(payload, out_path)
|
76 |
+
return len(files)
|
77 |
+
|
78 |
+
|
79 |
+
def list_bundle(bundle: Path) -> List[str]:
|
80 |
+
if bundle.suffix.lower() == ".zip":
|
81 |
+
with zipfile.ZipFile(bundle, "r") as zf:
|
82 |
+
return [i.filename for i in zf.infolist() if not i.is_dir()]
|
83 |
+
else:
|
84 |
+
if torch is None:
|
85 |
+
raise RuntimeError("torch is required to list PT container contents.")
|
86 |
+
data = torch.load(bundle, map_location="cpu")
|
87 |
+
if isinstance(data, dict):
|
88 |
+
return sorted(map(str, data.keys()))
|
89 |
+
raise ValueError("Unsupported PT container format: expected dict mapping.")
|
90 |
+
|
91 |
+
|
92 |
+
def extract_member(bundle: Path, member: str, out_path: Path) -> None:
|
93 |
+
if bundle.suffix.lower() == ".zip":
|
94 |
+
with zipfile.ZipFile(bundle, "r") as zf:
|
95 |
+
with zf.open(member, "r") as fh, open(out_path, "wb") as out:
|
96 |
+
out.write(fh.read())
|
97 |
+
else:
|
98 |
+
if torch is None:
|
99 |
+
raise RuntimeError("torch is required to extract from PT container.")
|
100 |
+
data = torch.load(bundle, map_location="cpu")
|
101 |
+
if not isinstance(data, dict):
|
102 |
+
raise ValueError("Unsupported PT container format: expected dict mapping.")
|
103 |
+
if member not in data:
|
104 |
+
raise FileNotFoundError(f"member not found in container: {member}")
|
105 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
106 |
+
with open(out_path, "wb") as fh:
|
107 |
+
fh.write(data[member])
|
108 |
+
|
109 |
+
|
110 |
+
def main(argv: List[str] | None = None) -> int:
|
111 |
+
parser = argparse.ArgumentParser(description="Bundle multiple .pt files without modifying originals.")
|
112 |
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
113 |
+
|
114 |
+
p_zip = sub.add_parser("zip", help="Create a ZIP archive of .pt files.")
|
115 |
+
p_zip.add_argument("--source", default=".", help="Root directory to scan for .pt files.")
|
116 |
+
p_zip.add_argument("--out", required=True, help="Output ZIP path.")
|
117 |
+
p_zip.add_argument("--include", nargs="*", default=["*.pt"], help="Glob patterns to include.")
|
118 |
+
p_zip.add_argument("--exclude", nargs="*", default=[], help="Glob patterns to exclude.")
|
119 |
+
|
120 |
+
p_pt = sub.add_parser("pt", help="Create a single .pt container (dict of bytes).")
|
121 |
+
p_pt.add_argument("--source", default=".", help="Root directory to scan for .pt files.")
|
122 |
+
p_pt.add_argument("--out", required=True, help="Output PT path (e.g., models_multi.pt).")
|
123 |
+
p_pt.add_argument("--include", nargs="*", default=["*.pt"], help="Glob patterns to include.")
|
124 |
+
p_pt.add_argument("--exclude", nargs="*", default=[], help="Glob patterns to exclude.")
|
125 |
+
|
126 |
+
p_list = sub.add_parser("list", help="List contents of a bundle (ZIP or PT container).")
|
127 |
+
p_list.add_argument("--bundle", required=True, help="Path to models_bundle.zip or models_multi.pt.")
|
128 |
+
|
129 |
+
p_ext = sub.add_parser("extract", help="Extract a single member from the bundle.")
|
130 |
+
p_ext.add_argument("--bundle", required=True, help="Bundle path (ZIP or PT container).")
|
131 |
+
p_ext.add_argument("--member", required=True, help="Member path inside the bundle.")
|
132 |
+
p_ext.add_argument("--out", required=True, help="Destination file path to write.")
|
133 |
+
|
134 |
+
args = parser.parse_args(argv)
|
135 |
+
|
136 |
+
if args.cmd == "zip":
|
137 |
+
count = create_zip_bundle(Path(args.source), Path(args.out), args.include, args.exclude)
|
138 |
+
print(f"ZIP bundle written: {args.out} ({count} files)")
|
139 |
+
return 0
|
140 |
+
if args.cmd == "pt":
|
141 |
+
count = create_pt_container(Path(args.source), Path(args.out), args.include, args.exclude)
|
142 |
+
print(f"PT container written: {args.out} ({count} files)")
|
143 |
+
return 0
|
144 |
+
if args.cmd == "list":
|
145 |
+
items = list_bundle(Path(args.bundle))
|
146 |
+
for it in items:
|
147 |
+
print(it)
|
148 |
+
return 0
|
149 |
+
if args.cmd == "extract":
|
150 |
+
extract_member(Path(args.bundle), args.member, Path(args.out))
|
151 |
+
print(f"Extracted {args.member} -> {args.out}")
|
152 |
+
return 0
|
153 |
+
|
154 |
+
parser.print_help()
|
155 |
+
return 1
|
156 |
+
|
157 |
+
|
158 |
+
if __name__ == "__main__":
|
159 |
+
raise SystemExit(main())
|