Files
test/examples/two_sensor_pipeline.py
T
2026-06-08 17:52:48 +08:00

300 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
示例 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()