ROI选区01

This commit is contained in:
2026-01-20 17:42:18 +08:00
parent 232a3827d4
commit 604ef82ffb
32 changed files with 3800 additions and 383 deletions

195
utils/helpers.py Normal file
View File

@@ -0,0 +1,195 @@
import os
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
import cv2
import numpy as np
def draw_bbox(
image: np.ndarray,
bbox: List[float],
label: str = "",
color: Tuple[int, int, int] = (0, 255, 0),
thickness: int = 2,
) -> np.ndarray:
x1, y1, x2, y2 = [int(v) for v in bbox]
cv2.rectangle(image, (x1, y1), (x2, y2), color, thickness)
if label:
(text_width, text_height), baseline = cv2.getTextSize(
label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, thickness
)
cv2.rectangle(
image,
(x1, y1 - text_height - 10),
(x1 + text_width + 10, y1),
color,
-1,
)
cv2.putText(
image,
label,
(x1 + 5, y1 - 5),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 0, 0),
thickness,
)
return image
def draw_roi(
image: np.ndarray,
points: List[List[float]],
roi_type: str = "polygon",
color: Tuple[int, int, int] = (255, 0, 0),
thickness: int = 2,
label: str = "",
) -> np.ndarray:
points = np.array(points, dtype=np.int32)
if roi_type == "polygon":
cv2.polylines(image, [points], True, color, thickness)
cv2.fillPoly(image, [points], color=(color[0], color[1], color[2], 30))
elif roi_type == "line":
cv2.line(image, tuple(points[0]), tuple(points[1]), color, thickness)
elif roi_type == "rectangle":
x1, y1 = points[0]
x2, y2 = points[1]
cv2.rectangle(image, (x1, y1), (x2, y2), color, thickness)
if label:
cx = int(np.mean(points[:, 0]))
cy = int(np.mean(points[:, 1]))
cv2.putText(
image,
label,
(cx, cy),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
color,
thickness,
)
return image
def draw_detections(
image: np.ndarray,
detections: List[Dict[str, Any]],
roi_configs: Optional[List[Dict[str, Any]]] = None,
) -> np.ndarray:
result = image.copy()
for detection in detections:
bbox = detection.get("bbox", [])
conf = detection.get("conf", 0.0)
cls = detection.get("cls", 0)
label = f"Person: {conf:.2f}"
color = (0, 255, 0)
if roi_configs:
matched_rois = detection.get("matched_rois", [])
for roi_conf in matched_rois:
if roi_conf.get("enabled", True):
roi_points = roi_conf.get("points", [])
roi_type = roi_conf.get("type", "polygon")
roi_label = roi_conf.get("name", "")
roi_color = (255, 0, 0) if roi_conf.get("rule") == "intrusion" else (0, 165, 255)
result = draw_roi(result, roi_points, roi_type, roi_color, 2, roi_label)
result = draw_bbox(result, bbox, label, color, 2)
return result
def resize_image(
image: np.ndarray,
max_size: int = 480,
maintain_aspect: bool = True,
) -> np.ndarray:
h, w = image.shape[:2]
if maintain_aspect:
scale = min(max_size / h, max_size / w)
new_h, new_w = int(h * scale), int(w * scale)
else:
new_h, new_w = max_size, max_size
scale = max_size / max(h, w)
result = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
return result, scale
def encode_image_base64(image: np.ndarray, quality: int = 85) -> str:
_, buffer = cv2.imencode(".jpg", image, [cv2.IMWRITE_JPEG_QUALITY, quality])
return base64.b64encode(buffer).decode("utf-8")
def decode_image_base64(data: str) -> np.ndarray:
import base64
buffer = base64.b64decode(data)
nparr = np.frombuffer(buffer, np.uint8)
return cv2.imdecode(nparr, cv2.IMREAD_COLOR)
def get_timestamp_str() -> str:
return datetime.now().strftime("%Y%m%d_%H%M%S")
def format_duration(seconds: float) -> str:
if seconds < 60:
return f"{int(seconds)}"
elif seconds < 3600:
minutes = int(seconds // 60)
secs = int(seconds % 60)
return f"{minutes}{secs}"
else:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
return f"{hours}小时{minutes}"
def is_time_in_ranges(
current_time: datetime,
time_ranges: List[Dict[str, List[int]]],
) -> bool:
if not time_ranges:
return True
current_minutes = current_time.hour * 60 + current_time.minute
for time_range in time_ranges:
start = time_range["start"]
end = time_range["end"]
start_minutes = start[0] * 60 + start[1]
end_minutes = end[0] * 60 + end[1]
if start_minutes <= current_minutes < end_minutes:
return True
return False
class FPSCounter:
def __init__(self, window_size: int = 30):
self.timestamps: List[float] = []
self.window_size = window_size
def update(self):
import time
now = time.time()
self.timestamps.append(now)
self.timestamps = self.timestamps[-self.window_size:]
@property
def fps(self) -> float:
if len(self.timestamps) < 2:
return 0.0
elapsed = self.timestamps[-1] - self.timestamps[0]
if elapsed <= 0:
return 0.0
return (len(self.timestamps) - 1) / elapsed

114
utils/logger.py Normal file
View File

@@ -0,0 +1,114 @@
import logging
import os
import sys
from datetime import datetime
from logging.handlers import RotatingFileHandler
from typing import Optional
from pythonjsonlogger import jsonlogger
def setup_logger(
name: str = "security_monitor",
log_file: str = "logs/app.log",
level: str = "INFO",
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
max_size: str = "100MB",
backup_count: int = 5,
json_format: bool = False,
) -> logging.Logger:
logger = logging.getLogger(name)
logger.setLevel(getattr(logging, level.upper(), logging.INFO))
if logger.handlers:
for handler in logger.handlers:
logger.removeHandler(handler)
os.makedirs(os.path.dirname(log_file), exist_ok=True)
if json_format:
handler = RotatingFileHandler(
log_file,
maxBytes=int(max_size.replace("MB", "")) * 1024 * 1024,
backupCount=backup_count,
encoding="utf-8",
)
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(name)s %(levelname)s %(message)s",
rename_fields={"levelname": "severity", "asctime": "timestamp"},
)
handler.setFormatter(formatter)
else:
handler = RotatingFileHandler(
log_file,
maxBytes=int(max_size.replace("MB", "")) * 1024 * 1024,
backupCount=backup_count,
encoding="utf-8",
)
formatter = logging.Formatter(format)
handler.setFormatter(formatter)
logger.addHandler(handler)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(format))
logger.addHandler(console_handler)
return logger
def get_logger(name: str = "security_monitor") -> logging.Logger:
return logging.getLogger(name)
class LoggerMixin:
@property
def logger(self) -> logging.Logger:
return get_logger(self.__class__.__name__)
def log_execution_time(logger: Optional[logging.Logger] = None):
def decorator(func):
import time
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
log = logger or get_logger(func.__module__)
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
log.info(
f"函数执行完成",
extra={
"function": func.__name__,
"execution_time_ms": int((end_time - start_time) * 1000),
},
)
return result
return wrapper
return decorator
def log_function_call(logger: Optional[logging.Logger] = None):
def decorator(func):
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
log = logger or get_logger(func.__module__)
log.info(
f"函数调用",
extra={
"function": func.__name__,
"args": str(args),
"kwargs": str(kwargs),
},
)
return func(*args, **kwargs)
return wrapper
return decorator

113
utils/metrics.py Normal file
View File

@@ -0,0 +1,113 @@
from typing import Optional
from prometheus_client import Counter, Gauge, Histogram, Info, start_http_server
from config import get_config
SYSTEM_INFO = Info("system", "System information")
CAMERA_COUNT = Gauge("camera_count", "Number of active cameras")
CAMERA_FPS = Gauge("camera_fps", "Camera FPS", ["camera_id"])
INFERENCE_LATENCY = Histogram(
"inference_latency_seconds",
"Inference latency in seconds",
["camera_id"],
buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0],
)
ALERT_COUNT = Counter(
"alert_total",
"Total number of alerts",
["camera_id", "event_type"],
)
EVENT_QUEUE_SIZE = Gauge(
"event_queue_size",
"Current size of event queue",
)
DETECTION_COUNT = Counter(
"detection_total",
"Total number of detections",
["camera_id", "roi_id"],
)
GPU_MEMORY_USED = Gauge(
"gpu_memory_used_bytes",
"GPU memory used",
["device"],
)
GPU_UTILIZATION = Gauge(
"gpu_utilization_percent",
"GPU utilization",
["device"],
)
class MetricsServer:
def __init__(self, port: int = 9090):
self.port = port
self.started = False
def start(self):
if self.started:
return
config = get_config()
if not config.monitoring.enabled:
return
start_http_server(self.port)
self.started = True
print(f"Prometheus metrics server started on port {self.port}")
def update_camera_metrics(self, camera_id: int, fps: float):
CAMERA_FPS.labels(camera_id=str(camera_id)).set(fps)
def record_inference(self, camera_id: int, latency: float):
INFERENCE_LATENCY.labels(camera_id=str(camera_id)).observe(latency)
def record_alert(self, camera_id: int, event_type: str):
ALERT_COUNT.labels(camera_id=str(camera_id), event_type=event_type).inc()
def update_event_queue(self, size: int):
EVENT_QUEUE_SIZE.set(size)
def record_detection(self, camera_id: int, roi_id: str):
DETECTION_COUNT.labels(camera_id=str(camera_id), roi_id=roi_id).inc()
def update_gpu_metrics(self, device: int, memory_bytes: float, utilization: float):
GPU_MEMORY_USED.labels(device=str(device)).set(memory_bytes)
GPU_UTILIZATION.labels(device=str(device)).set(utilization)
_metrics_server: Optional[MetricsServer] = None
def get_metrics_server() -> MetricsServer:
global _metrics_server
if _metrics_server is None:
config = get_config()
_metrics_server = MetricsServer(port=config.monitoring.port)
return _metrics_server
def start_metrics_server():
server = get_metrics_server()
server.start()
def update_system_info():
import platform
import psutil
SYSTEM_INFO.info({
"os": platform.system(),
"os_version": platform.version(),
"python_version": platform.python_version(),
"cpu_count": str(psutil.cpu_count()),
"memory_total_gb": str(round(psutil.virtual_memory().total / (1024**3), 2)),
})