diff --git a/odm_preprocess.py b/odm_preprocess.py index 0f2addc..392ac03 100644 --- a/odm_preprocess.py +++ b/odm_preprocess.py @@ -18,7 +18,6 @@ from utils.grid_divider import GridDivider 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_laz import MergePly @@ -69,6 +68,7 @@ class ImagePreprocessor: config.output_dir, mode=config.mode) self.visualizer = FilterVisualizer(config.output_dir) + # TODO 给出警告! def _clean_output_dir(self): """清理输出目录""" try: @@ -303,7 +303,7 @@ if __name__ == "__main__": filter_dense_distance_threshold=10, filter_time_threshold=timedelta(minutes=5), - grid_size=1000, + grid_size=800, grid_overlap=0.05, diff --git a/tools/gps_selector.py b/tools/gps_selector.py new file mode 100644 index 0000000..16a9950 --- /dev/null +++ b/tools/gps_selector.py @@ -0,0 +1,134 @@ +import os +import sys +import shutil +from pathlib import Path +import matplotlib.pyplot as plt +from matplotlib.widgets import RectangleSelector +import pandas as pd + +# 添加项目根目录到系统路径 +project_root = str(Path(__file__).parent.parent) +sys.path.append(project_root) + +from utils.gps_extractor import GPSExtractor + +class GPSSelector: + def __init__(self, image_dir: str, output_dir: str = None): + self.image_dir = image_dir + self.output_dir = output_dir + self.gps_points = None + self.selected_points = [] + self.fig, self.ax = plt.subplots(figsize=(12, 8)) + self.scatter = None + self.rs = None + self.setup_plot() + + def extract_gps(self): + """提取GPS数据""" + extractor = GPSExtractor(self.image_dir) + self.gps_points = extractor.extract_all_gps() + print(f"成功提取 {len(self.gps_points)} 个GPS点") + + def setup_plot(self): + """设置绘图""" + self.ax.set_title('GPS Points - 使用鼠标拖动选择要删除的点') + self.ax.set_xlabel('Longitude') + self.ax.set_ylabel('Latitude') + self.ax.grid(True) + + # 设置矩形选择器 + self.rs = RectangleSelector( + self.ax, self.on_select, + interactive=True, + useblit=True, + button=[1], # 只响应左键 + props=dict(facecolor='red', alpha=0.3) + ) + + # 添加按钮回调 + self.fig.canvas.mpl_connect('key_press_event', self.on_key_press) + + def plot_gps_points(self): + """绘制GPS点""" + if self.scatter is not None: + self.scatter.remove() + + self.scatter = self.ax.scatter( + self.gps_points['lon'], + self.gps_points['lat'], + c='blue', + s=20, + alpha=0.6 + ) + self.fig.canvas.draw_idle() + + def on_select(self, eclick, erelease): + """矩形选择回调""" + x1, y1 = eclick.xdata, eclick.ydata + x2, y2 = erelease.xdata, erelease.ydata + + # 获取选中区域内的点 + mask = ( + (self.gps_points['lon'] >= min(x1, x2)) & + (self.gps_points['lon'] <= max(x1, x2)) & + (self.gps_points['lat'] >= min(y1, y2)) & + (self.gps_points['lat'] <= max(y1, y2)) + ) + + selected = self.gps_points[mask] + self.selected_points.extend(selected['file'].tolist()) + + # 从数据中移除选中的点 + self.gps_points = self.gps_points[~mask] + + # 更新绘图 + self.plot_gps_points() + print(f"选中 {len(selected)} 个点,剩余 {len(self.gps_points)} 个点") + + def on_key_press(self, event): + """键盘事件回调""" + if event.key == 'enter': + self.save_results() + plt.close() + elif event.key == 'escape': + plt.close() + + def save_results(self): + """保存结果""" + if not self.output_dir: + return + + # 创建输出目录 + os.makedirs(self.output_dir, exist_ok=True) + removed_dir = os.path.join(self.output_dir, "removed_images") + os.makedirs(removed_dir, exist_ok=True) + + # 移动被删除的图像 + for img_name in self.selected_points: + src = os.path.join(self.image_dir, img_name) + dst = os.path.join(removed_dir, img_name) + shutil.move(src, dst) + + # 保存剩余点的信息 + self.gps_points.to_csv( + os.path.join(self.output_dir, "remaining_points.csv"), + index=False + ) + + print(f"已移动 {len(self.selected_points)} 张图片到 {removed_dir}") + print(f"保留 {len(self.gps_points)} 个点") + + def run(self): + """运行选择器""" + self.extract_gps() + self.plot_gps_points() + plt.show() + + +if __name__ == "__main__": + # 使用示例 + selector = GPSSelector( + image_dir=r"E:\datasets\UAV\1619\project\images", + output_dir=r"E:\datasets\UAV\1619\filtered" + ) + selector.run() \ No newline at end of file diff --git a/tools/merge_ply.py b/tools/merge_ply.py deleted file mode 100644 index a6330ff..0000000 --- a/tools/merge_ply.py +++ /dev/null @@ -1,22 +0,0 @@ -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]) diff --git a/utils/odm_monitor.py b/utils/odm_monitor.py index 3ebad81..8887ee9 100644 --- a/utils/odm_monitor.py +++ b/utils/odm_monitor.py @@ -13,26 +13,6 @@ class NotOverlapError(Exception): pass -class DockerNotRunError(Exception): - """Docker未启动异常""" - pass - - -class DockerShareError(Exception): - """Docker目录共享异常""" - pass - - -class OutOfMemoryError(Exception): - """内存不足异常""" - pass - - -class StrangeValuesError(Exception): - """异常值异常""" - pass - - class ODMProcessMonitor: """ODM处理监控器""" @@ -121,6 +101,8 @@ class ODMProcessMonitor: return False, "快拼模式下无法生成DEM,请调整生产参数" self.logger.info(f"开始处理网格 ({grid_id[0]},{grid_id[1]})") + success = False + error_msg = "" max_retries = 3 current_try = 0 use_lowest_quality = True # 初始使用lowest quality @@ -146,7 +128,7 @@ class ODMProcessMonitor: if use_lowest_quality: docker_command += f"--feature-quality lowest " - docker_command += f"--orthophoto-resolution 10 " + docker_command += f"--orthophoto-resolution 8 " if produce_dem: docker_command += ( @@ -177,11 +159,12 @@ class ODMProcessMonitor: self.logger.error(f"docker run指令执行失败") self.logger.error(f"==========stderr==========: {stderr}") if "error during connect" in stderr or "The system cannot find the file specified" in stderr: - raise DockerNotRunError + error_msg = "Docker没有启动,请启动Docker" elif "user declined directory sharing" in stderr: - raise DockerShareError + error_msg = "Docker无法访问目录,请检查目录权限和共享设置" else: - raise Exception(f"Docker运行失败,需要人工排查错误") + error_msg = "Docker运行失败,需要人工排查错误" + break # TODO 处理时间组删除,删多了的情况 else: self.logger.info("docker run指令执行成功") @@ -190,49 +173,37 @@ class ODMProcessMonitor: if self._check_success(grid_dir): self.logger.info( f"网格 ({grid_id[0]},{grid_id[1]}) 处理成功") - return True, "" + success = True + error_msg = "" + break else: self.logger.error( f"虽然ODM处理完成,但是生产产品质量可能不合格,需要人工检查") - raise Exception(f"虽然ODM处理完成,但是生产产品质量可能不合格,需要人工检查") + raise NotOverlapError + # TODO 先写成这样,后面这三种情况可能处理不一样 elif "enough overlap" in last_lines: raise NotOverlapError elif "out of memory" in last_lines: - raise OutOfMemoryError + raise NotOverlapError elif "strange values" in last_lines: - raise StrangeValuesError + raise NotOverlapError else: - raise Exception(f"ODM处理失败,需要人工排查错误") + raise NotOverlapError except NotOverlapError: if use_lowest_quality: self.logger.warning( "检测到not overlap错误,移除lowest quality参数后重试") use_lowest_quality = False + time.sleep(10) continue else: self.logger.error( - "即使移除lowest quality参数后仍然出现not overlap错误") - return False, "图像重叠度不足,请检查数据集的采样间隔情况" + "即使移除lowest quality参数后仍然出现错误") + error_msg = "图像重叠度不足,需要人工检查数据集的采样间隔情况" + break - except DockerNotRunError: - self.logger.error("Docker服务未启动") - return False, "Docker没有启动,请启动Docker" - - except DockerShareError: - self.logger.error("Docker无法访问目录") - return False, "Docker无法访问数据目录或输出目录,请检查目录权限和共享设置" - - except OutOfMemoryError: - self.logger.error("内存不足,请减少输入图像的数量") - return False, "内存不足" - except StrangeValuesError: - self.logger.error("重建过程中出现异常值") - return False, "检测到异常值,请检查输入数据集的采样间隔情况" - - time.sleep(10) - - return False, f"网格 ({grid_id[0]},{grid_id[1]}) 处理失败" + return success, error_msg def process_all_grids(self, grid_points: Dict[tuple, pd.DataFrame], produce_dem: bool) -> Dict[tuple, pd.DataFrame]: """处理所有网格