diff --git a/.gitignore b/.gitignore index 1143761..06fee09 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ build diff_rasterization/diff_rast.egg-info diff_rasterization/dist tensorboard_3d -screenshots \ No newline at end of file +screenshots +data \ No newline at end of file diff --git a/xy_utils/crop_points.py b/xy_utils/crop_points.py new file mode 100644 index 0000000..244181e --- /dev/null +++ b/xy_utils/crop_points.py @@ -0,0 +1,108 @@ +import argparse +import numpy as np +import open3d as o3d +from scipy.spatial.transform import Rotation as R + +def load_camera_centers(images_txt_path): + """ + 解析 COLMAP images.txt,计算每张图的相机光心。 + C = -R^T @ T,其中 R 由四元数 (qx, qy, qz, qw) 转换而来。 + """ + centers = [] + with open(images_txt_path, 'r') as f: + for line in f: + if line.startswith('#') or not line.strip(): + continue + elems = line.strip().split() + if len(elems) < 10: + continue + qw, qx, qy, qz = map(float, elems[1:5]) + tx, ty, tz = map(float, elems[5:8]) + rot = R.from_quat([qx, qy, qz, qw]).as_matrix() + centers.append((-rot.T @ np.array([tx, ty, tz]))) + return np.array(centers) + +def main(args): + # 1. 加载相机中心 + camera_centers = load_camera_centers(args.images_txt) + + # 2. 分割地面平面,获取法线 + pcd = o3d.io.read_point_cloud(args.pcd_path) + plane_model, inliers = pcd.segment_plane( + distance_threshold=0.02, # 根据点云尺度调整 + ransac_n=3, + num_iterations=1000 + ) + normal = np.array(plane_model[:3]) + normal /= np.linalg.norm(normal) + + # 3. 构造旋转矩阵,将地面法线对齐到 [0,0,1] + target = np.array([0.0, 0.0, 1.0]) + axis = np.cross(normal, target) + axis /= np.linalg.norm(axis) + angle = np.arccos(np.dot(normal, target)) + R_align = o3d.geometry.get_rotation_matrix_from_axis_angle(axis * angle) + + # 4. 旋转点云与相机中心到“水平”坐标系 + pcd_aligned = pcd.rotate(R_align, center=(0,0,0)) + camera_centers_aligned = (R_align @ camera_centers.T).T + + # 5. 基于所有相机中心的 mean + max_offset 构建 AABB,裁剪 + center_mean = camera_centers_aligned.mean(axis=0) + offsets = np.abs(camera_centers_aligned - center_mean) + max_offset = offsets.max(axis=0) + margin = args.margin + min_bound = center_mean - max_offset - margin + max_bound = center_mean + max_offset + margin + aabb = o3d.geometry.AxisAlignedBoundingBox(min_bound, max_bound) + + pcd_cropped_aligned = pcd_aligned.crop(aabb) + + # 6. 逆旋转回原坐标系 + R_inv = R_align.T + pcd_cropped = pcd_cropped_aligned.rotate(R_inv, center=(0,0,0)) + + # 7. 构造 kept vs removed 掩码 + all_pts = np.asarray(pcd.points) + inside_idx = set(aabb.get_point_indices_within_bounding_box( + o3d.utility.Vector3dVector(all_pts))) + mask_kept = np.array([i in inside_idx for i in range(len(all_pts))]) + kept_pts = all_pts[mask_kept] + removed_pts= all_pts[~mask_kept] + + # 8. 统计并打印 + total, kept, removed = len(all_pts), len(kept_pts), len(removed_pts) + print(f"总点数:{total},保留:{kept},删除:{removed}") + + # 9. 保存 COLMAP 格式 points3D.txt + all_colors = np.asarray(pcd.colors) if pcd.has_colors() else np.ones((total,3)) + kept_colors = all_colors[mask_kept] + with open(args.output_points_txt, "w") as f: + f.write("# COLMAP points3D.txt\n") + for idx, (pt, col) in enumerate(zip(kept_pts, kept_colors), start=1): + r, g, b = (col * 255).astype(int) + f.write(f"{idx} {pt[0]:.6f} {pt[1]:.6f} {pt[2]:.6f} {r} {g} {b} 0\n") + + # 10. 生成带红色删除点标记的 PLY + removed_colors = np.tile([1.0,0.0,0.0], (removed,1)) + all_kept = kept_pts + all_removed= removed_pts + all_colors_marked = np.vstack([kept_colors, removed_colors]) + pcd_marked = o3d.geometry.PointCloud() + pcd_marked.points = o3d.utility.Vector3dVector( + np.vstack([all_kept, all_removed])) + pcd_marked.colors = o3d.utility.Vector3dVector(all_colors_marked) + o3d.io.write_point_cloud(args.output_ply, pcd_marked) + +if __name__ == "__main__": + # 解析命令行参数 + parser = argparse.ArgumentParser(description="裁剪点云并保存为 COLMAP 格式和 PLY 文件") + parser.add_argument("--images_txt", type=str, required=True, help="COLMAP images.txt 文件路径") + parser.add_argument("--pcd_path", type=str, required=True, help="点云文件路径 (.pcd)") + parser.add_argument("--output_points_txt", type=str, required=True, help="输出 COLMAP points3D.txt 文件路径") + parser.add_argument("--output_ply", type=str, required=True, help="输出带标记的 PLY 文件路径") + parser.add_argument("--margin", type=float, default=15.0, help="裁剪边距,默认为 15.0") + args = parser.parse_args() + + # 执行主函数 + main(args) \ No newline at end of file diff --git a/xy_utils/learning.ipynb b/xy_utils/learning.ipynb new file mode 100644 index 0000000..9180fbe --- /dev/null +++ b/xy_utils/learning.ipynb @@ -0,0 +1,541 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b66179c8", + "metadata": {}, + "source": [ + "# 1.预处理数据" + ] + }, + { + "cell_type": "markdown", + "id": "533db061", + "metadata": {}, + "source": [ + "## 1.1 对点云数据进行处理 - 滤除环视范围外过远的点" + ] + }, + { + "cell_type": "markdown", + "id": "864843b2", + "metadata": {}, + "source": [ + "1. 解析 images.txt,计算所有相机在世界坐标系下的光心(位姿)。 \n", + "2. 使用 Open3D 加载 .pcd 点云,并基于与最近相机光心的距离进行滤除。 \n", + "3. 输出相机光心的范围(坐标轴最小/最大值)以及被滤除的点云数量。 \n", + "4. 将保留的点云按 COLMAP points3D.txt 格式保存,并额外生成一个 PLY 文件,其中红色标记被删除的点,原色保留未删除点。" + ] + }, + { + "cell_type": "markdown", + "id": "5dc3ac56", + "metadata": {}, + "source": [ + "#### ipynb内部函数 -- 未进行地面与boundingbox对齐" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc2e8c18", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import open3d as o3d\n", + "from scipy.spatial.transform import Rotation as R\n", + "\n", + "def filter_point_cloud(images_txt, pcd_path, output_points_txt, output_points_ply, output_ply_path):\n", + " \"\"\"\n", + " 根据相机位姿过滤点云,并保存结果。\n", + "\n", + " Args:\n", + " images_txt (str): 包含相机位姿的 images.txt 文件路径。\n", + " pcd_path (str): 点云文件路径。\n", + " output_points_txt (str): 保存过滤后点云的 points3D.txt 文件路径。\n", + " output_ply_path (str): 保存带颜色标记的 PLY 文件路径。\n", + " \"\"\"\n", + " def load_camera_centers(images_txt):\n", + " centers = []\n", + " with open(images_txt, 'r') as f:\n", + " for line in f:\n", + " if line.startswith('#') or len(line.strip()) == 0:\n", + " continue\n", + " elems = line.strip().split()\n", + " if len(elems) < 10:\n", + " continue\n", + " qw, qx, qy, qz = map(float, elems[1:5])\n", + " tx, ty, tz = map(float, elems[5:8])\n", + " rot = R.from_quat([qx, qy, qz, qw]).as_matrix()\n", + " center = -rot.T @ np.array([tx, ty, tz])\n", + " centers.append(center)\n", + " return np.array(centers)\n", + "\n", + " # 1. 加载相机中心\n", + " camera_centers = load_camera_centers(images_txt)\n", + "\n", + " # 2. 计算平均中心和最大偏移\n", + " center_mean = camera_centers.mean(axis=0)\n", + " offsets = np.abs(camera_centers - center_mean)\n", + " max_offset = offsets.max(axis=0)\n", + "\n", + " # 3. 加载点云\n", + " pcd = o3d.io.read_point_cloud(pcd_path)\n", + " points = np.asarray(pcd.points)\n", + " colors = np.asarray(pcd.colors) if pcd.has_colors() else np.ones_like(points)\n", + "\n", + " # 4. 构建包围盒并裁剪点云\n", + " min_bound = center_mean - max_offset - 15\n", + " max_bound = center_mean + max_offset + 15\n", + " aabb = o3d.geometry.AxisAlignedBoundingBox(min_bound, max_bound)\n", + " pcd_cropped = pcd.crop(aabb)\n", + "\n", + " # 5. 统计信息\n", + " total_points = len(points)\n", + " kept_points = np.asarray(pcd_cropped.points)\n", + " kept_colors = np.asarray(pcd_cropped.colors) if pcd_cropped.has_colors() else np.ones_like(kept_points)\n", + " removed_mask = np.ones(total_points, dtype=bool)\n", + " kept_indices = aabb.get_point_indices_within_bounding_box(pcd.points)\n", + " removed_mask[kept_indices] = False\n", + " removed_points = points[removed_mask]\n", + " removed_colors = colors[removed_mask]\n", + "\n", + " print(\"相机中心范围:\")\n", + " print(f\" X: [{min_bound[0]:.3f}, {max_bound[0]:.3f}]\")\n", + " print(f\" Y: [{min_bound[1]:.3f}, {max_bound[1]:.3f}]\")\n", + " print(f\" Z: [{min_bound[2]:.3f}, {max_bound[2]:.3f}]\")\n", + " print(f\"总点数:{total_points}\")\n", + " print(f\"保留点数:{len(kept_points)}\")\n", + " print(f\"删除点数:{len(removed_points)}\")\n", + "\n", + " # 6. 保存 points3D.txt (速度太慢,直接使用 ply )\n", + " with open(output_points_txt, \"w\") as f:\n", + " f.write(\"# 3D point list with one line of data per point:\\n\")\n", + " f.write(\"# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[]\\n\")\n", + " for idx, (pt, col) in enumerate(zip(kept_points, kept_colors), start=1):\n", + " r, g, b = (col * 255).astype(int)\n", + " f.write(f\"{idx} {pt[0]:.6f} {pt[1]:.6f} {pt[2]:.6f} {r} {g} {b} 0\\n\")\n", + " # 保存为 ply 文件\n", + " pcd_kept = o3d.geometry.PointCloud()\n", + " pcd_kept.points = o3d.utility.Vector3dVector(kept_points)\n", + " pcd_kept.colors = o3d.utility.Vector3dVector(kept_colors)\n", + " o3d.io.write_point_cloud(output_points_ply, pcd_kept)\n", + " print(\"保留的点云已保存为 {output_points_ply}\")\n", + " \n", + " # 7. 保存带颜色的 PLY 文件\n", + " all_points = np.vstack((kept_points, removed_points))\n", + " removed_colors_red = np.tile([1.0, 0.0, 0.0], (len(removed_points), 1))\n", + " all_colors = np.vstack((kept_colors, removed_colors_red))\n", + " pcd_all = o3d.geometry.PointCloud()\n", + " pcd_all.points = o3d.utility.Vector3dVector(all_points)\n", + " pcd_all.colors = o3d.utility.Vector3dVector(all_colors)\n", + " o3d.io.write_point_cloud(output_ply_path, pcd_all)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "80b924be", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "相机中心范围:\n", + " X: [-0.200, 28.270]\n", + " Y: [-16.082, 1.699]\n", + " Z: [-9.392, 0.714]\n", + "总点数:16404321\n", + "保留点数:10684495\n", + "删除点数:5719826\n", + "保留的点云已保存为 {output_points_ply}\n" + ] + } + ], + "source": [ + "import os\n", + "folder_path = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results'\n", + "\n", + "images_txt = os.path.join(folder_path, 'sparse/0/images.txt')\n", + "pcd_path = os.path.join(folder_path, 'pcd/all_raw_points.pcd')\n", + "output_points_ply = os.path.join(folder_path, 'pcd/points3D.ply')\n", + "vis_ply_path = os.path.join(folder_path, 'pcd/vis_filter.ply')\n", + "output_points_txt = os.path.join(folder_path, 'pcd/points3D.txt')\n", + "filter_point_cloud(images_txt,pcd_path,output_points_txt, output_points_ply,vis_ply_path)" + ] + }, + { + "cell_type": "markdown", + "id": "1a4206e8", + "metadata": {}, + "source": [ + "#### 调用 crop_points.py 文件" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d8c55ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "总点数:16404321,保留:15254842,删除:1149479\n" + ] + } + ], + "source": [ + "!python crop_points.py \\\n", + " --images_txt /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/sparse/0/images.txt \\\n", + " --pcd_path /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/all_raw_points.pcd \\\n", + " --output_points_txt /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/points3D.txt \\\n", + " --output_ply /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/filtered_colored.ply \\\n", + " --margin 10.0" + ] + }, + { + "cell_type": "markdown", + "id": "651ed72f", + "metadata": {}, + "source": [ + "## 1.2 利用体素化降采样" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56f5cba7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "raw points len : 15254842\n", + "downsample points len : 201667\n", + "降采样后的点云已保存到 /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/sparse/0/points3D_filter_0.2.ply,体素大小:0.2\n" + ] + } + ], + "source": [ + "import os\n", + "from tools.points_utils import voxel_downsample_and_save\n", + "\n", + "voxel_size = 0.2\n", + "folder_path = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results'\n", + "input_ply_path = os.path.join(folder_path,'pcd/points3D.ply')\n", + "output_ply_path = os.path.join(folder_path,f'pcd/points3D_{voxel_size}.ply')\n", + "input_ply_path = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/sparse/0/points3D_filter.ply'\n", + "output_ply_path = f'/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/sparse/0/points3D_filter_{voxel_size}.ply'\n", + "\n", + "voxel_downsample_and_save(voxel_size, input_ply_path, output_ply_path) # ply downsample to ply\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8544bb3e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# pcd 2 ply\n", + "# 将原pcd点云,转换为ply点云\n", + "from tools.points_utils import pcd_2_ply\n", + "pcd_file = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/all_raw_points.pcd'\n", + "ply_file = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/all_raw_points.ply'\n", + "\n", + "pcd_2_ply(pcd_file,ply_file)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2bf16bb1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Jupyter environment detected. Enabling Open3D WebVisualizer.\n", + "[Open3D INFO] WebRTC GUI backend enabled.\n", + "[Open3D INFO] WebRTCWindowSystem: HTTP handshake server disabled.\n", + "正在写入COLMAP TXT文件: /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/points3D_filter_white.txt\n", + "Converted /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/sparse/0/points3D_filter.ply to Colmap TXT in: /home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/points3D_filter_white.txt\n" + ] + } + ], + "source": [ + "# pcd 2 txt\n", + "from tools.points_utils import pcd_2_colmap_txt\n", + "pcd_file = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/sparse/0/points3D_filter.ply'\n", + "txt_file = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01_livo2/livo2_results/pcd/points3D_filter_white.txt'\n", + "\n", + "pcd_2_colmap_txt(pcd_file, txt_file, is_white=True)" + ] + }, + { + "cell_type": "markdown", + "id": "6fd0b306", + "metadata": {}, + "source": [ + "选择一个合适的 points3D.ply 文件复制到 sparse/0 下" + ] + }, + { + "cell_type": "markdown", + "id": "22e87bd3", + "metadata": {}, + "source": [ + "## 1.2 Resize for more images" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "91ce4cb8", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import cv2\n", + "from tqdm import tqdm\n", + "\n", + "def resize_images(input_dir, output_dir, extensions):\n", + " \"\"\"\n", + " 读取输入文件夹中的所有图片,调整为1/2大小后保存到输出文件夹\n", + " \n", + " Args:\n", + " input_dir: 输入图片文件夹路径\n", + " output_dir: 输出图片文件夹路径\n", + " extensions: 支持的图片扩展名列表\n", + " \"\"\"\n", + " # 确保输出目录存在\n", + " os.makedirs(output_dir, exist_ok=True)\n", + " \n", + " # 获取所有图片文件\n", + " image_files = []\n", + " for file in os.listdir(input_dir):\n", + " if any(file.lower().endswith(ext) for ext in extensions):\n", + " image_files.append(file)\n", + " \n", + " if not image_files:\n", + " print(f\"在 {input_dir} 中未找到支持的图片文件\")\n", + " return\n", + " \n", + " print(f\"找到 {len(image_files)} 张图片\")\n", + " \n", + " # 处理每张图片\n", + " count = 0\n", + " for file in tqdm(image_files, desc=\"处理中\"):\n", + " input_path = os.path.join(input_dir, file)\n", + " output_path = os.path.join(output_dir, file)\n", + " \n", + " try:\n", + " # 读取图片\n", + " img = cv2.imread(input_path)\n", + " if img is None:\n", + " print(f\"警告: 无法读取图片 {input_path},跳过\")\n", + " continue\n", + " \n", + " # 获取原始尺寸\n", + " height, width = img.shape[:2]\n", + " \n", + " # 计算新尺寸\n", + " new_width = width // 2\n", + " new_height = height // 2\n", + " \n", + " # 调整尺寸\n", + " resized_img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)\n", + " \n", + " # 保存图片\n", + " cv2.imwrite(output_path, resized_img)\n", + " \n", + " # 输出尺寸信息\n", + " if count == 0:\n", + " print(f\"{file}: {width}x{height} -> {new_width}x{new_height}\")\n", + " count += 1\n", + " except Exception as e:\n", + " print(f\"错误: 处理图片 {input_path} 时出错: {str(e)}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9b4da36", + "metadata": {}, + "outputs": [], + "source": [ + "input_path = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01/depth_maps'\n", + "output_path = '/home/qinllgroup/hongxiangyu/git_project/gaussian-splatting-xy/data/tree_01/depth_maps_2'\n", + "exts = ['jpg','jpeg','png']\n", + "\n", + "resize_images(input_path, output_path, exts)\n", + "print(\"所有图片处理完成!\") " + ] + }, + { + "cell_type": "markdown", + "id": "3830cb91", + "metadata": {}, + "source": [ + "# 2. LIVO2和Colmap的重建对比实验" + ] + }, + { + "cell_type": "markdown", + "id": "7dd6ff70", + "metadata": {}, + "source": [ + "colmap无法恢复相机位姿;所以这里我们使用livo2恢复位姿后,用colmap 进行三角测量获取关键点\n", + "https://www.cnblogs.com/Todd-Qi/p/15080968.html" + ] + }, + { + "cell_type": "markdown", + "id": "4ad9f127", + "metadata": {}, + "source": [ + "## 2.1 基于Livo2位姿进行稀疏重建" + ] + }, + { + "cell_type": "markdown", + "id": "40e8c7f1", + "metadata": {}, + "source": [ + "colmap无法恢复相机位姿;所以这里我们使用livo2恢复位姿后,用colmap 进行三角测量获取关键点\n", + "https://www.cnblogs.com/Todd-Qi/p/15080968.html" + ] + }, + { + "cell_type": "markdown", + "id": "497d715a", + "metadata": {}, + "source": [ + "1. 准备来自Livo2的位姿和相机数据 cameras.txt, images.txt\n", + " 将内参(camera intrinsics) 放入cameras.txt, 外参(camera extrinsics)放入 images.txt , points3D.txt 为空 \n", + " - images.txt 中全部 0.0 0.0 -1 删除; \n", + " - points3D.txt 内容清空;\n", + " - cameras.txt 中的内参进行修改 (对输入图像全部进行了 resize 操作,因此需要修改相机内参,将fx, fy, cx, cy 都除以2)" + ] + }, + { + "cell_type": "markdown", + "id": "497596e0", + "metadata": {}, + "source": [ + "2. 特征匹配与特征提取 \n", + "``` bash\n", + " colmap feature_extractor \\\n", + " --database_path /path/to/project/database.db \\ \n", + " --image_path /path/to/project/images\n", + "```\n", + "``` bash\n", + " colmap exhaustive_matcher \\\n", + " --database_path /path/to/project/database.db\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "590c5ba8", + "metadata": {}, + "source": [ + "3. 三角化重建 (保存的点云和其他文件均为bin格式)\n", + "``` bash\n", + " colmap point_triangulator \\\n", + " --database_path /path/to/project/database.db \\\n", + " --image_path /path/to/project/images \\\n", + " --input_path /path/to/sparse_model \\\n", + " --output_path /path/to/triangulated_model\n", + "\n", + "```\n", + "\n", + "查看txt结果\n", + "``` bash\n", + " colmap model_converter \\\n", + " --input_path 0 \\\n", + " --output_path 0_txt_from_livo2 \\\n", + " --output_type TXT\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "6c2b853f", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "id": "d359ac68", + "metadata": {}, + "source": [ + "4. 稠密重建(optional)" + ] + }, + { + "cell_type": "markdown", + "id": "77250b4b", + "metadata": {}, + "source": [ + "# 3.训练" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d44f71f7", + "metadata": {}, + "outputs": [], + "source": [ + "# baseline raw gs for training\n", + "!CUDA_VISIBLE_DEVICES=1 python train.py \\\n", + " -s data/tree_01_livo2 \\\n", + " -m data/tree_01_livo2/outputs/3dgs_baseline\n", + " \n", + "# render\n", + "!CUDA_VISIBLE_DEVICES=1 python render.py \\\n", + " -s data/tree_01_colmap \\\n", + " -m data/tree_01_colmap/outputs/3dgs_baseline " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/xy_utils/tools/points_utils.py b/xy_utils/tools/points_utils.py new file mode 100644 index 0000000..33ae19b --- /dev/null +++ b/xy_utils/tools/points_utils.py @@ -0,0 +1,58 @@ +import open3d as o3d +import numpy as np +import os + +def pcd_2_ply(pcd_file_path,ply_file_path): + point_cloud = o3d.io.read_point_cloud(pcd_file_path) + o3d.io.write_point_cloud(ply_file_path, point_cloud) + print(f"Converted {pcd_file_path} to {ply_file_path}") + + +def pcd_2_colmap_txt(pcd_file, txt_file, is_white=False): + ''' + is_white: 是否将点云颜色设置为白色 + ''' + point_cloud = o3d.io.read_point_cloud(pcd_file) + points = np.asarray(point_cloud.points) + colors = np.asarray(point_cloud.colors) + + + print(f"正在写入COLMAP TXT文件: {txt_file}") + with open(txt_file, 'w') as f: + f.write('# 3D point list with one line of data per point\n') + f.write('# POINT_ID, X, Y, Z, R, G, B, ERROR\n') + f.write('# Number of points: {}\n'.format(len(points))) + for i, point in enumerate(points): + x, y, z = point + if is_white: + r, g, b = 255, 255, 255 + else: + r, g, b = colors[i] + r,g,b = int(r * 255), int(g * 255), int(b * 255) + error = 0 + # 空的观察列表 (IMAGE_ID, POINT2D_IDX) + # 写入一行 + f.write(f"{i} {x:.6f} {y:.6f} {z:.6f} {r} {g} {b} {error}\n") + + print(f"Converted {pcd_file} to Colmap TXT in: {txt_file}") + +def voxel_downsample_and_save(voxel_size, input_ply_path, output_ply_path): + """ + 从PLY文件读取点云,进行体素化降采样,并保存结果。 + + Args: + input_ply_path (str): 输入PLY文件路径。 + voxel_size (float): 体素大小。 + output_ply_path (str): 保存降采样后点云的PLY文件路径。 + """ + # 读取输入点云 + pcd = o3d.io.read_point_cloud(input_ply_path) + print(f"raw points len : {len(pcd.points)}") + + # 体素化降采样 + pcd_downsampled = pcd.voxel_down_sample(voxel_size) + print(f"downsample points len : {len(pcd_downsampled.points)}") + + # 保存降采样后的点云 + o3d.io.write_point_cloud(output_ply_path, pcd_downsampled) + print(f"降采样后的点云已保存到 {output_ply_path},体素大小:{voxel_size}") \ No newline at end of file