refactor(main): 优化摄像头视频流启动逻辑 - 只启动有ROI配置的摄像头

问题:
- 旧逻辑启动所有数据库中的摄像头视频流(7个)
- 实际只有3个摄像头有ROI配置
- 浪费带宽和计算资源

解决方案:
1. 新增 _get_camera_ids_with_roi() 方法
   - 从ROI配置中提取有配置的摄像头ID集合
   - 返回去重后的摄像头ID set

2. 新增 _get_camera_config_by_id() 方法
   - 根据ID获取摄像头配置对象
   - 提高代码复用性和可维护性

3. 重构 _load_cameras() 方法
   - 只启动有ROI配置的摄像头
   - 添加详细的成功/失败统计
   - 改进日志信息,便于排查问题

4. 重构 _reload_cameras() 方法
   - 配置更新时只添加有ROI且未启动的摄像头
   - 使用集合运算提高性能
   - 统一错误处理逻辑

5. 新增 _cleanup_cameras_without_roi() 方法
   - 清理已启动但没有ROI的摄像头流
   - 当用户删除所有ROI时自动停止视频流
   - 节省系统资源

6. 更新配置回调逻辑
   - 先清理无ROI的摄像头
   - 再添加新增的有ROI的摄像头
   - 保证视频流与ROI配置同步

优势:
- 资源利用率提升:只启动必要的视频流
- 代码模块化:提取公共逻辑,提高复用性
- 可维护性强:清晰的注释和文档字符串
- 日志完善:详细的统计信息便于监控

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 09:18:58 +08:00
parent 6d408386bc
commit 4153efaae9

186
main.py
View File

@@ -98,7 +98,9 @@ class EdgeInferenceService:
def _on_config_update(topic, data):
if self._algorithm_manager:
self._algorithm_manager.reload_all_algorithms()
# 配置更新后动态加载新摄像头流
# 配置更新后清理无ROI的摄像头流
self._cleanup_cameras_without_roi()
# 配置更新后动态加载新摄像头流有ROI的
self._reload_cameras()
self._config_manager.register_callback("config_update", _on_config_update)
self._logger.info("配置管理器初始化成功")
@@ -264,11 +266,73 @@ class EdgeInferenceService:
self._logger.info("所有组件初始化完成")
def _load_cameras(self):
"""加载摄像头配置"""
cameras = self._config_manager.get_cameras()
def _get_camera_ids_with_roi(self) -> set:
"""获取有ROI配置的摄像头ID集合
Returns:
set: 有ROI配置的摄像头ID集合
"""
try:
all_rois = self._config_manager.get_roi_configs(force_refresh=True)
camera_ids = {roi.camera_id for roi in all_rois if roi.camera_id}
return camera_ids
except Exception as e:
self._logger.error(f"获取ROI配置失败: {e}")
return set()
def _get_camera_config_by_id(self, camera_id: str):
"""根据摄像头ID获取配置
Args:
camera_id: 摄像头ID
Returns:
CameraInfoModel or None: 摄像头配置对象未找到返回None
"""
try:
cameras = self._config_manager.get_cameras()
for camera in cameras:
if camera.camera_id == camera_id:
return camera
return None
except Exception as e:
self._logger.error(f"获取摄像头配置失败 {camera_id}: {e}")
return None
def _load_cameras(self):
"""加载摄像头配置 - 只启动有ROI配置的摄像头
逻辑:
1. 获取所有ROI配置提取关联的摄像头ID
2. 只为有ROI配置的摄像头启动视频流
3. 避免启动无用的视频流,节省资源
"""
# 获取有ROI配置的摄像头ID
camera_ids_with_roi = self._get_camera_ids_with_roi()
if not camera_ids_with_roi:
self._logger.warning("未找到任何ROI配置不启动视频流")
return
self._logger.info(f"检测到 {len(camera_ids_with_roi)} 个摄像头有ROI配置")
# 只启动有ROI的摄像头
success_count = 0
failed_count = 0
for camera_id in camera_ids_with_roi:
camera = self._get_camera_config_by_id(camera_id)
if not camera:
self._logger.warning(f"摄像头 {camera_id} 有ROI但未找到配置跳过")
failed_count += 1
continue
if not camera.rtsp_url:
self._logger.warning(f"摄像头 {camera_id} 缺少 rtsp_url跳过")
failed_count += 1
continue
for camera in cameras:
try:
self._stream_manager.add_stream(
camera_id=camera.camera_id,
@@ -277,23 +341,64 @@ class EdgeInferenceService:
on_frame_callback=self._create_frame_callback(camera.camera_id)
)
self._logger.info(f"已添加摄像头: {camera.camera_id}")
success_count += 1
except Exception as e:
self._logger.error(f"添加摄像头失败 {camera.camera_id}: {e}")
failed_count += 1
self._logger.info(
f"摄像头加载完成 - 成功: {success_count}, 失败: {failed_count}, "
f"总计: {len(camera_ids_with_roi)}"
)
def _reload_cameras(self):
"""配置更新后动态加载新摄像头(不重复添加已有的)"""
"""配置更新后动态加载新摄像头 - 只添加有ROI配置且未启动的摄像头
逻辑:
1. 获取当前有ROI配置的摄像头ID
2. 过滤出尚未启动的摄像头
3. 动态添加并启动新摄像头流
"""
if not self._stream_manager or not self._config_manager:
return
try:
cameras = self._config_manager.get_cameras(force_refresh=True)
existing = set(self._stream_manager._streams.keys())
added = 0
for camera in cameras:
if camera.camera_id in existing:
# 获取有ROI配置的摄像头ID
camera_ids_with_roi = self._get_camera_ids_with_roi()
if not camera_ids_with_roi:
self._logger.debug("配置更新后未找到有ROI配置的摄像头")
return
# 获取已启动的摄像头
existing_streams = set(self._stream_manager._streams.keys())
# 找出需要新增的摄像头有ROI但未启动
new_camera_ids = camera_ids_with_roi - existing_streams
if not new_camera_ids:
self._logger.debug("配置更新后无需新增摄像头流")
return
self._logger.info(f"检测到 {len(new_camera_ids)} 个新摄像头需要启动")
# 动态添加新摄像头
success_count = 0
failed_count = 0
for camera_id in new_camera_ids:
camera = self._get_camera_config_by_id(camera_id)
if not camera:
self._logger.warning(f"摄像头 {camera_id} 有ROI但未找到配置跳过")
failed_count += 1
continue
if not camera.rtsp_url:
self._logger.warning(f"摄像头 {camera.camera_id} rtsp_url跳过")
self._logger.warning(f"摄像头 {camera_id} 缺少 rtsp_url跳过")
failed_count += 1
continue
try:
self._stream_manager.add_stream(
camera_id=camera.camera_id,
@@ -301,16 +406,65 @@ class EdgeInferenceService:
target_fps=self._settings.video_stream.default_fps,
on_frame_callback=self._create_frame_callback(camera.camera_id)
)
# 立即启动新添加的流
self._stream_manager._streams[camera.camera_id].start()
added += 1
success_count += 1
self._logger.info(f"动态添加并启动摄像头: {camera.camera_id}")
except Exception as e:
self._logger.error(f"动态添加摄像头失败 {camera.camera_id}: {e}")
if added > 0:
self._logger.info(f"配置更新后新增 {added} 个摄像头流")
failed_count += 1
if success_count > 0:
self._logger.info(
f"配置更新后新增摄像头完成 - 成功: {success_count}, 失败: {failed_count}"
)
except Exception as e:
self._logger.error(f"动态加载摄像头失败: {e}")
def _cleanup_cameras_without_roi(self):
"""清理没有ROI配置的摄像头视频流
逻辑:
1. 获取当前有ROI配置的摄像头ID
2. 找出已启动但没有ROI的摄像头
3. 停止并移除这些摄像头的视频流,节省资源
"""
if not self._stream_manager or not self._config_manager:
return
try:
# 获取有ROI配置的摄像头ID
camera_ids_with_roi = self._get_camera_ids_with_roi()
# 获取已启动的摄像头
running_streams = set(self._stream_manager._streams.keys())
# 找出需要停止的摄像头已启动但没有ROI
cameras_to_remove = running_streams - camera_ids_with_roi
if not cameras_to_remove:
self._logger.debug("无需清理摄像头视频流")
return
self._logger.info(f"检测到 {len(cameras_to_remove)} 个摄像头无ROI配置需要停止")
# 停止并移除这些摄像头的视频流
removed_count = 0
for camera_id in cameras_to_remove:
try:
self._stream_manager.remove_stream(camera_id)
removed_count += 1
self._logger.info(f"已停止并移除摄像头流: {camera_id} (无ROI配置)")
except Exception as e:
self._logger.error(f"移除摄像头流失败 {camera_id}: {e}")
if removed_count > 0:
self._logger.info(f"清理完成 - 已移除 {removed_count} 个无ROI的摄像头流")
except Exception as e:
self._logger.error(f"清理摄像头流失败: {e}")
def _create_frame_callback(self, camera_id: str):
"""创建帧处理回调"""
def callback(frame):