0.3.1 stable release

This commit is contained in:
ykcheungab
2026-04-17 09:58:30 +08:00
parent 7a49fd76e6
commit fdc4ffd25f
11 changed files with 208 additions and 211 deletions
+65 -60
View File
@@ -1,22 +1,24 @@
"""
示例 4: 多传感器并行处理(多进程模式 - 不使用 MultiSensor)
示例 2:双传感器并行处理(多进程)
本示例演示如何使用 multiprocessing 同时运行两个传感器:
- 每个传感器运行在独立的进程中
- 每个传感器运行在独立进程中
- 每个进程拥有独立的 CUDA 上下文
- 使用 multiprocessing.Process 和 Queue 进行进程间通信
- 通过 Process 和 Queue 在主进程中汇总结果并显示
架构说明:
进程 1 (传感器 0): 采集 → 光流 → HHD → 接触检测 → 队列
进程 2 (传感器 1): 采集 → 光流 → HHD → 接触检测 → 队列
进程 1(传感器 0):采集 → 光流 → HHD → 接触检测 → 队列
进程 2(传感器 1):采集 → 光流 → HHD → 接触检测 → 队列
↓
主进程: 显示 ← ───────────────┘
主进程: 显示 ← ───────────────┘
适用场景:多指触觉传感器、多视角采集、需要 GPU 加速的多路处理
适用场景:多指触觉传感器、多视角采集,以及需要 GPU 加速的多路处理。
注意事项:
- Windows 下必须在 if __name__ == '__main__' 中调用 mp.freeze_support()
- 每个传感器进程会占用独立的 GPU 显存
- 每个传感器进程都会占用独立的 GPU 显存
SDK version: 0.3.1
"""
import os
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
@@ -26,20 +28,20 @@ import numpy as np
import multiprocessing as mp
from multiprocessing import Queue, Process
import time
import argparse
def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_event, target_fps=None):
def sensor_worker(sensor_id, vid_src, config_name, result_queue, stop_event, target_fps=None):
"""
传感器工作进程函数
传感器工作进程。
Args:
sensor_id: 传感器编号
vid_src: 视频源(文件路径或摄像头索引)
config_name: 配置名称
cal_path: 标定路径
result_queue: 结果队列,用于向主进程发送数据
stop_event: 停止事件,用于通知工作进程退出
target_fps: 目标帧率;若设置则用定时器限速,使多传感器 FPS 接近一致
target_fps: 目标帧率;设置后将对处理循环限速,使多路传感器 FPS 更接近
"""
try:
# 在每个进程中创建独立的传感器实例
@@ -48,27 +50,26 @@ def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_
isstitch=True,
cuda=True,
verbose=False, # 减少子进程输出
config_name=config_name,
cal_path=cal_path
config_name=config_name
)
print(f"传感器 {sensor_id} 已启动" + (f" (target_fps={target_fps})" if target_fps else ""))
print(f"传感器 {sensor_id} 已启动" + (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: 获取图像
# 步骤 1:获取图像
sensor.get_img()
# 步骤2: 计算形变场
# 步骤 2:计算形变场
sensor.compute_deformation()
# 步骤3: 计算触碰点
# 步骤 3:计算接触区域(如需要可取消注释)
# sensor.compute_contact()
# 步骤4: 读取信息
# 步骤 4:读取结果
fps, flow, vnormal, img, contour, centroid, depth_map = sensor.read_info(
sensor.info.FPS,
sensor.info.VRAW,
@@ -79,7 +80,7 @@ def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_
sensor.info.DEPTH
)
# 步骤5: 准备数据字典(确保所有数据都是可序列化的)
# 步骤 5:整理结果字典(确保数据可序列化)
data = {
'sensor_id': sensor_id,
'fps': fps,
@@ -92,100 +93,104 @@ def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_
'timestamp': time.time()
}
# 步骤6: 发送数据到主进程(非阻塞)
# 步骤 6:将结果发送到主进程(非阻塞)
try:
result_queue.put_nowait(data)
except Exception:
# 如果队列已满,跳过这一帧
# 如果队列已满,则丢弃当前帧,避免阻塞工作进程
pass
except Exception as e:
print(f"传感器 {sensor_id} 处理错误: {e}")
print(f"传感器 {sensor_id} 处理失败:{e}")
time.sleep(0.1)
# 步骤7: 按 target_fps 限速,使多传感器 FPS 接近
# 步骤 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} 已停止")
print(f"传感器 {sensor_id} 已停止。")
except Exception as e:
print(f"传感器 {sensor_id} 初始化失败: {e}")
print(f"传感器 {sensor_id} 初始化失败:{e}")
result_queue.put({'sensor_id': sensor_id, 'error': str(e)})
def main():
# =========================================================================
# 1. 配置多个传感器
# 1. 配置多路传感器
# =========================================================================
# 目标帧率:设为正数时,两路传感器会按此帧率限速,使 FPS 接近一致;设为 None 则不限速
# 目标帧率:设为正数时,两路传感器会按该帧率限速;设为 None 则不限速
parser = argparse.ArgumentParser()
parser.add_argument("--video1","-v1", type=str, default="0", help="视频文件路径")
parser.add_argument("--video2","-v2", type=str, default="0", help="视频文件路径")
args = parser.parse_args()
target_fps = 30
num_sensors = 2
sensor_configs = [
{
'sensor_id': 0,
'vid_src': 2,
'config_name': './config/prototype_of3_720p.json',
'cal_path': './config/ddjx01.npy'
'vid_src': args.video1,
'config_name': './config/ddjx01.json'
},
{
'sensor_id': 1,
'vid_src': 4, # 可以改为不同的视频源或摄像头索引
'config_name': './config/prototype_of3_720p.json',
'cal_path': './config/ddjx01.npy'
'vid_src': args.video2, # 可替换为其他视频源或摄像头索引
'config_name': './config/ddjx01.json'
},
]
# =========================================================================
# 2. 创建进程间通信对象
# =========================================================================
# 增大队列大小以缓冲更多帧,避免因主进程处理延迟导致的丢帧
# 适当增大队列容量,以缓冲更多帧并减轻主进程偶发延迟带来的丢帧
result_queues = [Queue(maxsize=30) for _ in sensor_configs]
stop_events = [mp.Event() for _ in sensor_configs]
# =========================================================================
# 3. 启动传感器工作进程
# 3. 启动工作进程
# =========================================================================
processes = []
for i, config in enumerate(sensor_configs):
p = Process(
target=sensor_worker,
args=(i, config['vid_src'], config['config_name'], config['cal_path'],
args=(i, config['vid_src'], config['config_name'],
result_queues[i], stop_events[i], target_fps)
)
p.start()
processes.append(p)
print(f"\n已启动 {len(processes)} 个传感器进程")
print("按 'q' 退出程序")
print(f"\n已启动 {len(processes)} 个传感器进程。")
print("按键说明:'q' 退出程序。")
# 用于存储每个传感器的最新数据
# 保存每个传感器的最新一帧数据
latest_data = [None] * len(sensor_configs)
# 用于控制打印频率,减少I/O开销
# 控制打印频率,减少 I/O 开销
print_counter = 0
print_interval = 30 # 每30帧打印一次,进一步减少I/O
print_interval = 30 # 每 30 帧打印一次
# 用于控制按键检查频率
# 控制按键检查频率
key_check_counter = 0
key_check_interval = 5 # 每5帧检查一次按键
key_check_interval = 5 # 每 5 帧检查一次按键
try:
# =====================================================================
# 4. 主循环:从队列获取数据并显示
# 4. 主循环:从队列获取结果并显示
# =====================================================================
while True:
# 从所有队列中快速获取最新数据(高效清空队列,只保留最新帧)
# 从所有队列中快速获取最新数据,只保留最后一帧
for i, queue in enumerate(result_queues):
# 快速清空队列,只保留最后一帧
# 清空队列,仅保留最后一帧
data = None
# 一次性获取所有可用数据,只保留最后一个
# 读取所有可用数据,只保留最新结果
while True:
try:
data = queue.get_nowait()
@@ -195,14 +200,14 @@ def main():
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'],
@@ -211,7 +216,7 @@ def main():
grid_spacing=10,
arrow_scale=1.0
)
cv2.imshow(f"Deformation {sid}", arrows)
cv2.imshow(f"传感器 {sid} - 形变矢量场", arrows)
# # 显示触碰点和区域
# if data['img'] is not None:
@@ -233,33 +238,33 @@ def main():
# cv2.imshow(f"Depth Map {sid}",
# cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET))
# 降低打印频率以减少I/O开销(在主循环级别控制)
# 降低打印频率,减少主循环中的 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(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退出程序...")
print("\n正在退出示例程序...")
break
key_check_counter = 0
finally:
# =====================================================================
# 5. 清理资源
# 5. 释放资源
# =====================================================================
# 通知所有工作进程停止
for event in stop_events:
event.set()
# 等待所有进程结束
# 等待所有工作进程结束
for p in processes:
p.join(timeout=2)
if p.is_alive():
@@ -267,10 +272,10 @@ def main():
p.join()
cv2.destroyAllWindows()
print("所有传感器进程已停止")
print("所有传感器进程已停止。")
if __name__ == '__main__':
# Windows 下多进程必需的初始化
# Windows 下运行多进程示例所需的初始化
mp.freeze_support()
main()