diff --git a/odm_preprocess.py b/odm_preprocess.py index adb7dd5..8702fbb 100644 --- a/odm_preprocess.py +++ b/odm_preprocess.py @@ -18,6 +18,8 @@ from utils.logger import setup_logger from utils.visualizer import FilterVisualizer from post_pro.merge_tif import MergeTif from tools.test_docker_run import run_docker_command +from post_pro.merge_obj import MergeObj +from post_pro.merge_ply import MergePly @dataclass @@ -228,63 +230,22 @@ class ImagePreprocessor: self.logger.info(f"网格 {grid_idx + 1} 包含 {len(points)} 张图像") def merge_tif(self, grid_points: Dict[int, pd.DataFrame]): - """合并所有网格的TIF影像""" - self.logger.info("开始合并TIF影像") + """合并所有网格的影像产品""" + self.logger.info("开始合并所有影像产品") + merger = MergeTif(self.config.output_dir) + merger.merge_all_tifs(grid_points) - # 检查是否有多个网格需要合并 - if len(grid_points) < 2: - self.logger.info("只有一个网格,无需合并TIF影像") - return + def merge_obj(self, grid_points: Dict[int, pd.DataFrame]): + """合并所有网格的OBJ模型""" + self.logger.info("开始合并OBJ模型") + merger = MergeObj(self.config.output_dir) + merger.merge_grid_obj(grid_points) - input_tif1, input_tif2 = None, None - merge_count = 0 - - try: - for grid_idx, points in grid_points.items(): - grid_tif = os.path.join( - self.config.output_dir, - f"grid_{grid_idx + 1}", - "project", - "odm_orthophoto", - "odm_orthophoto.original.tif" - ) - - # 检查TIF文件是否存在 - if not os.path.exists(grid_tif): - self.logger.error( - f"网格 {grid_idx + 1} 的TIF文件不存在: {grid_tif}") - continue - - if input_tif1 is None: - input_tif1 = grid_tif - self.logger.info(f"设置第一个输入TIF: {input_tif1}") - else: - input_tif2 = grid_tif - output_tif = os.path.join( - self.config.output_dir, "merged_orthophoto.tif") - - self.logger.info( - f"开始合并第 {merge_count + 1} 次:\n" - f"输入1: {input_tif1}\n" - f"输入2: {input_tif2}\n" - f"输出: {output_tif}" - ) - - merge_tif = MergeTif(input_tif1, input_tif2, output_tif) - merge_tif.merge() - merge_count += 1 - - input_tif1 = output_tif - input_tif2 = None - - self.logger.info( - f"TIF影像合并完成,共执行 {merge_count} 次合并," - f"最终输出文件: {input_tif1}" - ) - - except Exception as e: - self.logger.error(f"TIF影像合并过程中发生错误: {str(e)}", exc_info=True) - raise + def merge_ply(self, grid_points: Dict[int, pd.DataFrame]): + """合并所有网格的PLY点云""" + self.logger.info("开始合并PLY点云") + merger = MergePly(self.config.output_dir) + merger.merge_grid_ply(grid_points) def process(self): """执行完整的预处理流程""" @@ -297,19 +258,10 @@ class ImagePreprocessor: self.copy_images(grid_points) self.logger.info("预处理任务完成") - # for grid_idx in grid_points.keys(): - # grid_dir = os.path.abspath(os.path.join( - # self.config.output_dir, f'grid_{grid_idx + 1}' - # )) - # grid_dir = grid_dir[0].lower() + grid_dir[1:].replace("\\", "/") - # command = f"docker run -ti --rm -v {grid_dir}:/datasets opendronemap/odm --project-path /datasets project --max-concurrency 10 --force-gps --feature-quality lowest --orthophoto-resolution 10 --fast-orthophoto --skip-3dmodel --rerun-all" - # print(command) - # stdout, stderr = run_docker_command(command) - # print(stdout) - # print(stderr) - self.odm_monitor.process_all_grids(grid_points) self.merge_tif(grid_points) + self.merge_obj(grid_points) + self.merge_ply(grid_points) except Exception as e: self.logger.error(f"处理过程中发生错误: {str(e)}", exc_info=True) raise @@ -318,8 +270,8 @@ class ImagePreprocessor: if __name__ == "__main__": # 创建配置 config = PreprocessConfig( - image_dir=r"G:\error_data\20241104140457\code\images", - output_dir=r"G:\ODM_output\20241104140457", + image_dir=r"E:\datasets\UAV\1009\project\images", + output_dir=r"G:\ODM_output\1009", cluster_eps=0.01, cluster_min_samples=5, @@ -335,11 +287,11 @@ if __name__ == "__main__": filter_dense_distance_threshold=10, filter_time_threshold=timedelta(minutes=5), - grid_size=1000, + grid_size=300, grid_overlap=0.03, - mode="快拼模式", + mode="重建模式", ) # 创建处理器并执行 diff --git a/post_pro/merge_obj.py b/post_pro/merge_obj.py new file mode 100644 index 0000000..1fa46a6 --- /dev/null +++ b/post_pro/merge_obj.py @@ -0,0 +1,109 @@ +import os +import logging +import numpy as np +from typing import Dict +import pandas as pd +import open3d as o3d + + +class MergeObj: + def __init__(self, output_dir: str): + self.output_dir = output_dir + self.logger = logging.getLogger('UAV_Preprocess.MergeObj') + + def merge_two_objs(self, obj1_path: str, obj2_path: str, output_path: str): + """使用Open3D合并两个OBJ文件""" + try: + self.logger.info("开始合并OBJ模型") + self.logger.info(f"输入模型1: {obj1_path}") + self.logger.info(f"输入模型2: {obj2_path}") + self.logger.info(f"输出模型: {output_path}") + + # 检查输入文件是否存在 + if not os.path.exists(obj1_path) or not os.path.exists(obj2_path): + raise FileNotFoundError("输入模型文件不存在") + + # 读取OBJ文件 + mesh1 = o3d.io.read_triangle_mesh(obj1_path) + mesh2 = o3d.io.read_triangle_mesh(obj2_path) + + if mesh1.is_empty() or mesh2.is_empty(): + raise ValueError("无法读取OBJ文件或文件为空") + + # # 计算并对齐中心点 + # center1 = mesh1.get_center() + # center2 = mesh2.get_center() + # translation_vector = center2 - center1 + # mesh2.translate(translation_vector) + + # 不对齐,直接合并网格 + combined_mesh = mesh1 + mesh2 + + # 优化合并后的网格 + combined_mesh.remove_duplicated_vertices() + combined_mesh.remove_duplicated_triangles() + combined_mesh.compute_vertex_normals() + + # 保存合并后的模型 + if not o3d.io.write_triangle_mesh(output_path, combined_mesh): + raise RuntimeError("保存合并后的模型失败") + + self.logger.info(f"模型合并成功,已保存至: {output_path}") + + except Exception as e: + self.logger.error(f"合并OBJ模型时发生错误: {str(e)}", exc_info=True) + raise + + def merge_grid_obj(self, grid_points: Dict[int, pd.DataFrame]): + """合并所有网格的OBJ模型""" + self.logger.info("开始合并所有网格的OBJ模型") + + if len(grid_points) < 2: + self.logger.info("只有一个网格,无需合并") + return + + input_obj1, input_obj2 = None, None + merge_count = 0 + + try: + for grid_idx, points in grid_points.items(): + grid_obj = os.path.join( + self.output_dir, + f"grid_{grid_idx + 1}", + "project", + "odm_texturing", + "odm_textured_model_geo.obj" + ) + + if not os.path.exists(grid_obj): + self.logger.warning(f"网格 {grid_idx + 1} 的OBJ文件不存在: {grid_obj}") + continue + + if input_obj1 is None: + input_obj1 = grid_obj + self.logger.info(f"设置第一个输入OBJ: {input_obj1}") + else: + input_obj2 = grid_obj + output_obj = os.path.join(self.output_dir, "merged_model.obj") + + self.logger.info( + f"开始合并第 {merge_count + 1} 次:\n" + f"输入1: {input_obj1}\n" + f"输入2: {input_obj2}\n" + f"输出: {output_obj}" + ) + + self.merge_two_objs(input_obj1, input_obj2, output_obj) + merge_count += 1 + + input_obj1 = output_obj + input_obj2 = None + + self.logger.info( + f"OBJ模型合并完成,共执行 {merge_count} 次合并," + f"最终输出文件: {input_obj1}" + ) + + except Exception as e: + self.logger.error(f"OBJ模型合并过程中发生错误: {str(e)}", exc_info=True) + raise \ No newline at end of file diff --git a/post_pro/merge_ply.py b/post_pro/merge_ply.py new file mode 100644 index 0000000..570860b --- /dev/null +++ b/post_pro/merge_ply.py @@ -0,0 +1,108 @@ +import os +import logging +import numpy as np +from typing import Dict +import pandas as pd +import open3d as o3d + + +class MergePly: + def __init__(self, output_dir: str): + self.output_dir = output_dir + self.logger = logging.getLogger('UAV_Preprocess.MergePly') + + def merge_two_plys(self, ply1_path: str, ply2_path: str, output_path: str): + """合并两个PLY文件""" + try: + self.logger.info("开始合并PLY点云") + self.logger.info(f"输入点云1: {ply1_path}") + self.logger.info(f"输入点云2: {ply2_path}") + self.logger.info(f"输出点云: {output_path}") + + # 检查输入文件是否存在 + if not os.path.exists(ply1_path) or not os.path.exists(ply2_path): + raise FileNotFoundError("输入点云文件不存在") + + # 读取点云 + pcd1 = o3d.io.read_point_cloud(ply1_path) + pcd2 = o3d.io.read_point_cloud(ply2_path) + + if pcd1 is None or pcd2 is None: + raise ValueError("无法读取点云文件") + + # 获取点云中心 + center1 = pcd1.get_center() + center2 = pcd2.get_center() + + # 计算平移向量 + translation_vector = center2 - center1 + + # 对齐点云 + pcd2.translate(translation_vector) + + # 合并点云 + combined_pcd = pcd1 + pcd2 + + # 保存合并后的点云 + if not o3d.io.write_point_cloud(output_path, combined_pcd): + raise RuntimeError("保存合并后的点云失败") + + self.logger.info(f"点云合并成功,已保存至: {output_path}") + + except Exception as e: + self.logger.error(f"合并PLY点云时发生错误: {str(e)}", exc_info=True) + raise + + def merge_grid_ply(self, grid_points: Dict[int, pd.DataFrame]): + """合并所有网格的PLY点云""" + self.logger.info("开始合并所有网格的PLY点云") + + if len(grid_points) < 2: + self.logger.info("只有一个网格,无需合并") + return + + input_ply1, input_ply2 = None, None + merge_count = 0 + + try: + for grid_idx, points in grid_points.items(): + grid_ply = os.path.join( + self.output_dir, + f"grid_{grid_idx + 1}", + "project", + "odm_georeferencing", + "odm_georeferenced_model.ply" + ) + + if not os.path.exists(grid_ply): + self.logger.warning(f"网格 {grid_idx + 1} 的PLY文件不存在: {grid_ply}") + continue + + if input_ply1 is None: + input_ply1 = grid_ply + self.logger.info(f"设置第一个输入PLY: {input_ply1}") + else: + input_ply2 = grid_ply + output_ply = os.path.join(self.output_dir, "merged_pointcloud.ply") + + self.logger.info( + f"开始合并第 {merge_count + 1} 次:\n" + f"输入1: {input_ply1}\n" + f"输入2: {input_ply2}\n" + f"输出: {output_ply}" + ) + + self.merge_two_plys(input_ply1, input_ply2, output_ply) + merge_count += 1 + + input_ply1 = output_ply + input_ply2 = None + + self.logger.info( + f"PLY点云合并完成,共执行 {merge_count} 次合并," + f"最终输出文件: {input_ply1}" + ) + + except Exception as e: + self.logger.error(f"PLY点云合并过程中发生错误: {str(e)}", exc_info=True) + raise \ No newline at end of file diff --git a/post_pro/merge_tif.py b/post_pro/merge_tif.py index d4c64a0..2ab1a9e 100644 --- a/post_pro/merge_tif.py +++ b/post_pro/merge_tif.py @@ -1,34 +1,33 @@ from osgeo import gdal import logging import os +from typing import Dict +import pandas as pd import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) class MergeTif: - def __init__(self, input_tif1, input_tif2, output_tif): - self.input_tif1 = input_tif1 - self.input_tif2 = input_tif2 - self.output_tif = output_tif + def __init__(self, output_dir: str): + self.output_dir = output_dir self.logger = logging.getLogger('UAV_Preprocess.MergeTif') - def merge(self): + def merge_two_tifs(self, input_tif1: str, input_tif2: str, output_tif: str): """合并两张TIF影像""" try: self.logger.info("开始合并TIF影像") - self.logger.info(f"输入影像1: {self.input_tif1}") - self.logger.info(f"输入影像2: {self.input_tif2}") - self.logger.info(f"输出影像: {self.output_tif}") + self.logger.info(f"输入影像1: {input_tif1}") + self.logger.info(f"输入影像2: {input_tif2}") + self.logger.info(f"输出影像: {output_tif}") # 检查输入文件是否存在 - if not os.path.exists(self.input_tif1) or not os.path.exists(self.input_tif2): + if not os.path.exists(input_tif1) or not os.path.exists(input_tif2): error_msg = "输入影像文件不存在" self.logger.error(error_msg) raise FileNotFoundError(error_msg) # 打开影像,检查投影是否一致 - datasets = [gdal.Open(tif) - for tif in [self.input_tif1, self.input_tif2]] + datasets = [gdal.Open(tif) for tif in [input_tif1, input_tif2]] if None in datasets: error_msg = "无法打开输入影像文件" self.logger.error(error_msg) @@ -47,20 +46,14 @@ class MergeTif: # 创建 GDAL Warp 选项 warp_options = gdal.WarpOptions( format="GTiff", - resampleAlg="average", # 设置重采样方法为平均值 - srcNodata=0, # 输入影像中的无效值 - dstNodata=0, # 输出影像中的无效值 - multithread=True # 启用多线程优化 + resampleAlg="average", + srcNodata=0, + dstNodata=0, + multithread=True ) self.logger.info("开始执行影像拼接...") - - # 使用 GDAL 的 Warp 方法进行拼接 - result = gdal.Warp( - self.output_tif, - [self.input_tif1, self.input_tif2], # 输入多张影像 - options=warp_options - ) + result = gdal.Warp(output_tif, [input_tif1, input_tif2], options=warp_options) if result is None: error_msg = "影像拼接失败" @@ -68,19 +61,109 @@ class MergeTif: raise RuntimeError(error_msg) # 获取输出影像的基本信息 - output_dataset = gdal.Open(self.output_tif) + output_dataset = gdal.Open(output_tif) if output_dataset: width = output_dataset.RasterXSize height = output_dataset.RasterYSize bands = output_dataset.RasterCount self.logger.info(f"拼接完成,输出影像大小: {width}x{height},波段数: {bands}") - self.logger.info(f"影像拼接成功,输出文件保存至: {self.output_tif}") + self.logger.info(f"影像拼接成功,输出文件保存至: {output_tif}") except Exception as e: self.logger.error(f"影像拼接过程中发生错误: {str(e)}", exc_info=True) raise + def merge_grid_tif(self, grid_points: Dict[int, pd.DataFrame], product_info: dict): + """合并指定产品的所有网格""" + product_name = product_info['name'] + product_path = product_info['path'] + filename = product_info['filename'] + + self.logger.info(f"开始合并{product_name}") + + if len(grid_points) < 2: + self.logger.info("只有一个网格,无需合并") + return + + input_tif1, input_tif2 = None, None + merge_count = 0 + + try: + for grid_idx, points in grid_points.items(): + grid_tif = os.path.join( + self.output_dir, + f"grid_{grid_idx + 1}", + "project", + product_path, + filename + ) + + if not os.path.exists(grid_tif): + self.logger.warning(f"网格 {grid_idx + 1} 的{product_name}不存在: {grid_tif}") + continue + + if input_tif1 is None: + input_tif1 = grid_tif + self.logger.info(f"设置第一个输入{product_name}: {input_tif1}") + else: + input_tif2 = grid_tif + output_tif = os.path.join(self.output_dir, f"merged_{product_info['output']}") + + self.logger.info( + f"开始合并{product_name}第 {merge_count + 1} 次:\n" + f"输入1: {input_tif1}\n" + f"输入2: {input_tif2}\n" + f"输出: {output_tif}" + ) + + self.merge_two_tifs(input_tif1, input_tif2, output_tif) + merge_count += 1 + + input_tif1 = output_tif + input_tif2 = None + + self.logger.info( + f"{product_name}合并完成,共执行 {merge_count} 次合并," + f"最终输出文件: {input_tif1}" + ) + + except Exception as e: + self.logger.error(f"{product_name}合并过程中发生错误: {str(e)}", exc_info=True) + raise + + def merge_all_tifs(self, grid_points: Dict[int, pd.DataFrame]): + """合并所有产品(正射影像、DSM和DTM)""" + try: + products = [ + { + 'name': '正射影像', + 'path': 'odm_orthophoto', + 'filename': 'odm_orthophoto.original.tif', + 'output': 'orthophoto.tif' + }, + { + 'name': 'DSM', + 'path': 'odm_dem', + 'filename': 'dsm.original.tif', + 'output': 'dsm.tif' + }, + { + 'name': 'DTM', + 'path': 'odm_dem', + 'filename': 'dtm.original.tif', + 'output': 'dtm.tif' + } + ] + + for product in products: + self.merge_grid_product(grid_points, product) + + self.logger.info("所有产品合并完成") + except Exception as e: + self.logger.error(f"产品合并过程中发生错误: {str(e)}", exc_info=True) + raise + if __name__ == "__main__": from utils.logger import setup_logger @@ -95,5 +178,5 @@ if __name__ == "__main__": setup_logger(output_dir) # 执行拼接 - merge_tif = MergeTif(input_tif1, input_tif2, output_tif) - merge_tif.merge() + merge_tif = MergeTif(output_dir) + merge_tif.merge_two_tifs(input_tif1, input_tif2, output_tif) diff --git a/requirements.txt b/requirements.txt index ff696df..9bc2977 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ piexif geopy psutil docker>=6.1.3 +open3d diff --git a/tools/merge_obj.py b/tools/merge_obj.py new file mode 100644 index 0000000..2b3e714 --- /dev/null +++ b/tools/merge_obj.py @@ -0,0 +1,34 @@ +import open3d as o3d +import numpy as np + +# 读取 .obj 文件 +def load_obj(file_path): + mesh = o3d.io.read_triangle_mesh(file_path) + if not mesh.is_empty(): + return mesh + else: + raise ValueError(f"Failed to load {file_path}") + +# 合并两个网格 +def merge_meshes(mesh1, mesh2): + # 直接合并网格 + combined_mesh = mesh1 + mesh2 + return combined_mesh + +# 保存合并后的网格 +def save_merged_mesh(mesh, output_path): + o3d.io.write_triangle_mesh(output_path, mesh) + print(f"Saved merged mesh to {output_path}") + +# 示例用法 +mesh1 = load_obj("model1.obj") +mesh2 = load_obj("model2.obj") + +# 合并两个网格 +merged_mesh = merge_meshes(mesh1, mesh2) + +# 保存合并后的网格 +save_merged_mesh(merged_mesh, "merged_model.obj") + +# 可视化合并后的网格 +o3d.visualization.draw_geometries([merged_mesh]) diff --git a/tools/merge_ply.py b/tools/merge_ply.py new file mode 100644 index 0000000..a6330ff --- /dev/null +++ b/tools/merge_ply.py @@ -0,0 +1,22 @@ +import open3d as o3d +import numpy as np + +# 读取第一个PLY文件 +pcd1 = o3d.io.read_point_cloud("path_to_first_file.ply") + +# 读取第二个PLY文件 +pcd2 = o3d.io.read_point_cloud("path_to_second_file.ply") + +# 可选:如果需要调整坐标系,可以通过平移、旋转来对齐点云 +# 例如,平移第二个点云 +offset = np.array([1000, 2000, 3000]) +pcd2.translate(offset) + +# 合并点云 +combined_pcd = pcd1 + pcd2 + +# 保存合并后的点云为PLY文件 +o3d.io.write_point_cloud("merged_output.ply", combined_pcd) + +# 可视化 +o3d.visualization.draw_geometries([combined_pcd])