pytexrecon / pytexrecon.py
adsfda's picture
fix:pytex
662e51a
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()