diff --git a/main.py b/main.py index 4ce769d..fb7e39f 100644 --- a/main.py +++ b/main.py @@ -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):