video_monitor/app/analysis/engines/openvino_engine.py

203 lines
6.8 KiB
Python
Raw Normal View History

2026-08-30 22:22:11 +08:00
"""OpenVINO 引擎实现 —— YOLO 5/8/11/26 + 全任务 + 设备支持
依赖openvino, opencv-python, numpy
推理设备
cpu -> CPU
gpu -> GPU
cuda -> 不适用回退 CPUOpenVINO 不走 CUDA
"""
import logging
import os
from app.analysis.engines.base import BaseEngine, DetectionResult
logger = logging.getLogger("analysis.engines.openvino")
try:
import numpy as np
_NP_AVAILABLE = True
except Exception:
np = None
_NP_AVAILABLE = False
try:
import cv2
_CV2_AVAILABLE = True
except Exception:
cv2 = None
_CV2_AVAILABLE = False
try:
from openvino import Core
_OV_AVAILABLE = True
except Exception:
try:
from openvino.runtime import Core
_OV_AVAILABLE = True
except Exception:
Core = None
_OV_AVAILABLE = False
def _device_for(device):
d = (device or "cpu").lower()
if d in ("gpu", "cuda", "0"):
return "GPU"
return "CPU"
class OpenVinoEngine(BaseEngine):
ENGINE_NAME = "openvino"
def __init__(self, **kwargs):
super(OpenVinoEngine, self).__init__(**kwargs)
self._compiled = None
self._infer_req = None
self._input_key = None
self._output_keys = None
self.task_type = (kwargs.get("task_type") or "detect").lower()
self.device = kwargs.get("device") or "cpu"
@staticmethod
def is_available():
return _OV_AVAILABLE and _CV2_AVAILABLE and _NP_AVAILABLE
@staticmethod
def version():
if not _OV_AVAILABLE:
return None
try:
from openvino.runtime import get_version
return get_version()
except Exception:
pass
try:
import openvino
return getattr(openvino, "__version__", "unknown")
except Exception:
return "unknown"
def _resolve_model_path(self):
if not self.model_file:
return None
if self.model_file.lower().endswith(".onnx"):
return self.model_file
base, ext = os.path.splitext(self.model_file)
if ext.lower() == ".xml":
return self.model_file
xml_p = base + ".xml"
if os.path.exists(xml_p):
return xml_p
return None
def load(self):
if not self.is_available():
logger.warning("OpenVinoEngine: 依赖未安装")
return False
path = self._resolve_model_path()
if not path or not os.path.exists(path):
logger.warning("OpenVinoEngine: 模型文件不可用: %s", self.model_file)
return False
if not self.labels:
self.labels = self._resolve_labels(self.model_file)
try:
core = Core()
model = core.read_model(path)
dev = _device_for(self.device)
# GPU 不可用时回退 CPU
try:
self._compiled = core.compile_model(model, dev)
except Exception as e:
logger.warning("OpenVinoEngine: 设备 %s 编译失败,回退 CPU: %s", dev, e)
self._compiled = core.compile_model(model, "CPU")
dev = "CPU"
self._infer_req = self._compiled.create_infer_request()
self._input_key = list(self._compiled.inputs)[0]
self._output_keys = list(self._compiled.outputs)
self._loaded = True
logger.info("OpenVinoEngine: 已加载 %s, task=%s, device=%s, labels=%d",
path, self.task_type, dev, len(self.labels))
return True
except Exception as e:
logger.error("OpenVinoEngine: 加载失败: %s", e)
self._loaded = False
return False
def _preprocess(self, frame_bgr):
iw, ih = self.input_size
resized = cv2.resize(frame_bgr, (iw, ih))
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
blob = rgb.astype(np.float32) / 255.0
blob = np.transpose(blob, (2, 0, 1))[None, ...]
return blob
def detect(self, frame_bgr):
if not self.ready() or frame_bgr is None:
return []
try:
h, w = frame_bgr.shape[:2]
blob = self._preprocess(frame_bgr)
self._infer_req.infer({self._input_key: blob})
outputs = []
out_count = len(self._output_keys)
for i in range(out_count):
try:
outputs.append(self._infer_req.get_output_tensor(i).data)
except Exception:
try:
outputs.append(self._infer_req.get_output_tensor().data)
except Exception as e:
logger.warning("OpenVinoEngine: 读取输出 tensor[%d] 失败: %s", i, e)
if not outputs:
return []
from app.analysis.engines.yolo_postprocess import decode_outputs
results = decode_outputs(
outputs=outputs,
algorithm_type=self.algorithm_type,
task_type=self.task_type,
labels=self.labels,
input_size=self.input_size,
conf_threshold=self.conf_threshold,
iou_threshold=self.iou_threshold,
orig_size=(w, h),
)
return [DetectionResult(**r) for r in results]
except Exception as e:
logger.warning("OpenVinoEngine.detect() err: %s", e)
return []
def info(self):
d = super(OpenVinoEngine, self).info()
d["version"] = self.version()
d["task_type"] = self.task_type
d["device"] = _device_for(self.device)
return d
def probe(self):
info = {"engine": self.ENGINE_NAME, "available": self.is_available(),
"version": self.version(), "input_shape": None, "output_shape": None,
"labels": self.labels, "model_file": self.model_file,
"task_type": self.task_type, "device": _device_for(self.device)}
if not self.is_available() or not self.model_file:
return info
path = self._resolve_model_path()
if not path or not os.path.exists(path):
info["error"] = "model file not resolvable"
return info
try:
core = Core()
model = core.read_model(path)
if model.inputs:
shape = list(model.inputs[0].shape)
info["input_shape"] = shape
if len(shape) >= 4:
info["input_size_inferred"] = (int(shape[-1]), int(shape[-2]))
if model.outputs:
info["output_shape"] = [list(o.shape) for o in model.outputs]
if not self.labels:
self.labels = self._resolve_labels(self.model_file)
info["labels"] = self.labels
except Exception as e:
info["error"] = str(e)
return info