diff --git a/.gitignore b/.gitignore index b18cd66..ed52d69 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ orisys.egg-info/ /models/ /doc/.quarto/ *.npy -*.png \ No newline at end of file +*.png +SDK/ \ No newline at end of file diff --git a/build_nuitka.py b/build_nuitka.py index e58c054..dcf8de9 100644 --- a/build_nuitka.py +++ b/build_nuitka.py @@ -5,6 +5,28 @@ import subprocess from pathlib import Path from importlib.machinery import EXTENSION_SUFFIXES +INCLUDE_LAYOUT = { + "configs": { + "source_dir": Path("src/orisys/configs"), + "target_subdir": Path("configs"), + "patterns": ["ddjx01.json"], + }, + "examples": { + "source_dir": Path("."), + "target_subdir": Path("examples"), + "files": [ + Path("example1_basic_pipeline.py"), + Path("example2_two_sensor_pipeline.py"), + Path("example3_plot_data.py"), + ], + }, + "docs": { + "source_dir": Path("docs"), + "target_subdir": Path("docs"), + "patterns": ["*.md", "*.pdf"], + }, +} + def install_nuitka(): print("Checking Nuitka installation...") @@ -49,11 +71,49 @@ def move_wheel_to_sdk(wheel_path: Path, version: str) -> Path: return dest_wheel +def copy_included_files(pkg_dir: Path) -> dict[str, int]: + copied_counts = {} + for group_name, group_cfg in INCLUDE_LAYOUT.items(): + src_dir = group_cfg["source_dir"] + dst_dir = pkg_dir / group_cfg["target_subdir"] + dst_dir.mkdir(exist_ok=True) + + copied = 0 + if "files" in group_cfg: + for file_path in group_cfg["files"]: + if file_path.exists(): + shutil.copy(file_path, dst_dir) + copied += 1 + print(f"Copied {group_name}: {file_path.name}") + else: + for pattern in group_cfg.get("patterns", []): + for src_file in src_dir.glob(pattern): + shutil.copy(src_file, dst_dir) + copied += 1 + print(f"Copied {group_name}: {src_file.name}") + + if group_name == "examples": + (dst_dir / "__init__.py").write_text("# Examples for orisys package\n", encoding="utf-8") + + copied_counts[group_name] = copied + return copied_counts + + +def get_package_data_entries(ext_suffix: str) -> dict[str, list[str]]: + package_data = {"orisys": [f"*{ext_suffix}"], "orisys.examples": ["*.py"]} + for group_name, group_cfg in INCLUDE_LAYOUT.items(): + if group_name == "examples": + continue + target_subdir = str(group_cfg["target_subdir"]).replace("\\", "/") + for pattern in group_cfg.get("patterns", []): + package_data["orisys"].append(f"{target_subdir}/{pattern}") + return package_data + + def build(): install_nuitka() version = get_version() - src_dir = Path("src/orisys") output_dir = Path("build_nuitka") dist_dir = Path("dist_nuitka") @@ -93,26 +153,11 @@ def build(): shutil.copy(ext_file, dest_ext) print(f"Copied compiled package to {dest_ext}") - configs_src = src_dir / "configs" - configs_dst = pkg_dir / "configs" - configs_dst.mkdir() - json_count = 0 - for json_file in configs_src.glob("*.json"): - shutil.copy(json_file, configs_dst) - json_count += 1 - print(f"Copied {json_count} JSON config files") - - examples_dst = pkg_dir / "examples" - examples_dst.mkdir(exist_ok=True) - (examples_dst / "__init__.py").write_text("# Examples for orisys package\n", encoding="utf-8") - example_files = [Path("example1_basic_pipeline.py"), Path("example2_two_sensor_pipeline.py")] - example_count = 0 - for example_file in example_files: - if example_file.exists(): - shutil.copy(example_file, examples_dst) - example_count += 1 - print(f"Copied example: {example_file.name}") - print(f"Copied {example_count} example files to orisys/examples/") + copied_counts = copy_included_files(pkg_dir) + print(f"Copied {copied_counts['configs']} JSON config files") + print(f"Copied {copied_counts['examples']} example files to orisys/examples/") + print(f"Copied {copied_counts['docs']} doc files to orisys/docs/") + package_data = get_package_data_entries(ext_suffix) setup_py_path = dist_dir / "setup.py" setup_content = f""" @@ -129,10 +174,7 @@ setup( author="Orisys", packages=['orisys', 'orisys.examples'], package_dir={{'': 'wheel_root'}}, - package_data={{ - 'orisys': ['*{ext_suffix}', 'configs/*.json'], - 'orisys.examples': ['*.py'], - }}, + package_data={package_data}, include_package_data=True, install_requires=[ "numpy>=1.24.4", diff --git a/config/compound_eye.png b/config/compound_eye.png deleted file mode 100644 index 46b2c98..0000000 Binary files a/config/compound_eye.png and /dev/null differ diff --git a/config/ddjx01_wuxi.json b/config/ddjx01_wuxi.json new file mode 100644 index 0000000..0f0a874 --- /dev/null +++ b/config/ddjx01_wuxi.json @@ -0,0 +1,45 @@ +{ + "name": "prototype", + "version": "1.0", + "camera": { + "resolution_raw": [1280, 720], + "fps": 15, + "units": 15, + "units_used": 9, + "resolution_stitch": [263, 263], + "stitch_res_list": [365,263,123,63] + }, + "stitcher": { + "min_area_frac": 0.01, + "max_area_frac": 0.04, + "max_aspect_ratio": 25, + "index": [1,2,3,6,7,8,11,12,13], + "margin": 10, + "nominal_ov_pos": 67, + "n": [3,3], + "row_tol": 0.6, + "box_size": [180, 180] + }, + "tracker": { + "adaptive": true, + "lower_threshold": 12.0, + "upper_threshold": 18.0, + "loop_closure_interval": 1, + "algorithm": "dis", + "dis_preset": "medium", + "dis_mode": 3, + "algorithm_list": ["raft", "dis"], + "hhd_downsample": 3, + "backend": "cpu" + }, + "contact": { + "min_contour_area": 3000, + "flow_threshold":1, + "div_threshold":0.02, + "expansion_threshold": 0.01, + "kernel_size": [5,5], + "denoise_sigma": 10 + } +} + + diff --git a/API文档-0.3.0.pdf b/docs/API文档-0.3.0.pdf similarity index 100% rename from API文档-0.3.0.pdf rename to docs/API文档-0.3.0.pdf diff --git a/Readme.docx b/docs/Readme.docx similarity index 100% rename from Readme.docx rename to docs/Readme.docx diff --git a/Readme.md b/docs/Readme.md similarity index 100% rename from Readme.md rename to docs/Readme.md diff --git a/doc/get_started.docx b/docs/get_started.docx similarity index 100% rename from doc/get_started.docx rename to docs/get_started.docx diff --git a/doc/get_started.md b/docs/get_started.md similarity index 100% rename from doc/get_started.md rename to docs/get_started.md diff --git a/doc/render_command.txt b/docs/render_command.txt similarity index 100% rename from doc/render_command.txt rename to docs/render_command.txt diff --git a/doc/软件使用说明-0.3.0.pdf b/docs/软件使用说明-0.3.0.pdf similarity index 100% rename from doc/软件使用说明-0.3.0.pdf rename to docs/软件使用说明-0.3.0.pdf diff --git a/five_sensors.py b/five_sensors.py deleted file mode 100644 index 6277747..0000000 --- a/five_sensors.py +++ /dev/null @@ -1,296 +0,0 @@ -""" -示例 4: 多传感器并行处理(多进程模式 - 不使用 MultiSensor) - -本示例演示如何使用 multiprocessing 同时运行两个传感器: -- 每个传感器运行在独立的进程中 -- 每个进程拥有独立的 CUDA 上下文 -- 使用 multiprocessing.Process 和 Queue 进行进程间通信 - -架构说明: - 进程 1 (传感器 0): 采集 → 光流 → HHD → 接触检测 → 队列 - 进程 2 (传感器 1): 采集 → 光流 → HHD → 接触检测 → 队列 - ↓ - 主进程: 显示 ← ───────────────┘ - -适用场景:多指触觉传感器、多视角采集、需要 GPU 加速的多路处理 - -注意事项: -- Windows 下必须在 if __name__ == '__main__' 中调用 mp.freeze_support() -- 每个传感器进程会占用独立的 GPU 显存 -""" -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 - - -def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_event, target_fps=None): - """ - 传感器工作进程函数 - - Args: - sensor_id: 传感器编号 - vid_src: 视频源(文件路径或摄像头索引) - config_name: 配置名称 - cal_path: 标定路径 - result_queue: 结果队列,用于向主进程发送数据 - stop_event: 停止事件,用于通知工作进程退出 - target_fps: 目标帧率;若设置则用定时器限速,使多传感器 FPS 接近一致 - """ - try: - # 在每个进程中创建独立的传感器实例 - sensor = orisys.Sensor( - vid_src, - isstitch=True, - cuda=True, - verbose=False, # 减少子进程输出 - config_name=config_name, - cal_path=cal_path - ) - - print(f"传感器 {sensor_id} 已启动" + (f" (target_fps={target_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: 精确限速(time.sleep 在 Windows 上精度仅 ~15.6ms,需留足余量后 spin-wait) - if frame_interval > 0: - deadline = t_start + frame_interval - remaining = deadline - time.perf_counter() - if remaining > 0.018: - time.sleep(remaining - 0.018) # coarse sleep; leave 18ms margin for Windows granularity - while time.perf_counter() < deadline: - pass # spin-wait for the last few ms - - # 清理资源 - 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 不限速(可跑满 100+ FPS);设为正数则 sleep 限速,使 FPS ≤ target_fps - target_fps = 30 # 不限速;设为 30 等可 cap 帧率 - - num_sensors = 5 - sensor_configs = [ - { - 'sensor_id': 0, - 'vid_src': './s1_480p.mp4', - 'config_name': './config/prototype_of3_480p.json', - 'cal_path': './config/dd01a_0_480p.npy' - }, - { - 'sensor_id': 1, - 'vid_src': './s1_480p.mp4', # 可以改为不同的视频源或摄像头索引 - 'config_name': './config/prototype_of3_480p.json', - 'cal_path': './config/dd01a_0_480p.npy' - }, - { - 'sensor_id': 2, - 'vid_src': './s1_480p.mp4', # 可以改为不同的视频源或摄像头索引 - 'config_name': './config/prototype_of3_480p.json', - 'cal_path': './config/dd01a_0_480p.npy' - }, - { - 'sensor_id': 3, - 'vid_src': './s1_480p.mp4', # 可以改为不同的视频源或摄像头索引 - 'config_name': './config/prototype_of3_480p.json', - 'cal_path': './config/dd01a_0_480p.npy' - }, - { - 'sensor_id': 4, - 'vid_src': './s1_480p.mp4', # 可以改为不同的视频源或摄像头索引 - 'config_name': './config/prototype_of3_480p.json', - 'cal_path': './config/dd01a_0_480p.npy' - }, - ] - - # ========================================================================= - # 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['cal_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帧打印一次,进一步减少I/O - - # 用于控制按键检查频率 - 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"Deformation {sid}", 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() diff --git a/hand_test.py b/hand_test.py deleted file mode 100644 index 3f5d3de..0000000 --- a/hand_test.py +++ /dev/null @@ -1,354 +0,0 @@ -""" -示例 4: 多传感器并行处理(多进程模式 - 不使用 MultiSensor) - -本示例演示如何使用 multiprocessing 同时运行两个传感器: -- 每个传感器运行在独立的进程中 -- 每个进程拥有独立的 CUDA 上下文 -- 使用 multiprocessing.Process 和 Queue 进行进程间通信 - -架构说明: - 进程 1 (传感器 0): 采集 → 光流 → HHD → 接触检测 → 队列 - 进程 2 (传感器 1): 采集 → 光流 → HHD → 接触检测 → 队列 - ↓ - 主进程: 显示 ← ───────────────┘ - -适用场景:多指触觉传感器、多视角采集、需要 GPU 加速的多路处理 - -注意事项: -- Windows 下必须在 if __name__ == '__main__' 中调用 mp.freeze_support() -- 每个传感器进程会占用独立的 GPU 显存 -""" -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 ctypes -import platform -import ctypes - -# Multi-sensor FPS stabilization options -STABILITY_MODE = True # True: prioritize similar FPS across sensors -TARGET_FPS = 30.0 # Used only when STABILITY_MODE=True -SEND_VISUALIZATION_DATA = False # True sends img/flow/depth to main process (higher IPC cost) - -def _enable_high_res_sleep(): - """ - Enable high-resolution sleep for accurate FPS pacing. - Note: time.perf_counter() is already high-resolution on all platforms (Windows, macOS, Linux). - This function only improves time.sleep() accuracy on Windows. - """ - if os.name == "nt": # Windows (x64 and ARM64) - try: - # timeBeginPeriod improves time.sleep() accuracy (default ~15ms -> 1ms) - # time.perf_counter() is already high-res on Windows (uses QueryPerformanceCounter) - winmm = ctypes.WinDLL("winmm") - winmm.timeBeginPeriod(1) # Request 1ms timer resolution for sleep() - return winmm - except Exception: - return None - # macOS and Linux: time.perf_counter() and time.sleep() are already high-resolution - # No special setup needed - return None - - -def _disable_high_res_sleep(handle): - """Restore default scheduler timer resolution (Windows only).""" - if handle is None: - return - if os.name == "nt" and isinstance(handle, ctypes.WinDLL): - try: - handle.timeEndPeriod(1) - except Exception: - pass - # macOS and Linux don't need cleanup - - -def sensor_worker(sensor_id, vid_src, config_name, cal_path, result_queue, stop_event): - """ - 传感器工作进程函数 - - Args: - sensor_id: 传感器编号 - vid_src: 视频源(文件路径或摄像头索引) - config_name: 配置名称 - result_queue: 结果队列,用于向主进程发送数据 - stop_event: 停止事件,用于通知工作进程退出 - """ - timer_handle = None - try: - # Limit per-process CPU thread fan-out to reduce contention jitter. - os.environ["OMP_NUM_THREADS"] = "1" - os.environ["OPENBLAS_NUM_THREADS"] = "1" - os.environ["MKL_NUM_THREADS"] = "1" - cv2.setNumThreads(1) - if STABILITY_MODE and TARGET_FPS > 0: - timer_handle = _enable_high_res_sleep() - - # 在每个进程中创建独立的传感器实例 - sensor = orisys.Sensor( - vid_src, - isstitch=True, - cuda=True, - verbose=False, # 减少子进程输出 - config_name=config_name, - cal_path=cal_path - ) - - print(f"传感器 {sensor_id} 已启动") - - target_period = (1.0 / TARGET_FPS) if (STABILITY_MODE and TARGET_FPS > 0) else 0.0 - next_tick = time.perf_counter() - while not stop_event.is_set(): - try: - # 步骤1: 获取图像 - sensor.get_img() - - # 步骤2: 计算形变场 - sensor.compute_deformation() - - # 步骤3: 计算触碰点 - sensor.compute_contact() - - # 步骤4: 读取信息 - if SEND_VISUALIZATION_DATA: - fps, flow, img, contour, centroid, depth_map = sensor.read_info( - sensor.info.FPS, - sensor.info.VRAW, - sensor.info.IMG, - sensor.info.CONTOUR, - sensor.info.CENTROID, - sensor.info.DEPTH - ) - # Full payload (higher IPC cost) - data = { - 'sensor_id': sensor_id, - 'fps': fps, - 'flow': flow.copy() if flow 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() - } - else: - # Lightweight payload for stable multi-process throughput - fps, = sensor.read_info(sensor.info.FPS) - data = { - 'sensor_id': sensor_id, - 'fps': fps, - 'timestamp': time.time() - } - - # 步骤6: 发送数据到主进程(非阻塞) - try: - result_queue.put_nowait(data) - except: - # Queue full: keep latest data (drop oldest) - try: - result_queue.get_nowait() - result_queue.put_nowait(data) - except: - pass - - # Pace workers to a common target so sensor FPS stays similar. - # Use drift-free scheduler: next_tick increments by fixed period. - if target_period > 0: - next_tick += target_period - now = time.perf_counter() - sleep_time = next_tick - now - if sleep_time > 0: - time.sleep(sleep_time) - else: - # If we are behind, reset schedule to current time. - next_tick = now - - except Exception as e: - print(f"传感器 {sensor_id} 处理错误: {e}") - time.sleep(0.1) - - # 清理资源 - sensor.disconnect() - if timer_handle is not None: - _disable_high_res_sleep(timer_handle) - print(f"传感器 {sensor_id} 已停止") - - except Exception as e: - if timer_handle is not None: - _disable_high_res_sleep(timer_handle) - print(f"传感器 {sensor_id} 初始化失败: {e}") - result_queue.put({'sensor_id': sensor_id, 'error': str(e)}) - - -def main(): - # ========================================================================= - # 1. 配置多个传感器 - # ========================================================================= - num_sensors = 5 - sensor_configs = [ - { - 'sensor_id': 0, - 'vid_src': './s1_720p.mp4', - 'config_name': './config/prototype_of3_720p.json', - 'cal_path': './config/dd01a_0_720p.npy' - }, - { - 'sensor_id': 1, - 'vid_src': './s1_720p.mp4', - 'config_name': './config/prototype_of3_720p.json', - 'cal_path': './config/dd01a_0_720p.npy' - }, - { - 'sensor_id': 2, - 'vid_src': './s1_720p.mp4', - 'config_name': './config/prototype_of3_720p.json', - 'cal_path': './config/dd01a_0_720p.npy' - }, - { - 'sensor_id': 3, - 'vid_src': './s1_720p.mp4', - 'config_name': './config/prototype_of3_720p.json', - 'cal_path': './config/dd01a_0_720p.npy' - }, - { - 'sensor_id': 4, - 'vid_src': './s1_720p.mp4', - 'config_name': './config/prototype_of3_720p.json', - 'cal_path': './config/dd01a_0_720p.npy' - }, - ] - - # ========================================================================= - # 2. 创建进程间通信对象 - # ========================================================================= - # Keep queue short to prevent backlog-induced jitter (latest-data policy) - result_queues = [Queue(maxsize=2) 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['cal_path'], result_queues[i], stop_events[i]) - ) - 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帧打印一次,进一步减少I/O - - # 用于控制按键检查频率 - 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.get('img') is not None and data.get('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"Deformation {sid}", 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() diff --git a/test_cam.py b/test_cam.py deleted file mode 100644 index 6f5e2c7..0000000 --- a/test_cam.py +++ /dev/null @@ -1,75 +0,0 @@ -import cv2 -import time - -# Toggle these to test whether exposure is limiting delivered FPS. -USE_MANUAL_EXPOSURE = False -MANUAL_EXPOSURE = -6 # Typical V4L2/OpenCV manual range is negative values. - -cap = cv2.VideoCapture(4, cv2.CAP_V4L2) -cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('M', 'J', 'P', 'G')) -cap.set(cv2.CAP_PROP_FPS, 30) -cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) -cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) -cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) - -# For V4L2 in OpenCV: auto-exposure is usually 3, manual is usually 1. -if USE_MANUAL_EXPOSURE: - ok_auto = cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1) - ok_exp = cap.set(cv2.CAP_PROP_EXPOSURE, MANUAL_EXPOSURE) -else: - ok_auto = cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 3) - ok_exp = True - -actual_fourcc = int(cap.get(cv2.CAP_PROP_FOURCC)) -print(f"Backend: {cap.getBackendName()}") -print( - f"FOURCC: {chr(actual_fourcc & 0xFF)}" - f"{chr((actual_fourcc >> 8) & 0xFF)}" - f"{chr((actual_fourcc >> 16) & 0xFF)}" - f"{chr((actual_fourcc >> 24) & 0xFF)}" -) -print(f"Resolution: {int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))}x{int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))}") -print(f"FPS: {cap.get(cv2.CAP_PROP_FPS)}") -print( - f"Exposure mode: {'manual' if USE_MANUAL_EXPOSURE else 'auto'} " - f"(set_auto_ok={ok_auto}, set_exp_ok={ok_exp})" -) -print(f"AutoExposure(readback): {cap.get(cv2.CAP_PROP_AUTO_EXPOSURE)}") -print(f"Exposure(readback): {cap.get(cv2.CAP_PROP_EXPOSURE)}") - -frame_count = 0 -start = time.perf_counter() -while cap.isOpened(): - t0 = time.perf_counter() - ok = cap.grab() - t1 = time.perf_counter() - if not ok: - break - - ret, frame = cap.retrieve() - t2 = time.perf_counter() - if not ret: - continue - - cv2.imshow("webcam", frame) - t3 = time.perf_counter() - key = cv2.waitKey(1) & 0xFF - t4 = time.perf_counter() - - frame_count += 1 - if frame_count % 30 == 0: - elapsed = t4 - start - print( - f"Frame {frame_count}: " - f"grab={1000*(t1-t0):.1f}ms, " - f"retrieve={1000*(t2-t1):.1f}ms, " - f"imshow={1000*(t3-t2):.1f}ms, " - f"waitKey={1000*(t4-t3):.1f}ms, " - f"avg_fps={frame_count/elapsed:.1f}" - ) - - if key == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() \ No newline at end of file