initial commit

This commit is contained in:
ray
2026-07-12 10:17:17 -04:00
commit dab5a4ebc6
1424 changed files with 330463 additions and 0 deletions
@@ -0,0 +1,169 @@
#!/usr/bin/env python3
"""
GPU YOLO photo filter: keep people/scenic/screenshots, move everything else.
Run with: python3 -u yolo_no_people_filter.py (-u for unbuffered output)
Customize SRC, DST, and class_sets below.
When running via Hermes background processes, ALWAYS use python3 -u and flush=True
on all print() calls. Without this, progress output stays buffered and won't appear
in the process viewer until the script exits.
"""
import os, shutil
from pathlib import Path
from collections import Counter
import torch
from ultralytics import YOLO
# === CONFIGURATION ===
SRC = "/mnt/seagate8tb/Photos" # Source directory (recursive)
DST = "/mnt/seagate8tb/NO PEOPLE" # Destination for filtered photos
BATCH_SIZE = 2 # 2 for 4GB GPU, 4 for 8GB+
NUM_WORKERS = 4
DEVICE = "cuda:0"
CONFIDENCE = 0.3
MODEL = "yolov8s.pt" # s=11M params (4GB safe), n=3M, m=26M
HALF = True # FP16 for speed on CUDA
# === CLASSIFICATION SETS (COCO indices) ===
PEOPLE_CLASSES = {0}
SCREENSHOT_CLASSES = {
62, 63, 64, 65, 66, 67, # tv, laptop, mouse, remote, keyboard, cell phone
73, 76, # book, scissors
}
SCENIC_CLASSES = {
1, 2, 3, 4, 6, 7, 8, # bicycle, car, motorcycle, airplane, bus, train, truck
14, 15, 16, 17, 18, 19, # bird, cat, dog, horse, sheep, cow
20, 21, 22, 23, # elephant, bear, zebra, giraffe
25, 26, 27, 28, # umbrella, handbag, tie, suitcase
29, 30, 31, 32, 33, 34, # frisbee, skis, snowboard, sports ball, kite, baseball bat
35, 36, 37, 38, # baseball glove, skateboard, surfboard, tennis racket
39, 40, 41, # bottle, wine glass, cup
42, 43, 44, 45, 46, # fork, knife, spoon, bowl, banana
47, 48, 49, 50, 51, # apple, sandwich, orange, broccoli, carrot
52, 53, 54, 55, # hot dog, pizza, donut, cake
56, 57, 58, 59, 60, # chair, couch, potted plant, bed, dining table
72, 74, 75, 77, 78, 79, # clock, vase, teddy bear, hair drier, toothbrush
80, 81, # hair drier, toothbrush (duplicates from COCO)
}
def is_scenic(detections):
return any(cid in SCENIC_CLASSES for cid in detections)
def is_screenshot(detections):
return any(cid in SCREENSHOT_CLASSES for cid in detections)
def main():
print(f"Loading {MODEL} on {DEVICE}...", flush=True)
model = YOLO(MODEL)
model.to(DEVICE)
# Collect all image files
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp',
'.tiff', '.tif', '.gif', '.heic', '.heif'}
files = []
for root, dirs, fnames in os.walk(SRC):
for f in fnames:
if Path(f).suffix.lower() in extensions:
files.append(os.path.join(root, f))
total = len(files)
print(f"Found {total} images to scan", flush=True)
if total == 0:
print("No images found!")
return
stats = Counter()
moved = 0
kept_people = 0
kept_scenic = 0
kept_screenshot = 0
errors = 0
for i in range(0, total, BATCH_SIZE):
batch = files[i:i + BATCH_SIZE]
batch_idx = i // BATCH_SIZE + 1
total_batches = (total + BATCH_SIZE - 1) // BATCH_SIZE
try:
results = model(batch, device=DEVICE, verbose=False,
conf=CONFIDENCE, imgsz=640, half=HALF, stream=False)
except Exception as e:
# Batch failed — likely corrupt images in the mix.
# Move them all directly to NO PEOPLE (no GPU retry — too slow).
for filepath in batch:
try:
rel = os.path.relpath(filepath, SRC)
dst = os.path.join(DST, rel)
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.move(filepath, dst)
moved += 1
stats['no_people'] += 1
except Exception as e2:
errors += 1
continue
for result, filepath in zip(results, batch):
boxes = result.boxes
if boxes is None or len(boxes) == 0:
# No detections → move
rel = os.path.relpath(filepath, SRC)
dst = os.path.join(DST, rel)
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.move(filepath, dst)
moved += 1
stats['empty'] += 1
continue
class_ids = boxes.cls.cpu().numpy().astype(int)
has_people = any(cid in PEOPLE_CLASSES for cid in class_ids)
scenic = is_scenic(class_ids)
screenshot = is_screenshot(class_ids)
if has_people:
kept_people += 1
stats['people'] += 1
elif screenshot:
kept_screenshot += 1
stats['screenshot'] += 1
elif scenic:
kept_scenic += 1
stats['scenic'] += 1
else:
rel = os.path.relpath(filepath, SRC)
dst = os.path.join(DST, rel)
os.makedirs(os.path.dirname(dst), exist_ok=True)
try:
shutil.move(filepath, dst)
moved += 1
stats['no_people'] += 1
except Exception as e:
print(f" Move error: {filepath}: {e}", flush=True)
errors += 1
if batch_idx % 50 == 0 or batch_idx == total_batches:
elapsed = min(batch_idx * BATCH_SIZE, total)
pct = elapsed / total * 100
print(f" [{elapsed}/{total}] {pct:.1f}% | moved={moved} | "
f"people={kept_people} | scenic={kept_scenic} | "
f"ss={kept_screenshot} | err={errors}", flush=True)
print(f"\n{'='*60}")
print(f"DONE: {total} images processed")
print(f" 👤 People (kept): {kept_people}")
print(f" 🏔️ Scenic (kept): {kept_scenic}")
print(f" 📱 Screenshot (kept): {kept_screenshot}")
print(f" 📦 Moved to NO PEOPLE: {moved}")
print(f" ❌ Errors: {errors}")
print(f" 💾 Empty (no detections): {stats.get('empty', 0)}")
if __name__ == "__main__":
main()