Compare commits

...

3 Commits

7 changed files with 247 additions and 92 deletions

View File

@@ -15,6 +15,7 @@ from db.models import get_db
from inference.pipeline import get_pipeline
router = APIRouter(prefix="/api/cameras", tags=["摄像头管理"])
router2 = APIRouter(prefix="/api/camera", tags=["摄像头状态"])
class CameraUpdateRequest(BaseModel):
@@ -163,3 +164,42 @@ def get_camera_status(camera_id: int, db: Session = Depends(get_db)):
"last_check_time": None,
"stream": stream_info,
}
router2 = APIRouter(prefix="/api/camera", tags=["摄像头状态"])
@router2.get("/status/all")
def get_all_camera_status(db: Session = Depends(get_db)):
from db.crud import get_all_cameras, get_camera_status as get_status
cameras = get_all_cameras(db, enabled_only=False)
pipeline = get_pipeline()
result = []
for cam in cameras:
status = get_status(db, cam.id)
stream = pipeline.stream_manager.get_stream(str(cam.id))
stream_info = stream.get_info() if stream else None
if status:
result.append({
"camera_id": cam.id,
"is_running": status.is_running,
"fps": status.fps,
"error_message": status.error_message,
"last_check_time": status.last_check_time.isoformat() if status.last_check_time else None,
"stream": stream_info,
})
else:
result.append({
"camera_id": cam.id,
"is_running": False,
"fps": 0.0,
"error_message": None,
"last_check_time": None,
"stream": stream_info,
})
return result

View File

@@ -23,16 +23,17 @@ class DatabaseConfig(BaseModel):
class ModelConfig(BaseModel):
engine_path: str = "models/yolo11s.engine"
onnx_path: str = "models/yolo11s.onnx"
pt_model_path: str = "models/yolo11s.pt"
engine_path: str = "models/yolo11n.engine"
onnx_path: str = "models/yolo11n.onnx"
pt_model_path: str = "models/yolo11n.pt"
imgsz: List[int] = [640, 640]
conf_threshold: float = 0.5
iou_threshold: float = 0.45
device: int = 0
batch_size: int = 8
half: bool = True
use_onnx: bool = True
half: bool = False
use_onnx: bool = False
use_trt: bool = False
class StreamConfig(BaseModel):

View File

@@ -24,6 +24,10 @@ const Dashboard: React.FC = () => {
const [recentAlerts, setRecentAlerts] = useState<Alert[]>([]);
const [cameraStatus, setCameraStatus] = useState<any[]>([]);
const handleViewSnapshot = (alert: Alert) => {
window.open(`/api/alarms/${alert.id}/snapshot`, '_blank');
};
useEffect(() => {
fetchStats();
fetchAlerts();
@@ -139,7 +143,7 @@ const Dashboard: React.FC = () => {
description={formatTime(alert.created_at)}
/>
{alert.snapshot_path && (
<Button type="link" size="small">
<Button type="link" size="small" onClick={() => handleViewSnapshot(alert)}>
</Button>
)}

View File

@@ -49,6 +49,10 @@ class ONNXEngine:
return img
def postprocess(self, output: np.ndarray, orig_img: np.ndarray) -> List[Results]:
import torch
import numpy as np
from ultralytics.engine.results import Boxes as BoxesObj, Results
c, n = output.shape
output = output.T
@@ -74,6 +78,9 @@ class ONNXEngine:
orig_h, orig_w = orig_img.shape[:2]
scale_x, scale_y = orig_w / self.imgsz[1], orig_h / self.imgsz[0]
if len(indices) == 0:
return [Results(orig_img=orig_img, path="", names={0: "person"})]
filtered_boxes = []
for idx in indices:
if idx >= len(boxes):
@@ -82,30 +89,30 @@ class ONNXEngine:
x1, y1, x2, y2 = box
w, h = x2 - x1, y2 - y1
filtered_boxes.append([
int(x1 * scale_x),
int(y1 * scale_y),
int(w * scale_x),
int(h * scale_y),
float(x1 * scale_x),
float(y1 * scale_y),
float(w * scale_x),
float(h * scale_y),
float(scores[idx]),
int(classes[idx])
])
from ultralytics.engine.results import Boxes as BoxesObj
if filtered_boxes:
box_tensor = torch.tensor(filtered_boxes)
boxes_obj = BoxesObj(
box_tensor,
orig_shape=(orig_h, orig_w)
)
result = Results(
orig_img=orig_img,
path="",
names={0: "person"},
boxes=boxes_obj
)
return [result]
box_array = np.array(filtered_boxes, dtype=np.float32)
else:
box_array = np.zeros((0, 6), dtype=np.float32)
return [Results(orig_img=orig_img, path="", names={0: "person"})]
boxes_obj = BoxesObj(
torch.from_numpy(box_array),
orig_shape=(orig_h, orig_w)
)
result = Results(
orig_img=orig_img,
path="",
names={0: "person"},
boxes=boxes_obj
)
return [result]
def inference(self, images: List[np.ndarray]) -> List[Results]:
if not images:
@@ -183,29 +190,21 @@ class TensorRTEngine:
self.context = self.engine.create_execution_context()
self.stream = torch.cuda.Stream(device=self.device)
self.batch_size = 1
for i in range(self.engine.num_io_tensors):
name = self.engine.get_tensor_name(i)
dtype = self.engine.get_tensor_dtype(name)
shape = list(self.engine.get_tensor_shape(name))
if dtype == trt.float16:
buffer = torch.zeros(shape, dtype=torch.float16, device=self.device)
else:
buffer = torch.zeros(shape, dtype=torch.float32, device=self.device)
if self.engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT:
if -1 in shape:
shape = [self.batch_size if d == -1 else d for d in shape]
if dtype == trt.float16:
buffer = torch.zeros(shape, dtype=torch.float16, device=self.device)
else:
buffer = torch.zeros(shape, dtype=torch.float32, device=self.device)
self.input_buffer = buffer
self.input_name = name
else:
if -1 in shape:
shape = [self.batch_size if d == -1 else d for d in shape]
if dtype == trt.float16:
buffer = torch.zeros(shape, dtype=torch.float16, device=self.device)
else:
buffer = torch.zeros(shape, dtype=torch.float32, device=self.device)
self.output_buffers.append(buffer)
if self.output_name is None:
self.output_name = name
@@ -215,8 +214,6 @@ class TensorRTEngine:
stream_handle = torch.cuda.current_stream(self.device).cuda_stream
self.context.set_optimization_profile_async(0, stream_handle)
self.batch_size = 1
def preprocess(self, frame: np.ndarray) -> torch.Tensor:
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, self.imgsz)
@@ -247,9 +244,6 @@ class TensorRTEngine:
self.input_name, input_tensor.contiguous().data_ptr()
)
input_shape = list(input_tensor.shape)
self.context.set_input_shape(self.input_name, input_shape)
torch.cuda.synchronize(self.stream)
self.context.execute_async_v3(self.stream.cuda_stream)
torch.cuda.synchronize(self.stream)
@@ -336,6 +330,10 @@ class Boxes:
self.orig_shape = orig_shape
self.is_track = is_track
@property
def ndim(self) -> int:
return self.data.ndim
@property
def xyxy(self):
if self.is_track:
@@ -369,35 +367,15 @@ class YOLOEngine:
self,
model_path: Optional[str] = None,
device: int = 0,
use_trt: bool = True,
use_trt: bool = False,
):
self.use_trt = False
self.onnx_engine = None
self.trt_engine = None
self.model = None
self.device = device
config = get_config()
if use_trt:
try:
self.trt_engine = TensorRTEngine(device=device)
self.trt_engine.warmup()
self.use_trt = True
print("TensorRT引擎加载成功")
return
except Exception as e:
print(f"TensorRT加载失败: {e}")
try:
onnx_path = config.model.onnx_path
if os.path.exists(onnx_path):
self.onnx_engine = ONNXEngine(device=device)
self.onnx_engine.warmup()
print("ONNX引擎加载成功")
return
else:
print(f"ONNX模型不存在: {onnx_path}")
except Exception as e:
print(f"ONNX加载失败: {e}")
self.config = config
try:
pt_path = model_path or config.model.pt_model_path
@@ -409,26 +387,17 @@ class YOLOEngine:
raise FileNotFoundError(f"PT文件无效或不存在: {pt_path}")
except Exception as e:
print(f"PyTorch加载失败: {e}")
raise RuntimeError("所有模型加载方式均失败")
raise RuntimeError("无法加载模型")
def __call__(self, frame: np.ndarray, **kwargs) -> List[Results]:
if self.use_trt and self.trt_engine:
if self.model is not None:
try:
return self.trt_engine.inference_single(frame)
return self.model(frame, imgsz=self.config.model.imgsz, conf=self.config.model.conf_threshold, iou=self.config.model.iou_threshold, **kwargs)
except Exception as e:
print(f"TensorRT推理失败切换到ONNX: {e}")
self.use_trt = False
if self.onnx_engine:
return self.onnx_engine.inference_single(frame)
elif self.model:
return self.model(frame, imgsz=get_config().model.imgsz, **kwargs)
else:
return []
elif self.onnx_engine:
return self.onnx_engine.inference_single(frame)
else:
results = self.model(frame, imgsz=get_config().model.imgsz, **kwargs)
return results
print(f"PyTorch推理失败: {e}")
print("警告: 模型不可用,返回空结果")
return []
def __del__(self):
if self.trt_engine:

View File

@@ -7,6 +7,7 @@ from collections import deque
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
import cv2
import numpy as np
from config import get_config
@@ -322,20 +323,23 @@ class InferencePipeline:
print("推理pipeline已停止")
def get_status(self) -> Dict[str, Any]:
return {
result = {
"running": self.running,
"camera_count": len(self.camera_threads),
"cameras": {
cid: {
"running": self.camera_stop_events[cid] is not None and not self.camera_stop_events[cid].is_set(),
"fps": self.get_camera_fps(cid),
"frame_time": self.camera_frame_times.get(cid).isoformat() if self.camera_frame_times.get(cid) else None,
}
for cid in self.camera_threads
},
"cameras": {},
"event_queue_size": len(self.event_queue),
}
for cid in self.camera_threads:
frame_time = self.camera_frame_times.get(cid)
result["cameras"][str(cid)] = {
"is_running": self.camera_stop_events[cid] is not None and not self.camera_stop_events[cid].is_set(),
"fps": self.get_camera_fps(cid),
"last_check_time": frame_time.isoformat() if frame_time else None,
}
return result
_pipeline: Optional[InferencePipeline] = None

View File

@@ -133,3 +133,109 @@
2026-01-21 13:18:55,795 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 13:18:55,809 - security_monitor - INFO - 数据库初始化完成
2026-01-21 13:19:08,492 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:01:21,015 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:01:21,257 - security_monitor - INFO - 系统已关闭
2026-01-21 14:03:48,547 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:03:48,563 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:04:01,197 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:04:20,191 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:04:20,414 - security_monitor - INFO - 系统已关闭
2026-01-21 14:05:48,342 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:05:48,355 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:06:00,984 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:07:24,065 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:07:24,222 - security_monitor - INFO - 系统已关闭
2026-01-21 14:08:10,073 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:08:10,088 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:08:22,715 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:09:05,249 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:09:05,480 - security_monitor - INFO - 系统已关闭
2026-01-21 14:11:29,491 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:11:29,513 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:11:42,900 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:14:04,974 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:14:05,161 - security_monitor - INFO - 系统已关闭
2026-01-21 14:14:41,203 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:14:41,220 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:14:54,380 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:15:30,975 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:15:31,180 - security_monitor - INFO - 系统已关闭
2026-01-21 14:16:24,472 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:16:24,485 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:16:37,611 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:17:01,178 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:17:01,420 - security_monitor - INFO - 系统已关闭
2026-01-21 14:18:00,008 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:18:00,022 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:18:13,126 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:18:13,128 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:18:21,683 - security_monitor - INFO - 系统已关闭
2026-01-21 14:20:04,985 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:20:04,999 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:20:18,151 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:21:24,782 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:21:24,927 - security_monitor - INFO - 系统已关闭
2026-01-21 14:22:48,064 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:22:48,078 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:23:01,270 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:23:13,509 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:23:13,628 - security_monitor - INFO - 系统已关闭
2026-01-21 14:24:16,374 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:24:16,386 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:24:29,425 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:24:42,751 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:24:42,846 - security_monitor - INFO - 系统已关闭
2026-01-21 14:25:25,549 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:25:25,562 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:25:38,636 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:26:02,871 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:26:03,124 - security_monitor - INFO - 系统已关闭
2026-01-21 14:26:45,885 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:26:45,899 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:26:59,042 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:27:26,873 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:27:26,980 - security_monitor - INFO - 系统已关闭
2026-01-21 14:31:38,376 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:31:38,390 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:31:51,594 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:32:17,471 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:32:17,536 - security_monitor - INFO - 系统已关闭
2026-01-21 14:32:53,841 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:32:53,855 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:33:06,946 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:34:30,645 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:34:30,818 - security_monitor - INFO - 系统已关闭
2026-01-21 14:38:24,673 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:38:24,685 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:38:37,183 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:39:04,359 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:39:04,486 - security_monitor - INFO - 系统已关闭
2026-01-21 14:40:07,246 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:40:07,259 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:40:19,745 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 14:40:33,742 - security_monitor - INFO - 正在关闭系统...
2026-01-21 14:40:33,863 - security_monitor - INFO - 系统已关闭
2026-01-21 14:41:27,191 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 14:41:27,205 - security_monitor - INFO - 数据库初始化完成
2026-01-21 14:41:39,701 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 15:03:14,674 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 15:03:14,688 - security_monitor - INFO - 数据库初始化完成
2026-01-21 15:03:27,230 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 15:06:28,976 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 15:06:28,990 - security_monitor - INFO - 数据库初始化完成
2026-01-21 15:06:41,537 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 15:06:41,539 - security_monitor - INFO - 正在关闭系统...
2026-01-21 15:06:49,686 - security_monitor - INFO - 系统已关闭
2026-01-21 15:07:27,870 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 15:07:27,884 - security_monitor - INFO - 数据库初始化完成
2026-01-21 15:07:40,380 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 15:07:58,160 - security_monitor - INFO - 正在关闭系统...
2026-01-21 15:07:58,299 - security_monitor - INFO - 系统已关闭
2026-01-21 15:08:28,521 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 15:08:28,533 - security_monitor - INFO - 数据库初始化完成
2026-01-21 15:08:41,019 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2
2026-01-21 15:09:16,894 - security_monitor - INFO - 正在关闭系统...
2026-01-21 15:09:17,139 - security_monitor - INFO - 系统已关闭
2026-01-21 15:09:41,042 - security_monitor - INFO - 启动安保异常行为识别系统
2026-01-21 15:09:41,055 - security_monitor - INFO - 数据库初始化完成
2026-01-21 15:09:53,555 - security_monitor - INFO - 推理Pipeline启动活跃摄像头数: 2

35
main.py
View File

@@ -11,15 +11,28 @@ os.environ["TENSORRT_DISABLE_MYELIN"] = "1"
import cv2
import numpy as np
from fastapi import FastAPI, HTTPException
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, StreamingResponse
from sqlalchemy.orm import Session
from prometheus_client import start_http_server
from db.models import get_db
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ultralytics.engine.results import Boxes as UltralyticsBoxes
def _patch_boxes_ndim():
if not hasattr(UltralyticsBoxes, 'ndim'):
@property
def ndim(self):
return self.data.ndim
UltralyticsBoxes.ndim = ndim
_patch_boxes_ndim()
from api.alarm import router as alarm_router
from api.camera import router as camera_router
from api.camera import router as camera_router, router2 as camera_status_router
from api.roi import router as roi_router
from api.sync import router as sync_router
from config import get_config, load_config
@@ -82,6 +95,7 @@ app.add_middleware(
)
app.include_router(camera_router)
app.include_router(camera_status_router)
app.include_router(roi_router)
app.include_router(alarm_router)
app.include_router(sync_router)
@@ -134,6 +148,23 @@ async def get_snapshot_base64(camera_id: int):
return {"image": base64.b64encode(buffer).decode("utf-8")}
@app.get("/api/alarms/{alarm_id}/snapshot")
async def get_alarm_snapshot(alarm_id: int, db: Session = Depends(get_db)):
from db.models import Alarm
alarm = db.query(Alarm).filter(Alarm.id == alarm_id).first()
if not alarm:
raise HTTPException(status_code=404, detail="告警不存在")
if not alarm.snapshot_path:
raise HTTPException(status_code=404, detail="该告警没有截图")
if not os.path.exists(alarm.snapshot_path):
raise HTTPException(status_code=404, detail="截图文件不存在")
return FileResponse(alarm.snapshot_path, media_type="image/jpeg")
@app.get("/api/camera/{camera_id}/detect")
async def get_detection_with_overlay(camera_id: int):
pipeline = get_pipeline()