diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..c0a4599
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+# Ensure shell scripts uses the correct line ending.
+Dockerfile eol=lf
+*.sh eol=lf
+*.bat eol=crlf
+*.cmd eol=crlf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c301e5a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,39 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# Custom
+*.tmp
+.DS_Store
+CMakeSettings.json
+.vs/
+.idea/
+.vscode/
+out/
+bin*/
+make*/
diff --git a/COPYRIGHT.md b/COPYRIGHT.md
new file mode 100644
index 0000000..81d7796
--- /dev/null
+++ b/COPYRIGHT.md
@@ -0,0 +1,89 @@
+## OpenMVS License
+
+* __OpenMVS__
+ [http://cdcseacave.github.io/openMVS](http://cdcseacave.github.io/openMVS)
+ Copyright (c) OpenMVS authors
+ Licensed under the [AGPL](http://opensource.org/licenses/AGPL-3.0) license.
+
+## Included third parties license details
+
+This program includes works distributed under the terms of another license(s) and other copyright notice(s).
+
+* __SeaCave__
+ Copyright (c) 2007 SEACAVE SRL.
+ Licensed under a [Boost license](http://www.boost.org/users/license.html).
+
+* __easyexif__
+ [https://github.com/mayanklahiri/easyexif](https://github.com/mayanklahiri/easyexif)
+ Copyright (c) 2010 Mayank Lahiri.
+ Distributed under the [New BSD License](http://opensource.org/licenses/BSD-3-Clause).
+
+* __histogram__
+ Copyright (c) Jansson Consulting & Pierre Moulon.
+ Licensed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0).
+
+* __htmlDoc__
+ Copyright (c) Pierre Moulon.
+ Licensed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0).
+
+* __ACRANSAC__
+ Copyright (c) Pierre Moulon.
+ Licensed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0).
+
+* __stlplus3__
+ [http://stlplus.sourceforge.net](http://stlplus.sourceforge.net)
+ Copyright (c) 1999-2004 Southampton University, 2004 onwards Andy Rushton. All rights reserved.
+ Licensed under the [BSD license](http://opensource.org/licenses/bsd-license.php).
+
+* __rectangle-bin-packing__
+ [http://clb.demon.fi/projects/rectangle-bin-packing](http://clb.demon.fi/projects/rectangle-bin-packing)
+ Copyright (c) Jukka Jylänki.
+ Released to Public Domain, do whatever you want with it.
+
+* __ceres-solver__
+ [http://ceres-solver.org](http://ceres-solver.org)
+ Copyright 2015 Google Inc. All rights reserved.
+ Licensed under the [New BSD license](http://ceres-solver.org/license.html).
+
+* __lmfit__
+ [http://apps.jcns.fz-juelich.de/doku/sc/lmfit](http://apps.jcns.fz-juelich.de/doku/sc/lmfit)
+ Copyright (c) Joachim Wuttke.
+ Licensed under the [FreeBSD license](http://opensource.org/licenses/BSD-2-Clause).
+
+* __TRWS__
+ [http://pub.ist.ac.at/~vnk/software.html](http://pub.ist.ac.at/~vnk/software.html)
+ Copyright (c) Vladimir Kolmogorov.
+ Licensed under the [MSR-SSLA license](http://research.microsoft.com/en-us/um/people/antr/vrr/vrr/license.htm).
+
+* __ibfs__
+ [http://www.cs.tau.ac.il/~sagihed/ibfs](http://www.cs.tau.ac.il/~sagihed/ibfs)
+ Copyright (c) Haim Kaplan and Sagi Hed.
+ This software can be used for research purposes only.
+
+* __loopy-belief-propagation__
+ [https://github.com/nmoehrle/mvs-texturing](https://github.com/nmoehrle/mvs-texturing)
+ Copyright (c) Michael Waechter.
+ Licensed under the [BSD 3-Clause license](http://opensource.org/licenses/BSD-3-Clause).
+
+* __eigen__
+ [http://eigen.tuxfamily.org](http://eigen.tuxfamily.org)
+ Copyright (c) Eigen authors.
+ Distributed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0).
+ Compiled with EIGEN_MPL2_ONLY to ensure MPL2 compatible code.
+
+* __OpenCV__
+ [http://opencv.org](http://opencv.org)
+ Copyright (c) 2015, Itseez.
+ Licensed under the [BSD license](http://opensource.org/licenses/bsd-license.php).
+
+* __Boost__
+ [http://www.boost.org](http://www.boost.org)
+ Copyright Beman Dawes, David Abrahams, 1998-2005.
+ Copyright Rene Rivera 2004-2007.
+ Licensed under a [Boost license](http://www.boost.org/users/license.html).
+
+* __CGAL__
+ [http://www.cgal.org](http://www.cgal.org)
+ Copyright (c) 1995-2015 The CGAL Project. All rights reserved.
+ Licensed under the [GPL](http://www.gnu.org/copyleft/gpl.html)/[LGPL license](http://www.gnu.org/copyleft/lesser.html).
+
diff --git a/fill_all_empty_faces_v1.2.py b/fill_all_empty_faces_v1.2.py
new file mode 100644
index 0000000..7fc0d80
--- /dev/null
+++ b/fill_all_empty_faces_v1.2.py
@@ -0,0 +1,551 @@
+import torch
+import argparse
+import os
+import logging
+import subprocess
+import numpy as np
+import cv2
+import time
+from collections import defaultdict
+import tqdm
+from multiprocessing import Pool, cpu_count
+import multiprocessing
+from typing import List, Tuple, Dict # 如果还未导入
+
+
+def read_vertices(obj_path):
+ vertices = []
+ with open(obj_path, 'r') as file:
+ lines = file.readlines()
+ for line in lines:
+ if line.startswith('v '): # 顶点坐标
+ vertices.append(list(map(float, line.split()[1:4])))
+ vertices = torch.tensor(vertices)
+ return vertices
+
+def read_uvs(obj_path):
+ uv_coordinates = []
+ with open(obj_path, 'r') as file:
+ lines = file.readlines()
+ for line in lines:
+ if line.startswith('vt '): # UV 坐标
+ uv_coordinates.append(list(map(float, line.split()[1:3])))
+ uv_coordinates = torch.tensor(uv_coordinates)
+ return uv_coordinates
+
+def read_faces(obj_path):
+ vertex_indices = []
+ uv_indices = []
+ with open(obj_path, 'r') as file:
+ lines = file.readlines()
+
+ for line in lines:
+ if line.startswith('f '): # 面
+ parts = line.split()[1:]
+ v_indices = []
+ uv_indices_temp = []
+ for face in parts:
+ v, vt = map(int, face.split('/')[:2])
+ v_indices.append(v - 1)
+ uv_indices_temp.append(vt - 1)
+ vertex_indices.append(v_indices)
+ uv_indices.append(uv_indices_temp)
+ vertex_indices = torch.tensor(vertex_indices)
+ uv_indices = torch.tensor(uv_indices)
+ return vertex_indices, uv_indices
+
+def read_missing_faces(missing_faces_path):
+ with open(missing_faces_path, 'r') as file:
+ lines = file.readlines()
+ missing_color_faces = torch.tensor(
+ [int(line.strip()) for line in lines]
+ )
+ return missing_color_faces
+
+def read_uv_map(input_texture_path):
+ uv_map = cv2.imread(input_texture_path)
+ uv_map = cv2.cvtColor(uv_map, cv2.COLOR_BGR2RGB)
+ uv_map = torch.from_numpy(uv_map)
+ return uv_map
+
+def parse_obj_file_and_uv_map(obj_path, missing_faces_path, input_texture_path, device):
+ print(f"Reading OBJ file: {obj_path}")
+
+ # vertices = []
+ # uv_coordinates = []
+ # vertex_indices = []
+ # uv_indices = []
+ # multiprocessing.set_start_method('spawn', force=True)
+ # multiprocessing.freeze_support()
+ start_time = time.time()
+
+
+ p = Pool(5)
+ uv_map_result = p.apply_async(read_uv_map, (input_texture_path,))
+ vertices_result = p.apply_async(read_vertices, (obj_path,))
+ uv_coordinates_result = p.apply_async(read_uvs, (obj_path,))
+ faces_result = p.apply_async(read_faces, (obj_path,))
+ missing_faces_result = p.apply_async(read_missing_faces, (missing_faces_path,))
+
+ p.close()
+ p.join()
+
+ vertices = vertices_result.get()
+ uv_coordinates = uv_coordinates_result.get()
+ vertex_indices, uv_indices = faces_result.get()
+ missing_color_faces = missing_faces_result.get()
+ uv_map = uv_map_result.get()
+
+ vertices = vertices.to(device)
+ uv_coordinates = uv_coordinates.to(device)
+ vertex_indices = vertex_indices.to(device)
+ uv_indices = uv_indices.to(device)
+ missing_color_faces = missing_color_faces.to(device)
+ uv_map = uv_map.to(device)
+
+ end_time = time.time()
+ print(f"using: {end_time - start_time} seconds")
+
+ # exit()
+ print("Converting to tensors...")
+
+ return vertices, uv_coordinates, vertex_indices, uv_indices, missing_color_faces, uv_map
+
+def write_obj_with_uv_coordinates(filename, vertices, uvs, vertex_indices, uv_indices):
+ """
+ 高性能OBJ文件写入函数
+
+ Parameters:
+ filename (str): 输出OBJ文件路径
+ vertices (np.ndarray): 顶点数组
+ uvs (np.ndarray): UV坐标数组
+ vertex_indices (np.ndarray): 面的顶点索引
+ uv_indices (np.ndarray): 面的UV索引
+ """
+ # 估算数据大小(以字节为单位)
+ estimated_size = (
+ len(vertices) * 40 + # 每个顶点约40字节 (v x.xxxxxx y.xxxxxx z.xxxxxx\n)
+ len(uvs) * 30 + # 每个UV坐标约30字节 (vt x.xxxxxx y.xxxxxx\n)
+ len(vertex_indices) * 40 # 每个面约40字节 (f v1/vt1 v2/vt2 v3/vt3\n)
+ )
+
+ # 设置缓冲区大小为估算大小的1.2倍,最小256MB,最大1GB
+ buffer_size = min(max(int(estimated_size * 1.2), 256 * 1024 * 1024), 1024 * 1024 * 1024)
+
+ # 使用格式化字符串和列表推导式优化字符串生成
+ vertex_lines = ['v %.6f %.6f %.6f' % (v[0], v[1], v[2]) for v in vertices]
+ uv_lines = ['vt %.6f %.6f' % (uv[0], uv[1]) for uv in uvs]
+
+ # 优化face数据处理
+ face_lines = []
+ face_format = 'f %d/%d %d/%d %d/%d'
+ for v_idx, uv_idx in zip(vertex_indices, uv_indices):
+ face_lines.append(face_format % (
+ v_idx[0] + 1, uv_idx[0] + 1,
+ v_idx[1] + 1, uv_idx[1] + 1,
+ v_idx[2] + 1, uv_idx[2] + 1
+ ))
+
+ # 使用join一次性构建完整内容
+ content = ['mtllib mesh.mtl'] + vertex_lines + [''] + uv_lines + [''] + ['usemtl material_0'] + face_lines
+
+ # 一次性写入所有数据
+ with open(filename, 'w', buffering=buffer_size) as f:
+ f.write('\n'.join(content))
+
+def load_regions(filename):
+ regions = []
+ with open(filename, 'r') as file:
+ for line in file:
+ parts = line.split(";")
+ if len(parts) != 2:
+ continue # Skip any lines that don't have exactly two parts
+
+ first_set = set(int(x) for x in parts[0].strip().split())
+ second_set = set(int(x) for x in parts[1].strip().split())
+ regions.append((first_set, second_set))
+
+ return regions
+
+
+def build_face_adjacency(vertices, faces):
+ """
+ 构建面的邻接关系,基于共享边
+
+ Args:
+ vertices: 顶点数组
+ faces: 面片索引数组 (N x 3)
+
+ Returns:
+ dict: 面片邻接关系字典,key为面片索引,value为邻接面片索引列表
+ """
+ # 将faces转换为numpy数组以加快处理速度
+ faces = np.asarray(faces)
+ num_faces = len(faces)
+
+ # 为每个面创建所有边 (Nx3x2)
+ edges = np.stack([
+ np.column_stack((faces[:, i], faces[:, (i + 1) % 3]))
+ for i in range(3)
+ ], axis=1)
+
+ # 确保边的方向一致 (较小的顶点索引在前)
+ edges.sort(axis=2)
+
+ # 将边展平为 (Nx3, 2) 的形状
+ edges = edges.reshape(-1, 2)
+
+ # 创建边到面的映射
+ edge_faces = np.repeat(np.arange(num_faces), 3)
+
+ # 使用复合键对边进行排序
+ edge_keys = edges[:, 0] * vertices.shape[0] + edges[:, 1]
+ sort_idx = np.argsort(edge_keys)
+ edges = edges[sort_idx]
+ edge_faces = edge_faces[sort_idx]
+
+ # 找到重复的边(共享边)
+ same_edges = (edge_keys[sort_idx][1:] == edge_keys[sort_idx][:-1])
+ edge_start_idx = np.where(same_edges)[0]
+
+ # 构建邻接字典
+ face_adjacency = defaultdict(list)
+ for idx in edge_start_idx:
+ face1, face2 = edge_faces[idx], edge_faces[idx + 1]
+ face_adjacency[face1].append(face2)
+ face_adjacency[face2].append(face1)
+
+ return dict(face_adjacency)
+
+def find_groups_and_subgroups(face_adjacency, missing_faces):
+ """
+ 找到相连的面组和它们的邻接面
+ 返回:
+ regions: 列表,每个元素是一个元组 (missing_faces_set, adjacent_faces_set),
+ 与 load_regions() 函数返回格式保持一致
+ """
+ missing_faces_set = set(missing_faces.cpu().numpy())
+ unused_faces = set(missing_faces.cpu().numpy())
+ regions = []
+
+ total_faces = len(unused_faces)
+ with tqdm.tqdm(total=total_faces, desc="Processing faces") as pbar:
+ while unused_faces:
+ start_face = unused_faces.pop()
+ current_group = {start_face}
+ current_subgroup = set()
+
+ stack = [start_face]
+ while stack:
+ face_idx = stack.pop()
+
+ for neighbor in face_adjacency.get(face_idx, []):
+ if neighbor in unused_faces:
+ current_group.add(neighbor)
+ unused_faces.remove(neighbor)
+ stack.append(neighbor)
+ elif neighbor not in missing_faces_set:
+ current_subgroup.add(neighbor)
+
+ regions.append((current_group, current_subgroup))
+ pbar.update(total_faces - len(unused_faces) - pbar.n)
+
+ # 输出统计信息
+ print(f"\nTotal regions: {len(regions)}")
+ print(f"Average missing faces group size: {sum(len(g[0]) for g in regions)/len(regions):.2f}")
+ print(f"Largest missing faces group size: {max(len(g[0]) for g in regions)}")
+ print(f"Smallest missing faces group size: {min(len(g[0]) for g in regions)}")
+
+ # 检查每个组是否都有邻接面
+ for i, (group, subgroup) in enumerate(regions):
+ if not subgroup:
+ print(f"Warning: Region {i} with {len(group)} missing faces has no adjacent faces!")
+
+ return regions
+
+def compute_regions_face_colors(
+ regions: List[Tuple[set, set]],
+ uv_map: torch.Tensor,
+ uvs: torch.Tensor,
+ face_uv_indices: torch.Tensor,
+ device: str
+) -> Dict[int, torch.Tensor]:
+ """
+ 根据每个区域的边缘面UV坐标计算加权平均的颜色,
+ 当无有效采样时更新对应face_uv_indices。
+
+ 参数:
+ regions (List[Tuple[set, set]]): 每个区域为 (缺失面集合, 邻接面集合)
+ uv_map (torch.Tensor): 原始纹理贴图,RGB格式
+ uvs (torch.Tensor): 原始UV坐标
+ face_uv_indices (torch.Tensor): 每个面对应的UV索引
+ device (str): 使用的设备("cuda"或"cpu")
+
+ 返回:
+ Dict[int, torch.Tensor]: 键为区域索引,值为该区域加权平均计算得到的颜色(uint8)
+ """
+ regions_face_color: Dict[int, torch.Tensor] = {}
+ for r_index, region in enumerate(tqdm.tqdm(regions, desc="Processing regions")):
+ region_faces_indexes = torch.tensor(list(region[0]), device=device)
+ region_edge_faces_indexes = torch.tensor(list(region[1]), device=device)
+
+ if len(region_edge_faces_indexes) == 0:
+ continue
+
+ # 获取边缘面的UV索引
+ edge_face_uv_indices = face_uv_indices[region_edge_faces_indexes]
+ # 使用三角形的质心UV坐标来采样颜色
+ triangle_uvs = uvs[edge_face_uv_indices] # shape: [num_faces, 3, 2]
+ centroid_uvs = triangle_uvs.mean(dim=1) # shape: [num_faces, 2]
+
+ # 将UV坐标转换为像素坐标
+ scale_tensor = torch.tensor([uv_map.shape[1] - 1, uv_map.shape[0] - 1], device=device)
+ pixel_coords = torch.round(centroid_uvs * scale_tensor)
+ pixel_coords[:, 1] = uv_map.shape[0] - 1 - pixel_coords[:, 1]
+ pixel_coords = pixel_coords.long().clamp(0, uv_map.shape[0] - 1)
+
+ # 直接采样质心位置的颜色
+ colors = uv_map[pixel_coords[:, 1], pixel_coords[:, 0]] # shape: [num_faces, 3]
+
+ # 使用面积加权平均来计算最终颜色
+ areas = torch.abs(
+ (triangle_uvs[:, 1, 0] - triangle_uvs[:, 0, 0]) * (triangle_uvs[:, 2, 1] - triangle_uvs[:, 0, 1]) -
+ (triangle_uvs[:, 2, 0] - triangle_uvs[:, 0, 0]) * (triangle_uvs[:, 1, 1] - triangle_uvs[:, 0, 1])
+ ) * 0.5
+
+ if len(colors) > 0:
+ weighted_color = (colors.float() * areas.unsqueeze(1)).sum(dim=0) / areas.sum()
+ regions_face_color[r_index] = weighted_color.round().clamp(0, 255).to(torch.uint8)
+ else:
+ # 如果没有有效的采样点,使用第一个相邻面的UV坐标更新face_uv_indices
+ face_uv_indices[region_faces_indexes] = face_uv_indices[region_edge_faces_indexes[0]].unsqueeze(dim=0).clone()
+
+ return regions_face_color
+
+
+def update_uv_map_and_indices(
+ uv_map: torch.Tensor,
+ uvs: torch.Tensor,
+ face_uv_indices: torch.Tensor,
+ regions: List[Tuple[set, set]],
+ regions_face_color: Dict[int, torch.Tensor],
+ device: str
+) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ """
+ 根据计算得到的区域颜色,更新UV贴图及对应的UV坐标,并批量更新face_uv_indices。
+
+ 参数:
+ uv_map (torch.Tensor): 原始纹理贴图,RGB格式
+ uvs (torch.Tensor): 原始UV坐标
+ face_uv_indices (torch.Tensor): 原始面的UV索引
+ regions (List[Tuple[set, set]]): 每个区域为 (缺失面集合, 邻接面集合)
+ regions_face_color (Dict[int, torch.Tensor]): 每个区域计算得到的颜色
+ device (str): 使用的设备("cuda"或"cpu")
+
+ 返回:
+ Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ new_uv_map: 更新后的UV贴图
+ uvs_updated: 更新后的UV坐标(拼接上新计算的UV)
+ face_uv_indices: 更新后的face UV索引
+ """
+ total_regions = len(regions_face_color)
+ grid_size = uv_map.shape[1] // 3
+ all_c = torch.div(torch.arange(total_regions, device=device), grid_size, rounding_mode='floor')
+ all_r = torch.remainder(torch.arange(total_regions, device=device), grid_size)
+
+ # 创建新的颜色UV贴图
+ color_uv_map = torch.full((int(uv_map.shape[0] / 2), uv_map.shape[1], 3),
+ 255, dtype=torch.uint8, device=device)
+ # 调整原始uvs的纵坐标
+ uvs[:, 1] = uvs[:, 1] * (2 / 3) + 1 / 3
+
+ # 批量创建所有颜色块的坐标
+ c_indices = all_c.unsqueeze(1).repeat(1, 9) * 3 + torch.tensor([0, 1, 2, 0, 1, 2, 0, 1, 2],
+ device=device).unsqueeze(0)
+ r_indices = all_r.unsqueeze(1).repeat(1, 9) * 3 + torch.tensor([0, 0, 0, 1, 1, 1, 2, 2, 2],
+ device=device).unsqueeze(0)
+
+ # 批量设置颜色
+ colors = torch.stack([color for _, color in sorted(regions_face_color.items(), key=lambda x: x[0])])
+ colors_repeated = colors.unsqueeze(1).repeat(1, 9, 1)
+ color_uv_map[c_indices.flatten(), r_indices.flatten()] = colors_repeated.reshape(-1, 3)
+
+ # 批量计算新的UV坐标
+ pixels = torch.stack([
+ all_r * 3 + 1,
+ uv_map.shape[0] + all_c * 3 + 1
+ ], dim=1).to(device)
+ u_new = pixels[:, 0].float() / (uv_map.shape[1] - 1)
+ new_height = int(uv_map.shape[0] + uv_map.shape[0] / 2)
+ v_new = (new_height - 1 - pixels[:, 1].float()) / (new_height - 1)
+ new_uvs = torch.stack([u_new, v_new], dim=1)
+
+ # 更新UV坐标:拼接新计算的UV
+ uvs_updated = torch.cat([uvs, new_uvs], dim=0)
+ uv_coordinates_start = uvs_updated.shape[0] - total_regions
+
+ # 批量更新face_uv_indices
+ for i, (region_index, _) in enumerate(sorted(regions_face_color.items(), key=lambda x: x[0])):
+ region_faces_indexes = torch.tensor(list(regions[region_index][0]), device=device)
+ face_uv_indices[region_faces_indexes] = torch.full((1, 3), uv_coordinates_start + i, device=device)
+
+ # 合并原始UV贴图和新的颜色UV贴图
+ new_uv_map = torch.cat((uv_map, color_uv_map), dim=0)
+
+ return new_uv_map, uvs_updated, face_uv_indices
+
+def group_regions_by_y_axis(
+ regions: List[Tuple[set, set]],
+ vertices: torch.Tensor,
+ triangle_vertex_indices: torch.Tensor,
+ device: str,
+ interval_size: float = 0.1
+) -> Dict[int, List[int]]:
+ """
+ 将区域按照y轴高度分组
+
+ Args:
+ regions: 区域列表,每个区域为(缺失面集合, 邻接面集合)的元组
+ vertices: 顶点坐标张量
+ triangle_vertex_indices: 三角形顶点索引张量
+ device: 计算设备 ('cuda' 或 'cpu')
+ interval_size: y轴分组的间隔大小,默认为0.1
+
+ Returns:
+ Dict[int, List[int]]: 以y轴区间为键,区域索引列表为值的字典
+ """
+ y_intervals = defaultdict(list)
+ for r_index, region in enumerate(regions):
+ region_faces_indexes = torch.tensor(list(region[0]), device=device)
+ # 计算面组的平均y轴位置
+ face_vertices = vertices[triangle_vertex_indices[region_faces_indexes]]
+ avg_y = face_vertices[:, :, 1].mean(dim=(0, 1))
+
+ # 根据y轴位置分配到对应区间
+ interval_key = int(avg_y // interval_size)
+ y_intervals[interval_key].append(r_index)
+
+ return dict(y_intervals)
+
+def align_regions_colors(
+ regions_face_color: Dict[int, torch.Tensor],
+ y_intervals: Dict[int, List[int]],
+ regions: List[Tuple[set, set]]
+) -> Dict[int, torch.Tensor]:
+ """
+ 对齐区间内的颜色
+
+ Args:
+ regions_face_color: 每个区域的颜色
+ y_intervals: 每个y轴区间的区域索引列表
+
+ Returns:
+ Dict[int, torch.Tensor]: 以y轴区间为键,颜色为值的字典
+ """
+ # aligned_regions_face_color = {}
+ large_group_threshold_min = 5000
+ large_group_threshold_max = 100000
+ for interval_key, region_indices in y_intervals.items():
+ large_groups = []
+ # normal_groups = []
+ for r_index in region_indices:
+ region = regions[r_index]
+ if len(region[0]) >= large_group_threshold_min and len(region[0]) <= large_group_threshold_max:
+ large_groups.append((r_index, len(region[0]), regions_face_color[r_index]))
+
+ # 查找 large_groups 中 len(region[0]) 最大的组,并获取其颜色
+ if large_groups:
+ largest_group = max(large_groups, key=lambda x: x[1])
+ color: torch.Tensor = largest_group[2]
+ for large_group in large_groups:
+ regions_face_color[large_group[0]] = color
+
+ return regions_face_color
+
+
+def process(input_obj_path, input_texture_path, missing_faces_path, output_obj_path, output_texture_path):
+ start_time = time.time()
+
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
+ vertices, uvs, triangle_vertex_indices, face_uv_indices, missing_color_faces, uv_map = parse_obj_file_and_uv_map(
+ input_obj_path, missing_faces_path, input_texture_path, device=device)
+
+ # 构建面的邻接关系和找到区域
+ start_face_adjacency_time = time.time()
+ face_adjacency = build_face_adjacency(vertices.cpu().numpy(), triangle_vertex_indices.cpu().numpy())
+ end_face_adjacency_time = time.time()
+ print(f"face_adjacency using: {end_face_adjacency_time - start_face_adjacency_time} seconds")
+
+ start_find_groups_time = time.time()
+ regions = find_groups_and_subgroups(face_adjacency, missing_color_faces)
+ end_find_groups_time = time.time()
+ print(f"find_groups_and_subgroups using: {end_find_groups_time - start_find_groups_time} seconds")
+
+ start_texture_map_time = time.time()
+ # 使用新封装的函数计算每个区域的加权平均颜色
+ regions_face_color = compute_regions_face_colors(regions, uv_map, uvs, face_uv_indices, device)
+ end_texture_map_time = time.time()
+ print(f"texture_mapping_to_triangle using: {end_texture_map_time - start_texture_map_time} seconds")
+
+ # 按y轴区间分组
+ y_intervals = group_regions_by_y_axis(
+ regions,
+ vertices,
+ triangle_vertex_indices,
+ device
+ )
+
+ # 对齐区间内的颜色
+ regions_face_color = align_regions_colors(regions_face_color, y_intervals, regions)
+
+ # 更新UV贴图和面索引
+ start_color_map_time = time.time()
+ new_uv_map, uvs, face_uv_indices = update_uv_map_and_indices(uv_map, uvs, face_uv_indices, regions,
+ regions_face_color, device)
+ end_color_map_time = time.time()
+ print(f"color_mapping_to_triangle using: {end_color_map_time - start_color_map_time} seconds")
+
+ end_time = time.time()
+ print(f"using: {end_time - start_time} seconds")
+
+ # 写入OBJ和纹理贴图
+ start_write_time = time.time()
+
+ vertices_cpu = vertices.cpu().numpy()
+ uvs_cpu = uvs.cpu().numpy()
+ triangle_vertex_indices_cpu = triangle_vertex_indices.cpu().numpy()
+ face_uv_indices_cpu = face_uv_indices.cpu().numpy()
+ new_uv_map_cpu = new_uv_map.cpu().numpy()
+ new_uv_map_bgr = cv2.cvtColor(new_uv_map_cpu, cv2.COLOR_RGB2BGR)
+
+ with Pool(2) as p:
+ # 异步执行OBJ和纹理图写入操作
+ obj_future = p.apply_async(write_obj_with_uv_coordinates,
+ (output_obj_path, vertices_cpu, uvs_cpu,
+ triangle_vertex_indices_cpu, face_uv_indices_cpu))
+
+ img_future = p.apply_async(cv2.imwrite,
+ (output_texture_path, new_uv_map_bgr,
+ [cv2.IMWRITE_PNG_COMPRESSION, 3]))
+
+ obj_future.get()
+ img_future.get()
+
+ end_write_time = time.time()
+ end_time = time.time()
+ print(f"Total file writing time: {end_write_time - start_write_time:.2f} seconds")
+ print(f"using: {end_time - start_time} seconds")
+
+def main():
+ parser = argparse.ArgumentParser(description='Process OBJ files to fix missing color faces.')
+ parser.add_argument('--input_obj', type=str, required = True, help='Path to the input OBJ file')
+ parser.add_argument('--input_texture', type=str, required = True, help='Path to the texture file')
+ parser.add_argument('--missing_faces', type=str, required = True, help='Path to the file with indices of missing color faces')
+ parser.add_argument('--output_obj', type=str, required = True, help='Path to the output OBJ file')
+ parser.add_argument('--output_texture', type=str, required = True, help='Path to the texture file')
+
+ args = parser.parse_args()
+ process(args.input_obj, args.input_texture, args.missing_faces, args.output_obj, args.output_texture)
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file