# # import json # import os # import time # # import cv2 # import rasterio # import numpy as np # from pyproj import Transformer, CRS # # # def convert_to_wgs84(x, y, src_crs): # """将投影坐标转换为WGS84经纬度,增加错误处理""" # try: # if not (isinstance(x, (int, float)) and isinstance(y, (int, float))): # print(f"警告: 坐标值无效 (x={x}, y={y})") # return None, None # # transformer = Transformer.from_crs(src_crs, "EPSG:4326", always_xy=True) # lon, lat = transformer.transform(x, y) # # if not np.isfinite(lon) or not np.isfinite(lat): # print(f"警告: 坐标转换结果无效 (lon={lon}, lat={lat})") # return None, None # # return lon, lat # except Exception as e: # print(f"坐标转换异常: {str(e)}") # return None, None # # # def visualize_pil_segmentation_mask_opencv(mask_path, tif_path, output_path=None, colormap=cv2.COLORMAP_VIRIDIS, save=True): # """ # 使用OpenCV实现掩码可视化+边界提取(避免Matplotlib后端问题) # 返回: (src_crs, json_result_path, vis_output_path, raw_json_path) # """ # # 初始化返回值 # src_crs = None # json_result_path = None # vis_output_path = None # raw_json_path = None # # try: # # 1. 读取并验证掩码文件 # if not os.path.exists(mask_path): # raise FileNotFoundError(f"掩码文件不存在: {mask_path}") # # mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) # if mask is None: # raise ValueError(f"无法读取掩码文件(可能已损坏): {mask_path}") # # # 2. 可视化处理 # mask_vis = cv2.applyColorMap(mask, colormap) # contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # cv2.drawContours(mask_vis, contours, -1, (0, 0, 255), 1) # # # 3. 设置输出路径 # if output_path is None: # vis_output_path = os.path.splitext(mask_path)[0] + '_vis_cv2.png' # else: # vis_output_path = output_path # # # 4. 保存可视化结果 # if save: # if not cv2.imwrite(vis_output_path, mask_vis): # raise IOError(f"无法保存可视化结果到: {vis_output_path}") # print(f"可视化结果已保存到: {vis_output_path}") # # # 5. 处理轮廓数据 # instances = [] # for i, contour in enumerate(contours): # if len(contour) < 1: # print(f"警告: 轮廓 {i} 为空,跳过") # continue # # # 简化轮廓(可选,减少点数) # epsilon = 0.001 * cv2.arcLength(contour, True) # approx_contour = cv2.approxPolyDP(contour, epsilon, True) # # # 转换为列表格式(JSON 兼容) # contour_list = [] # for point in approx_contour.squeeze(): # if isinstance(point, np.ndarray) and point.size >= 2: # contour_list.append(point.tolist()) # elif isinstance(point, (list, tuple)) and len(point) >= 2: # contour_list.append(list(map(int, point[:2]))) # 确保坐标是整数 # else: # print(f"警告: 无效的轮廓点格式: {point}") # continue # # if len(contour_list)> 3: # contour_list.append(contour_list[0]) #多加一个点,构成封闭平面 # instances.append({ # "instance_id": i + 1, # "contour": contour_list, # "area": int(cv2.contourArea(contour)) # }) # # if not instances: # print("警告: 未检测到任何有效轮廓") # return None, None, vis_output_path, None # # # 6. 保存原始JSON # raw_json_path = os.path.splitext(mask_path)[0] + '.json' # try: # with open(raw_json_path, 'w') as f: # json.dump(instances, f, indent=2) # print(f"基础轮廓数据已保存到: {raw_json_path}") # except Exception as e: # print(f"警告: 无法保存基础JSON文件 - {str(e)}") # raw_json_path = None # # start = time.perf_counter() # # count=0 # # 7. 处理TIFF坐标转换(仅当提供有效tif_path时) # if tif_path and isinstance(tif_path, str) and tif_path.lower().endswith(('.tif', '.tiff')): # try: # with rasterio.open(tif_path) as src: # src_crs = src.crs # if not src_crs: # print("警告: TIFF文件缺少坐标系信息,跳过坐标转换") # json_result_path = raw_json_path # return src_crs, json_result_path, vis_output_path, raw_json_path # # transform = src.transform # band_data = src.read(1) # height, width = band_data.shape # # for instance in instances: # coord = [] # for point in instance["contour"]: # try: # col, row = point[:2] # 提取坐标 # if not (0 <= col < width and 0 <= row < height): # print(f"警告: 坐标({col}, {row})超出图像范围 ({width}x{height})") # coord.append(None) # continue # # count=count+1 # # 像素坐标 → 投影坐标 # x, y = transform * (col, row) # lon, lat = convert_to_wgs84(x, y, src_crs) # # # 获取像素值 # z = float(band_data[int(row), int(col)]) # # if lon is not None and lat is not None: # coord.append([lon, lat, z]) # else: # coord.append([None, None, z]) # except Exception as e: # print(f"警告: 处理轮廓点 {point} 时出错 - {str(e)}") # coord.append(None) # # instance["coord"] = [[c for c in coord if c is not None]] # 过滤无效坐标 # # # 保存带坐标的结果 # json_result_path = os.path.join( # os.path.dirname(raw_json_path), # "result_" + os.path.basename(raw_json_path) # ) # # try: # with open(json_result_path, 'w') as f: # json.dump(instances, f, indent=2, default=lambda obj: float(obj) if isinstance(obj, np.generic) else str(obj)) # print(f"完整结果(含坐标)已保存到: {json_result_path}") # except Exception as e: # print(f"错误: 无法保存结果JSON文件 - {str(e)}") # json_result_path = raw_json_path # 回退到原始JSON # # except Exception as e: # print(f"处理TIFF坐标时发生错误: {str(e)}") # json_result_path = raw_json_path # 回退到原始JSON # else: # json_result_path = raw_json_path # 非TIFF文件直接返回原始JSON # # end = time.perf_counter() # # print(f"总计 {count} 点,总计耗时: {end - start:.6f} 秒") # # return src_crs, json_result_path, vis_output_path, raw_json_path # # except Exception as e: # print(f"可视化处理过程中发生严重错误: {str(e)}") # return None, None, vis_output_path, None # 确保返回路径变量 import json import os import numpy as np import cv2 import rasterio from pyproj import Transformer, CRS from geographiclib.geodesic import Geodesic # 用于精确计算球面多边形面积 from pyproj import Geod def calculate_polygon_area(coords): """计算球面多边形面积(单位:平方米)""" if len(coords) < 3: return 0.0 try: geod = Geod(ellps="WGS84") # WGS84 椭球体 # 注意: pyproj.Geod 要求坐标顺序为 (lon, lat) lons, lats = zip(*[(lon, lat) for lon, lat in coords]) area, _ = geod.polygon_area_perimeter(lons, lats) return abs(area) # 返回绝对值(平方米) except Exception as e: print(f"面积计算异常: {str(e)}") return 0.0 def convert_to_wgs84(x, y, src_crs): """将投影坐标转换为WGS84经纬度,增加错误处理""" try: if not (isinstance(x, (int, float)) and isinstance(y, (int, float))): print(f"警告: 坐标值无效 (x={x}, y={y})") return None, None transformer = Transformer.from_crs(src_crs, "EPSG:4326", always_xy=True) lon, lat = transformer.transform(x, y) if not np.isfinite(lon) or not np.isfinite(lat): print(f"警告: 坐标转换结果无效 (lon={lon}, lat={lat})") return None, None return lon, lat except Exception as e: print(f"坐标转换异常: {str(e)}") return None, None # # def visualize_pil_segmentation_mask_opencv(mask_path, tif_path, output_path=None, colormap=cv2.COLORMAP_VIRIDIS, save=True): # """ # 修改版:过滤面积 < 10㎡ 的轮廓,并更新可视化结果 # """ # src_crs = None # json_result_path = None # vis_output_path = None # raw_json_path = None # # try: # # 1. 读取掩码文件 # if not os.path.exists(mask_path): # raise FileNotFoundError(f"掩码文件不存在: {mask_path}") # # mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) # if mask is None: # raise ValueError(f"无法读取掩码文件(可能已损坏): {mask_path}") # # # 2. 初始化可视化图像(后续会更新) # mask_vis = cv2.applyColorMap(mask, colormap) # contours, _ = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # cv2.drawContours(mask_vis, contours, -1, (0, 0, 255), 1) # # # 3. 处理轮廓数据 # valid_instances = [] # filtered_contours = [] # 存储过滤后的轮廓(用于可视化) # # for i, contour in enumerate(contours): # if len(contour) < 3: # 至少需要3个点构成多边形 # print(f"警告: 轮廓 {i} 点数不足,跳过") # continue # # # 简化轮廓 # epsilon = 0.001 * cv2.arcLength(contour, True) # approx_contour = cv2.approxPolyDP(contour, epsilon, True) # # # 转换为列表格式 # contour_list = [] # for point in approx_contour.squeeze(): # if isinstance(point, np.ndarray) and point.size >= 2: # contour_list.append(point.tolist()) # elif isinstance(point, (list, tuple)) and len(point) >= 2: # contour_list.append(list(map(int, point[:2]))) # else: # print(f"警告: 无效的轮廓点格式: {point}") # continue # # if len(contour_list) < 3: # print(f"警告: 轮廓 {i} 简化后点数不足,跳过") # continue # # # 临时存储当前轮廓(后续可能被过滤) # temp_instance = { # "instance_id": i + 1, # "contour": contour_list, # "area_pixels": int(cv2.contourArea(contour)), # "coord": None # 后续填充 # } # # # 4. 处理TIFF坐标转换(如果提供) # if tif_path and tif_path.lower().endswith(('.tif', '.tiff')): # try: # with rasterio.open(tif_path) as src: # src_crs = src.crs # if not src_crs: # print("警告: TIFF文件缺少坐标系信息,跳过坐标转换") # valid_instances.append(temp_instance) # filtered_contours.append(contour) # continue # # transform = src.transform # height, width = src.read(1).shape # coords = [] # # # 修复后的代码 # valid_coords = [] # for point in contour_list: # col, row = point[:2] # # 检查坐标是否在图像范围内 # if not (0 <= col < width and 0 <= row < height): # print(f"警告: 坐标({col}, {row})超出图像范围") # continue # 跳过这个点但不中断整个轮廓 # # try: # # 转换为投影坐标 # x, y = transform * (col, row) # # 转换为经纬度 # lon, lat = convert_to_wgs84(x, y, src_crs) # if lon is not None and lat is not None: # valid_coords.append([lon, lat]) # except Exception as e: # print(f"坐标处理错误: {str(e)}") # continue # # # 过滤无效坐标 # valid_coords = [c for c in coords if c is not None] # if len(valid_coords) < 3: # print(f"警告: 轮廓 {i} 有效坐标不足,跳过") # continue # # # 计算面积(平方米) # area_m2 = calculate_polygon_area(valid_coords) # temp_instance["coord"] = valid_coords # temp_instance["area_m2"] = area_m2 # # # 过滤面积 < 10㎡ 的轮廓 # if area_m2 >= 10: # valid_instances.append(temp_instance) # filtered_contours.append(contour) # 保留轮廓用于可视化 # else: # print(f"过滤小面积轮廓: ID={i+1}, 面积={area_m2:.2f}㎡") # # except Exception as e: # print(f"处理TIFF坐标时发生错误: {str(e)}") # valid_instances.append(temp_instance) # 保留原始数据 # filtered_contours.append(contour) # else: # # 非TIFF文件:直接保留轮廓 # valid_instances.append(temp_instance) # filtered_contours.append(contour) # # # 5. 更新可视化图像(仅绘制保留的轮廓) # mask_vis_filtered = cv2.applyColorMap(mask, colormap) # if filtered_contours: # cv2.drawContours(mask_vis_filtered, filtered_contours, -1, (0, 0, 255), 1) # # # 6. 保存可视化结果 # if output_path is None: # vis_output_path = os.path.splitext(mask_path)[0] + '_vis_cv2.png' # else: # vis_output_path = output_path # # if save: # if not cv2.imwrite(vis_output_path, mask_vis_filtered): # raise IOError(f"无法保存可视化结果到: {vis_output_path}") # print(f"可视化结果已保存到: {vis_output_path}") # # # 7. 保存JSON数据 # raw_json_path = os.path.splitext(mask_path)[0] + '.json' # with open(raw_json_path, 'w') as f: # json.dump(valid_instances, f, indent=2) # print(f"基础轮廓数据已保存到: {raw_json_path}") # # # 8. 返回结果(src_crs 可能为None) # return src_crs, raw_json_path, vis_output_path, raw_json_path # # except Exception as e: # print(f"可视化处理过程中发生严重错误: {str(e)}") # return None, None, vis_output_path, None def visualize_pil_segmentation_mask_opencv(mask_path, tif_path, output_path=None, colormap=cv2.COLORMAP_VIRIDIS, save=True): """ 修改版:过滤面积 < 10㎡ 的轮廓,并更新可视化结果 """ src_crs = None json_result_path = None vis_output_path = None raw_json_path = None try: # 1. 读取掩码文件(同上) if not os.path.exists(mask_path): raise FileNotFoundError(f"掩码文件不存在: {mask_path}") mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) if mask is None: raise ValueError(f"无法读取掩码文件(可能已损坏): {mask_path}") # 2. 初始化可视化图像 mask_vis = cv2.applyColorMap(mask, colormap) contours, _ = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(mask_vis, contours, -1, (0, 0, 255), 1) # 3. 处理轮廓数据 valid_instances = [] filtered_contours = [] for i, contour in enumerate(contours): if len(contour) < 3: print(f"警告: 轮廓 {i} 点数不足,跳过") continue # 简化轮廓 epsilon = 0.001 * cv2.arcLength(contour, True) approx_contour = cv2.approxPolyDP(contour, epsilon, True) # 转换为列表格式并确保闭合 contour_list = [] for point in approx_contour.squeeze(): if isinstance(point, np.ndarray) and point.size >= 2: contour_list.append(point.tolist()) elif isinstance(point, (list, tuple)) and len(point) >= 2: contour_list.append(list(map(float, point[:2]))) # 使用float保持精度 if len(contour_list) < 3: print(f"警告: 轮廓 {i} 简化后点数不足,跳过") continue # 闭合多边形 # if contour_list[0] != contour_list[-1]: # contour_list.append(contour_list[0]) contour_list.append(contour_list[0]) temp_instance = { "instance_id": i + 1, "contour": contour_list, "area_pixels": int(cv2.contourArea(contour)), "coord": None } # 4. 处理TIFF坐标转换 if tif_path and tif_path.lower().endswith(('.tif', '.tiff')): try: with rasterio.open(tif_path) as src: src_crs = src.crs if not src_crs: print("警告: TIFF文件缺少坐标系信息") valid_instances.append(temp_instance) filtered_contours.append(contour) continue transform = src.transform height, width = src.read(1).shape all_coords = [] valid_coords = [] for point in contour_list[:-1]: # 不处理重复的闭合点 col, row = point[:2] if not (0 <= col < width and 0 <= row < height): print(f"警告: 坐标({col}, {row})超出图像范围") continue try: x, y = transform * (col, row) lon, lat = convert_to_wgs84(x, y, src_crs) if lon is not None and lat is not None: all_coords.append([lon, lat]) except Exception as e: print(f"坐标处理错误: {str(e)}") continue # 必须至少有3个有效坐标才能构成多边形 if len(all_coords) >= 3: # 计算面积(平方米) area_m2 = calculate_polygon_area(all_coords) # # 闭合多边形 all_coords.append(all_coords[0]) # 适配geojson的三层格式 temp_instance["coord"] = [all_coords] temp_instance["area_m2"] = area_m2 # 过滤面积 < 10㎡ 的轮廓 if area_m2 >= 10: valid_instances.append(temp_instance) filtered_contours.append(contour) else: print(f"过滤小面积轮廓: ID={i+1}, 面积={area_m2:.2f}㎡") else: print(f"警告: 轮廓 {i} 有效坐标不足3个") except Exception as e: print(f"处理TIFF坐标时发生错误: {str(e)}") valid_instances.append(temp_instance) filtered_contours.append(contour) else: valid_instances.append(temp_instance) filtered_contours.append(contour) # 5. 更新可视化图像 mask_vis_filtered = cv2.applyColorMap(mask, colormap) if filtered_contours: cv2.drawContours(mask_vis_filtered, filtered_contours, -1, (0, 0, 255), 1) # ...(剩余的保存和返回逻辑保持不变) # 6. 保存可视化结果 if output_path is None: vis_output_path = os.path.splitext(mask_path)[0] + '_vis_cv2.png' else: vis_output_path = output_path if save: if not cv2.imwrite(vis_output_path, mask_vis_filtered): raise IOError(f"无法保存可视化结果到: {vis_output_path}") print(f"可视化结果已保存到: {vis_output_path}") # 7. 保存JSON数据 raw_json_path = os.path.splitext(mask_path)[0] + '.json' with open(raw_json_path, 'w') as f: json.dump(valid_instances, f, indent=2) print(f"基础轮廓数据已保存到: {raw_json_path}") # 8. 返回结果(src_crs 可能为None) return src_crs, raw_json_path, vis_output_path, raw_json_path except Exception as e: print(f"可视化处理过程中发生严重错误: {str(e)}") return None, None, vis_output_path, None