Spaces:
Sleeping
Sleeping
| import subprocess | |
| import os | |
| import tempfile | |
| from typing import Optional, Dict, Tuple | |
| class TexRecon: | |
| def __init__(self, verbose: bool = True): | |
| """初始化TexRecon绑定,适配mvs-texturing项目和Hugging Face Space环境""" | |
| self.verbose = verbose | |
| self.texrecon_path = self._find_texrecon() | |
| # 检查是否可用,若未找到则尝试自动编译 | |
| if not self.texrecon_path: | |
| # Hugging Face Space特征路径 | |
| self.texrecon_path = self._compile_texrecon() | |
| self._check_texrecon() | |
| # 创建临时目录用于处理中间文件 | |
| self.temp_dir = tempfile.TemporaryDirectory() | |
| self.work_dir = self.temp_dir.name | |
| def _find_texrecon(self) -> Optional[str]: | |
| """在常见路径中查找mvs-texturing编译的texrecon可执行文件""" | |
| # 重点:mvs-texturing的可执行文件在build/apps/texrecon路径下 | |
| search_paths = [ | |
| "./mvs-texturing/build/apps/texrecon/texrecon", | |
| "/home/user/app/mvs-texturing/build/apps/texrecon/texrecon", | |
| os.path.expanduser("~/mvs-texturing/build/apps/texrecon/texrecon") | |
| ] | |
| for path in search_paths: | |
| if os.path.exists(path) and os.access(path, os.X_OK): | |
| if self.verbose: | |
| print(f"找到TexRecon可执行文件:{path}") | |
| return path | |
| return None | |
| def _compile_texrecon(self) -> str: | |
| """在Hugging Face Space中自动编译mvs-texturing项目""" | |
| try: | |
| if self.verbose: | |
| print("=== 开始自动编译mvs-texturing ===") | |
| # 1. 安装项目所需依赖(根据官方文档) | |
| if self.verbose: | |
| print("=== 安装编译依赖 ===") | |
| deps = [ | |
| "git", "cmake", "make", "g++", # 基础编译工具 | |
| "libpng-dev", "libjpeg-dev", "libtiff-dev", "libtbb-dev" # 图像和并行库 | |
| ] | |
| # Space环境无需sudo | |
| subprocess.run( | |
| ["apt-get", "update"], | |
| check=True, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None | |
| ) | |
| subprocess.run( | |
| ["apt-get", "install", "-y"] + deps, | |
| check=True, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None | |
| ) | |
| # 2. 克隆mvs-texturing源码(使用官方仓库) | |
| if self.verbose: | |
| print("=== 克隆mvs-texturing源码 ===") | |
| repo_dir = "/home/user/app/mvs-texturing" | |
| src_dir = os.path.join(repo_dir, "src") | |
| build_dir = os.path.join(repo_dir, "build") | |
| # 确保目录存在 | |
| os.makedirs(repo_dir, exist_ok=True) | |
| # 若目录为空则克隆 | |
| if not os.listdir(repo_dir): | |
| subprocess.run( | |
| ["git", "clone", "https://github.com/nmoehrle/mvs-texturing.git", repo_dir], | |
| check=True, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None | |
| ) | |
| else: | |
| if self.verbose: | |
| print("mvs-texturing源码已存在,跳过克隆") | |
| # 3. 按照官方文档步骤编译 | |
| if self.verbose: | |
| print("=== 编译mvs-texturing ===") | |
| os.makedirs(build_dir, exist_ok=True) | |
| # 运行cmake(官方命令) | |
| subprocess.run( | |
| ["cmake", ".."], | |
| cwd=build_dir, | |
| check=True, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None | |
| ) | |
| # 运行make(官方命令,-j4并行编译) | |
| subprocess.run( | |
| ["make", "-j4"], | |
| cwd=build_dir, | |
| check=True, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None | |
| ) | |
| # 4. 验证可执行文件(mvs-texturing的texrecon在build/apps/texrecon下) | |
| texrecon_path = os.path.join(build_dir, "apps", "texrecon", "texrecon") | |
| if os.path.exists(texrecon_path) and os.access(texrecon_path, os.X_OK): | |
| if self.verbose: | |
| print(f"=== mvs-texturing编译成功:{texrecon_path} ===") | |
| return texrecon_path | |
| else: | |
| raise RuntimeError(f"编译成功但未找到可执行文件:{texrecon_path}") | |
| except subprocess.CalledProcessError as e: | |
| error_msg = f"命令执行失败(返回码:{e.returncode})" | |
| if self.verbose and e.output: | |
| error_msg += f"\n输出:{e.output.decode('utf-8', errors='ignore')}" | |
| raise RuntimeError(error_msg) | |
| except Exception as e: | |
| raise RuntimeError(f"自动编译失败:{str(e)}") | |
| def _check_texrecon(self) -> None: | |
| """检查TexRecon是否可执行""" | |
| try: | |
| subprocess.run( | |
| [self.texrecon_path, "--help"], | |
| check=True, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None | |
| ) | |
| except FileNotFoundError: | |
| raise RuntimeError(f"未找到TexRecon可执行文件:{self.texrecon_path}") | |
| except subprocess.CalledProcessError: | |
| raise RuntimeError(f"TexRecon可执行文件无效(可能编译不完整):{self.texrecon_path}") | |
| def run_texturing(self, | |
| mesh_data: bytes, | |
| images: Dict[str, bytes], | |
| intrinsics: str, | |
| poses: Dict[str, str], | |
| texture_resolution: int = 2048, | |
| padding: int = 5, | |
| fill_holes: bool = True) -> Tuple[bytes, bytes]: | |
| """运行纹理重建(适配mvs-texturing的参数要求)""" | |
| # 创建工作目录结构 | |
| mesh_path = os.path.join(self.work_dir, "mesh.ply") | |
| images_dir = os.path.join(self.work_dir, "images") | |
| intrinsics_path = os.path.join(self.work_dir, "intrinsics.txt") | |
| poses_dir = os.path.join(self.work_dir, "poses") | |
| output_dir = os.path.join(self.work_dir, "output") | |
| os.makedirs(images_dir, exist_ok=True) | |
| os.makedirs(poses_dir, exist_ok=True) | |
| os.makedirs(output_dir, exist_ok=True) | |
| # 写入输入文件 | |
| with open(mesh_path, "wb") as f: | |
| f.write(mesh_data) | |
| for img_name, img_data in images.items(): | |
| img_path = os.path.join(images_dir, img_name) | |
| with open(img_path, "wb") as f: | |
| f.write(img_data) | |
| with open(intrinsics_path, "w") as f: | |
| f.write(intrinsics) | |
| for img_name, pose_data in poses.items(): | |
| pose_path = os.path.join(poses_dir, f"{img_name}.txt") | |
| with open(pose_path, "w") as f: | |
| f.write(pose_data) | |
| # 构建mvs-texturing的命令(符合官方文档参数) | |
| cmd = [ | |
| self.texrecon_path, | |
| "--mesh", mesh_path, | |
| "--images", images_dir, | |
| "--intrinsics", intrinsics_path, | |
| "--poses", poses_dir, | |
| "--output", output_dir, | |
| "--resolution", str(texture_resolution), | |
| "--padding", str(padding) | |
| ] | |
| if fill_holes: | |
| cmd.append("--fill-holes") | |
| try: | |
| subprocess.run( | |
| cmd, | |
| stdout=subprocess.PIPE if not self.verbose else None, | |
| stderr=subprocess.STDOUT if not self.verbose else None, | |
| check=True | |
| ) | |
| # mvs-texturing的输出文件命名 | |
| textured_mesh_path = os.path.join(output_dir, "mesh_textured.obj") | |
| texture_path = os.path.join(output_dir, "texture_0.png") | |
| # 读取输出结果 | |
| with open(textured_mesh_path, "rb") as f: | |
| mesh_bytes = f.read() | |
| with open(texture_path, "rb") as f: | |
| texture_bytes = f.read() | |
| return mesh_bytes, texture_bytes | |
| except subprocess.CalledProcessError as e: | |
| error_msg = f"纹理重建命令失败(返回码:{e.returncode})" | |
| if self.verbose and e.output: | |
| error_msg += f"\n输出:{e.output.decode('utf-8', errors='ignore')}" | |
| raise RuntimeError(error_msg) | |
| def __del__(self): | |
| """清理临时目录""" | |
| if hasattr(self, 'temp_dir'): | |
| self.temp_dir.cleanup() | |