0.3.1 stable release
This commit is contained in:
@@ -73,7 +73,7 @@ conda install conda-forge::pyvkfft
|
|||||||
|
|
||||||
* **vid_src** (`int | str`, 必填): 传感器 ID、相机编号或视频路径。默认为 `0`。
|
* **vid_src** (`int | str`, 必填): 传感器 ID、相机编号或视频路径。默认为 `0`。
|
||||||
* **config_name** (`str`,可选): 传感器配置`json`文件路径,用于加载对应的参数配置,默认为 `./config/ddjx01`。
|
* **config_name** (`str`,可选): 传感器配置`json`文件路径,用于加载对应的参数配置,默认为 `./config/ddjx01`。
|
||||||
* **cal_path** (`str`,可选): 复眼拼接校准文件路径,默认为 `./config/cal.npy`。有多个传感器同时运行时建议传感器类创建时建议填写,避免校准文件覆写。在首次运行时程序会在找不到文件时自动创建。
|
* * **cal_path** (`str`,可选,已弃用): 该参数已弃用,当前版本中不再使用,传入后也会被忽略。为兼容旧代码暂时保留。: 复眼拼接校准文件路径,默认为 `./config/cal.npy`。有多个传感器同时运行时建议传感器类创建时建议填写,避免校准文件覆写。在首次运行时程序会在找不到文件时自动创建。
|
||||||
* **backend** (`str`,可选): 光流场解耦计算后端,默认为 `auto`,可选`cupy`,`opencl`,`cpu`。
|
* **backend** (`str`,可选): 光流场解耦计算后端,默认为 `auto`,可选`cupy`,`opencl`,`cpu`。
|
||||||
* **verbose** (`bool`,可选): 是否输出初始化及运行过程中的详细日志信息,默认为 `False`。
|
* **verbose** (`bool`,可选): 是否输出初始化及运行过程中的详细日志信息,默认为 `False`。
|
||||||
|
|
||||||
@@ -99,12 +99,12 @@ sensor = Sensor("/data/data.mp4")
|
|||||||
|
|
||||||
采集并预处理一帧图像并存到`Sensor` 类别中。
|
采集并预处理一帧图像并存到`Sensor` 类别中。
|
||||||
|
|
||||||
|
|
||||||
### 返回
|
### 返回
|
||||||
* `np.ndarray`:处理后的BGR格式图像(`uint8`,尺寸为json文件中`resolution_stitch`对应分辨率)。
|
* `bool`:是否成功读取图像。
|
||||||
* `None`:当输入源读取失败(例如视频结束、摄像头断开)时返回。
|
|
||||||
|
|
||||||
### 状态更新
|
### 状态更新
|
||||||
* 成功时更新`sensor`类里面的图像。
|
* 成功时更新`sensor`类里面的`sensor.img`。处理后的BGR格式图像(`uint8`,尺寸为json文件中`resolution_stitch`对应分辨率)。
|
||||||
### 示例
|
### 示例
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -123,6 +123,8 @@ sensor.get_img()
|
|||||||
### 输入参数
|
### 输入参数
|
||||||
* **check_motion**(`bool`,可选): 为 `True` 时先根据光流判断是否有运动,有运动才做 HHD 分解;为 `False` 时每帧都做 HHD。默认 `True`。
|
* **check_motion**(`bool`,可选): 为 `True` 时先根据光流判断是否有运动,有运动才做 HHD 分解;为 `False` 时每帧都做 HHD。默认 `True`。
|
||||||
* **threshold**(`float`,可选): **check_motion** 功能的阈值,默认为`6`。
|
* **threshold**(`float`,可选): **check_motion** 功能的阈值,默认为`6`。
|
||||||
|
* **tail_frames** (`int`,可选): 光流变化低于阈值后继续解算多少帧,确保传感器回复,,默认为 `10`.
|
||||||
|
|
||||||
### 返回
|
### 返回
|
||||||
|
|
||||||
* `bool`:是否接触到物体,如果**check_motion**为`False`时返回`None`。
|
* `bool`:是否接触到物体,如果**check_motion**为`False`时返回`None`。
|
||||||
|
|||||||
+13
-2
@@ -39,6 +39,16 @@ def find_compiled_extension(output_dir: Path) -> Path:
|
|||||||
return sorted(candidates, key=lambda p: len(p.name))[0]
|
return sorted(candidates, key=lambda p: len(p.name))[0]
|
||||||
|
|
||||||
|
|
||||||
|
def move_wheel_to_sdk(wheel_path: Path, version: str) -> Path:
|
||||||
|
sdk_version_dir = Path("SDK") / version
|
||||||
|
sdk_version_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
dest_wheel = sdk_version_dir / wheel_path.name
|
||||||
|
if dest_wheel.exists():
|
||||||
|
dest_wheel.unlink()
|
||||||
|
shutil.move(str(wheel_path), str(dest_wheel))
|
||||||
|
return dest_wheel
|
||||||
|
|
||||||
|
|
||||||
def build():
|
def build():
|
||||||
install_nuitka()
|
install_nuitka()
|
||||||
|
|
||||||
@@ -143,9 +153,10 @@ setup(
|
|||||||
print("\n=== Building Wheel ===")
|
print("\n=== Building Wheel ===")
|
||||||
subprocess.check_call([sys.executable, "setup.py", "bdist_wheel"], cwd=dist_dir)
|
subprocess.check_call([sys.executable, "setup.py", "bdist_wheel"], cwd=dist_dir)
|
||||||
|
|
||||||
wheels = list((dist_dir / "dist").glob("*.whl"))
|
wheels = sorted((dist_dir / "dist").glob("*.whl"))
|
||||||
if wheels:
|
if wheels:
|
||||||
print(f"\n[SUCCESS] Wheel created: {wheels[0]}")
|
final_wheel = move_wheel_to_sdk(wheels[0], version)
|
||||||
|
print(f"\n[SUCCESS] Wheel created and moved to: {final_wheel}")
|
||||||
else:
|
else:
|
||||||
print("\n[ERROR] Wheel not found.")
|
print("\n[ERROR] Wheel not found.")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"stitcher": {
|
"stitcher": {
|
||||||
"min_area_frac": 0.01,
|
"min_area_frac": 0.01,
|
||||||
"max_area_frac": 0.04,
|
"max_area_frac": 0.05,
|
||||||
"max_aspect_ratio": 50,
|
"max_aspect_ratio": 50,
|
||||||
"index": [1,2,3,6,7,8,11,12,13],
|
"index": [1,2,3,6,7,8,11,12,13],
|
||||||
"margin": 10,
|
"margin": 10,
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
叠动科技软件使用手册
|
叠动科技软件使用手册
|
||||||
版本: 0.3.1
|
版本: 0.3.1
|
||||||
更新日期: 2026年3月6日
|
更新日期: 2026年4月16日
|
||||||
|
|
||||||
# 简介
|
# 简介
|
||||||
本文件介绍如何上手和运行叠动视触觉传感器配套的开发套件。本套件是配合叠动视触觉传感器进行图像处理,计算形变、力以及接触信息。它可以从摄像头或视频文件中读取图像,处理后打印和展示处理后信息。
|
本文件介绍如何上手和运行叠动视触觉传感器配套的开发套件。本套件是配合叠动视触觉传感器进行图像处理,计算形变、力以及接触信息。它可以从摄像头或视频文件中读取图像,处理后打印和展示处理后信息。
|
||||||
|
|||||||
+42
-40
@@ -1,13 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
示例 1: 基础用法 - 单传感器同步处理
|
示例 1:单传感器基础流程
|
||||||
|
|
||||||
本示例演示 orisys 的基本使用流程:
|
|
||||||
1. 初始化传感器(视频文件或摄像头)
|
|
||||||
2. 循环获取图像并计算形变和触碰点
|
|
||||||
3. 读取各类传感器数据
|
|
||||||
4. 可视化显示
|
|
||||||
|
|
||||||
|
本示例演示 Orisys SDK 的标准处理流程:
|
||||||
|
1. 初始化传感器(摄像头或视频文件)
|
||||||
|
2. 获取图像并计算形变
|
||||||
|
3. 按需计算接触区域
|
||||||
|
4. 读取并可视化结果
|
||||||
|
|
||||||
|
SDK version: 0.3.1
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||||
@@ -18,39 +18,38 @@ import argparse
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--video","-v", type=str, default="0", help="Camera ID / Video file path")
|
parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径")
|
||||||
parser.add_argument("--config","-c", type=str, default="./config/ddjx01.json", help="Configuration file path")
|
parser.add_argument("--config","-c", type=str, default="./config/ddjx01.json", help="配置文件名称或路径")
|
||||||
parser.add_argument("--cal","-cal", type=str, default="./config/ddjx01.npy", help="Calibration file path")
|
parser.add_argument("--verbose","-verbose", type=bool, default=True, help="是否输出详细日志")
|
||||||
parser.add_argument("--verbose","-verbose", type=bool, default=True, help="Print detailed information")
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# 步骤1:创建传感器对象 (必要步骤),
|
# 步骤 1:创建传感器对象(必需)
|
||||||
# config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径)
|
# config_name 可以是内置配置名称,也可以是自定义配置文件路径
|
||||||
# cal_path为校准文件路径, 每一个设备需要创建/使用一个校准文件
|
# cal_path 为标定文件路径;每台设备建议使用独立的标定文件
|
||||||
|
|
||||||
# 方式1: 从视频文件读取
|
# 输入既可以是摄像头编号,也可以是视频文件路径
|
||||||
try:
|
try:
|
||||||
input = int(args.video)
|
input = int(args.video)
|
||||||
is_camera = True
|
is_camera = True
|
||||||
sensor = orisys.Sensor(input, config_name=args.config, cal_path= args.cal, verbose=args.verbose)
|
sensor = orisys.Sensor(input, config_name=args.config, verbose=args.verbose)
|
||||||
except:
|
except:
|
||||||
is_camera = False
|
is_camera = False
|
||||||
sensor = orisys.Sensor(args.video, config_name=args.config, cal_path= args.cal, verbose=args.verbose)
|
sensor = orisys.Sensor(args.video, config_name=args.config, verbose=args.verbose)
|
||||||
|
|
||||||
print("\n按 'q' 退出程序,按 'r' 重置传感器追踪器")
|
print("\n按键说明:'q' 退出程序,'r' 重置追踪器。")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# 步骤2:信息提取
|
# 步骤 2:数据处理
|
||||||
# 步骤 2.1: 获取并拼接图像 (必要步骤)
|
# 步骤 2.1:获取并拼接图像(必需)
|
||||||
sensor.get_img()
|
sensor.get_img()
|
||||||
# 步骤 2.2: 计算形变场 (必要步骤)
|
# 步骤 2.2:计算形变场(必需)
|
||||||
is_touched = sensor.compute_deformation(check_motion=True, threshold=6)
|
is_touched = sensor.compute_deformation(check_motion=True, threshold=6)
|
||||||
# 步骤 2.3: 计算触碰点
|
# 步骤 2.3:检测到接触后再计算接触区域
|
||||||
if is_touched:
|
if is_touched:
|
||||||
sensor.compute_contact()
|
sensor.compute_contact()
|
||||||
|
|
||||||
|
|
||||||
#步骤 2.4: 读取信息
|
# 步骤 2.4:读取结果
|
||||||
fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = sensor.read_info(
|
fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = sensor.read_info(
|
||||||
sensor.info.FPS,
|
sensor.info.FPS,
|
||||||
sensor.info.VRAW,
|
sensor.info.VRAW,
|
||||||
@@ -64,8 +63,8 @@ def main():
|
|||||||
sensor.info.FSHEARY
|
sensor.info.FSHEARY
|
||||||
)
|
)
|
||||||
|
|
||||||
#步骤 3: 可视化显示
|
# 步骤 3:可视化显示
|
||||||
# 步骤 3.1: 绘制形变场
|
# 步骤 3.1:显示形变矢量场
|
||||||
arrows = orisys.util.draw_arrows(
|
arrows = orisys.util.draw_arrows(
|
||||||
img,
|
img,
|
||||||
flow,
|
flow,
|
||||||
@@ -73,40 +72,43 @@ def main():
|
|||||||
grid_spacing=20,
|
grid_spacing=20,
|
||||||
arrow_scale=1.0
|
arrow_scale=1.0
|
||||||
)
|
)
|
||||||
cv2.imshow("Deformation", arrows)
|
cv2.imshow("形变矢量场", arrows)
|
||||||
|
|
||||||
# 步骤 3.2: 在图像上触碰点和区域
|
# 步骤 3.2:显示接触区域和质心
|
||||||
image_with_foe = orisys.util.draw_contact(img, contour, centroid)
|
image_with_foe = orisys.util.draw_contact(img, contour, centroid)
|
||||||
cv2.imshow("Contact Centroid", image_with_foe)
|
cv2.imshow("接触区域与质心", image_with_foe)
|
||||||
|
|
||||||
# 步骤 3.3: 绘制深度图
|
# 步骤 3.3:显示深度图
|
||||||
# 归一化散度值到0-255范围(使用绝对值,然后归一化)
|
# 将深度值归一化到 0-255 范围
|
||||||
div_abs = depth_map
|
div_abs = depth_map
|
||||||
if div_abs.max() > div_abs.min():
|
if div_abs.max() > div_abs.min():
|
||||||
div_normalized = ((div_abs - div_abs.min()) / (div_abs.max() - div_abs.min()) * 255).astype(np.uint8)
|
div_normalized = ((div_abs - div_abs.min()) / (div_abs.max() - div_abs.min()) * 255).astype(np.uint8)
|
||||||
else:
|
else:
|
||||||
div_normalized = np.zeros_like(div_abs, dtype=np.uint8)
|
div_normalized = np.zeros_like(div_abs, dtype=np.uint8)
|
||||||
|
|
||||||
# 应用颜色映射(使用JET:蓝色=低散度,红色=高散度)
|
# 应用伪彩色映射
|
||||||
cv2.imshow("Depth Map", cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET))
|
cv2.imshow("深度图", cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET))
|
||||||
|
|
||||||
#步骤 3.4: 打印资料
|
# 步骤 3.4:打印关键结果
|
||||||
print(f"fps: {fps}, fnormal: {fnormal}, fshearx: {fshearx}, fsheary: {fsheary}, depth_shape: {depth_map.shape}, sensor.resolution_raw: {sensor.img_size_raw}")
|
print(
|
||||||
|
f"FPS={fps:.2f}, 法向力={fnormal:.4f}, 切向力X={fshearx:.4f}, "
|
||||||
|
f"切向力Y={fsheary:.4f}, 深度图尺寸={depth_map.shape}, 原始分辨率={sensor.img_size_raw}"
|
||||||
|
)
|
||||||
|
|
||||||
#步骤 3.5: 键盘控制
|
# 步骤 3.5:键盘控制
|
||||||
key = cv2.waitKey(1) & 0xFF
|
key = cv2.waitKey(1) & 0xFF
|
||||||
if key == ord("q"):
|
if key == ord("q"):
|
||||||
print("\n退出程序...")
|
print("\n正在退出示例程序...")
|
||||||
break
|
break
|
||||||
if key == ord("r"):
|
if key == ord("r"):
|
||||||
# 重置光流追踪器(当追踪漂移时使用)
|
# 当光流追踪出现漂移时,可手动重置追踪器
|
||||||
sensor.reset()
|
sensor.reset()
|
||||||
print("传感器已重置")
|
print("追踪器已重置。")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# 4. 清理资源
|
# 4. 释放资源
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
sensor.disconnect() # 释放视频源和相关资源
|
sensor.disconnect() # 释放视频源及相关资源
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
"""
|
"""
|
||||||
示例 4: 多传感器并行处理(多进程模式 - 不使用 MultiSensor)
|
示例 2:双传感器并行处理(多进程)
|
||||||
|
|
||||||
本示例演示如何使用 multiprocessing 同时运行两个传感器:
|
本示例演示如何使用 multiprocessing 同时运行两个传感器:
|
||||||
- 每个传感器运行在独立的进程中
|
- 每个传感器运行在独立进程中
|
||||||
- 每个进程拥有独立的 CUDA 上下文
|
- 每个进程拥有独立的 CUDA 上下文
|
||||||
- 使用 multiprocessing.Process 和 Queue 进行进程间通信
|
- 通过 Process 和 Queue 在主进程中汇总结果并显示
|
||||||
|
|
||||||
架构说明:
|
架构说明:
|
||||||
进程 1 (传感器 0): 采集 → 光流 → HHD → 接触检测 → 队列
|
进程 1(传感器 0):采集 → 光流 → HHD → 接触检测 → 队列
|
||||||
进程 2 (传感器 1): 采集 → 光流 → HHD → 接触检测 → 队列
|
进程 2(传感器 1):采集 → 光流 → HHD → 接触检测 → 队列
|
||||||
↓
|
↓
|
||||||
主进程: 显示 ← ───────────────┘
|
主进程: 显示 ← ───────────────┘
|
||||||
|
|
||||||
适用场景:多指触觉传感器、多视角采集、需要 GPU 加速的多路处理
|
适用场景:多指触觉传感器、多视角采集,以及需要 GPU 加速的多路处理。
|
||||||
|
|
||||||
注意事项:
|
注意事项:
|
||||||
- Windows 下必须在 if __name__ == '__main__' 中调用 mp.freeze_support()
|
- Windows 下必须在 if __name__ == '__main__' 中调用 mp.freeze_support()
|
||||||
- 每个传感器进程会占用独立的 GPU 显存
|
- 每个传感器进程都会占用独立的 GPU 显存
|
||||||
|
|
||||||
|
SDK version: 0.3.1
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||||
@@ -26,20 +28,20 @@ import numpy as np
|
|||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
from multiprocessing import Queue, Process
|
from multiprocessing import Queue, Process
|
||||||
import time
|
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:
|
Args:
|
||||||
sensor_id: 传感器编号
|
sensor_id: 传感器编号
|
||||||
vid_src: 视频源(文件路径或摄像头索引)
|
vid_src: 视频源(文件路径或摄像头索引)
|
||||||
config_name: 配置名称
|
config_name: 配置名称
|
||||||
cal_path: 标定路径
|
|
||||||
result_queue: 结果队列,用于向主进程发送数据
|
result_queue: 结果队列,用于向主进程发送数据
|
||||||
stop_event: 停止事件,用于通知工作进程退出
|
stop_event: 停止事件,用于通知工作进程退出
|
||||||
target_fps: 目标帧率;若设置则用定时器限速,使多传感器 FPS 接近一致
|
target_fps: 目标帧率;设置后将对处理循环限速,使多路传感器 FPS 更接近
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 在每个进程中创建独立的传感器实例
|
# 在每个进程中创建独立的传感器实例
|
||||||
@@ -48,27 +50,26 @@ def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_
|
|||||||
isstitch=True,
|
isstitch=True,
|
||||||
cuda=True,
|
cuda=True,
|
||||||
verbose=False, # 减少子进程输出
|
verbose=False, # 减少子进程输出
|
||||||
config_name=config_name,
|
config_name=config_name
|
||||||
cal_path=cal_path
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
frame_interval = 1.0 / target_fps if target_fps and target_fps > 0 else 0.0
|
||||||
|
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
t_start = time.perf_counter()
|
t_start = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
# 步骤1: 获取图像
|
# 步骤 1:获取图像
|
||||||
sensor.get_img()
|
sensor.get_img()
|
||||||
|
|
||||||
# 步骤2: 计算形变场
|
# 步骤 2:计算形变场
|
||||||
sensor.compute_deformation()
|
sensor.compute_deformation()
|
||||||
|
|
||||||
# 步骤3: 计算触碰点
|
# 步骤 3:计算接触区域(如需要可取消注释)
|
||||||
# sensor.compute_contact()
|
# sensor.compute_contact()
|
||||||
|
|
||||||
# 步骤4: 读取信息
|
# 步骤 4:读取结果
|
||||||
fps, flow, vnormal, img, contour, centroid, depth_map = sensor.read_info(
|
fps, flow, vnormal, img, contour, centroid, depth_map = sensor.read_info(
|
||||||
sensor.info.FPS,
|
sensor.info.FPS,
|
||||||
sensor.info.VRAW,
|
sensor.info.VRAW,
|
||||||
@@ -79,7 +80,7 @@ def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_
|
|||||||
sensor.info.DEPTH
|
sensor.info.DEPTH
|
||||||
)
|
)
|
||||||
|
|
||||||
# 步骤5: 准备数据字典(确保所有数据都是可序列化的)
|
# 步骤 5:整理结果字典(确保数据可序列化)
|
||||||
data = {
|
data = {
|
||||||
'sensor_id': sensor_id,
|
'sensor_id': sensor_id,
|
||||||
'fps': fps,
|
'fps': fps,
|
||||||
@@ -92,100 +93,104 @@ def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_
|
|||||||
'timestamp': time.time()
|
'timestamp': time.time()
|
||||||
}
|
}
|
||||||
|
|
||||||
# 步骤6: 发送数据到主进程(非阻塞)
|
# 步骤 6:将结果发送到主进程(非阻塞)
|
||||||
try:
|
try:
|
||||||
result_queue.put_nowait(data)
|
result_queue.put_nowait(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
# 如果队列已满,跳过这一帧
|
# 如果队列已满,则丢弃当前帧,避免阻塞工作进程
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"传感器 {sensor_id} 处理错误: {e}")
|
print(f"传感器 {sensor_id} 处理失败:{e}")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
# 步骤7: 按 target_fps 限速,使多传感器 FPS 接近
|
# 步骤 7:按 target_fps 限速,使多路传感器 FPS 更接近
|
||||||
if frame_interval > 0:
|
if frame_interval > 0:
|
||||||
elapsed = time.perf_counter() - t_start
|
elapsed = time.perf_counter() - t_start
|
||||||
sleep_time = frame_interval - elapsed
|
sleep_time = frame_interval - elapsed
|
||||||
if sleep_time > 0.001: # 至少 1ms 才 sleep,避免无意义调度
|
if sleep_time > 0.001: # 至少 1ms 才 sleep,避免无意义调度
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
# 清理资源
|
# 释放资源
|
||||||
sensor.disconnect()
|
sensor.disconnect()
|
||||||
print(f"传感器 {sensor_id} 已停止")
|
print(f"传感器 {sensor_id} 已停止。")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"传感器 {sensor_id} 初始化失败: {e}")
|
print(f"传感器 {sensor_id} 初始化失败:{e}")
|
||||||
result_queue.put({'sensor_id': sensor_id, 'error': str(e)})
|
result_queue.put({'sensor_id': sensor_id, 'error': str(e)})
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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
|
target_fps = 30
|
||||||
|
|
||||||
num_sensors = 2
|
num_sensors = 2
|
||||||
sensor_configs = [
|
sensor_configs = [
|
||||||
{
|
{
|
||||||
'sensor_id': 0,
|
'sensor_id': 0,
|
||||||
'vid_src': 2,
|
'vid_src': args.video1,
|
||||||
'config_name': './config/prototype_of3_720p.json',
|
'config_name': './config/ddjx01.json'
|
||||||
'cal_path': './config/ddjx01.npy'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'sensor_id': 1,
|
'sensor_id': 1,
|
||||||
'vid_src': 4, # 可以改为不同的视频源或摄像头索引
|
'vid_src': args.video2, # 可替换为其他视频源或摄像头索引
|
||||||
'config_name': './config/prototype_of3_720p.json',
|
'config_name': './config/ddjx01.json'
|
||||||
'cal_path': './config/ddjx01.npy'
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# 2. 创建进程间通信对象
|
# 2. 创建进程间通信对象
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# 增大队列大小以缓冲更多帧,避免因主进程处理延迟导致的丢帧
|
# 适当增大队列容量,以缓冲更多帧并减轻主进程偶发延迟带来的丢帧
|
||||||
result_queues = [Queue(maxsize=30) for _ in sensor_configs]
|
result_queues = [Queue(maxsize=30) for _ in sensor_configs]
|
||||||
stop_events = [mp.Event() for _ in sensor_configs]
|
stop_events = [mp.Event() for _ in sensor_configs]
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# 3. 启动传感器工作进程
|
# 3. 启动工作进程
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
processes = []
|
processes = []
|
||||||
for i, config in enumerate(sensor_configs):
|
for i, config in enumerate(sensor_configs):
|
||||||
p = Process(
|
p = Process(
|
||||||
target=sensor_worker,
|
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)
|
result_queues[i], stop_events[i], target_fps)
|
||||||
)
|
)
|
||||||
p.start()
|
p.start()
|
||||||
processes.append(p)
|
processes.append(p)
|
||||||
|
|
||||||
print(f"\n已启动 {len(processes)} 个传感器进程")
|
print(f"\n已启动 {len(processes)} 个传感器进程。")
|
||||||
print("按 'q' 退出程序")
|
print("按键说明:'q' 退出程序。")
|
||||||
|
|
||||||
# 用于存储每个传感器的最新数据
|
# 保存每个传感器的最新一帧数据
|
||||||
latest_data = [None] * len(sensor_configs)
|
latest_data = [None] * len(sensor_configs)
|
||||||
|
|
||||||
# 用于控制打印频率,减少I/O开销
|
# 控制打印频率,减少 I/O 开销
|
||||||
print_counter = 0
|
print_counter = 0
|
||||||
print_interval = 30 # 每30帧打印一次,进一步减少I/O
|
print_interval = 30 # 每 30 帧打印一次
|
||||||
|
|
||||||
# 用于控制按键检查频率
|
# 控制按键检查频率
|
||||||
key_check_counter = 0
|
key_check_counter = 0
|
||||||
key_check_interval = 5 # 每5帧检查一次按键
|
key_check_interval = 5 # 每 5 帧检查一次按键
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# 4. 主循环:从队列获取数据并显示
|
# 4. 主循环:从队列获取结果并显示
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
while True:
|
while True:
|
||||||
# 从所有队列中快速获取最新数据(高效清空队列,只保留最新帧)
|
# 从所有队列中快速获取最新数据,只保留最后一帧
|
||||||
for i, queue in enumerate(result_queues):
|
for i, queue in enumerate(result_queues):
|
||||||
# 快速清空队列,只保留最后一帧
|
# 清空队列,仅保留最后一帧
|
||||||
data = None
|
data = None
|
||||||
# 一次性获取所有可用数据,只保留最后一个
|
# 读取所有可用数据,只保留最新结果
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
data = queue.get_nowait()
|
data = queue.get_nowait()
|
||||||
@@ -195,14 +200,14 @@ def main():
|
|||||||
if data is not None:
|
if data is not None:
|
||||||
latest_data[i] = data
|
latest_data[i] = data
|
||||||
|
|
||||||
# 显示所有传感器的数据
|
# 显示所有传感器的最新结果
|
||||||
for data in latest_data:
|
for data in latest_data:
|
||||||
if data is None or 'error' in data:
|
if data is None or 'error' in data:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sid = data['sensor_id']
|
sid = data['sensor_id']
|
||||||
|
|
||||||
# 显示形变场
|
# 显示形变矢量场
|
||||||
if data['img'] is not None and data['flow'] is not None:
|
if data['img'] is not None and data['flow'] is not None:
|
||||||
arrows = orisys.util.draw_arrows(
|
arrows = orisys.util.draw_arrows(
|
||||||
data['img'],
|
data['img'],
|
||||||
@@ -211,7 +216,7 @@ def main():
|
|||||||
grid_spacing=10,
|
grid_spacing=10,
|
||||||
arrow_scale=1.0
|
arrow_scale=1.0
|
||||||
)
|
)
|
||||||
cv2.imshow(f"Deformation {sid}", arrows)
|
cv2.imshow(f"传感器 {sid} - 形变矢量场", arrows)
|
||||||
|
|
||||||
# # 显示触碰点和区域
|
# # 显示触碰点和区域
|
||||||
# if data['img'] is not None:
|
# if data['img'] is not None:
|
||||||
@@ -233,33 +238,33 @@ def main():
|
|||||||
# cv2.imshow(f"Depth Map {sid}",
|
# cv2.imshow(f"Depth Map {sid}",
|
||||||
# cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET))
|
# cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET))
|
||||||
|
|
||||||
# 降低打印频率以减少I/O开销(在主循环级别控制)
|
# 降低打印频率,减少主循环中的 I/O 开销
|
||||||
print_counter += 1
|
print_counter += 1
|
||||||
if print_counter >= print_interval:
|
if print_counter >= print_interval:
|
||||||
for data in latest_data:
|
for data in latest_data:
|
||||||
if data is not None and 'error' not in data:
|
if data is not None and 'error' not in data:
|
||||||
sid = data['sensor_id']
|
sid = data['sensor_id']
|
||||||
print(f"传感器 {sid}: FPS={data['fps']:5.1f}")
|
print(f"传感器 {sid}:FPS={data['fps']:5.1f}")
|
||||||
print_counter = 0
|
print_counter = 0
|
||||||
|
|
||||||
# 降低按键检查频率以减少开销
|
# 降低按键检查频率,减少额外开销
|
||||||
key_check_counter += 1
|
key_check_counter += 1
|
||||||
if key_check_counter >= key_check_interval:
|
if key_check_counter >= key_check_interval:
|
||||||
key = cv2.waitKey(1) & 0xFF
|
key = cv2.waitKey(1) & 0xFF
|
||||||
if key == ord('q'):
|
if key == ord('q'):
|
||||||
print("\n退出程序...")
|
print("\n正在退出示例程序...")
|
||||||
break
|
break
|
||||||
key_check_counter = 0
|
key_check_counter = 0
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# 5. 清理资源
|
# 5. 释放资源
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# 通知所有工作进程停止
|
# 通知所有工作进程停止
|
||||||
for event in stop_events:
|
for event in stop_events:
|
||||||
event.set()
|
event.set()
|
||||||
|
|
||||||
# 等待所有进程结束
|
# 等待所有工作进程结束
|
||||||
for p in processes:
|
for p in processes:
|
||||||
p.join(timeout=2)
|
p.join(timeout=2)
|
||||||
if p.is_alive():
|
if p.is_alive():
|
||||||
@@ -267,10 +272,10 @@ def main():
|
|||||||
p.join()
|
p.join()
|
||||||
|
|
||||||
cv2.destroyAllWindows()
|
cv2.destroyAllWindows()
|
||||||
print("所有传感器进程已停止")
|
print("所有传感器进程已停止。")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Windows 下多进程必需的初始化
|
# Windows 下运行多进程示例所需的初始化
|
||||||
mp.freeze_support()
|
mp.freeze_support()
|
||||||
main()
|
main()
|
||||||
|
|||||||
+70
-55
@@ -1,3 +1,15 @@
|
|||||||
|
"""
|
||||||
|
示例 3:实时曲线与幅值图显示
|
||||||
|
|
||||||
|
本示例演示如何在读取传感器结果的同时:
|
||||||
|
1. 显示形变矢量场
|
||||||
|
2. 显示流场幅值热力图
|
||||||
|
3. 使用 PyQtGraph 实时绘制法向力与切向力曲线
|
||||||
|
4. 按键控制录像开始与结束
|
||||||
|
|
||||||
|
SDK version: 0.3.1
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
||||||
import cv2
|
import cv2
|
||||||
@@ -5,10 +17,14 @@ import orisys
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import time
|
import time
|
||||||
import argparse
|
import argparse
|
||||||
import pyqtgraph as pg
|
try:
|
||||||
|
import pyqtgraph as pg
|
||||||
|
except:
|
||||||
|
print("PyQtGraph is not installed. Please install it using 'pip install pyqt5 pyqtgraph'.")
|
||||||
|
exit()
|
||||||
|
|
||||||
def create_plot():
|
def create_plot():
|
||||||
p1 = win.addPlot(title="法相力")
|
p1 = win.addPlot(title="法向力")
|
||||||
p1.setLabel('bottom', '时间', units='s')
|
p1.setLabel('bottom', '时间', units='s')
|
||||||
win.nextRow()
|
win.nextRow()
|
||||||
p2 = win.addPlot(title="切向力 X")
|
p2 = win.addPlot(title="切向力 X")
|
||||||
@@ -33,53 +49,53 @@ def update(plot_af, plot_tfx, plot_tfy, data, p1, p2, p3):
|
|||||||
# 深度图
|
# 深度图
|
||||||
def draw_magnitude_map(vfield, threshold=3.0, colormap=cv2.COLORMAP_JET) -> np.ndarray:
|
def draw_magnitude_map(vfield, threshold=3.0, colormap=cv2.COLORMAP_JET) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Draw color map of pointwise magnitude of velocity field.
|
绘制流场幅值伪彩色图。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vfield: Velocity field to visualize (2D vector field)
|
vfield: 需要可视化的二维矢量场
|
||||||
threshold: Minimum magnitude to display (below threshold shows as zero/blue)
|
threshold: 最小显示阈值;低于阈值的区域显示为低值颜色
|
||||||
colormap: OpenCV colormap to use (default: COLORMAP_JET)
|
colormap: OpenCV 颜色映射类型(默认 COLORMAP_JET)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.ndarray: 400x400 BGR image with colored magnitude map
|
np.ndarray: 400x400 的 BGR 伪彩图像
|
||||||
"""
|
"""
|
||||||
flow_field = vfield
|
flow_field = vfield
|
||||||
|
|
||||||
# Compute magnitude at each point
|
# 计算每个像素点的幅值
|
||||||
magnitude = np.sqrt(flow_field[:, :, 0]**2 + flow_field[:, :, 1]**2)
|
magnitude = np.sqrt(flow_field[:, :, 0]**2 + flow_field[:, :, 1]**2)
|
||||||
|
|
||||||
# Smooth by downsampling and interpolating back
|
# 先降采样再插值回原尺寸,使显示更平滑
|
||||||
downsample_factor = 10
|
downsample_factor = 10
|
||||||
h, w = magnitude.shape
|
h, w = magnitude.shape
|
||||||
magnitude_downsampled = magnitude[::downsample_factor, ::downsample_factor]
|
magnitude_downsampled = magnitude[::downsample_factor, ::downsample_factor]
|
||||||
magnitude_smoothed = cv2.resize(magnitude_downsampled, (w, h), interpolation=cv2.INTER_CUBIC)
|
magnitude_smoothed = cv2.resize(magnitude_downsampled, (w, h), interpolation=cv2.INTER_CUBIC)
|
||||||
|
|
||||||
# Apply threshold - set below-threshold values to zero
|
# 应用阈值,将低于阈值的区域置零
|
||||||
magnitude_thresholded = magnitude_smoothed.copy()
|
magnitude_thresholded = magnitude_smoothed.copy()
|
||||||
magnitude_thresholded[magnitude_smoothed < threshold] = 0
|
magnitude_thresholded[magnitude_smoothed < threshold] = 0
|
||||||
|
|
||||||
# Normalize to 0-255 range
|
# 归一化到 0-255
|
||||||
mag_min, mag_max = magnitude_thresholded.min(), magnitude_thresholded.max()
|
mag_min, mag_max = magnitude_thresholded.min(), magnitude_thresholded.max()
|
||||||
if mag_max > mag_min:
|
if mag_max > mag_min:
|
||||||
magnitude_norm = ((magnitude_thresholded - mag_min) / (mag_max - mag_min) * 255).astype(np.uint8)
|
magnitude_norm = ((magnitude_thresholded - mag_min) / (mag_max - mag_min) * 255).astype(np.uint8)
|
||||||
else:
|
else:
|
||||||
magnitude_norm = np.zeros_like(magnitude_thresholded, dtype=np.uint8)
|
magnitude_norm = np.zeros_like(magnitude_thresholded, dtype=np.uint8)
|
||||||
|
|
||||||
# Apply colormap
|
# 应用伪彩色映射
|
||||||
magnitude_colored = cv2.applyColorMap(magnitude_norm, colormap)
|
magnitude_colored = cv2.applyColorMap(magnitude_norm, colormap)
|
||||||
|
|
||||||
# Resize to output size
|
# 缩放到固定输出尺寸
|
||||||
out_img = cv2.resize(magnitude_colored, (400, 400), interpolation=cv2.INTER_CUBIC)
|
out_img = cv2.resize(magnitude_colored, (400, 400), interpolation=cv2.INTER_CUBIC)
|
||||||
|
|
||||||
return out_img
|
return out_img
|
||||||
|
|
||||||
# 实时图表
|
# 实时图表
|
||||||
npoints = 100 # 图表数据长度
|
npoints = 100 # 曲线缓冲区长度
|
||||||
pg.setConfigOption('background', 'w')
|
pg.setConfigOption('background', 'w')
|
||||||
pg.setConfigOption('foreground', 'k')
|
pg.setConfigOption('foreground', 'k')
|
||||||
app = pg.mkQApp("Plotting Example")
|
app = pg.mkQApp("Orisys Plot Example")
|
||||||
win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples")
|
win = pg.GraphicsLayoutWidget(show=True, title="Orisys 实时力曲线")
|
||||||
win.setWindowTitle('pyqtgraph example: Plotting')
|
win.setWindowTitle('Orisys 实时力曲线')
|
||||||
p1, p2, p3, plot_af, plot_tfx, plot_tfy = create_plot()
|
p1, p2, p3, plot_af, plot_tfx, plot_tfy = create_plot()
|
||||||
data = np.zeros([npoints,2,3])
|
data = np.zeros([npoints,2,3])
|
||||||
start_time = None # Will be set when sensor starts
|
start_time = None # Will be set when sensor starts
|
||||||
@@ -91,17 +107,17 @@ for i in range(3):
|
|||||||
def main():
|
def main():
|
||||||
global start_time
|
global start_time
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--video","-v", type=str, default="0", help="Camera ID / Video file path")
|
parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径")
|
||||||
parser.add_argument("--config","-c", type=str, default="./config/ddjx01.json", help="Configuration file path")
|
parser.add_argument("--config","-c", type=str, default="./config/ddjx01.json", help="配置文件名称或路径")
|
||||||
parser.add_argument("--cal","-cal", type=str, default="./config/ddjx01.npy", help="Calibration file path")
|
parser.add_argument("--cal","-cal", type=str, default="./config/ddjx01.npy", help="标定文件路径")
|
||||||
parser.add_argument("--verbose","-verbose", type=bool, default=True, help="Print detailed information")
|
parser.add_argument("--verbose","-verbose", type=bool, default=True, help="是否输出详细日志")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# 步骤1:创建传感器对象 (必要步骤),
|
# 步骤 1:创建传感器对象(必需)
|
||||||
# config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径)
|
# config_name 可以是内置配置名称,也可以是自定义配置文件路径
|
||||||
# cal_path为校准文件路径, 每一个设备需要创建/使用一个校准文件
|
# cal_path 为标定文件路径;每台设备建议使用独立的标定文件
|
||||||
|
|
||||||
# 方式1: 从视频文件读取
|
# 输入既可以是摄像头编号,也可以是视频文件路径
|
||||||
try:
|
try:
|
||||||
input = int(args.video)
|
input = int(args.video)
|
||||||
is_camera = True
|
is_camera = True
|
||||||
@@ -109,23 +125,23 @@ def main():
|
|||||||
except:
|
except:
|
||||||
is_camera = False
|
is_camera = False
|
||||||
sensor = orisys.Sensor(args.video, config_name=args.config, cal_path= args.cal, verbose=args.verbose)
|
sensor = orisys.Sensor(args.video, config_name=args.config, cal_path= args.cal, verbose=args.verbose)
|
||||||
# 打开视频
|
# 下面保留了历史示例写法,便于参考
|
||||||
# sensor1 = Sensor("./s1.mp4",isstitch=True,cuda = True,verbose = True)
|
|
||||||
# 方式1: 从视频文件读取
|
|
||||||
# sensor1 = orisys.Sensor("./s1.mp4", config_name="./config/ddjx01.json", cal_path= "./config/ddjx01.npy", cuda=False, verbose=True)
|
# sensor1 = orisys.Sensor("./s1.mp4", config_name="./config/ddjx01.json", cal_path= "./config/ddjx01.npy", cuda=False, verbose=True)
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
is_save = False
|
is_save = False
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
writer = None
|
writer = None
|
||||||
|
save_path = None
|
||||||
|
print("\n按键说明:'q' 退出程序,'s' 开始录像,'e' 结束录像。")
|
||||||
while True:
|
while True:
|
||||||
sensor.get_img() # 提取并拼接复眼图像
|
sensor.get_img() # 获取并拼接复眼图像
|
||||||
|
|
||||||
if is_save and writer is not None and sensor.frame is not None:
|
if is_save and writer is not None and sensor.frame is not None:
|
||||||
frame_count += 1
|
frame_count += 1
|
||||||
save_img = sensor.frame
|
save_img = sensor.frame
|
||||||
|
|
||||||
# Convert grayscale (1 channel) to BGR (3 channels) if needed
|
# 若为灰度图,则先转换为 BGR 再写入视频
|
||||||
if len(save_img.shape) == 2 or (len(save_img.shape) == 3 and save_img.shape[2] == 1):
|
if len(save_img.shape) == 2 or (len(save_img.shape) == 3 and save_img.shape[2] == 1):
|
||||||
if len(save_img.shape) == 3:
|
if len(save_img.shape) == 3:
|
||||||
save_img = save_img.squeeze(2)
|
save_img = save_img.squeeze(2)
|
||||||
@@ -133,44 +149,43 @@ def main():
|
|||||||
|
|
||||||
writer.write(save_img)
|
writer.write(save_img)
|
||||||
|
|
||||||
sensor.compute_deformation(check_motion=False) # 计算形变和分解
|
sensor.compute_deformation(check_motion=False) # 计算形变与分解结果
|
||||||
# 提取信息:
|
# 读取信息:
|
||||||
# 向量场: VRAW, VNORMAL, VSHEAR: 原始,法向,切向
|
# 向量场:VRAW、VNORMAL、VSHEAR -> 原始 / 法向 / 切向
|
||||||
# 帧率,时间戳: FPS, TIMESTAMP
|
# 标量:FNORMAL、FSHEARX、FSHEARY -> 法向力 / X 向切向力 / Y 向切向力
|
||||||
# 变形量总和: FNORMAL , FSHEARX, FSHEARY : 法向,切向(X),切向(Y)
|
|
||||||
fps, fn, fx, fy, flow, vnormal, vshear = sensor.read_info(sensor.info.FPS, sensor.info.FNORMAL, sensor.info.FSHEARX, sensor.info.FSHEARY, sensor.info.VRAW, sensor.info.VNORMAL,sensor.info.VSHEAR)
|
fps, fn, fx, fy, flow, vnormal, vshear = sensor.read_info(sensor.info.FPS, sensor.info.FNORMAL, sensor.info.FSHEARX, sensor.info.FSHEARY, sensor.info.VRAW, sensor.info.VNORMAL,sensor.info.VSHEAR)
|
||||||
print(fps,fn,fx,fy)
|
print(f"FPS={fps:.2f}, 法向力={fn:.4f}, 切向力X={fx:.4f}, 切向力Y={fy:.4f}")
|
||||||
# 显示向量场
|
# 显示形变矢量场
|
||||||
arrows = orisys.util.draw_arrows(sensor.img, flow, threshold=5, grid_spacing=10, arrow_scale=1.0)
|
arrows = orisys.util.draw_arrows(sensor.img, flow, threshold=5, grid_spacing=10, arrow_scale=1.0)
|
||||||
cv2.imshow("Deformation", arrows)
|
cv2.imshow("形变矢量场", arrows)
|
||||||
# 显示深度图
|
# 显示流场幅值图
|
||||||
mag = draw_magnitude_map(flow)
|
mag = draw_magnitude_map(flow)
|
||||||
cv2.imshow("Magnitude", mag)
|
cv2.imshow("流场幅值图", mag)
|
||||||
|
|
||||||
# 显示图表 - 更新时间轴
|
# 更新实时曲线
|
||||||
current_time = time.time() - start_time
|
current_time = time.time() - start_time
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
data[:-1, 0, i] = data[1:, 0, i] # shift time data left
|
data[:-1, 0, i] = data[1:, 0, i] # 时间轴左移
|
||||||
data[:-1, 1, i] = data[1:, 1, i] # shift force data left
|
data[:-1, 1, i] = data[1:, 1, i] # 力数据左移
|
||||||
data[-1, 0, :] = current_time # set current time for all plots
|
data[-1, 0, :] = current_time # 写入当前时间
|
||||||
data[-1, 1, 0] = fn
|
data[-1, 1, 0] = fn
|
||||||
data[-1, 1, 1] = fx
|
data[-1, 1, 1] = fx
|
||||||
data[-1, 1, 2] = fy
|
data[-1, 1, 2] = fy
|
||||||
update(plot_af, plot_tfx, plot_tfy, data, p1, p2, p3) # Update plot
|
update(plot_af, plot_tfx, plot_tfy, data, p1, p2, p3) # 更新曲线
|
||||||
|
|
||||||
key = cv2.waitKey(1)
|
key = cv2.waitKey(1)
|
||||||
if key & 0xFF == ord("q"):
|
if key & 0xFF == ord("q"):
|
||||||
print("\n 停止程序")
|
print("\n正在退出示例程序...")
|
||||||
break
|
break
|
||||||
if key == ord("s"):
|
if key == ord("s"):
|
||||||
save_path = f"./data/pulse_test.mp4"
|
save_path = f"./data/pulse_test.mp4"
|
||||||
# Ensure directory exists
|
# 确保输出目录存在
|
||||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||||
# Get frame dimensions from the actual frame
|
# 从当前帧获取视频尺寸
|
||||||
save_img = sensor.frame
|
save_img = sensor.frame
|
||||||
|
|
||||||
if save_img is not None:
|
if save_img is not None:
|
||||||
# Convert grayscale (1 channel) to BGR (3 channels) if needed for dimension check
|
# 如为灰度图,先转换为 BGR 以获取视频尺寸
|
||||||
temp_img = save_img.copy()
|
temp_img = save_img.copy()
|
||||||
if len(temp_img.shape) == 2 or (len(temp_img.shape) == 3 and temp_img.shape[2] == 1):
|
if len(temp_img.shape) == 2 or (len(temp_img.shape) == 3 and temp_img.shape[2] == 1):
|
||||||
if len(temp_img.shape) == 3:
|
if len(temp_img.shape) == 3:
|
||||||
@@ -181,24 +196,24 @@ def main():
|
|||||||
writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), 30, (frame_width, frame_height))
|
writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), 30, (frame_width, frame_height))
|
||||||
if writer.isOpened():
|
if writer.isOpened():
|
||||||
is_save = True
|
is_save = True
|
||||||
print(f"Start writing to {save_path}")
|
print(f"开始录像:{save_path}")
|
||||||
else:
|
else:
|
||||||
print(f"Failed to open video writer for {save_path}")
|
print(f"无法创建录像文件:{save_path}")
|
||||||
writer = None
|
writer = None
|
||||||
else:
|
else:
|
||||||
print("No frame available to determine video dimensions")
|
print("当前没有可用帧,无法确定录像尺寸。")
|
||||||
if key == ord("e"):
|
if key == ord("e"):
|
||||||
is_save = False
|
is_save = False
|
||||||
if writer is not None:
|
if writer is not None:
|
||||||
writer.release()
|
writer.release()
|
||||||
writer = None
|
writer = None
|
||||||
if save_path is not None:
|
if save_path is not None:
|
||||||
print(f"Stop writing to {save_path}, total frames: {frame_count}")
|
print(f"录像结束:{save_path},总帧数:{frame_count}")
|
||||||
else:
|
else:
|
||||||
print(f"Stop writing, total frames: {frame_count}")
|
print(f"录像结束,总帧数:{frame_count}")
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
|
|
||||||
sensor.disconnect() # 断开传感器连接
|
sensor.disconnect() # 断开传感器连接并释放资源
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "prototype",
|
"name": "prototype",
|
||||||
"version": "1.0",
|
"version": "0.3.1",
|
||||||
"camera": {
|
"camera": {
|
||||||
"resolution_raw": [1920, 1080],
|
"resolution_raw": [1920, 1080],
|
||||||
"fps": 30,
|
"fps": 30,
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
},
|
},
|
||||||
"stitcher": {
|
"stitcher": {
|
||||||
"min_area_frac": 0.01,
|
"min_area_frac": 0.01,
|
||||||
"max_area_frac": 0.04,
|
"max_area_frac": 0.05,
|
||||||
"max_aspect_ratio": 100,
|
"max_aspect_ratio": 50,
|
||||||
"index": [1,2,3,6,7,8,11,12,13],
|
"index": [1,2,3,6,7,8,11,12,13],
|
||||||
"margin": 10,
|
"margin": 10,
|
||||||
"nominal_ov_pos": 100,
|
"nominal_ov_pos": 100,
|
||||||
"n": [3,3],
|
"n": [3,3],
|
||||||
"row_tol": 0.6,
|
"row_tol": 0.6,
|
||||||
"box_size": [220, 220]
|
"box_size": [250, 250]
|
||||||
},
|
},
|
||||||
"tracker": {
|
"tracker": {
|
||||||
"adaptive": true,
|
"adaptive": true,
|
||||||
@@ -28,15 +28,16 @@
|
|||||||
"algorithm": "dis",
|
"algorithm": "dis",
|
||||||
"dis_preset": "medium",
|
"dis_preset": "medium",
|
||||||
"dis_mode": 3,
|
"dis_mode": 3,
|
||||||
"algorithm_list": ["raft", "dis"]
|
"algorithm_list": ["raft", "dis"],
|
||||||
|
"hhd_downsample": 3
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"min_contour_area": 3000,
|
"min_contour_area": 3000,
|
||||||
"flow_threshold":1,
|
"flow_threshold":5,
|
||||||
"div_threshold":0.02,
|
"div_threshold":0.05,
|
||||||
"expansion_threshold": 0.01,
|
"expansion_threshold": 0.01,
|
||||||
"kernel_size": [5,5],
|
"kernel_size": [5,5],
|
||||||
"denoise_sigma": 15
|
"denoise_sigma": 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "prototype",
|
|
||||||
"version": "1.0",
|
|
||||||
"camera": {
|
|
||||||
"resolution_raw": [1920, 1080],
|
|
||||||
"fps": 30,
|
|
||||||
"units": 15,
|
|
||||||
"units_used": 9,
|
|
||||||
"resolution_stitch": [400, 400]
|
|
||||||
},
|
|
||||||
"stitcher": {
|
|
||||||
"min_area_frac": 0.01,
|
|
||||||
"max_area_frac": 0.04,
|
|
||||||
"max_aspect_ratio": 100,
|
|
||||||
"index": [1,2,3,6,7,8,11,12,13],
|
|
||||||
"margin": 10,
|
|
||||||
"nominal_ov_pos": 100,
|
|
||||||
"n": [3,3],
|
|
||||||
"row_tol": 0.6
|
|
||||||
},
|
|
||||||
"tracker": {
|
|
||||||
"adaptive": true,
|
|
||||||
"lower_threshold": 12.0,
|
|
||||||
"upper_threshold": 18.0,
|
|
||||||
"loop_closure_interval": 1,
|
|
||||||
"dis_preset": "medium",
|
|
||||||
"finest_scale": 3
|
|
||||||
},
|
|
||||||
"contact": {
|
|
||||||
"min_contour_area": 3000,
|
|
||||||
"flow_threshold":1,
|
|
||||||
"div_threshold":0.02,
|
|
||||||
"expansion_threshold": 0.01,
|
|
||||||
"kernel_size": [5,5],
|
|
||||||
"denoise_sigma": 15
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ class Sensor:
|
|||||||
self.compute_hhd()
|
self.compute_hhd()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
return None
|
return None
|
||||||
# return True
|
# return True
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user