feat: 重构数据库schema分离空间与业务配置 - 新增algorithm_registry和roi_algo_bind表 - roi_configs简化为纯空间配置 - 新增AlgorithmInfo/ROIAlgoBind数据模型

This commit is contained in:
2026-02-03 14:26:52 +08:00
parent 1caba41625
commit fa0304aa47
5 changed files with 942 additions and 185 deletions

View File

@@ -215,7 +215,8 @@ class AlertInfo:
alert_id: str
camera_id: str
roi_id: str
alert_type: str
bind_id: Optional[str] = None
alert_type: str = "detection"
target_class: Optional[str] = None
confidence: Optional[float] = None
bbox: Optional[List[float]] = None
@@ -224,6 +225,7 @@ class AlertInfo:
level: AlertLevel = AlertLevel.MEDIUM
timestamp: Optional[str] = None
extra_data: Optional[Dict[str, Any]] = None
detections: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
@@ -231,6 +233,7 @@ class AlertInfo:
"alert_id": self.alert_id,
"camera_id": self.camera_id,
"roi_id": self.roi_id,
"bind_id": self.bind_id,
"alert_type": self.alert_type,
"target_class": self.target_class,
"confidence": self.confidence,
@@ -240,6 +243,7 @@ class AlertInfo:
"level": self.level.value if isinstance(self.level, AlertLevel) else self.level,
"timestamp": self.timestamp,
"extra_data": self.extra_data,
"detections": self.detections,
}
def to_json(self) -> str:
@@ -291,3 +295,184 @@ class ConfigVersion:
"description": self.description,
"affected_items": self.affected_items,
}
@dataclass
class AlgorithmInfo:
"""算法配置信息数据模型"""
algo_code: str
algo_name: str
target_class: str = "person"
param_schema: Optional[Dict[str, Any]] = None
description: Optional[str] = None
is_active: bool = True
created_at: Optional[str] = None
updated_at: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"algo_code": self.algo_code,
"algo_name": self.algo_name,
"target_class": self.target_class,
"param_schema": self.param_schema,
"description": self.description,
"is_active": self.is_active,
"created_at": self.created_at,
"updated_at": self.updated_at,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'AlgorithmInfo':
"""从字典创建实例"""
return cls(
algo_code=data.get("algo_code", ""),
algo_name=data.get("algo_name", ""),
target_class=data.get("target_class", "person"),
param_schema=data.get("param_schema"),
description=data.get("description"),
is_active=data.get("is_active", True),
created_at=data.get("created_at"),
updated_at=data.get("updated_at"),
)
@dataclass
class ROIAlgoBind:
"""ROI与算法绑定关系数据模型"""
bind_id: str
roi_id: str
algo_code: str
params: Dict[str, Any] = field(default_factory=dict)
priority: int = 0
enabled: bool = True
created_at: Optional[str] = None
updated_at: Optional[str] = None
algo_name: Optional[str] = None
target_class: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"bind_id": self.bind_id,
"roi_id": self.roi_id,
"algo_code": self.algo_code,
"params": self.params,
"priority": self.priority,
"enabled": self.enabled,
"created_at": self.created_at,
"updated_at": self.updated_at,
"algo_name": self.algo_name,
"target_class": self.target_class,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'ROIAlgoBind':
"""从字典创建实例"""
return cls(
bind_id=data.get("bind_id", ""),
roi_id=data.get("roi_id", ""),
algo_code=data.get("algo_code", ""),
params=data.get("params", {}),
priority=data.get("priority", 0),
enabled=data.get("enabled", True),
created_at=data.get("created_at"),
updated_at=data.get("updated_at"),
algo_name=data.get("algo_name"),
target_class=data.get("target_class"),
)
@dataclass
class ROIInfoNew:
"""ROI区域信息数据模型新版本包含绑定信息"""
roi_id: str
camera_id: str
roi_type: ROIType
coordinates: List[List[float]]
enabled: bool = True
priority: int = 0
extra_params: Optional[Dict[str, Any]] = None
bindings: List[ROIAlgoBind] = field(default_factory=list)
updated_at: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"roi_id": self.roi_id,
"camera_id": self.camera_id,
"roi_type": self.roi_type.value if isinstance(self.roi_type, ROIType) else self.roi_type,
"coordinates": self.coordinates,
"enabled": self.enabled,
"priority": self.priority,
"extra_params": self.extra_params,
"bindings": [b.to_dict() for b in self.bindings],
"updated_at": self.updated_at,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'ROIInfoNew':
"""从字典创建实例"""
roi_type_str = data.get("roi_type", "polygon")
roi_type = ROIType(roi_type_str) if roi_type_str in [e.value for e in ROIType] else ROIType.POLYGON
bindings = data.get("bindings", [])
if isinstance(bindings, list):
bindings = [ROIAlgoBind.from_dict(b) for b in bindings]
return cls(
roi_id=data.get("roi_id", ""),
camera_id=data.get("camera_id", ""),
roi_type=roi_type,
coordinates=data.get("coordinates", []),
enabled=data.get("enabled", True),
priority=data.get("priority", 0),
extra_params=data.get("extra_params"),
bindings=bindings,
updated_at=data.get("updated_at"),
)
def is_point_inside(self, point: List[float]) -> bool:
"""判断点是否在ROI区域内"""
if self.roi_type == ROIType.RECTANGLE:
return self._is_point_in_rectangle(point)
elif self.roi_type == ROIType.POLYGON:
return self._is_point_in_polygon(point)
return False
def _is_point_in_rectangle(self, point: List[float]) -> bool:
"""判断点是否在矩形区域内"""
if len(self.coordinates) < 2:
return False
x, y = point[0], point[1]
x1, y1 = self.coordinates[0]
x2, y2 = self.coordinates[1]
left = min(x1, x2)
right = max(x1, x2)
top = min(y1, y2)
bottom = max(y1, y2)
return left <= x <= right and top <= y <= bottom
def _is_point_in_polygon(self, point: List[float]) -> bool:
"""判断点是否在多边形区域内(射线法)"""
if len(self.coordinates) < 3:
return False
x, y = point[0], point[1]
n = len(self.coordinates)
inside = False
j = n - 1
for i in range(n):
xi, yi = self.coordinates[i]
xj, yj = self.coordinates[j]
if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi):
inside = not inside
j = i
return inside

View File

@@ -9,6 +9,7 @@ SQLite 数据库模块
"""
import os
import json
import sqlite3
import threading
import queue
@@ -39,7 +40,8 @@ class AlertRecord:
alert_id: str
camera_id: str
roi_id: str
alert_type: str
bind_id: Optional[str] = None # 关联 roi_algo_bind 表
alert_type: str = "detection"
target_class: Optional[str] = None
confidence: Optional[float] = None
bbox: Optional[List[float]] = None
@@ -49,6 +51,7 @@ class AlertRecord:
created_at: datetime = field(default_factory=datetime.now)
processed_at: Optional[datetime] = None
duration_minutes: Optional[float] = None
detections: Optional[str] = None # JSON格式的检测结果
class SQLiteManager:
@@ -108,12 +111,73 @@ class SQLiteManager:
cursor = self._conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS algorithm_registry (
algo_code TEXT PRIMARY KEY,
algo_name TEXT NOT NULL,
target_class TEXT DEFAULT 'person',
param_schema TEXT,
description TEXT,
is_active BOOLEAN DEFAULT 1,
created_at TEXT,
updated_at TEXT
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_algo_active
ON algorithm_registry(is_active)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS roi_configs (
roi_id TEXT PRIMARY KEY,
camera_id TEXT NOT NULL,
roi_type TEXT NOT NULL,
coordinates TEXT NOT NULL,
enabled BOOLEAN DEFAULT 1,
priority INTEGER DEFAULT 0,
extra_params TEXT,
updated_at TEXT
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_roi_camera
ON roi_configs(camera_id)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS roi_algo_bind (
bind_id TEXT PRIMARY KEY,
roi_id TEXT NOT NULL,
algo_code TEXT NOT NULL,
params TEXT NOT NULL,
priority INTEGER DEFAULT 0,
enabled BOOLEAN DEFAULT 1,
created_at TEXT,
updated_at TEXT,
FOREIGN KEY (roi_id) REFERENCES roi_configs(roi_id) ON DELETE CASCADE,
FOREIGN KEY (algo_code) REFERENCES algorithm_registry(algo_code) ON DELETE RESTRICT
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_bind_roi
ON roi_algo_bind(roi_id)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_bind_algo
ON roi_algo_bind(algo_code)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS alert_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alert_id TEXT UNIQUE NOT NULL,
camera_id TEXT NOT NULL,
roi_id TEXT NOT NULL,
bind_id TEXT,
alert_type TEXT NOT NULL,
target_class TEXT,
confidence REAL,
@@ -123,7 +187,9 @@ class SQLiteManager:
status TEXT DEFAULT 'pending',
created_at TEXT NOT NULL,
processed_at TEXT,
duration_minutes REAL
duration_minutes REAL,
detections TEXT,
FOREIGN KEY (bind_id) REFERENCES roi_algo_bind(bind_id) ON DELETE SET NULL
)
""")
@@ -139,6 +205,10 @@ class SQLiteManager:
CREATE INDEX IF NOT EXISTS idx_alert_status
ON alert_records(status)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_alert_bind
ON alert_records(bind_id)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS camera_configs (
@@ -148,31 +218,12 @@ class SQLiteManager:
status BOOLEAN DEFAULT 1,
enabled BOOLEAN DEFAULT 1,
location TEXT,
roi_group_id TEXT,
extra_params TEXT,
updated_at TEXT
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS roi_configs (
roi_id TEXT PRIMARY KEY,
camera_id TEXT NOT NULL,
roi_type TEXT NOT NULL,
coordinates TEXT NOT NULL,
algorithm_type TEXT NOT NULL,
alert_threshold INTEGER DEFAULT 3,
alert_cooldown INTEGER DEFAULT 300,
enabled BOOLEAN DEFAULT 1,
extra_params TEXT,
working_hours TEXT,
confirm_on_duty_sec INTEGER DEFAULT 10,
confirm_leave_sec INTEGER DEFAULT 10,
cooldown_sec INTEGER DEFAULT 300,
target_class TEXT DEFAULT 'person',
updated_at TEXT
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS config_update_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -186,6 +237,66 @@ class SQLiteManager:
""")
self._conn.commit()
self._init_default_algorithms()
def _init_default_algorithms(self):
"""初始化默认算法配置"""
try:
cursor = self._conn.cursor()
algorithms = [
{
'algo_code': 'leave_post',
'algo_name': '离岗检测',
'target_class': 'person',
'param_schema': json.dumps({
"confirm_on_duty_sec": {"type": "int", "default": 10, "min": 1},
"confirm_leave_sec": {"type": "int", "default": 10, "min": 1},
"cooldown_sec": {"type": "int", "default": 300, "min": 0},
"working_hours": {"type": "list", "default": []},
}),
'description': '检测人员是否在岗,支持工作时间段配置'
},
{
'algo_code': 'intrusion',
'algo_name': '周界入侵检测',
'target_class': 'person',
'param_schema': json.dumps({
"cooldown_seconds": {"type": "int", "default": 120, "min": 0},
"confirm_seconds": {"type": "int", "default": 5, "min": 1},
}),
'description': '检测人员进入指定区域,支持确认时间和冷却时间配置'
},
{
'algo_code': 'crowd_detection',
'algo_name': '人群聚集检测',
'target_class': 'person',
'param_schema': json.dumps({
"max_count": {"type": "int", "default": 10, "min": 1},
"cooldown_seconds": {"type": "int", "default": 300, "min": 0},
}),
'description': '检测区域内人员数量是否超过阈值'
},
]
for algo in algorithms:
cursor.execute("""
INSERT OR IGNORE INTO algorithm_registry (
algo_code, algo_name, target_class, param_schema, description,
is_active, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, 1, ?, ?)
""", (
algo['algo_code'], algo['algo_name'], algo['target_class'],
algo['param_schema'], algo['description'],
datetime.now().isoformat(), datetime.now().isoformat()
))
self._conn.commit()
logger.info(f"已初始化 {len(algorithms)} 个默认算法配置")
except Exception as e:
logger.error(f"初始化默认算法失败: {e}")
def _start_background_threads(self):
"""启动后台线程"""
@@ -242,15 +353,16 @@ class SQLiteManager:
for record in batch:
cursor.execute("""
INSERT OR REPLACE INTO alert_records (
alert_id, camera_id, roi_id, alert_type,
alert_id, camera_id, roi_id, bind_id, alert_type,
target_class, confidence, bbox, message,
image_path, status, created_at, processed_at,
duration_minutes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
duration_minutes, detections
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
record['alert_id'],
record['camera_id'],
record['roi_id'],
record.get('bind_id'),
record['alert_type'],
record.get('target_class'),
record.get('confidence'),
@@ -261,6 +373,7 @@ class SQLiteManager:
record['created_at'],
record.get('processed_at'),
record.get('duration_minutes'),
record.get('detections'),
))
self._conn.commit()
@@ -285,6 +398,7 @@ class SQLiteManager:
'alert_id': alert.alert_id,
'camera_id': alert.camera_id,
'roi_id': alert.roi_id,
'bind_id': alert.bind_id,
'alert_type': alert.alert_type,
'target_class': alert.target_class,
'confidence': alert.confidence,
@@ -295,6 +409,7 @@ class SQLiteManager:
'created_at': alert.created_at.isoformat(),
'processed_at': alert.processed_at.isoformat() if alert.processed_at else None,
'duration_minutes': alert.duration_minutes,
'detections': alert.detections,
}
self._write_queue.put(record)
@@ -427,14 +542,15 @@ class SQLiteManager:
cursor.execute("""
INSERT OR REPLACE INTO camera_configs (
camera_id, rtsp_url, camera_name, status, enabled,
location, extra_params, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
location, roi_group_id, extra_params, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
camera_id, rtsp_url,
kwargs.get('camera_name'),
kwargs.get('status', True),
kwargs.get('enabled', True),
kwargs.get('location'),
kwargs.get('roi_group_id'),
str(kwargs.get('extra_params')) if kwargs.get('extra_params') else None,
now
))
@@ -452,7 +568,7 @@ class SQLiteManager:
row = cursor.fetchone()
if row:
columns = ['camera_id', 'rtsp_url', 'camera_name', 'status',
'enabled', 'location', 'extra_params', 'updated_at']
'enabled', 'location', 'roi_group_id', 'extra_params', 'updated_at']
return dict(zip(columns, row))
return None
except Exception as e:
@@ -465,7 +581,7 @@ class SQLiteManager:
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM camera_configs ORDER BY camera_id")
columns = ['camera_id', 'rtsp_url', 'camera_name', 'status',
'enabled', 'location', 'extra_params', 'updated_at']
'enabled', 'location', 'roi_group_id', 'extra_params', 'updated_at']
return [dict(zip(columns, row)) for row in cursor.fetchall()]
except Exception as e:
logger.error(f"获取所有摄像头配置失败: {e}")
@@ -483,29 +599,21 @@ class SQLiteManager:
return False
def save_roi_config(self, roi_id: str, camera_id: str, roi_type: str,
coordinates: List, algorithm_type: str, **kwargs) -> bool:
"""保存ROI配置"""
coordinates: List, **kwargs) -> bool:
"""保存ROI配置(空间信息,不包含业务参数)"""
try:
cursor = self._conn.cursor()
now = datetime.now().isoformat()
cursor.execute("""
INSERT OR REPLACE INTO roi_configs (
roi_id, camera_id, roi_type, coordinates, algorithm_type,
alert_threshold, alert_cooldown, enabled, extra_params,
working_hours, confirm_on_duty_sec, confirm_leave_sec,
cooldown_sec, target_class, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
roi_id, camera_id, roi_type, coordinates,
enabled, priority, extra_params, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
roi_id, camera_id, roi_type, str(coordinates), algorithm_type,
kwargs.get('alert_threshold', 3),
kwargs.get('alert_cooldown', 300),
roi_id, camera_id, roi_type, str(coordinates),
kwargs.get('enabled', True),
kwargs.get('priority', 0),
str(kwargs.get('extra_params')) if kwargs.get('extra_params') else None,
str(kwargs.get('working_hours')) if kwargs.get('working_hours') else None,
kwargs.get('confirm_on_duty_sec', 10),
kwargs.get('confirm_leave_sec', 10),
kwargs.get('cooldown_sec', 300),
kwargs.get('target_class', 'person'),
now
))
self._conn.commit()
@@ -522,20 +630,12 @@ class SQLiteManager:
row = cursor.fetchone()
if row:
columns = ['roi_id', 'camera_id', 'roi_type', 'coordinates',
'algorithm_type', 'alert_threshold', 'alert_cooldown',
'enabled', 'extra_params', 'working_hours',
'confirm_on_duty_sec', 'confirm_leave_sec', 'cooldown_sec',
'target_class', 'updated_at']
'enabled', 'priority', 'extra_params', 'updated_at']
result = dict(zip(columns, row))
try:
result['coordinates'] = eval(result['coordinates'])
except:
pass
try:
if result.get('working_hours'):
result['working_hours'] = eval(result['working_hours'])
except:
pass
return result
return None
except Exception as e:
@@ -548,10 +648,7 @@ class SQLiteManager:
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM roi_configs WHERE camera_id = ?", (camera_id,))
columns = ['roi_id', 'camera_id', 'roi_type', 'coordinates',
'algorithm_type', 'alert_threshold', 'alert_cooldown',
'enabled', 'extra_params', 'working_hours',
'confirm_on_duty_sec', 'confirm_leave_sec', 'cooldown_sec',
'target_class', 'updated_at']
'enabled', 'priority', 'extra_params', 'updated_at']
results = []
for row in cursor.fetchall():
r = dict(zip(columns, row))
@@ -559,11 +656,6 @@ class SQLiteManager:
r['coordinates'] = eval(r['coordinates'])
except:
pass
try:
if r.get('working_hours'):
r['working_hours'] = eval(r['working_hours'])
except:
pass
results.append(r)
return results
except Exception as e:
@@ -576,10 +668,7 @@ class SQLiteManager:
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM roi_configs ORDER BY camera_id, roi_id")
columns = ['roi_id', 'camera_id', 'roi_type', 'coordinates',
'algorithm_type', 'alert_threshold', 'alert_cooldown',
'enabled', 'extra_params', 'working_hours',
'confirm_on_duty_sec', 'confirm_leave_sec', 'cooldown_sec',
'target_class', 'updated_at']
'enabled', 'priority', 'extra_params', 'updated_at']
results = []
for row in cursor.fetchall():
r = dict(zip(columns, row))
@@ -587,11 +676,6 @@ class SQLiteManager:
r['coordinates'] = eval(r['coordinates'])
except:
pass
try:
if r.get('working_hours'):
r['working_hours'] = eval(r['working_hours'])
except:
pass
results.append(r)
return results
except Exception as e:
@@ -609,6 +693,200 @@ class SQLiteManager:
logger.error(f"删除ROI配置失败: {e}")
return False
def save_algorithm(self, algo_code: str, algo_name: str, target_class: str = "person",
param_schema: Optional[str] = None, description: Optional[str] = None,
is_active: bool = True) -> bool:
"""保存算法配置"""
try:
cursor = self._conn.cursor()
now = datetime.now().isoformat()
cursor.execute("""
INSERT OR REPLACE INTO algorithm_registry (
algo_code, algo_name, target_class, param_schema, description,
is_active, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
algo_code, algo_name, target_class, param_schema, description,
is_active, now, now
))
self._conn.commit()
return True
except Exception as e:
logger.error(f"保存算法配置失败: {e}")
return False
def get_algorithm(self, algo_code: str) -> Optional[Dict[str, Any]]:
"""获取算法配置"""
try:
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM algorithm_registry WHERE algo_code = ?", (algo_code,))
row = cursor.fetchone()
if row:
columns = ['algo_code', 'algo_name', 'target_class', 'param_schema',
'description', 'is_active', 'created_at', 'updated_at']
result = dict(zip(columns, row))
try:
if result.get('param_schema'):
result['param_schema'] = json.loads(result['param_schema'])
except:
pass
return result
return None
except Exception as e:
logger.error(f"获取算法配置失败: {e}")
return None
def get_all_algorithms(self, active_only: bool = True) -> List[Dict[str, Any]]:
"""获取所有算法配置"""
try:
cursor = self._conn.cursor()
query = "SELECT * FROM algorithm_registry"
if active_only:
query += " WHERE is_active = 1"
cursor.execute(query)
columns = ['algo_code', 'algo_name', 'target_class', 'param_schema',
'description', 'is_active', 'created_at', 'updated_at']
results = []
for row in cursor.fetchall():
r = dict(zip(columns, row))
try:
if r.get('param_schema'):
r['param_schema'] = json.loads(r['param_schema'])
except:
pass
results.append(r)
return results
except Exception as e:
logger.error(f"获取所有算法配置失败: {e}")
return []
def save_roi_algo_bind(self, bind_id: str, roi_id: str, algo_code: str,
params: Dict[str, Any], priority: int = 0,
enabled: bool = True) -> bool:
"""保存ROI与算法的绑定关系"""
try:
cursor = self._conn.cursor()
now = datetime.now().isoformat()
cursor.execute("""
INSERT OR REPLACE INTO roi_algo_bind (
bind_id, roi_id, algo_code, params, priority,
enabled, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
bind_id, roi_id, algo_code, json.dumps(params),
priority, enabled, now, now
))
self._conn.commit()
return True
except Exception as e:
logger.error(f"保存ROI算法绑定失败: {e}")
return False
def get_roi_algo_bind(self, bind_id: str) -> Optional[Dict[str, Any]]:
"""获取ROI算法绑定配置"""
try:
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM roi_algo_bind WHERE bind_id = ?", (bind_id,))
row = cursor.fetchone()
if row:
columns = ['bind_id', 'roi_id', 'algo_code', 'params',
'priority', 'enabled', 'created_at', 'updated_at']
result = dict(zip(columns, row))
try:
if result.get('params'):
result['params'] = json.loads(result['params'])
except:
pass
return result
return None
except Exception as e:
logger.error(f"获取ROI算法绑定失败: {e}")
return None
def get_bindings_by_roi(self, roi_id: str) -> List[Dict[str, Any]]:
"""获取指定ROI的所有算法绑定"""
try:
cursor = self._conn.cursor()
cursor.execute("""
SELECT b.*, a.algo_name, a.target_class
FROM roi_algo_bind b
LEFT JOIN algorithm_registry a ON b.algo_code = a.algo_code
WHERE b.roi_id = ? AND b.enabled = 1
ORDER BY b.priority DESC
""", (roi_id,))
results = []
for row in cursor.fetchall():
result = dict(zip(
['bind_id', 'roi_id', 'algo_code', 'params', 'priority',
'enabled', 'created_at', 'updated_at', 'algo_name', 'target_class'],
row
))
try:
if result.get('params'):
result['params'] = json.loads(result['params'])
except:
pass
results.append(result)
return results
except Exception as e:
logger.error(f"获取ROI算法绑定失败: {e}")
return []
def get_bindings_by_camera(self, camera_id: str) -> List[Dict[str, Any]]:
"""获取指定摄像头的所有ROI算法绑定"""
try:
cursor = self._conn.cursor()
cursor.execute("""
SELECT b.*, a.algo_name, a.target_class, r.roi_type, r.coordinates
FROM roi_algo_bind b
LEFT JOIN algorithm_registry a ON b.algo_code = a.algo_code
LEFT JOIN roi_configs r ON b.roi_id = r.roi_id
WHERE r.camera_id = ? AND b.enabled = 1 AND r.enabled = 1
ORDER BY r.priority DESC, b.priority DESC
""", (camera_id,))
results = []
for row in cursor.fetchall():
result = dict(zip(
['bind_id', 'roi_id', 'algo_code', 'params', 'priority',
'enabled', 'created_at', 'updated_at', 'algo_name', 'target_class',
'roi_type', 'coordinates'],
row
))
try:
if result.get('params'):
result['params'] = json.loads(result['params'])
if result.get('coordinates'):
result['coordinates'] = eval(result['coordinates'])
except:
pass
results.append(result)
return results
except Exception as e:
logger.error(f"获取摄像头算法绑定失败: {e}")
return []
def delete_roi_algo_bind(self, bind_id: str) -> bool:
"""删除ROI算法绑定"""
try:
cursor = self._conn.cursor()
cursor.execute("DELETE FROM roi_algo_bind WHERE bind_id = ?", (bind_id,))
self._conn.commit()
return cursor.rowcount > 0
except Exception as e:
logger.error(f"删除ROI算法绑定失败: {e}")
return False
def delete_bindings_by_roi(self, roi_id: str) -> int:
"""删除指定ROI的所有算法绑定"""
try:
cursor = self._conn.cursor()
cursor.execute("DELETE FROM roi_algo_bind WHERE roi_id = ?", (roi_id,))
self._conn.commit()
return cursor.rowcount
except Exception as e:
logger.error(f"删除ROI算法绑定失败: {e}")
return 0
def log_config_update(
self,
config_type: str,