""" 示例 2:双传感器并行处理(多进程) 本示例演示如何使用 multiprocessing 同时运行两个传感器: - 每个传感器运行在独立进程中 - 每个进程拥有独立的 CUDA 上下文 - 通过 Process 和 Queue 在主进程中汇总结果并显示 架构说明: 进程 1(传感器 0):采集 → 光流 → HHD → 接触检测 → 队列 进程 2(传感器 1):采集 → 光流 → HHD → 接触检测 → 队列 ↓ 主进程: 显示 ← ───────────────┘ 适用场景:多指触觉传感器、多视角采集,以及需要 GPU 加速的多路处理。 注意事项: - Windows 下必须在 if __name__ == '__main__' 中调用 mp.freeze_support() - 每个传感器进程都会占用独立的 GPU 显存 SDK version: 0.3.1 """ import os os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" import cv2 import orisys import numpy as np import multiprocessing as mp from multiprocessing import Queue, Process import time import argparse def parse_video_source(value): """Convert camera index strings like "0" to int; keep paths as strings.""" if isinstance(value, str): value = value.strip() if value.isdigit(): return int(value) return value def sensor_worker(sensor_id, vid_src, config_name, rotation_config_path, result_queue, stop_event, target_fps=None): """ 传感器工作进程。 Args: sensor_id: 传感器编号 vid_src: 视频源(文件路径或摄像头索引) config_name: 配置名称 result_queue: 结果队列,用于向主进程发送数据 stop_event: 停止事件,用于通知工作进程退出 target_fps: 目标帧率;设置后将对处理循环限速,使多路传感器 FPS 更接近 """ try: # 在每个进程中创建独立的传感器实例 sensor = orisys.Sensor( vid_src, isstitch=True, cuda=True, verbose=False, # 减少子进程输出 config_name=config_name, rotation_config_path=rotation_config_path ) print( f"传感器 {sensor_id} 已启动,视频源={vid_src!r}({type(vid_src).__name__})," f"方向配置={rotation_config_path}" + (f"(目标帧率:{target_fps} FPS)" if target_fps else "") ) frame_interval = 1.0 / target_fps if target_fps and target_fps > 0 else 0.0 while not stop_event.is_set(): t_start = time.perf_counter() try: # 步骤 1:获取图像 sensor.get_img() # 步骤 2:计算形变场 sensor.compute_deformation() # 步骤 3:计算接触区域(如需要可取消注释) # sensor.compute_contact() # 步骤 4:读取结果 fps, flow, vnormal, img, contour, centroid, depth_map = sensor.read_info( sensor.info.FPS, sensor.info.VRAW, sensor.info.VNORMAL, sensor.info.IMG, sensor.info.CONTOUR, sensor.info.CENTROID, sensor.info.DEPTH ) # 步骤 5:整理结果字典(确保数据可序列化) data = { 'sensor_id': sensor_id, 'fps': fps, 'flow': flow.copy() if flow is not None else None, 'vnormal': vnormal.copy() if vnormal is not None else None, 'img': img.copy() if img is not None else None, 'contour': contour, 'centroid': centroid, 'depth_map': depth_map.copy() if depth_map is not None else None, 'timestamp': time.time() } # 步骤 6:将结果发送到主进程(非阻塞) try: result_queue.put_nowait(data) except Exception: # 如果队列已满,则丢弃当前帧,避免阻塞工作进程 pass except Exception as e: print(f"传感器 {sensor_id} 处理失败:{e}") time.sleep(0.1) # 步骤 7:按 target_fps 限速,使多路传感器 FPS 更接近 if frame_interval > 0: elapsed = time.perf_counter() - t_start sleep_time = frame_interval - elapsed if sleep_time > 0.001: # 至少 1ms 才 sleep,避免无意义调度 time.sleep(sleep_time) # 释放资源 sensor.disconnect() print(f"传感器 {sensor_id} 已停止。") except Exception as e: print(f"传感器 {sensor_id} 初始化失败:{e}") result_queue.put({'sensor_id': sensor_id, 'error': str(e)}) def main(): # ========================================================================= # 1. 配置多路传感器 # ========================================================================= # 目标帧率:设为正数时,两路传感器会按该帧率限速;设为 None 则不限速 parser = argparse.ArgumentParser() parser.add_argument("--video1","-v1", type=str, default="0", help="视频源:摄像头编号或视频文件路径") parser.add_argument("--video2","-v2", type=str, default="0", help="视频源:摄像头编号或视频文件路径") parser.add_argument("--rotation1", "-r1", type=str, default="./config/rotation_config_0.json", help="传感器 0 的方向配置") parser.add_argument("--rotation2", "-r2", type=str, default="./config/rotation_config_1.json", help="传感器 1 的方向配置") args = parser.parse_args() target_fps = 30 num_sensors = 2 sensor_configs = [ { 'sensor_id': 0, 'vid_src': parse_video_source(args.video1), 'config_name': './config/ddjx01.json', 'rotation_config_path': args.rotation1 }, { 'sensor_id': 1, 'vid_src': parse_video_source(args.video2), # 可替换为其他视频源或摄像头索引 'config_name': './config/ddjx01.json', 'rotation_config_path': args.rotation2 }, ] # ========================================================================= # 2. 创建进程间通信对象 # ========================================================================= # 适当增大队列容量,以缓冲更多帧并减轻主进程偶发延迟带来的丢帧 result_queues = [Queue(maxsize=30) for _ in sensor_configs] stop_events = [mp.Event() for _ in sensor_configs] # ========================================================================= # 3. 启动工作进程 # ========================================================================= processes = [] for i, config in enumerate(sensor_configs): p = Process( target=sensor_worker, args=(i, config['vid_src'], config['config_name'], config['rotation_config_path'], result_queues[i], stop_events[i], target_fps) ) p.start() processes.append(p) print(f"\n已启动 {len(processes)} 个传感器进程。") print("按键说明:'q' 退出程序。") # 保存每个传感器的最新一帧数据 latest_data = [None] * len(sensor_configs) # 控制打印频率,减少 I/O 开销 print_counter = 0 print_interval = 30 # 每 30 帧打印一次 # 控制按键检查频率 key_check_counter = 0 key_check_interval = 5 # 每 5 帧检查一次按键 try: # ===================================================================== # 4. 主循环:从队列获取结果并显示 # ===================================================================== while True: # 从所有队列中快速获取最新数据,只保留最后一帧 for i, queue in enumerate(result_queues): # 清空队列,仅保留最后一帧 data = None # 读取所有可用数据,只保留最新结果 while True: try: data = queue.get_nowait() except: break if data is not None: latest_data[i] = data # 显示所有传感器的最新结果 for data in latest_data: if data is None or 'error' in data: continue sid = data['sensor_id'] # 显示形变矢量场 if data['img'] is not None and data['flow'] is not None: arrows = orisys.util.draw_arrows( data['img'], data['flow'], threshold=5, grid_spacing=10, arrow_scale=1.0 ) cv2.imshow(f"传感器 {sid} - 形变矢量场".encode("gbk"), arrows) # # 显示触碰点和区域 # if data['img'] is not None: # image_with_foe = orisys.util.draw_contact( # data['img'], # data['contour'], # data['centroid'] # ) # cv2.imshow(f"Contact Centroid {sid}", image_with_foe) # # 显示深度图 # if data['depth_map'] is not None and data['depth_map'].size > 0: # div_abs = data['depth_map'] # if div_abs.max() > div_abs.min(): # div_normalized = ((div_abs - div_abs.min()) / # (div_abs.max() - div_abs.min()) * 255).astype(np.uint8) # else: # div_normalized = np.zeros_like(div_abs, dtype=np.uint8) # cv2.imshow(f"Depth Map {sid}", # cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET)) # 降低打印频率,减少主循环中的 I/O 开销 print_counter += 1 if print_counter >= print_interval: for data in latest_data: if data is not None and 'error' not in data: sid = data['sensor_id'] print(f"传感器 {sid}:FPS={data['fps']:5.1f}") print_counter = 0 # 降低按键检查频率,减少额外开销 key_check_counter += 1 if key_check_counter >= key_check_interval: key = cv2.waitKey(1) & 0xFF if key == ord('q'): print("\n正在退出示例程序...") break key_check_counter = 0 finally: # ===================================================================== # 5. 释放资源 # ===================================================================== # 通知所有工作进程停止 for event in stop_events: event.set() # 等待所有工作进程结束 for p in processes: p.join(timeout=2) if p.is_alive(): p.terminate() p.join() cv2.destroyAllWindows() print("所有传感器进程已停止。") if __name__ == '__main__': # Windows 下运行多进程示例所需的初始化 mp.freeze_support() main()