ROI选区01
This commit is contained in:
195
utils/helpers.py
Normal file
195
utils/helpers.py
Normal 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
114
utils/logger.py
Normal 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
113
utils/metrics.py
Normal 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)),
|
||||
})
|
||||
Reference in New Issue
Block a user