diff --git a/Point.py b/Point.py index 1b1c795..158d60b 100644 --- a/Point.py +++ b/Point.py @@ -2,7 +2,6 @@ import numpy as np from PIL import Image import math -import SimpleView_SaveImage import config from cat import clip_and_rotate_point_cloud, merge_point_clouds from image import detect_large_holes @@ -126,6 +125,85 @@ def tiff_depth_to_point_cloud(tiff_path,sn, dedup=True): return points + +# 新增点云数据转换函数 +def convert_sdk_points(sValue, width, height): + """ + 将 SDK 原始点云数据转换为 NumPy 点云数组 + + :param sValue: tuple(float), SDK 输出的扁平化点云数据 + :param width: int, 点云宽度 + :param height: int, 点云高度 + :return: np.ndarray(shape=(N,3)), 三维点云数组 + """ + points = np.array(sValue).reshape(-1, 3) # 形状转换 (N,3) + # 剔除无效点(X/Y/Z 任一为 NaN 或 Inf) + mask = np.all(np.isfinite(points), axis=1) + return points[mask] + + +def process_point_cloud(points, sn, dedup=True): + """ + 对原始点云应用旋转、裁剪和去重 + + :param points: np.ndarray(shape=(N,3)), 原始点云数据 + :param sn: str, 设备序列号(用于加载配置) + :param dedup: bool, 是否启用去重 + :return: list of [x, y, z], 处理后的点云列表 + """ + # 加载配置参数 + clip_config = config.CUT_CONFIG_MAP.get(sn) + if clip_config: + min_pt = np.array(clip_config.get("min_pt", [-np.inf] * 3)) + max_pt = np.array(clip_config.get("max_pt", [np.inf] * 3)) + rotation = np.array(clip_config.get("rotation", [1, 0, 0, 0, 1, 0, 0, 0, 1])).reshape(3, 3) + else: + min_pt = max_pt = rotation = None + + processed_points = [] + seen_xy = set() + + for point in points: + x, y, z = point + + # 无效点过滤(Z ≤ 0 或超出最大距离) + if z <= 0 or z > config.CAMERA_CONFIG_MAP[sn].get("max_z", np.inf): + continue + + # 应用旋转矩阵 + if clip_config: + rotated = rotation @ point + if not np.all(rotated >= min_pt) or not np.all(rotated <= max_pt): + continue + x_final, y_final, z_final = rotated + else: + x_final, y_final, z_final = x, y, z + + # 去重逻辑(保留浮点精度) + if dedup: + # 使用浮点哈希避免离散化损失 + key = (round(x_final, 3), round(y_final, 3)) + if key in seen_xy: + continue + seen_xy.add(key) + + processed_points.append([x_final, y_final, z_final]) + + return processed_points + + +def sValue_to_pcd(sValue,stPointCloudImage,sn): + # 数据格式转换 + points = convert_sdk_points(sValue, stPointCloudImage.nWidth, stPointCloudImage.nHeight) + + # 应用旋转、裁剪、去重(假设设备序列号已知) + processed_points = process_point_cloud(points, sn, dedup=True) + + output_ply_path = config.save_path("pcd", sn + ".pcd") + # 保存结果 + write_pcd(output_ply_path, processed_points) + + def write_pcd(filename, points): """ 将点云写入 PCD 文件 @@ -155,8 +233,4 @@ def write_pcd(filename, points): if __name__ == '__main__': - paths = SimpleView_SaveImage.pic("00DA6823936") - tiff_depth_to_point_clouds(paths, "00DA6823936") - # tiff_depth_to_point_clouds(["D:/PycharmProjects/Hik3D/image/2025-06-24/depth/145209062_-Depth.tiff" - # ,"D:/PycharmProjects/Hik3D/image/2025-06-24/depth/145209062_-Depth.tiff" - # ,"D:/PycharmProjects/Hik3D/image/2025-06-24/depth/145209062_-Depth.tiff"], "00DA6823936") \ No newline at end of file + tiff_depth_to_point_cloud("D:/git/test/hik3d-python/image/2025-06-26/depth/191330147_-Depth.tiff", "00DA6823936") diff --git a/SimpleView_SaveImage.py b/SimpleView_SaveImage.py index 450667e..936f1b1 100644 --- a/SimpleView_SaveImage.py +++ b/SimpleView_SaveImage.py @@ -7,7 +7,7 @@ import os import struct from ctypes import * from datetime import datetime - +import Point as point from Mv3dRgbdImport.Mv3dRgbdDefine import * from Mv3dRgbdImport.Mv3dRgbdApi import * from Mv3dRgbdImport.Mv3dRgbdDefine import DeviceType_Ethernet, DeviceType_USB, DeviceType_Ethernet_Vir, DeviceType_USB_Vir, MV3D_RGBD_FLOAT_EXPOSURETIME, \ @@ -15,7 +15,6 @@ from Mv3dRgbdImport.Mv3dRgbdDefine import DeviceType_Ethernet, DeviceType_USB, D FileType_BMP,FileType_TIFF,ImageType_Depth, ImageType_RGB8_Planar, ImageType_YUV420SP_NV12 ,ImageType_YUV420SP_NV21 , ImageType_YUV422, ImageType_Mono8 import config as configMap - # 全局变量 SN_MAP = {} # {sn: camera_instance} @@ -52,7 +51,6 @@ def initialize_devices(): SN_MAP[serial_number] = camera print(f"Successfully added device {serial_number} to SN_MAP") - def pic(sn): camera = SN_MAP.get(sn) @@ -65,19 +63,15 @@ def pic(sn): print(f"No config found for SN: {sn}") return - time_on = config.get("time_on", 0) # 延时开始(毫秒) - time_off = config.get("time_off", 0) # 拍照总时长(毫秒) - time_hop = config.get("time_hop", 0) # 每次拍照间隔(毫秒) - - end_time = time.time() + (time_off / 1000.0) + time_on = config.get("time_on", 0) # 延时开始(毫秒) print(f"Delaying start by {time_on}ms...") time.sleep(time_on / 1000.0) # 转成秒 - - frame_count = 0 - - saved_tiff_files = [] # 用于存储保存的 TIFF 文件路径 - print(f"Start continuous capturing for {time_off}ms...") + saved_files = { + "depth": [], + "color": [], + "pcd": [] + } # 开始取流 ret = camera.MV3D_RGBD_Start() @@ -86,56 +80,67 @@ def pic(sn): return try: - while time.time() < end_time: - stFrameData = MV3D_RGBD_FRAME_DATA() - - # 获取帧数据 - ret = camera.MV3D_RGBD_FetchFrame(pointer(stFrameData), 5000) - if ret == MV3D_RGBD_OK: - frame_count += 1 - - for i in range(stFrameData.nImageCount): - image_info = stFrameData.stImageData[i] - - # 保存深度图 - if image_info.enImageType == ImageType_Depth: - file_name = configMap.save_path("depth","-Depth") - ret_save = camera.MV3D_RGBD_SaveImage(pointer(image_info), FileType_TIFF, file_name) - print("Saved depth image." if ret_save == MV3D_RGBD_OK else "Failed to save depth image.") - if ret_save == MV3D_RGBD_OK: - saved_tiff_files.append(file_name) # 记录保存的 TIFF 文件路径 - # print(f"Saved depth image: {file_name}") - else: - print("Failed to save depth image.") - - # 保存彩色图 - elif image_info.enImageType in ( + stFrameData = MV3D_RGBD_FRAME_DATA() + + # 获取单帧数据 + ret = camera.MV3D_RGBD_FetchFrame(pointer(stFrameData), 5000) + if ret == MV3D_RGBD_OK: + + for i in range(stFrameData.nImageCount): + image_info = stFrameData.stImageData[i] + + # 保存深度图 + if image_info.enImageType == ImageType_Depth: + file_name = configMap.save_path("depth", "-Depth") + ret_save = camera.MV3D_RGBD_SaveImage(pointer(image_info), FileType_TIFF, file_name) + print("Saved depth image." if ret_save == MV3D_RGBD_OK else "Failed to save depth image.") + if ret_save == MV3D_RGBD_OK: + saved_files["depth"].append(file_name) + + # 点云转换与保存 + stPointCloudImage = MV3D_RGBD_IMAGE_DATA() + ret = camera.MV3D_RGBD_MapDepthToPointCloud(pointer(stFrameData.stImageData[i]), pointer(stPointCloudImage)) + if MV3D_RGBD_OK != ret: + print("_MapDepthToPointCloud() Run failed...") + else: + print( + "_MapDepthToPointCloud() Run Succeed: framenum (%d) height(%d) width(%d) len (%d)!" % ( + stPointCloudImage.nFrameNum, + stPointCloudImage.nHeight, stPointCloudImage.nWidth, stPointCloudImage.nDataLen)) + strMode = string_at(stPointCloudImage.pData, stPointCloudImage.nDataLen) + sValue = struct.unpack('f' * int(stPointCloudImage.nHeight * stPointCloudImage.nWidth * 3), + strMode) + pcd_file = point.sValue_to_pcd(sValue, stPointCloudImage, sn) + if pcd_file: + saved_files["pcd"].append(pcd_file) + + # 保存彩色图 + elif image_info.enImageType in ( ImageType_RGB8_Planar, ImageType_YUV420SP_NV12, ImageType_YUV420SP_NV21, ImageType_YUV422 - ): - file_name = configMap.save_path("color","-_Color") - ret_save = camera.MV3D_RGBD_SaveImage(pointer(image_info), FileType_BMP, file_name) - print("Saved color image." if ret_save == MV3D_RGBD_OK else "Failed to save color image.") + ): + file_name = configMap.save_path("color", "-_Color") + ret_save = camera.MV3D_RGBD_SaveImage(pointer(image_info), FileType_BMP, file_name) + print("Saved color image." if ret_save == MV3D_RGBD_OK else "Failed to save color image.") + if ret_save == MV3D_RGBD_OK: + saved_files["color"].append(file_name) - # 保存灰度图 - elif image_info.enImageType == ImageType_Mono8: - file_name = configMap.save_path("Mono","-_Mono") - ret_save = camera.MV3D_RGBD_SaveImage(pointer(image_info), FileType_BMP, file_name) - print("Saved mono image." if ret_save == MV3D_RGBD_OK else "Failed to save mono image.") - - else: - print(f"Unknown image type: {image_info.enImageType}") - - else: - print("Failed to fetch frame.") - - time.sleep(time_hop / 1000.0) # 控制采集频率 + else: + print("Failed to fetch frame.") finally: - # 停止取流 camera.MV3D_RGBD_Stop() - print("Continuous capture completed.") - return saved_tiff_files + print("Single capture completed.") + + # 输出结果路径 + print("Saved files:") + for key, paths in saved_files.items(): + if paths: + print(f" {key.upper()}:") + for path in paths: + print(f" - {path}") + + return saved_files initialize_devices() diff --git a/__pycache__/Point.cpython-311.pyc b/__pycache__/Point.cpython-311.pyc index 782ed1b..42d59a1 100644 Binary files a/__pycache__/Point.cpython-311.pyc and b/__pycache__/Point.cpython-311.pyc differ diff --git a/__pycache__/SimpleView_SaveImage.cpython-311.pyc b/__pycache__/SimpleView_SaveImage.cpython-311.pyc index eea7f62..a43789e 100644 Binary files a/__pycache__/SimpleView_SaveImage.cpython-311.pyc and b/__pycache__/SimpleView_SaveImage.cpython-311.pyc differ diff --git a/config.py b/config.py index b230331..c41cc90 100644 --- a/config.py +++ b/config.py @@ -6,6 +6,7 @@ from datetime import datetime CAMERA_CONFIG_MAP = {} # {sn: config_dict} CUT_CONFIG_MAP = {} # {filename_without_ext: config_dict} DIRECTION_CAMERA = {} +TEMPLATE_MAP = {} def load_camera_configs(config_dir="./config/camera"): """ @@ -30,6 +31,32 @@ def load_camera_configs(config_dir="./config/camera"): print(f"[WARN] No 'sn' found in {filename}") except Exception as e: print(f"[ERROR] Failed to load {file_path}: {e}") + + + +def load_template_configs(config_dir="./config/template"): + """ + 加载 camera 配置,按 sn 建立映射 + """ + if not os.path.exists(config_dir): + print(f"[ERROR] Camera config directory does not exist: {config_dir}") + return + + for filename in os.listdir(config_dir): + if filename.endswith(".json"): + file_path = os.path.join(config_dir, filename) + try: + with open(file_path, "r", encoding="utf-8") as f: + config = json.load(f) + type = config.get("type") + if type: + TEMPLATE_MAP[type] = config + print(f"Loaded camera config: {type}") + else: + print(f"[WARN] No 'sn' found in {filename}") + except Exception as e: + print(f"[ERROR] Failed to load {file_path}: {e}") + load_camera_configs() def load_cut_configs(config_dir="./config/cut"): @@ -57,8 +84,10 @@ def load_configs(): CAMERA_CONFIG_MAP = {} # {sn: config_dict} CUT_CONFIG_MAP = {} # {filename_without_ext: config_dict} DIRECTION_CAMERA = {} + TEMPLATE_MAP = {} load_camera_configs() load_cut_configs() + load_template_configs() load_configs() diff --git a/config/camera/00DA6823936.json b/config/camera/00DA6823936.json index 55333d6..f22b34e 100644 --- a/config/camera/00DA6823936.json +++ b/config/camera/00DA6823936.json @@ -1,13 +1,13 @@ { "sn": "00DA6823936", "direction": "1", - "x_angle": 55, - "y_angle": 84, + "x_angle": 125, + "y_angle": 75, "save_pcd": true, "resolution": 8, - "max_z": 2000, + "max_z": 1800, "reverse_order": false, - "time_on": 300, - "time_off": 3500, - "time_hop": 500 + "time_on": 100, + "time_off": 5500, + "time_hop": 800 } \ No newline at end of file diff --git a/config/cut/00DA6823936.json b/config/cut/00DA6823936.json deleted file mode 100644 index 37c2314..0000000 --- a/config/cut/00DA6823936.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "floorHeight": 1, - "max_pt": [ - 59.17729568481445, - -177.37328052520752, - 823.2836303710938 - ], - "min_pt": [ - 0, - -1263.9830322265625, - 200.47930908203125 - ], - "rotation": [ - -0.0070345401763916016, - -0.9998821020126343, - 0.013652533292770386, - 0.4579751193523407, - -0.015358328819274902, - -0.8888324499130249, - 0.88893723487854, - -2.9802322387695312e-08, - 0.45802903175354004 - ] -} diff --git a/config/template/45.json b/config/template/45.json new file mode 100644 index 0000000..cd50c33 --- /dev/null +++ b/config/template/45.json @@ -0,0 +1,6 @@ +{ + "type": "45", + "width":"299", + "length":"355", + "high":"314" +} \ No newline at end of file diff --git a/config/配置文件说明.txt b/config/配置文件说明.txt index e69de29..caf4797 100644 --- a/config/配置文件说明.txt +++ b/config/配置文件说明.txt @@ -0,0 +1,14 @@ +camera文档 +{ + "sn": "00DA6823936",#sn + "direction": "1",#方向 + "x_angle": 55,#相机的水平视场角(相机自身的) + "y_angle": 84,#相机的垂直视场角 + "save_pcd": true,#是否保存pcd + "resolution": 8,#像素大小(可以调整来区别 + "max_z": 2000,#最大深度(裁剪之前的 + "reverse_order": false,#是否pcd组成的图片是否翻转 + "time_on": 300,#开始时间 + "time_off": 3500,#结束时间 + "time_hop": 500#时间间隔 +} \ No newline at end of file