先合并obj再转换osgb
This commit is contained in:
parent
ad6f4fb1ae
commit
954e87121f
@ -18,6 +18,7 @@ from utils.grid_divider import GridDivider
|
|||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
from utils.visualizer import FilterVisualizer
|
from utils.visualizer import FilterVisualizer
|
||||||
from post_pro.merge_tif import MergeTif
|
from post_pro.merge_tif import MergeTif
|
||||||
|
from post_pro.merge_obj import MergeObj
|
||||||
from post_pro.obj_post_pro import ObjPostProcessor
|
from post_pro.obj_post_pro import ObjPostProcessor
|
||||||
from post_pro.merge_laz import MergePly
|
from post_pro.merge_laz import MergePly
|
||||||
|
|
||||||
@ -255,10 +256,16 @@ class ImagePreprocessor:
|
|||||||
merger.merge_grid_laz(grid_points)
|
merger.merge_grid_laz(grid_points)
|
||||||
|
|
||||||
def merge_obj(self, grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
def merge_obj(self, grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
||||||
"""合并所有网格的OBJ模型"""
|
"""合并所有网格的OBJ模型并转换为OSGB格式"""
|
||||||
self.logger.info("开始合并OBJ模型")
|
self.logger.info("开始合并OBJ模型")
|
||||||
|
merger = MergeObj(self.config.output_dir)
|
||||||
|
center_coords = merger.merge_grid_obj(grid_points)
|
||||||
|
|
||||||
|
# 转换为OSGB格式
|
||||||
|
self.logger.info("开始转换为OSGB格式")
|
||||||
processor = ObjPostProcessor(self.config.output_dir)
|
processor = ObjPostProcessor(self.config.output_dir)
|
||||||
processor.process_obj_files()
|
if not processor.convert_to_osgb(center_coords):
|
||||||
|
self.logger.error("OSGB转换失败")
|
||||||
|
|
||||||
def post_process(self, successful_grid_points: Dict[tuple, pd.DataFrame], grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
def post_process(self, successful_grid_points: Dict[tuple, pd.DataFrame], grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
||||||
"""后处理:合并或复制处理结果"""
|
"""后处理:合并或复制处理结果"""
|
||||||
|
@ -18,6 +18,7 @@ from utils.grid_divider import GridDivider
|
|||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
from utils.visualizer import FilterVisualizer
|
from utils.visualizer import FilterVisualizer
|
||||||
from post_pro.merge_tif import MergeTif
|
from post_pro.merge_tif import MergeTif
|
||||||
|
from post_pro.merge_obj import MergeObj
|
||||||
from post_pro.obj_post_pro import ObjPostProcessor
|
from post_pro.obj_post_pro import ObjPostProcessor
|
||||||
from post_pro.merge_laz import MergePly
|
from post_pro.merge_laz import MergePly
|
||||||
|
|
||||||
@ -255,10 +256,16 @@ class ImagePreprocessor:
|
|||||||
merger.merge_grid_laz(grid_points)
|
merger.merge_grid_laz(grid_points)
|
||||||
|
|
||||||
def merge_obj(self, grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
def merge_obj(self, grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
||||||
"""合并所有网格的OBJ模型"""
|
"""合并所有网格的OBJ模型并转换为OSGB格式"""
|
||||||
self.logger.info("开始合并OBJ模型")
|
self.logger.info("开始合并OBJ模型")
|
||||||
|
merger = MergeObj(self.config.output_dir)
|
||||||
|
center_coords = merger.merge_grid_obj(grid_points)
|
||||||
|
|
||||||
|
# 转换为OSGB格式
|
||||||
|
self.logger.info("开始转换为OSGB格式")
|
||||||
processor = ObjPostProcessor(self.config.output_dir)
|
processor = ObjPostProcessor(self.config.output_dir)
|
||||||
processor.process_obj_files()
|
if not processor.convert_to_osgb(center_coords):
|
||||||
|
self.logger.error("OSGB转换失败")
|
||||||
|
|
||||||
def post_process(self, successful_grid_points: Dict[tuple, pd.DataFrame], grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
def post_process(self, successful_grid_points: Dict[tuple, pd.DataFrame], grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
||||||
"""后处理:合并或复制处理结果"""
|
"""后处理:合并或复制处理结果"""
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
|
||||||
from typing import Dict
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
import numpy as np
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
import cv2
|
import cv2
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from pyproj import Transformer
|
||||||
|
|
||||||
|
|
||||||
class MergeObj:
|
class MergeObj:
|
||||||
def __init__(self, output_dir: str):
|
def __init__(self, output_dir: str):
|
||||||
self.output_dir = output_dir
|
self.output_dir = output_dir
|
||||||
self.logger = logging.getLogger('UAV_Preprocess.MergeObj')
|
self.logger = logging.getLogger('UAV_Preprocess.MergeObj')
|
||||||
|
# 用于存储所有grid的UTM范围
|
||||||
|
self.min_east = float('inf')
|
||||||
|
self.min_north = float('inf')
|
||||||
|
self.max_east = float('-inf')
|
||||||
|
self.max_north = float('-inf')
|
||||||
|
# 初始化UTM到WGS84的转换器
|
||||||
|
self.transformer = Transformer.from_crs("EPSG:32649", "EPSG:4326", always_xy=True)
|
||||||
|
|
||||||
def read_obj(self, file_path):
|
def read_obj(self, file_path):
|
||||||
"""读取.obj文件,返回顶点、纹理坐标、法线、面的列表和MTL文件名"""
|
"""读取.obj文件,返回顶点、纹理坐标、法线、面的列表和MTL文件名"""
|
||||||
@ -103,11 +111,7 @@ class MergeObj:
|
|||||||
face_str += "/"
|
face_str += "/"
|
||||||
file.write(face_str + "\n")
|
file.write(face_str + "\n")
|
||||||
|
|
||||||
def translate_vertices(self, vertices, translation):
|
def merge_two_objs(self, obj1_path: str, obj2_path: str, output_path: str, grid_id1: tuple, grid_id2: tuple):
|
||||||
"""平移顶点"""
|
|
||||||
return [[v[0] + translation[0], v[1] + translation[1], v[2] + translation[2]] for v in vertices]
|
|
||||||
|
|
||||||
def merge_two_objs(self, obj1_path: str, obj2_path: str, output_path: str, translation, grid_id1: tuple, grid_id2: tuple):
|
|
||||||
"""合并两个OBJ文件"""
|
"""合并两个OBJ文件"""
|
||||||
try:
|
try:
|
||||||
self.logger.info(f"开始合并OBJ模型:\n输入1: {obj1_path}\n输入2: {obj2_path}")
|
self.logger.info(f"开始合并OBJ模型:\n输入1: {obj1_path}\n输入2: {obj2_path}")
|
||||||
@ -144,17 +148,13 @@ class MergeObj:
|
|||||||
for old_name in materials2.keys():
|
for old_name in materials2.keys():
|
||||||
material_map2[old_name] = f"material_{grid_id2[0]}_{grid_id2[1]}_{old_name}"
|
material_map2[old_name] = f"material_{grid_id2[0]}_{grid_id2[1]}_{old_name}"
|
||||||
|
|
||||||
# 平移第二个模型的顶点
|
|
||||||
vertices2_translated = self.translate_vertices(
|
|
||||||
vertices2, translation)
|
|
||||||
|
|
||||||
# 计算偏移量
|
# 计算偏移量
|
||||||
v_offset = len(vertices1)
|
v_offset = len(vertices1)
|
||||||
vt_offset = len(tex_coords1)
|
vt_offset = len(tex_coords1)
|
||||||
vn_offset = len(normals1)
|
vn_offset = len(normals1)
|
||||||
|
|
||||||
# 合并顶点、纹理坐标和法线
|
# 合并顶点、纹理坐标和法线
|
||||||
all_vertices = vertices1 + vertices2_translated
|
all_vertices = vertices1 + vertices2
|
||||||
all_tex_coords = tex_coords1 + tex_coords2
|
all_tex_coords = tex_coords1 + tex_coords2
|
||||||
all_normals = normals1 + normals2
|
all_normals = normals1 + normals2
|
||||||
|
|
||||||
@ -186,6 +186,188 @@ class MergeObj:
|
|||||||
self.logger.error(f"合并OBJ模型时发生错误: {str(e)}", exc_info=True)
|
self.logger.error(f"合并OBJ模型时发生错误: {str(e)}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def merge_grid_obj(self, grid_points: Dict[tuple, pd.DataFrame]) -> Tuple[float, float]:
|
||||||
|
"""合并所有网格的OBJ模型
|
||||||
|
Args:
|
||||||
|
grid_points: 网格点数据字典
|
||||||
|
Returns:
|
||||||
|
Tuple[float, float]: (longitude, latitude)中心点经纬度坐标
|
||||||
|
"""
|
||||||
|
if len(grid_points) == 1:
|
||||||
|
grid_id = list(grid_points.keys())[0]
|
||||||
|
shutil.copytree(os.path.join(self.output_dir,
|
||||||
|
f"grid_{grid_id[0]}_{grid_id[1]}",
|
||||||
|
"project",
|
||||||
|
"odm_texturing"),
|
||||||
|
os.path.join(self.output_dir, "texturing"))
|
||||||
|
os.rename(os.path.join(self.output_dir, "texturing", "odm_textured_model_geo.obj"),
|
||||||
|
os.path.join(self.output_dir, "texturing", "textured_model.obj"))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建输出目录
|
||||||
|
output_model_dir = os.path.join(self.output_dir, "texturing")
|
||||||
|
os.makedirs(output_model_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 第一次遍历:获取所有grid的UTM范围
|
||||||
|
for grid_id, points in grid_points.items():
|
||||||
|
base_dir = os.path.join(
|
||||||
|
self.output_dir,
|
||||||
|
f"grid_{grid_id[0]}_{grid_id[1]}",
|
||||||
|
"project"
|
||||||
|
)
|
||||||
|
log_file = os.path.join(base_dir, "odm_orthophoto", "odm_orthophoto_log.txt")
|
||||||
|
east_offset, north_offset = self.read_utm_offset(log_file)
|
||||||
|
|
||||||
|
# 更新UTM范围
|
||||||
|
self.min_east = min(self.min_east, east_offset)
|
||||||
|
self.min_north = min(self.min_north, north_offset)
|
||||||
|
self.max_east = max(self.max_east, east_offset)
|
||||||
|
self.max_north = max(self.max_north, north_offset)
|
||||||
|
|
||||||
|
# 获取所有有效的网格文件
|
||||||
|
grid_files = {}
|
||||||
|
all_vertices = [] # 用于存储所有顶点坐标
|
||||||
|
for grid_id, points in grid_points.items():
|
||||||
|
base_dir = os.path.join(
|
||||||
|
self.output_dir,
|
||||||
|
f"grid_{grid_id[0]}_{grid_id[1]}",
|
||||||
|
"project",
|
||||||
|
"odm_texturing"
|
||||||
|
)
|
||||||
|
obj_path = os.path.join(base_dir, "odm_textured_model_geo.obj")
|
||||||
|
mtl_path = os.path.join(base_dir, "odm_textured_model_geo.mtl")
|
||||||
|
|
||||||
|
if not os.path.exists(obj_path) or not os.path.exists(mtl_path):
|
||||||
|
self.logger.warning(
|
||||||
|
f"网格 ({grid_id[0]},{grid_id[1]}) 的文件不存在")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 读取UTM偏移量
|
||||||
|
log_file = os.path.join(base_dir, "..", "odm_orthophoto", "odm_orthophoto_log.txt")
|
||||||
|
utm_offset = self.read_utm_offset(log_file)
|
||||||
|
|
||||||
|
# 修改obj文件的顶点坐标
|
||||||
|
modified_obj = self.modify_obj_coordinates(obj_path, utm_offset)
|
||||||
|
|
||||||
|
grid_files[grid_id] = {
|
||||||
|
'obj': modified_obj,
|
||||||
|
'mtl': mtl_path.replace('.mtl', '_utm.mtl'),
|
||||||
|
'dir': base_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
# 读取obj文件的顶点坐标
|
||||||
|
vertices, _, _, _, _, _ = self.read_obj(modified_obj)
|
||||||
|
all_vertices.extend(vertices)
|
||||||
|
|
||||||
|
if not grid_files:
|
||||||
|
self.logger.error("没有找到有效的文件")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 收集所有材质和纹理信息
|
||||||
|
all_materials = {}
|
||||||
|
for grid_id, files in grid_files.items():
|
||||||
|
# 复制并重命名纹理文件
|
||||||
|
texture_map = self.copy_and_rename_texture(
|
||||||
|
files['dir'],
|
||||||
|
output_model_dir,
|
||||||
|
grid_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# 读取并更新MTL内容
|
||||||
|
materials = self.read_mtl(files['mtl'])
|
||||||
|
updated_materials = self.update_mtl_content(
|
||||||
|
materials,
|
||||||
|
texture_map,
|
||||||
|
grid_id
|
||||||
|
)
|
||||||
|
all_materials.update(updated_materials)
|
||||||
|
|
||||||
|
# 写入合并后的MTL文件
|
||||||
|
final_mtl = os.path.join(output_model_dir, "textured_model.mtl")
|
||||||
|
with open(final_mtl, 'w') as f:
|
||||||
|
for mat_name, content in all_materials.items():
|
||||||
|
f.write(f"newmtl {mat_name}\n")
|
||||||
|
for line in content:
|
||||||
|
f.write(f"{line}\n")
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
# 合并OBJ文件
|
||||||
|
reference_id = list(grid_files.keys())[0]
|
||||||
|
merged_obj = grid_files[reference_id]['obj']
|
||||||
|
temp_files = [] # 记录所有中间文件
|
||||||
|
|
||||||
|
for grid_id, files in list(grid_files.items())[1:]:
|
||||||
|
# 生成临时输出文件名
|
||||||
|
temp_output = os.path.join(
|
||||||
|
output_model_dir,
|
||||||
|
f"temp_merged_{int(time.time())}.obj"
|
||||||
|
)
|
||||||
|
temp_files.append(temp_output) # 添加到临时文件列表
|
||||||
|
|
||||||
|
self.merge_two_objs(
|
||||||
|
merged_obj, files['obj'], temp_output, reference_id, grid_id)
|
||||||
|
|
||||||
|
merged_obj = temp_output
|
||||||
|
|
||||||
|
# 最终结果
|
||||||
|
final_obj = os.path.join(output_model_dir, "textured_model.obj")
|
||||||
|
try:
|
||||||
|
if os.path.exists(final_obj):
|
||||||
|
os.remove(final_obj)
|
||||||
|
os.rename(merged_obj, final_obj)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"重命名最终文件失败: {str(e)}")
|
||||||
|
shutil.copy2(merged_obj, final_obj)
|
||||||
|
try:
|
||||||
|
os.remove(merged_obj)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 清理所有临时文件
|
||||||
|
for temp_file in temp_files:
|
||||||
|
if os.path.exists(temp_file):
|
||||||
|
try:
|
||||||
|
os.remove(temp_file)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(
|
||||||
|
f"删除临时文件失败: {temp_file}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
# 计算中心点经纬度
|
||||||
|
center_lon, center_lat = self.get_center_coordinates(all_vertices)
|
||||||
|
self.logger.info(f"模型中心点经纬度: ({center_lon}, {center_lat})")
|
||||||
|
|
||||||
|
return center_lon, center_lat
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"合并过程中发生错误: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_center_coordinates(self, vertices: List[List[float]]) -> Tuple[float, float]:
|
||||||
|
"""计算顶点的中心点UTM坐标,并转换为WGS84经纬度。
|
||||||
|
注意:顶点坐标是相对于整体最小UTM坐标的偏移值,需要加回最小UTM坐标。
|
||||||
|
Args:
|
||||||
|
vertices: 顶点列表,每个顶点是[x, y, z]格式,x和y是相对于最小UTM坐标的偏移
|
||||||
|
Returns:
|
||||||
|
Tuple[float, float]: (longitude, latitude)经纬度坐标
|
||||||
|
"""
|
||||||
|
# 计算相对坐标的边界框
|
||||||
|
x_coords = [v[0] for v in vertices]
|
||||||
|
y_coords = [v[1] for v in vertices]
|
||||||
|
|
||||||
|
# 计算中心点相对坐标
|
||||||
|
center_x_relative = (min(x_coords) + max(x_coords)) / 2
|
||||||
|
center_y_relative = (min(y_coords) + max(y_coords)) / 2
|
||||||
|
|
||||||
|
# 加回最小UTM坐标得到实际的UTM坐标
|
||||||
|
center_x_utm = center_x_relative + self.min_east
|
||||||
|
center_y_utm = center_y_relative + self.min_north
|
||||||
|
|
||||||
|
# 转换为WGS84经纬度
|
||||||
|
lon, lat = self.transformer.transform(center_x_utm, center_y_utm)
|
||||||
|
self.logger.info(f"模型UTM中心点: ({center_x_utm}, {center_y_utm})")
|
||||||
|
return lon, lat
|
||||||
|
|
||||||
def read_mtl(self, mtl_path: str) -> dict:
|
def read_mtl(self, mtl_path: str) -> dict:
|
||||||
"""读取MTL文件内容
|
"""读取MTL文件内容
|
||||||
Returns:
|
Returns:
|
||||||
@ -291,153 +473,54 @@ class MergeObj:
|
|||||||
|
|
||||||
return updated_materials
|
return updated_materials
|
||||||
|
|
||||||
def merge_grid_obj(self, grid_points: Dict[tuple, pd.DataFrame], translations: Dict[tuple, tuple]):
|
def read_utm_offset(self, log_file: str) -> tuple:
|
||||||
"""合并所有网格的OBJ模型"""
|
"""读取UTM偏移量"""
|
||||||
if len(grid_points) == 1:
|
try:
|
||||||
grid_id = list(grid_points.keys())[0]
|
east_offset = None
|
||||||
shutil.copytree(os.path.join(self.output_dir,
|
north_offset = None
|
||||||
f"grid_{grid_id[0]}_{grid_id[1]}",
|
|
||||||
"project",
|
with open(log_file, 'r') as f:
|
||||||
"odm_texturing"),
|
lines = f.readlines()
|
||||||
os.path.join(self.output_dir, "texturing"))
|
for i, line in enumerate(lines):
|
||||||
os.rename(os.path.join(self.output_dir, "texturing", "odm_textured_model_geo.obj"),
|
if 'utm_north_offset' in line and i + 1 < len(lines):
|
||||||
os.path.join(self.output_dir, "texturing", "textured_model.obj"))
|
north_offset = float(lines[i + 1].strip())
|
||||||
self.logger.info(f"开始执行格式转换")
|
elif 'utm_east_offset' in line and i + 1 < len(lines):
|
||||||
docker_command = (
|
east_offset = float(lines[i + 1].strip())
|
||||||
f"docker run --rm -it "
|
|
||||||
f"-v {self.output_dir}/texturing:/data "
|
if east_offset is None or north_offset is None:
|
||||||
f"-e LD_LIBRARY_PATH=/opt/osg/build/lib:$LD_LIBRARY_PATH "
|
raise ValueError("未找到UTM偏移量")
|
||||||
f"osg-ubuntu2004 osgconv /data/textured_model.obj /data/textured_model.osgb"
|
|
||||||
)
|
return east_offset, north_offset
|
||||||
self.logger.info(docker_command)
|
except Exception as e:
|
||||||
subprocess.run(
|
self.logger.error(f"读取UTM偏移量时发生错误: {str(e)}")
|
||||||
docker_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
raise
|
||||||
self.logger.info(f"格式转换完成")
|
|
||||||
return
|
def modify_obj_coordinates(self, obj_file: str, utm_offset: tuple) -> str:
|
||||||
|
"""修改obj文件中的顶点坐标,使用相对坐标系"""
|
||||||
|
east_offset, north_offset = utm_offset
|
||||||
|
output_obj = obj_file.replace('.obj', '_utm.obj')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 创建输出目录
|
with open(obj_file, 'r') as f_in, open(output_obj, 'w') as f_out:
|
||||||
output_model_dir = os.path.join(self.output_dir, "texturing")
|
for line in f_in:
|
||||||
os.makedirs(output_model_dir, exist_ok=True)
|
if line.startswith('v '):
|
||||||
|
# 处理顶点坐标行
|
||||||
|
parts = line.strip().split()
|
||||||
|
# 使用相对于整体最小UTM坐标的偏移
|
||||||
|
x = float(parts[1]) + (east_offset - self.min_east)
|
||||||
|
y = float(parts[2]) + (north_offset - self.min_north)
|
||||||
|
z = float(parts[3])
|
||||||
|
f_out.write(f'v {x:.6f} {y:.6f} {z:.6f}\n')
|
||||||
|
else:
|
||||||
|
# 其他行直接写入
|
||||||
|
f_out.write(line)
|
||||||
|
|
||||||
# 获取所有有效的网格文件
|
# 复制材质文件
|
||||||
grid_files = {}
|
mtl_file = obj_file.replace('.obj', '.mtl')
|
||||||
for grid_id, points in grid_points.items():
|
if os.path.exists(mtl_file):
|
||||||
base_dir = os.path.join(
|
shutil.copy2(mtl_file, mtl_file.replace('.mtl', '_utm.mtl'))
|
||||||
self.output_dir,
|
|
||||||
f"grid_{grid_id[0]}_{grid_id[1]}",
|
|
||||||
"project",
|
|
||||||
"odm_texturing"
|
|
||||||
)
|
|
||||||
obj_path = os.path.join(base_dir, "odm_textured_model_geo.obj")
|
|
||||||
mtl_path = os.path.join(base_dir, "odm_textured_model_geo.mtl")
|
|
||||||
|
|
||||||
if not os.path.exists(obj_path) or not os.path.exists(mtl_path):
|
|
||||||
self.logger.warning(
|
|
||||||
f"网格 ({grid_id[0]},{grid_id[1]}) 的文件不存在")
|
|
||||||
continue
|
|
||||||
|
|
||||||
grid_files[grid_id] = {
|
|
||||||
'obj': obj_path,
|
|
||||||
'mtl': mtl_path,
|
|
||||||
'dir': base_dir
|
|
||||||
}
|
|
||||||
|
|
||||||
if not grid_files:
|
|
||||||
self.logger.error("没有找到有效的文件")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 收集所有材质和纹理信息
|
|
||||||
all_materials = {}
|
|
||||||
for grid_id, files in grid_files.items():
|
|
||||||
# 复制并重命名纹理文件
|
|
||||||
texture_map = self.copy_and_rename_texture(
|
|
||||||
files['dir'],
|
|
||||||
output_model_dir,
|
|
||||||
grid_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# 读取并更新MTL内容
|
|
||||||
materials = self.read_mtl(files['mtl'])
|
|
||||||
updated_materials = self.update_mtl_content(
|
|
||||||
materials,
|
|
||||||
texture_map,
|
|
||||||
grid_id
|
|
||||||
)
|
|
||||||
all_materials.update(updated_materials)
|
|
||||||
|
|
||||||
# 写入合并后的MTL文件
|
|
||||||
final_mtl = os.path.join(output_model_dir, "textured_model.mtl")
|
|
||||||
with open(final_mtl, 'w') as f:
|
|
||||||
for mat_name, content in all_materials.items():
|
|
||||||
f.write(f"newmtl {mat_name}\n")
|
|
||||||
for line in content:
|
|
||||||
f.write(f"{line}\n")
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
# 合并OBJ文件
|
|
||||||
reference_id = list(grid_files.keys())[0]
|
|
||||||
merged_obj = grid_files[reference_id]['obj']
|
|
||||||
temp_files = [] # 记录所有中间文件
|
|
||||||
|
|
||||||
for grid_id, files in list(grid_files.items())[1:]:
|
|
||||||
translation = translations[grid_id]
|
|
||||||
translation = (translation[0], translation[1], 0)
|
|
||||||
|
|
||||||
# 生成临时输出文件名
|
|
||||||
temp_output = os.path.join(
|
|
||||||
output_model_dir,
|
|
||||||
f"temp_merged_{int(time.time())}.obj"
|
|
||||||
)
|
|
||||||
temp_files.append(temp_output) # 添加到临时文件列表
|
|
||||||
|
|
||||||
self.merge_two_objs(
|
|
||||||
merged_obj, files['obj'], temp_output, translation, reference_id, grid_id)
|
|
||||||
|
|
||||||
merged_obj = temp_output
|
|
||||||
|
|
||||||
# 最终结果
|
|
||||||
final_obj = os.path.join(output_model_dir, "textured_model.obj")
|
|
||||||
try:
|
|
||||||
if os.path.exists(final_obj):
|
|
||||||
os.remove(final_obj)
|
|
||||||
os.rename(merged_obj, final_obj)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"重命名最终文件失败: {str(e)}")
|
|
||||||
shutil.copy2(merged_obj, final_obj)
|
|
||||||
try:
|
|
||||||
os.remove(merged_obj)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 清理所有临时文件
|
|
||||||
for temp_file in temp_files:
|
|
||||||
if os.path.exists(temp_file):
|
|
||||||
try:
|
|
||||||
os.remove(temp_file)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(
|
|
||||||
f"删除临时文件失败: {temp_file}, 错误: {str(e)}")
|
|
||||||
|
|
||||||
self.logger.info(
|
|
||||||
f"模型合并完成,输出目录: {output_model_dir}\n"
|
|
||||||
f"- OBJ文件: textured_model.obj\n"
|
|
||||||
f"- MTL文件: textured_model.mtl\n"
|
|
||||||
f"- 纹理文件: {len(os.listdir(output_model_dir)) - 2}个"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"开始执行格式转换")
|
|
||||||
docker_command = (
|
|
||||||
f"docker run --rm -it "
|
|
||||||
f"-v {output_model_dir}:/data "
|
|
||||||
f"-e LD_LIBRARY_PATH=/opt/osg/build/lib:$LD_LIBRARY_PATH "
|
|
||||||
f"osg-ubuntu2004 osgconv /data/textured_model.obj /data/textured_model.osgb"
|
|
||||||
)
|
|
||||||
self.logger.info(docker_command)
|
|
||||||
subprocess.run(
|
|
||||||
docker_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
self.logger.info(f"格式转换完成")
|
|
||||||
|
|
||||||
|
return output_obj
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"合并过程中发生错误: {str(e)}", exc_info=True)
|
self.logger.error(f"修改obj坐标时发生错误: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
@ -1,140 +1,63 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
from typing import Tuple
|
||||||
from typing import List, Tuple
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
class ObjPostProcessor:
|
class ObjPostProcessor:
|
||||||
def __init__(self, output_dir: str):
|
def __init__(self, output_dir: str):
|
||||||
self.output_dir = output_dir
|
self.output_dir = output_dir
|
||||||
self.logger = logging.getLogger('UAV_Preprocess.ObjPostProcessor')
|
self.logger = logging.getLogger('UAV_Preprocess.ObjPostProcessor')
|
||||||
# 用于存储所有grid的UTM范围
|
|
||||||
self.min_east = float('inf')
|
|
||||||
self.min_north = float('inf')
|
|
||||||
self.max_east = float('-inf')
|
|
||||||
self.max_north = float('-inf')
|
|
||||||
|
|
||||||
def process_obj_files(self):
|
def create_metadata_xml(self, osgb_dir: str, lon: float, lat: float):
|
||||||
"""处理所有grid中的obj文件"""
|
"""创建metadata.xml文件,包含地理参考信息
|
||||||
|
Args:
|
||||||
|
osgb_dir: osgb输出目录
|
||||||
|
lon: 中心点经度
|
||||||
|
lat: 中心点纬度
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# 1. 遍历所有grid文件夹
|
metadata_content = f'''<?xml version="1.0" encoding="utf-8"?>
|
||||||
grid_dirs = [d for d in os.listdir(
|
<ModelMetadata version="1">
|
||||||
self.output_dir) if d.startswith('grid_')]
|
<!--Spatial Reference System-->
|
||||||
|
<SRS>EPSG:4326</SRS>
|
||||||
|
<!--Origin in Spatial Reference System-->
|
||||||
|
<SRSOrigin>{lon},{lat},0.000000</SRSOrigin>
|
||||||
|
<Texture>
|
||||||
|
<ColorSource>Visible</ColorSource>
|
||||||
|
</Texture>
|
||||||
|
</ModelMetadata>'''
|
||||||
|
|
||||||
# 第一次遍历:获取所有grid的UTM范围
|
# metadata.xml 放在根目录
|
||||||
for grid_dir in grid_dirs:
|
metadata_path = os.path.join(osgb_dir, 'metadata.xml')
|
||||||
grid_path = os.path.join(self.output_dir, grid_dir)
|
with open(metadata_path, 'w', encoding='utf-8') as f:
|
||||||
log_file = os.path.join(
|
f.write(metadata_content)
|
||||||
grid_path, 'project', 'odm_orthophoto', 'odm_orthophoto_log.txt')
|
|
||||||
east_offset, north_offset = self.read_utm_offset(log_file)
|
|
||||||
|
|
||||||
# 更新UTM范围
|
self.logger.info(f"已创建metadata.xml: {metadata_path}")
|
||||||
self.min_east = min(self.min_east, east_offset)
|
|
||||||
self.min_north = min(self.min_north, north_offset)
|
|
||||||
self.max_east = max(self.max_east, east_offset)
|
|
||||||
self.max_north = max(self.max_north, north_offset)
|
|
||||||
|
|
||||||
# 创建osgb输出目录
|
except Exception as e:
|
||||||
|
self.logger.error(f"创建metadata.xml时发生错误: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def convert_to_osgb(self, center_coords: Tuple[float, float]):
|
||||||
|
"""将obj转换为osgb,并创建metadata.xml
|
||||||
|
Args:
|
||||||
|
center_coords: (longitude, latitude)中心点经纬度坐标
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取合并后的obj文件路径
|
||||||
|
obj_dir = os.path.join(self.output_dir, 'texturing')
|
||||||
|
obj_file = os.path.join(obj_dir, 'textured_model.obj')
|
||||||
|
if not os.path.exists(obj_file):
|
||||||
|
raise Exception(f"未找到obj文件: {obj_file}")
|
||||||
|
|
||||||
|
# 创建osgb目录结构
|
||||||
osgb_dir = os.path.join(self.output_dir, 'osgb')
|
osgb_dir = os.path.join(self.output_dir, 'osgb')
|
||||||
os.makedirs(os.path.join(osgb_dir, 'Data'), exist_ok=True)
|
osgb_data_dir = os.path.join(osgb_dir, 'Data', 'textured_model')
|
||||||
|
os.makedirs(osgb_data_dir, exist_ok=True)
|
||||||
|
|
||||||
# 第二次遍历:处理每个grid
|
# 输出文件路径
|
||||||
for grid_dir in grid_dirs:
|
output_osgb = os.path.join(osgb_data_dir, 'textured_model.osgb')
|
||||||
grid_path = os.path.join(self.output_dir, grid_dir)
|
|
||||||
self.process_single_grid(grid_path, osgb_dir)
|
|
||||||
|
|
||||||
# 创建metadata.xml
|
|
||||||
self.create_metadata_xml(osgb_dir)
|
|
||||||
|
|
||||||
self.logger.info("所有grid处理完成")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"处理obj文件时发生错误: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def process_single_grid(self, grid_path: str, osgb_dir: str):
|
|
||||||
"""处理单个grid的obj文件"""
|
|
||||||
try:
|
|
||||||
# 1. 读取UTM偏移量
|
|
||||||
log_file = os.path.join(
|
|
||||||
grid_path, 'project', 'odm_orthophoto', 'odm_orthophoto_log.txt')
|
|
||||||
utm_offset = self.read_utm_offset(log_file)
|
|
||||||
|
|
||||||
# 2. 修改obj文件的顶点坐标
|
|
||||||
obj_file = os.path.join(
|
|
||||||
grid_path, 'project', 'odm_texturing', 'odm_textured_model_geo.obj')
|
|
||||||
modified_obj = self.modify_obj_coordinates(obj_file, utm_offset)
|
|
||||||
|
|
||||||
# 3. 使用osgconv转换为osgb
|
|
||||||
grid_name = os.path.basename(grid_path)
|
|
||||||
self.convert_to_osgb(modified_obj, grid_name, osgb_dir)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"处理grid {grid_path} 时发生错误: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def read_utm_offset(self, log_file: str) -> Tuple[float, float]:
|
|
||||||
"""读取UTM偏移量"""
|
|
||||||
try:
|
|
||||||
east_offset = None
|
|
||||||
north_offset = None
|
|
||||||
|
|
||||||
with open(log_file, 'r') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if 'utm_north_offset' in line and i + 1 < len(lines):
|
|
||||||
north_offset = float(lines[i + 1].strip())
|
|
||||||
elif 'utm_east_offset' in line and i + 1 < len(lines):
|
|
||||||
east_offset = float(lines[i + 1].strip())
|
|
||||||
|
|
||||||
if east_offset is None or north_offset is None:
|
|
||||||
raise ValueError("未找到UTM偏移量")
|
|
||||||
|
|
||||||
return east_offset, north_offset
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"读取UTM偏移量时发生错误: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def modify_obj_coordinates(self, obj_file: str, utm_offset: Tuple[float, float]) -> str:
|
|
||||||
"""修改obj文件中的顶点坐标,使用相对坐标系"""
|
|
||||||
east_offset, north_offset = utm_offset
|
|
||||||
output_obj = obj_file.replace('.obj', '_utm.obj')
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(obj_file, 'r') as f_in, open(output_obj, 'w') as f_out:
|
|
||||||
for line in f_in:
|
|
||||||
if line.startswith('v '):
|
|
||||||
# 处理顶点坐标行
|
|
||||||
parts = line.strip().split()
|
|
||||||
# 使用相对于整体最小UTM坐标的偏移
|
|
||||||
x = float(parts[1]) + (east_offset - self.min_east)
|
|
||||||
y = float(parts[2]) + (north_offset - self.min_north)
|
|
||||||
z = float(parts[3])
|
|
||||||
f_out.write(f'v {x:.6f} {y:.6f} {z:.6f}\n')
|
|
||||||
else:
|
|
||||||
# 其他行直接写入
|
|
||||||
f_out.write(line)
|
|
||||||
|
|
||||||
# 复制材质文件
|
|
||||||
mtl_file = obj_file.replace('.obj', '.mtl')
|
|
||||||
if os.path.exists(mtl_file):
|
|
||||||
shutil.copy2(mtl_file, mtl_file.replace('.mtl', '_utm.mtl'))
|
|
||||||
|
|
||||||
return output_obj
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"修改obj坐标时发生错误: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def convert_to_osgb(self, obj_file: str, grid_name: str, osgb_dir: str):
|
|
||||||
"""使用osgconv将obj转换为osgb"""
|
|
||||||
try:
|
|
||||||
# 创建tile目录
|
|
||||||
tile_dir = os.path.join(osgb_dir, 'Data', grid_name)
|
|
||||||
os.makedirs(tile_dir, exist_ok=True)
|
|
||||||
|
|
||||||
output_osgb = os.path.join(tile_dir, f'{grid_name}.osgb')
|
|
||||||
|
|
||||||
# 构建osgconv命令
|
# 构建osgconv命令
|
||||||
cmd = [
|
cmd = [
|
||||||
@ -148,35 +71,19 @@ class ObjPostProcessor:
|
|||||||
]
|
]
|
||||||
|
|
||||||
# 执行命令
|
# 执行命令
|
||||||
self.logger.info(f"执行osgconv命令:{cmd}")
|
self.logger.info(f"执行osgconv命令:{' '.join(cmd)}")
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise Exception(f"osgconv执行失败: {result.stderr}")
|
raise Exception(f"osgb格式转换失败: {result.stderr}")
|
||||||
|
|
||||||
|
# 创建metadata.xml
|
||||||
|
lon, lat = center_coords
|
||||||
|
self.create_metadata_xml(osgb_dir, lon, lat)
|
||||||
|
|
||||||
self.logger.info(f"转换完成: {output_osgb}")
|
self.logger.info(f"转换完成: {output_osgb}")
|
||||||
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"转换osgb时发生错误: {str(e)}")
|
self.logger.error(f"转换osgb时发生错误: {str(e)}")
|
||||||
raise
|
return False
|
||||||
|
|
||||||
def create_metadata_xml(self, osgb_dir: str):
|
|
||||||
"""创建metadata.xml文件,包含UTM偏移信息"""
|
|
||||||
try:
|
|
||||||
# 这里需要将UTM坐标转换为WGS84经纬度坐标
|
|
||||||
# 这里使用示例值,实际应用中需要进行真实的坐标转换
|
|
||||||
metadata_content = f'''<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ModelMetadata version="1">
|
|
||||||
<SRS>EPSG:32649</SRS>
|
|
||||||
<SRSOrigin>{self.min_east:.6f},{self.min_north:.6f},0.000000</SRSOrigin>
|
|
||||||
<Texture>
|
|
||||||
<ColorSource>Visible</ColorSource>
|
|
||||||
</Texture>
|
|
||||||
</ModelMetadata>'''
|
|
||||||
|
|
||||||
with open(os.path.join(osgb_dir, 'metadata.xml'), 'w') as f:
|
|
||||||
f.write(metadata_content)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"创建metadata.xml时发生错误: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
Loading…
Reference in New Issue
Block a user