feat/igpu-ov: OpenVINO iGPU detector (--gpu igpu) on Intel Level Zero

- ov_detector.OVFaceDetector: OV inference + ultralytics letterbox(1280 sq)
  + greedy NMS; model.predict()-compatible shim, process_video() unchanged
- anime_facesaver.py: --gpu igpu branch; CUDA path untouched (moved to else)
- CUDA path untouched; main() unchanged
- validate_ov.py: cross-check vs torch CPU baseline
  measured 2026-10-20 i3-N305: 59/59 matched, mean IoU 0.987,
  only_base=0 only_ov=0; 5.0s/img GPU vs 14.5s/img CPU OpenVINO-CPU vs
  torch-CPU baseline (25 real stills, yolov8x6_animeface)
This commit is contained in:
2026-10-20 13:49:57 -04:00
parent de177c8f59
commit 460ab5f73e
3 changed files with 206 additions and 9 deletions
+29 -9
View File
@@ -161,6 +161,10 @@ def resolve_gpu(gpu_arg):
if gpu_arg.lower() == "cpu":
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
return "cpu"
if gpu_arg.lower() == "igpu":
# Intel iGPU via OpenVINO (Level Zero) — no CUDA, no torch pinning.
# OVFaceDetector takes over model loading in main().
return "igpu"
if gpu_arg.lower() == "auto":
return None # let ultralytics/torch decide
os.environ["CUDA_VISIBLE_DEVICES"] = gpu_arg
@@ -353,16 +357,32 @@ def main():
from ultralytics import YOLO
import cv2
if device == "cuda:0" and not torch.cuda.is_available():
print("CUDA requested but not visible after pinning "
f"(--gpu {args.gpu}). Falling back to CPU.")
device = "cpu"
print(f"Device: {device}"
+ (f" (physical GPU {args.gpu})" if device == "cuda" else ""))
if device == "igpu":
# Intel iGPU path: OpenVINO replaces ultralytics' torch predict loop.
# Ultralytics import stays (shared pre/post code paths), but weights
# load as ONNX through OVFaceDetector instead of torch.
from ov_detector import OVFaceDetector
onnx_path = os.path.splitext(args.model)[0] + ".onnx"
if not os.path.exists(onnx_path):
print(f"Error: --gpu igpu needs an exported ONNX at {onnx_path}\n"
" (one-time: YOLO('<pt>').export(format='onnx', "
"dynamic=True, simplify=True, opset=17))")
sys.exit(1)
model = OVFaceDetector(onnx_path, device="GPU", imgsz=args.imgsz)
names = model.names
print("Device: igpu (OpenVINO, Intel iGPU via Level Zero)")
print(f"Model loaded (OpenVINO GPU). Classes: {names}")
else:
if device == "cuda:0" and not torch.cuda.is_available():
print("CUDA requested but not visible after pinning "
f"(--gpu {args.gpu}). Falling back to CPU.")
device = "cpu"
print(f"Device: {device}"
+ (f" (physical GPU {args.gpu})" if device == "cuda" else ""))
model = YOLO(args.model)
names = model.names
print(f"Model loaded. Classes: {names}")
model = YOLO(args.model)
names = model.names
print(f"Model loaded. Classes: {names}")
def draw(img, boxes):
for (a, b, c, d) in boxes.int().tolist():