adsfda commited on
Commit
22c90d8
·
1 Parent(s): eca8afc

fix:mvstexture

Browse files
Files changed (1) hide show
  1. pytexrecon.py +96 -88
pytexrecon.py CHANGED
@@ -1,23 +1,18 @@
1
  import subprocess
2
  import os
3
  import tempfile
4
- from typing import Optional, List, Dict, Tuple
5
 
6
  class TexRecon:
7
- def __init__(self, verbose: bool = False):
8
- """
9
- 初始化TexRecon绑定,适配Hugging Face Space环境
10
-
11
- Args:
12
- verbose: 是否显示详细输出
13
- """
14
  self.verbose = verbose
15
  self.texrecon_path = self._find_texrecon()
16
 
17
- # 检查是否可用
18
  if not self.texrecon_path:
19
- # 尝试自动编译(仅在Space环境中)
20
- if os.path.exists("/app"): # Hugging Face Space特征路径
21
  self.texrecon_path = self._compile_texrecon()
22
  else:
23
  raise RuntimeError("未找到TexRecon可执行文件,请确保已编译")
@@ -29,76 +24,110 @@ class TexRecon:
29
  self.work_dir = self.temp_dir.name
30
 
31
  def _find_texrecon(self) -> Optional[str]:
32
- """在常见路径中查找TexRecon可执行文件"""
 
33
  search_paths = [
34
- "./texrecon",
35
- "/app/texrecon/build/texrecon",
36
- "/usr/local/bin/texrecon",
37
- os.path.expanduser("~/texrecon/build/texrecon")
38
  ]
39
 
40
  for path in search_paths:
41
  if os.path.exists(path) and os.access(path, os.X_OK):
 
 
42
  return path
43
  return None
44
 
45
  def _compile_texrecon(self) -> str:
46
- """在Hugging Face Space中自动编译TexRecon"""
47
  try:
48
- # 安装依赖
49
- subprocess.run(
50
- ["sudo", "apt-get", "update"],
51
- check=True,
52
- stdout=subprocess.PIPE,
53
- stderr=subprocess.PIPE
54
- )
55
 
 
 
 
56
  deps = [
57
- "git", "cmake", "build-essential",
58
- "libboost-program-options-dev", "libopencv-dev"
59
  ]
 
 
60
  subprocess.run(
61
- ["sudo", "apt-get", "install", "-y"] + deps,
62
  check=True,
63
- stdout=subprocess.PIPE,
64
- stderr=subprocess.PIPE
65
  )
66
-
67
- # 克隆源码并编译
68
- os.makedirs("/app/texrecon", exist_ok=True)
69
  subprocess.run(
70
- ["git", "clone", "https://github.com/texrecon/texrecon.git", "/app/texrecon/src"],
71
  check=True,
72
- stdout=subprocess.PIPE,
73
- stderr=subprocess.PIPE
74
  )
75
 
76
- os.makedirs("/app/texrecon/build", exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  subprocess.run(
78
- ["cmake", "../src"],
79
- cwd="/app/texrecon/build",
80
  check=True,
81
- stdout=subprocess.PIPE,
82
- stderr=subprocess.PIPE
83
  )
84
 
 
85
  subprocess.run(
86
  ["make", "-j4"],
87
- cwd="/app/texrecon/build",
88
  check=True,
89
- stdout=subprocess.PIPE,
90
- stderr=subprocess.PIPE
91
  )
92
 
93
- texrecon_path = "/app/texrecon/build/texrecon"
94
- if os.path.exists(texrecon_path):
95
- os.chmod(texrecon_path, 0o755)
 
 
96
  return texrecon_path
97
  else:
98
- raise RuntimeError("TexRecon编译成功但未找到可执行文件")
99
 
 
 
 
 
 
100
  except Exception as e:
101
- raise RuntimeError(f"自动编译TexRecon失败: {str(e)}")
102
 
103
  def _check_texrecon(self) -> None:
104
  """检查TexRecon是否可执行"""
@@ -106,37 +135,23 @@ class TexRecon:
106
  subprocess.run(
107
  [self.texrecon_path, "--help"],
108
  check=True,
109
- stdout=subprocess.PIPE,
110
- stderr=subprocess.PIPE
111
  )
112
  except FileNotFoundError:
113
- raise RuntimeError(f"未找到TexRecon可执行文件: {self.texrecon_path}")
114
  except subprocess.CalledProcessError:
115
- raise RuntimeError(f"TexRecon可执行文件无效: {self.texrecon_path}")
116
 
117
  def run_texturing(self,
118
- mesh_data: bytes, # 网格文件二进制数据
119
- images: Dict[str, bytes], # 图像文件名到二进制数据的映射
120
- intrinsics: str, # 内参字符串
121
- poses: Dict[str, str], # 图像名到姿态的映射
122
  texture_resolution: int = 2048,
123
  padding: int = 5,
124
  fill_holes: bool = True) -> Tuple[bytes, bytes]:
125
- """
126
- 运行纹理重建(适配Gradio的文件上传方式)
127
-
128
- Args:
129
- mesh_data: 网格文件二进制数据
130
- images: 图像文件名到二进制数据的映射
131
- intrinsics: 相机内参字符串
132
- poses: 图像名到姿态的映射
133
- texture_resolution: 纹理贴图分辨率
134
- padding: UV边界填充像素数
135
- fill_holes: 是否填充纹理孔洞
136
-
137
- Returns:
138
- 带纹理的模型文件和纹理贴图的二进制数据
139
- """
140
  # 创建工作目录结构
141
  mesh_path = os.path.join(self.work_dir, "mesh.ply")
142
  images_dir = os.path.join(self.work_dir, "images")
@@ -148,30 +163,26 @@ class TexRecon:
148
  os.makedirs(poses_dir, exist_ok=True)
149
  os.makedirs(output_dir, exist_ok=True)
150
 
151
- # 写入网格文件
152
  with open(mesh_path, "wb") as f:
153
  f.write(mesh_data)
154
 
155
- # 写入图像文件
156
  for img_name, img_data in images.items():
157
  img_path = os.path.join(images_dir, img_name)
158
  with open(img_path, "wb") as f:
159
  f.write(img_data)
160
 
161
- # 写入内参文件
162
  with open(intrinsics_path, "w") as f:
163
  f.write(intrinsics)
164
 
165
- # 写入姿态文件
166
  for img_name, pose_data in poses.items():
167
  pose_path = os.path.join(poses_dir, f"{img_name}.txt")
168
  with open(pose_path, "w") as f:
169
  f.write(pose_data)
170
 
171
- # 构建命令
172
  cmd = [
173
  self.texrecon_path,
174
- "texturing",
175
  "--mesh", mesh_path,
176
  "--images", images_dir,
177
  "--intrinsics", intrinsics_path,
@@ -184,22 +195,19 @@ class TexRecon:
184
  if fill_holes:
185
  cmd.append("--fill-holes")
186
 
187
- # 执行命令
188
  try:
189
- stdout = subprocess.PIPE if not self.verbose else None
190
- stderr = subprocess.STDOUT if not self.verbose else None
191
-
192
  subprocess.run(
193
  cmd,
194
- stdout=stdout,
195
- stderr=stderr,
196
  check=True
197
  )
198
 
199
- # 读取输出结果
200
  textured_mesh_path = os.path.join(output_dir, "mesh_textured.obj")
201
  texture_path = os.path.join(output_dir, "texture_0.png")
202
 
 
203
  with open(textured_mesh_path, "rb") as f:
204
  mesh_bytes = f.read()
205
 
@@ -209,12 +217,12 @@ class TexRecon:
209
  return mesh_bytes, texture_bytes
210
 
211
  except subprocess.CalledProcessError as e:
212
- if self.verbose:
213
- print(f"命令执行失败: {e}")
214
- raise RuntimeError(f"纹理重建失败: {str(e)}")
 
215
 
216
  def __del__(self):
217
  """清理临时目录"""
218
  if hasattr(self, 'temp_dir'):
219
  self.temp_dir.cleanup()
220
-
 
1
  import subprocess
2
  import os
3
  import tempfile
4
+ from typing import Optional, Dict, Tuple
5
 
6
  class TexRecon:
7
+ def __init__(self, verbose: bool = True):
8
+ """初始化TexRecon绑定,适配mvs-texturing项目和Hugging Face Space环境"""
 
 
 
 
 
9
  self.verbose = verbose
10
  self.texrecon_path = self._find_texrecon()
11
 
12
+ # 检查是否可用,若未找到则尝试自动编译
13
  if not self.texrecon_path:
14
+ # Hugging Face Space特征路径
15
+ if os.path.exists("/home/user/app"):
16
  self.texrecon_path = self._compile_texrecon()
17
  else:
18
  raise RuntimeError("未找到TexRecon可执行文件,请确保已编译")
 
24
  self.work_dir = self.temp_dir.name
25
 
26
  def _find_texrecon(self) -> Optional[str]:
27
+ """在常见路径中查找mvs-texturing编译的texrecon可执行文件"""
28
+ # 重点:mvs-texturing的可执行文件在build/apps/texrecon路径下
29
  search_paths = [
30
+ "./mvs-texturing/build/apps/texrecon/texrecon",
31
+ "/home/user/app/mvs-texturing/build/apps/texrecon/texrecon",
32
+ os.path.expanduser("~/mvs-texturing/build/apps/texrecon/texrecon")
 
33
  ]
34
 
35
  for path in search_paths:
36
  if os.path.exists(path) and os.access(path, os.X_OK):
37
+ if self.verbose:
38
+ print(f"找到TexRecon可执行文件:{path}")
39
  return path
40
  return None
41
 
42
  def _compile_texrecon(self) -> str:
43
+ """在Hugging Face Space中自动编译mvs-texturing项目"""
44
  try:
45
+ if self.verbose:
46
+ print("=== 开始自动编译mvs-texturing ===")
 
 
 
 
 
47
 
48
+ # 1. 安装项目所需依赖(根据官方文档)
49
+ if self.verbose:
50
+ print("=== 安装编译依赖 ===")
51
  deps = [
52
+ "git", "cmake", "make", "g++", # 基础编译工具
53
+ "libpng-dev", "libjpeg-dev", "libtiff-dev", "libtbb-dev" # 图像和并行库
54
  ]
55
+
56
+ # Space环境无需sudo
57
  subprocess.run(
58
+ ["apt-get", "update"],
59
  check=True,
60
+ stdout=subprocess.PIPE if not self.verbose else None,
61
+ stderr=subprocess.STDOUT if not self.verbose else None
62
  )
 
 
 
63
  subprocess.run(
64
+ ["apt-get", "install", "-y"] + deps,
65
  check=True,
66
+ stdout=subprocess.PIPE if not self.verbose else None,
67
+ stderr=subprocess.STDOUT if not self.verbose else None
68
  )
69
 
70
+ # 2. 克隆mvs-texturing源码(使用官方仓库)
71
+ if self.verbose:
72
+ print("=== 克隆mvs-texturing源码 ===")
73
+ repo_dir = "/home/user/app/mvs-texturing"
74
+ src_dir = os.path.join(repo_dir, "src")
75
+ build_dir = os.path.join(repo_dir, "build")
76
+
77
+ # 确保目录存在
78
+ os.makedirs(repo_dir, exist_ok=True)
79
+
80
+ # 若目录为空则克隆
81
+ if not os.listdir(repo_dir):
82
+ subprocess.run(
83
+ ["git", "clone", "https://github.com/nmoehrle/mvs-texturing.git", repo_dir],
84
+ check=True,
85
+ stdout=subprocess.PIPE if not self.verbose else None,
86
+ stderr=subprocess.STDOUT if not self.verbose else None
87
+ )
88
+ else:
89
+ if self.verbose:
90
+ print("mvs-texturing源码已存在,跳过克隆")
91
+
92
+ # 3. 按照官方文档步骤编译
93
+ if self.verbose:
94
+ print("=== 编译mvs-texturing ===")
95
+ os.makedirs(build_dir, exist_ok=True)
96
+
97
+ # 运行cmake(官方命令)
98
  subprocess.run(
99
+ ["cmake", ".."],
100
+ cwd=build_dir,
101
  check=True,
102
+ stdout=subprocess.PIPE if not self.verbose else None,
103
+ stderr=subprocess.STDOUT if not self.verbose else None
104
  )
105
 
106
+ # 运行make(官方命令,-j4并行编译)
107
  subprocess.run(
108
  ["make", "-j4"],
109
+ cwd=build_dir,
110
  check=True,
111
+ stdout=subprocess.PIPE if not self.verbose else None,
112
+ stderr=subprocess.STDOUT if not self.verbose else None
113
  )
114
 
115
+ # 4. 验证可执行文件(mvs-texturing的texreconbuild/apps/texrecon下)
116
+ texrecon_path = os.path.join(build_dir, "apps", "texrecon", "texrecon")
117
+ if os.path.exists(texrecon_path) and os.access(texrecon_path, os.X_OK):
118
+ if self.verbose:
119
+ print(f"=== mvs-texturing编译成功:{texrecon_path} ===")
120
  return texrecon_path
121
  else:
122
+ raise RuntimeError(f"编译成功但未找到可执行文件:{texrecon_path}")
123
 
124
+ except subprocess.CalledProcessError as e:
125
+ error_msg = f"命令执行失败(返回码:{e.returncode})"
126
+ if self.verbose and e.output:
127
+ error_msg += f"\n输出:{e.output.decode('utf-8', errors='ignore')}"
128
+ raise RuntimeError(error_msg)
129
  except Exception as e:
130
+ raise RuntimeError(f"自动编译失败{str(e)}")
131
 
132
  def _check_texrecon(self) -> None:
133
  """检查TexRecon是否可执行"""
 
135
  subprocess.run(
136
  [self.texrecon_path, "--help"],
137
  check=True,
138
+ stdout=subprocess.PIPE if not self.verbose else None,
139
+ stderr=subprocess.STDOUT if not self.verbose else None
140
  )
141
  except FileNotFoundError:
142
+ raise RuntimeError(f"未找到TexRecon可执行文件{self.texrecon_path}")
143
  except subprocess.CalledProcessError:
144
+ raise RuntimeError(f"TexRecon可执行文件无效(可能编译不完整):{self.texrecon_path}")
145
 
146
  def run_texturing(self,
147
+ mesh_data: bytes,
148
+ images: Dict[str, bytes],
149
+ intrinsics: str,
150
+ poses: Dict[str, str],
151
  texture_resolution: int = 2048,
152
  padding: int = 5,
153
  fill_holes: bool = True) -> Tuple[bytes, bytes]:
154
+ """运行纹理重建(适配mvs-texturing的参数要求)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  # 创建工作目录结构
156
  mesh_path = os.path.join(self.work_dir, "mesh.ply")
157
  images_dir = os.path.join(self.work_dir, "images")
 
163
  os.makedirs(poses_dir, exist_ok=True)
164
  os.makedirs(output_dir, exist_ok=True)
165
 
166
+ # 写入输入文件
167
  with open(mesh_path, "wb") as f:
168
  f.write(mesh_data)
169
 
 
170
  for img_name, img_data in images.items():
171
  img_path = os.path.join(images_dir, img_name)
172
  with open(img_path, "wb") as f:
173
  f.write(img_data)
174
 
 
175
  with open(intrinsics_path, "w") as f:
176
  f.write(intrinsics)
177
 
 
178
  for img_name, pose_data in poses.items():
179
  pose_path = os.path.join(poses_dir, f"{img_name}.txt")
180
  with open(pose_path, "w") as f:
181
  f.write(pose_data)
182
 
183
+ # 构建mvs-texturing的命令(符合官方文档参数)
184
  cmd = [
185
  self.texrecon_path,
 
186
  "--mesh", mesh_path,
187
  "--images", images_dir,
188
  "--intrinsics", intrinsics_path,
 
195
  if fill_holes:
196
  cmd.append("--fill-holes")
197
 
 
198
  try:
 
 
 
199
  subprocess.run(
200
  cmd,
201
+ stdout=subprocess.PIPE if not self.verbose else None,
202
+ stderr=subprocess.STDOUT if not self.verbose else None,
203
  check=True
204
  )
205
 
206
+ # mvs-texturing的输出文件命名
207
  textured_mesh_path = os.path.join(output_dir, "mesh_textured.obj")
208
  texture_path = os.path.join(output_dir, "texture_0.png")
209
 
210
+ # 读取输出结果
211
  with open(textured_mesh_path, "rb") as f:
212
  mesh_bytes = f.read()
213
 
 
217
  return mesh_bytes, texture_bytes
218
 
219
  except subprocess.CalledProcessError as e:
220
+ error_msg = f"纹理重建命令失败(返回码:{e.returncode})"
221
+ if self.verbose and e.output:
222
+ error_msg += f"\n输出:{e.output.decode('utf-8', errors='ignore')}"
223
+ raise RuntimeError(error_msg)
224
 
225
  def __del__(self):
226
  """清理临时目录"""
227
  if hasattr(self, 'temp_dir'):
228
  self.temp_dir.cleanup()