From cf70f74ed91ef85cdec251c1e8bea6d472fc4541 Mon Sep 17 00:00:00 2001 From: ChK Date: Wed, 20 May 2026 16:43:31 +0200 Subject: [PATCH] initial --- .gitignore | 3 ++ docker-config.yaml | 12 +++++ watcher.py | 130 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 .gitignore create mode 100755 docker-config.yaml create mode 100755 watcher.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac13c3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +# Ignore all PyTorch model files +*.pt diff --git a/docker-config.yaml b/docker-config.yaml new file mode 100755 index 0000000..440bfea --- /dev/null +++ b/docker-config.yaml @@ -0,0 +1,12 @@ +version: "3.8" + +services: + yolo: + image: ultralytics/ultralytics:latest + container_name: appRobot_Yolo + working_dir: /app + volumes: + - /home/chk/Documents/appRobotYolo:/app + - /home/chk/Documents/AppRobotVideo/public/snapshots:/snapshots + command: python watcher.py + restart: unless-stopped \ No newline at end of file diff --git a/watcher.py b/watcher.py new file mode 100755 index 0000000..cf0d470 --- /dev/null +++ b/watcher.py @@ -0,0 +1,130 @@ +import os +import time +import json +import re +from ultralytics import YOLO + +SNAPSHOT_DIR = "/snapshots" + +# only process images from last N minutes +MAX_AGE_MINUTES = 3 +MAX_AGE_MS = MAX_AGE_MINUTES * 60 * 1000 + +# remember processed files (for runtime) +PROCESSED = set() + +# strict filename pattern +PATTERN = re.compile(r"^snapshot_video\d+_(\d+)\.jpg$") + +model = YOLO("yolov8m.pt") +#model = YOLO("yolov8s-pose.pt") + + +def extract_timestamp(filename): + match = PATTERN.match(filename) + if match: + return int(match.group(1)) + return None + + +def get_recent_files(): + now_ms = int(time.time() * 1000) + files = [] + + for f in os.listdir(SNAPSHOT_DIR): + ts = extract_timestamp(f) + if ts is None: + continue + + # filter by age + if now_ms - ts > MAX_AGE_MS: + continue + + files.append((f, ts)) + + # newest first + files.sort(key=lambda x: x[1], reverse=True) + + return [f[0] for f in files] + + +def is_file_stable(path): + """Avoid processing partially written files""" + try: + size1 = os.path.getsize(path) + time.sleep(0.1) + size2 = os.path.getsize(path) + return size1 == size2 + except: + return False + + +def detect_and_save(file): + full_path = os.path.join(SNAPSHOT_DIR, file) + + if not is_file_stable(full_path): + return + + results = model(full_path, imgsz=960) + + detections = [] + + for r in results: + boxes = r.boxes + if boxes is None: + continue + + for b in boxes: + detections.append({ + "class": model.names[int(b.cls)], + "confidence": float(b.conf), + "bbox_xyxy": b.xyxy[0].tolist() + }) + + # ✅ --- NEW: create annotated image --- + annotated_img = r.plot() # returns image with boxes drawn + + out_img_file = file.replace(".jpg", "_yolo.jpg") + out_img_path = os.path.join(SNAPSHOT_DIR, out_img_file) + + # save using OpenCV + import cv2 + cv2.imwrite(out_img_path, annotated_img) + + + out_file = file.replace(".jpg", "_yolo.json") + out_path = os.path.join(SNAPSHOT_DIR, out_file) + + with open(out_path, "w") as f: + json.dump(detections, f, indent=2) + + print(f"Processed {file} → {out_file}") + + +while True: + try: + files = get_recent_files() + + for file in files[:10]: # limit per loop + if file in PROCESSED: + continue + + json_file = file.replace(".jpg", "_yolo.json") + json_path = os.path.join(SNAPSHOT_DIR, json_file) + + # skip if already processed (persistent) + if os.path.exists(json_path): + PROCESSED.add(file) + continue + + detect_and_save(file) + PROCESSED.add(file) + + # cleanup memory + if len(PROCESSED) > 1000: + PROCESSED.clear() + + except Exception as e: + print("Error:", e) + + time.sleep(1) \ No newline at end of file