diff --git a/assets/finger/30fps-0703/camera_intrinsics.npz b/assets/finger/30fps-0703/camera_intrinsics.npz new file mode 100644 index 0000000..888b1eb Binary files /dev/null and b/assets/finger/30fps-0703/camera_intrinsics.npz differ diff --git a/assets/finger/30fps-0703/mesh.npz b/assets/finger/30fps-0703/mesh.npz new file mode 100644 index 0000000..4da6bb1 Binary files /dev/null and b/assets/finger/30fps-0703/mesh.npz differ diff --git a/assets/finger/30fps-0703/pixel_to_xyz.npz b/assets/finger/30fps-0703/pixel_to_xyz.npz new file mode 100644 index 0000000..15eee73 Binary files /dev/null and b/assets/finger/30fps-0703/pixel_to_xyz.npz differ diff --git a/assets/finger/30fps-0703/unroll_map_uv_s0.4.npz b/assets/finger/30fps-0703/unroll_map_uv_s0.4.npz new file mode 100644 index 0000000..7cbb8cb Binary files /dev/null and b/assets/finger/30fps-0703/unroll_map_uv_s0.4.npz differ diff --git a/assets/finger/30fps-0703/world_to_cam.npy b/assets/finger/30fps-0703/world_to_cam.npy new file mode 100644 index 0000000..3d6f897 Binary files /dev/null and b/assets/finger/30fps-0703/world_to_cam.npy differ diff --git a/build/verify.py b/build/verify.py index b4aa4ca..6e97516 100644 --- a/build/verify.py +++ b/build/verify.py @@ -53,7 +53,7 @@ def verify_gui_dist(dist_dir: Path | None = None) -> None: required = [ dist_dir / "config" / "dd02-ov.json", dist_dir / "assets" / "finger" / "visualization" / "world_to_cam.npy", - dist_dir / "assets" / "finger" / "30fps" / "mesh.npz", + dist_dir / "assets" / "finger" / "30fps-0703" / "mesh.npz", ] missing = [str(path.relative_to(dist_dir)) for path in required if not path.is_file()] if missing: diff --git a/data/jx01/jx01_hex1.3_arrows.mp4 b/data/jx01/jx01_hex1.3_arrows.mp4 deleted file mode 100644 index 4e3969e..0000000 Binary files a/data/jx01/jx01_hex1.3_arrows.mp4 and /dev/null differ diff --git a/data/jx01/jx01_hex1.3_depth.mp4 b/data/jx01/jx01_hex1.3_depth.mp4 deleted file mode 100644 index 4fd91e7..0000000 Binary files a/data/jx01/jx01_hex1.3_depth.mp4 and /dev/null differ diff --git a/data/jx01/jx01_hex1.3_pyqt.mp4 b/data/jx01/jx01_hex1.3_pyqt.mp4 deleted file mode 100644 index 4d4fea9..0000000 Binary files a/data/jx01/jx01_hex1.3_pyqt.mp4 and /dev/null differ diff --git a/data/jx01/jx01_hex1.5_2_arrows.mp4 b/data/jx01/jx01_hex1.5_2_arrows.mp4 deleted file mode 100644 index bf92053..0000000 Binary files a/data/jx01/jx01_hex1.5_2_arrows.mp4 and /dev/null differ diff --git a/data/jx01/jx01_hex1.5_2_depth.mp4 b/data/jx01/jx01_hex1.5_2_depth.mp4 deleted file mode 100644 index a09711a..0000000 Binary files a/data/jx01/jx01_hex1.5_2_depth.mp4 and /dev/null differ diff --git a/data/jx01/jx01_hex1.5_2_pyqt.mp4 b/data/jx01/jx01_hex1.5_2_pyqt.mp4 deleted file mode 100644 index daf398a..0000000 Binary files a/data/jx01/jx01_hex1.5_2_pyqt.mp4 and /dev/null differ diff --git a/docs/api.md b/docs/api.md index e07c23f..7ae81b8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -25,7 +25,7 @@ pip install orisys-{version}-{python-version}-{python-version}-{os}_{machine}.wh ``` 如 ```bash -pip install orisys-0.4.0-cp310-cp310-win_amd64.whl +pip install orisys-0.4.1-cp310-cp310-win_amd64.whl ``` 3. **安装 CuPy(根据您的 CUDA 版本选择):** @@ -244,6 +244,243 @@ flow = sensor.read_info(sensor.info.VRAW) *** *** + +## `orisys.finger` 指纹 UV pipeline + +`examples/finger_pipeline.py` 展示了一个更适合指纹/单相机场景的高层 API: + +1. 使用 `FingerRuntime.open()` 打开视频源、加载 Blender 导出的资产,并构建 UV 展开映射。 +2. 用 `grab_and_process()` 或 `read_source() + process_frame()` 获取一帧展开后的触觉图像。 +3. 通过 `runtime.tactile`(`TactileProcessor`)调用 `compute_deformation()`、`compute_contact()`、`read_info()`。 +4. 退出前调用 `runtime.close()` 释放视频源和内部 `Sensor` 资源。 + +相比直接使用 `Sensor`,`FingerRuntime` 已封装了视频读取、相机尺寸设置、UV 展开和有效区域掩码传递,更适合作为指纹 2D/3D 示例与上层应用的入口。 + +### 1. `FingerRuntime.open` 方法 + +#### 描述 + +创建一个可复用的指纹 UV runtime。该方法会完成以下初始化: + +- 解析 `export_dir` 下的 `mesh.npz`、`camera_intrinsics.npz`、`world_to_cam.npy` +- 根据配置和标定信息构建/加载 `unroll_map_uv.npz` +- 打开相机或视频文件 +- 创建内部 `Sensor("external", ...)` +- 创建 `UVUnrollFrameProcessor` 与 `TactileProcessor` + +#### 输入参数 + +* **export_dir** (`str`,可选): 指纹导出资产目录,默认值来自 `orisys.finger.DEFAULT_FINGER_EXPORT_DIR`。目录下通常需要包含 `mesh.npz`、`camera_intrinsics.npz`、`world_to_cam.npy`。 +* **config** (`str`,可选): Orisys 配置文件路径,默认 `./config/dd02-ov.json`。 +* **map_cache** (`str`,可选): UV 展开缓存文件路径,默认值来自 `orisys.finger.DEFAULT_FINGER_MAP_CACHE`。 +* **source_scale** (`float`,可选): 输入相机分辨率缩放比例;未提供时根据配置自动推断。 +* **calib** (`str | None`,可选): 外部相机标定文件路径;未提供时优先使用导出资产中的内参。 +* **video** (`int | str`,可选): 相机编号、数字字符串或视频路径,默认 `0`。 +* **camera_id** (`int | None`,可选): `video` 的别名;若提供则优先使用。 +* **flip_source_vertical** (`bool`,可选): 是否在 UV 展开前垂直翻转输入源,默认 `True`。 +* **verbose** (`bool`,可选): 是否输出初始化日志。 +* **mirror** (`bool`,可选): 是否对内部触觉图再做镜像,默认 `False`。 + +#### 返回 + +* `FingerRuntime` 实例。 + +#### 示例 + +```python +from orisys.finger import FingerRuntime + +runtime = FingerRuntime.open( + config="./config/dd02-ov.json", + video=0, + verbose=True, +) +``` + +### 2. `FingerRuntime` 常用属性 + +* **runtime.geometry**: `FingerGeometry`,包含输入/输出分辨率、导出资产路径、相机内参、缓存路径等一次性初始化结果。 +* **runtime.domain**: UV 展开域对象,内部包含 remap、valid mask 以及可选 surface 数据。 +* **runtime.tactile**: `TactileProcessor`,用于执行形变、接触和数据读取。 +* **runtime.capture**: OpenCV `VideoCapture` 对象。 +* **runtime.valid_mask**: 当前 UV 域有效像素掩码。 + +### 3. `FingerRuntime.read_source` 方法 + +#### 描述 + +仅读取一帧原始视频源,等价于对内部 `cv2.VideoCapture` 调用 `read()`。适合需要把采集和 UV 展开分别计时的场景。 + +#### 返回 + +* `(ok, frame)`: + * **ok** (`bool`): 是否成功读取。 + * **frame** (`np.ndarray | None`): 原始 BGR 帧。 + +### 4. `FingerRuntime.process_frame` 方法 + +#### 描述 + +对一帧原始输入执行源图预处理、UV 展开,并把展开后的结果送入内部 `TactileProcessor`。这是 `examples/finger_pipeline.py` 中 profile 模式使用的方法。 + +#### 输入参数 + +* **frame** (`np.ndarray`,必填): 原始输入帧。 +* **color** (`bool`,可选): 为 `False` 时走灰度展开流程;为 `True` 时保留 BGR 颜色展开。默认 `False`。 + +#### 返回 + +* `np.ndarray`: 展开后的触觉图像。`color=False` 时通常为单通道灰度图;`color=True` 时为 BGR 图。 + +### 5. `FingerRuntime.grab_and_process` 方法 + +#### 描述 + +先读取一帧视频源,再调用 `process_frame()` 完成 UV 展开并推入内部触觉处理器。非 profile 模式下,`examples/finger_pipeline.py` 使用该方法作为主循环入口。 + +#### 输入参数 + +* **color** (`bool`,可选): 是否保留颜色,默认 `False`。 + +#### 返回 + +* `(ok, unrolled)`: + * **ok** (`bool`): 是否成功读取并处理。 + * **unrolled** (`np.ndarray | None`): 展开后的触觉图像。 + +### 6. `FingerRuntime.get_img` 方法 + +#### 描述 + +`Sensor.get_img()` 风格的别名:内部执行一次采集和 UV 展开,并在成功时返回当前展开图,否则返回 `None`。 + +#### 输入参数 + +* **color** (`bool`,可选): 是否保留颜色,默认 `False`。 + +#### 返回 + +* `np.ndarray | None` + +### 7. `FingerRuntime.warmup` 方法 + +#### 描述 + +先执行一次采集与 UV 展开,然后调用 `runtime.tactile.warmup()` 预热内部光流/HHD 相关缓存。`examples/finger_pipeline.py` 在进入主循环前会先执行一次 warmup。 + +#### 输入参数 + +* **color** (`bool`,可选): 是否保留颜色,默认 `False`。 + +#### 返回 + +* `(ok, unrolled)`: + * **ok** (`bool`): 是否成功完成 warmup 前的采集。 + * **unrolled** (`np.ndarray | None`): warmup 时得到的展开图。 + +### 8. `FingerRuntime.close` 方法 + +#### 描述 + +释放内部 `VideoCapture` 并调用 `runtime.tactile.disconnect()` 清理 `Sensor` 资源。程序退出前应调用。 + +### 9. `TactileProcessor` 类别 + +#### 描述 + +`TactileProcessor` 是对内部 `Sensor("external", ...)` 的窄封装。它保留了 `compute_deformation()`、`compute_contact()`、`read_info()`、`reset()` 等常用接口,同时屏蔽了视频采集与 UV 展开细节。 + +#### 常用属性 + +* **raw_sensor**: 底层 `Sensor` 对象。 +* **info**: 与 `Sensor.info` 相同的枚举入口。 +* **img / flow / vnormal / vshear / depth_map**: 当前帧处理结果。 +* **mask_stats**: 有效 UV 区域统计信息;`examples/finger_pipeline.py` 会读取 `mask_stats["coverage"]` 输出当前掩码覆盖率。 +* **valid_mask**: 当前有效区域掩码。 + +#### 说明 + +除 `push_frame()` / `warmup()` 之外,`TactileProcessor` 的 `compute_deformation()`、`compute_contact()`、`read_info()`、`reset()` 调用方式与 `Sensor` 基本一致,因此可直接复用前文 `Sensor` 章节中的参数说明。 + +### 10. 最小指纹 pipeline 示例 + +下面的流程与 `examples/finger_pipeline.py` 一致: + +```python +import time +import cv2 +import orisys +from orisys.finger import FingerRuntime + +runtime = FingerRuntime.open( + config="./config/dd02-ov.json", + video=0, + verbose=True, +) + +runtime.warmup(color=False) +tactile = runtime.tactile + +try: + while True: + ok, unrolled = runtime.grab_and_process(color=False) + if not ok: + break + + tactile.compute_deformation(check_motion=True, threshold=0) + tactile.compute_contact() + + fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = tactile.read_info( + tactile.info.FPS, + tactile.info.VRAW, + tactile.info.VNORMAL, + tactile.info.IMG, + tactile.info.CONTOUR, + tactile.info.CENTROID, + tactile.info.DEPTH, + tactile.info.FNORMAL, + tactile.info.FSHEARX, + tactile.info.FSHEARY, + ) + + arrows = orisys.util.draw_arrows( + unrolled, + flow, + threshold=0.4, + grid_spacing=10, + arrow_scale=1.0, + ) + cv2.imshow("UV Unrolled", arrows) + + print( + f"FPS={fps:.2f} normal={fnormal:.2f} " + f"shear=({fshearx:.2f}, {fsheary:.2f}) " + f"mask={tactile.mask_stats['coverage']:.2f}", + end="\r", + ) + + key = cv2.waitKey(1) & 0xFF + if key == ord("q"): + break + if key == ord("r"): + tactile.reset() +finally: + runtime.close() + cv2.destroyAllWindows() +``` + +### 11. 关于 profile 模式 + +`examples/finger_pipeline.py` 中的 `--profile` / `--profile-every` 是示例脚本参数,不属于 `orisys` SDK 核心 API。本质上它只是把主循环拆成: + +1. `runtime.read_source()` +2. `runtime.process_frame(...)` +3. `tactile.compute_deformation(...)` +4. `tactile.compute_contact()` +5. `tactile.read_info(...)` + +并对各阶段做耗时统计。因此在业务代码中,也可以按同样方式自行插入计时逻辑。 + # 可视化相关 ## 1. `util.draw_arrows` 方法 diff --git a/docs/get_started.md b/docs/get_started.md index cc11ec2..42e73e6 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -1,5 +1,5 @@ 叠动科技软件使用手册 -版本: 0.4.0 +版本: 0.4.1 更新日期: 2026年4月16日 # 简介 @@ -57,9 +57,9 @@ orisys SDK 以 .whl 文件形式提供,您需要根据操作系统、Python ## 下载正确的 .whl 文件 从官方渠道获取 .whl 文件,文件名格式通常为: orisys-{版本}-{Python版本}-{Python版本}-{操作系统}_{硬件架构}.whl -例如:orisys-0.4.0-cp310-cp310-win_amd64.whl 表示: +例如:orisys-0.4.1-cp310-cp310-win_amd64.whl 表示: -版本:0.4.0 +版本:0.4.1 Python 版本:3.10(cp310) 操作系统:Windows(win) 硬件架构:64位(amd64) @@ -71,7 +71,7 @@ Python 版本:3.10(cp310) ``` bash # 请将文件名替换为您实际下载的文件名 -pip install orisys-0.4.0-cp310-cp310-win_amd64.whl +pip install orisys-0.4.1-cp310-cp310-win_amd64.whl ``` ## 安装 CuPy(可选) @@ -108,11 +108,17 @@ python import orisys print(orisys.__version__) ``` -如果输出版本号(例如 0.4.0),则说明安装成功。 +如果输出版本号(例如 0.4.1),则说明安装成功。 # 使用方法 ## 运行示例程序 -套件里面包含了例子展示用法,运行步骤如下: +套件里面包含了多个例子,建议都在仓库根目录运行(即 `config/`、`examples/`、`assets/` 同级目录)。常见入口分为两类: + +1. `examples/basic_pipeline.py`:复眼/拼接传感器基础 OpenCV 示例 +2. `examples/finger_pipeline.py`:指纹传感器 UV 展开最小流程示例 +3. `examples/finger_pipeline_3dview.py`:指纹传感器 Qt 3D 可视化界面 + +下面先介绍最基础的 `basic_pipeline.py` 运行方式。 ### Windows: 1. 检查是否有其他正在连接的USB摄像头设备:打开powershell, 运行命令`Get-PnpDevice -PresentOnly | Where-Object { $_.Class -match '^Camera' }`,如果没有的话不会有输出。 @@ -134,5 +140,54 @@ print(orisys.__version__) ``` 5. 程序会运行,并自动打开可视化窗口和打印资料,在窗口上按键盘”q”键可以停止运行,按”r”键可以重设数据 +### 指纹传感器 UV 示例 + +如果您使用的是指纹/单相机 UV 展开流程,可运行: + +``` bash +python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json +``` + +如果输入来自视频文件,也可以改用: + +``` bash +python examples/finger_pipeline.py --video ./data/sample.mp4 --config ./config/dd02-ov.json +``` + +该示例会: + +- 读取相机或视频源 +- 将图像展开到 UV 平面 +- 计算光流、法向/切向力和接触区域 +- 在 OpenCV 窗口中显示展开后的光流箭头图 + +常用参数: + +- `--verbose`:输出更详细的初始化日志 +- `--profile`:打印每个阶段的耗时统计 +- `--profile-every N`:每 N 帧输出一次平均耗时 + +例如: + +``` bash +python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json --profile --profile-every 30 +``` + +### 指纹 3D 可视化界面 + +如果已经安装 GUI 依赖,还可以运行: + +``` bash +pip install -e ".[gui]" +python examples/finger_pipeline_3dview.py --video 0 --config ./config/dd02-ov.json +``` + +该界面会提供: + +- 指纹 UV 展开图 +- 力曲线 +- 3D mesh 视图 +- 录制与重置等交互控件 + ## 开发套件使用方法 参考开发套件附带的开发文档。 \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 4ee9a3d..8d5a197 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,7 +18,7 @@ For the Qt dashboard: ```bash pip install -e ".[gui]" -python examples/finger_pipeline_3dview.py -v 0 -c ./config/dd02-ov.json +python examples/finger_pipeline_3dview.py --video 0 --config ./config/dd02-ov.json ``` For the minimal finger pipeline: @@ -27,6 +27,18 @@ For the minimal finger pipeline: python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json ``` +For the minimal finger pipeline with profiling: + +```bash +python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json --profile --profile-every 30 +``` + +For a video-file finger run: + +```bash +python examples/finger_pipeline.py --video ./data/sample.mp4 --config ./config/dd02-ov.json +``` + For the basic compound-eye pipeline: ```bash @@ -50,3 +62,5 @@ pip install -e ".[gui]" - `finger_pipeline_3dview.py` is the main interactive demo. It uses `app_paths.py` so the same entry point works in editable and packaged runs. - Dashboard controls: click **启动** to start acquisition; `Q` quits, `R` resets, `S` starts recording, `E` stops recording, and `Esc` toggles fullscreen. - `finger_pipeline.py` is the leanest finger example if you want to validate UV unroll + force/contact processing without the full dashboard. +- `finger_pipeline.py` uses `orisys.finger.FingerRuntime` as the high-level entry point, then runs tactile analysis through `runtime.tactile`. +- In `--profile` mode, the example splits the loop into capture, UV unroll, deformation, contact, and display stages so you can measure each step separately. diff --git a/src/orisys/__init__.py b/src/orisys/__init__.py index 0b7eb55..3069532 100644 --- a/src/orisys/__init__.py +++ b/src/orisys/__init__.py @@ -5,7 +5,7 @@ A Python library for processing and analyzing optical tactile sensor data, including image stitching, optical flow tracking, and deformation decomposition. """ -__version__ = "0.4.0" +__version__ = "0.4.1" __author__ = "Orisys" from .sensor import Sensor diff --git a/src/orisys/finger/exports.py b/src/orisys/finger/exports.py index 7f39d22..d451df7 100644 --- a/src/orisys/finger/exports.py +++ b/src/orisys/finger/exports.py @@ -11,7 +11,7 @@ from ..unroll import ( scaled_map_cache_path, ) -DEFAULT_FINGER_EXPORT_DIR = "assets/finger/30fps" +DEFAULT_FINGER_EXPORT_DIR = "assets/finger/30fps-0703" DEFAULT_FINGER_MAP_CACHE = f"{DEFAULT_FINGER_EXPORT_DIR}/unroll_map_uv.npz" diff --git a/tests/test_pyproject.py b/tests/test_pyproject.py index f4533f7..92d4768 100644 --- a/tests/test_pyproject.py +++ b/tests/test_pyproject.py @@ -35,7 +35,7 @@ def test_pyproject_dynamic_version(packaging_utils): sys.path.insert(0, str(build_dir)) from pyproject_utils import get_version - assert get_version(REPO_ROOT) == "0.4.0" + assert get_version(REPO_ROOT) == "0.4.1" def test_pyproject_bundle_manifest(packaging_utils): diff --git a/tools/3d_patch_generation_blender/dd02c_0607.blend b/tools/3d_patch_generation_blender/dd02c_0607.blend index 6abfed0..eac715a 100644 Binary files a/tools/3d_patch_generation_blender/dd02c_0607.blend and b/tools/3d_patch_generation_blender/dd02c_0607.blend differ diff --git a/tools/3d_patch_generation_blender/dd02c_0607.blend1 b/tools/3d_patch_generation_blender/dd02c_0607.blend1 index d140d5a..23f70c5 100644 Binary files a/tools/3d_patch_generation_blender/dd02c_0607.blend1 and b/tools/3d_patch_generation_blender/dd02c_0607.blend1 differ diff --git a/tools/3d_patch_generation_blender/patch_texture.png b/tools/3d_patch_generation_blender/patch_texture.png deleted file mode 100644 index 7d3d9fa..0000000 Binary files a/tools/3d_patch_generation_blender/patch_texture.png and /dev/null differ diff --git a/tools/3d_patch_generation_blender/patch_texture_0621_X80Y45.png b/tools/3d_patch_generation_blender/patch_texture_0621_X80Y45.png new file mode 100644 index 0000000..e1d1229 Binary files /dev/null and b/tools/3d_patch_generation_blender/patch_texture_0621_X80Y45.png differ diff --git a/tools/3d_patch_generation_blender/patch_texture_0703_X80Y45.png b/tools/3d_patch_generation_blender/patch_texture_0703_X80Y45.png new file mode 100644 index 0000000..70ab865 Binary files /dev/null and b/tools/3d_patch_generation_blender/patch_texture_0703_X80Y45.png differ