adsfda commited on
Commit
f813a93
·
1 Parent(s): 77f696e

Add application file

Browse files
Files changed (4) hide show
  1. README.md +25 -0
  2. app.py +86 -0
  3. pytexrecon.py +220 -0
  4. requirements.txt +4 -0
README.md CHANGED
@@ -10,3 +10,28 @@ pinned: false
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
13
+ # TexRecon 纹理重建工具
14
+
15
+ 这是一个基于TexRecon的纹理重建Gradio应用,可在Hugging Face Space中运行。
16
+
17
+ ## 功能
18
+ 将3D网格模型与图像序列结合,自动生成带纹理的3D模型。
19
+
20
+ ## 使用方法
21
+ 1. 上传PLY格式的网格模型
22
+ 2. 上传图像序列(JPG或PNG格式)
23
+ 3. 输入相机内参数据
24
+ 4. 输入每个图像对应的相机姿态
25
+ 5. 点击"开始纹理重建"按钮
26
+
27
+ ## 注意事项
28
+ - 首次运行时会自动编译TexRecon,可能需要5-10分钟
29
+ - 请确保输入的相机参数与图像匹配
30
+ - 较大的纹理分辨率会增加处理时间
31
+
32
+ ## 技术细节
33
+ 应用使用pytexrecon库封装TexRecon功能,该库会:
34
+ 1. 自动检测或编译TexRecon可执行文件
35
+ 2. 处理输入输出数据转换
36
+ 3. 管理临时文件和工作目录
37
+
app.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pytexrecon
3
+ import os
4
+
5
+ def process_texturing(mesh_file, image_files, intrinsics_text, pose_texts):
6
+ try:
7
+ # 初始化TexRecon
8
+ tex = pytexrecon.TexRecon(verbose=True)
9
+
10
+ # 处理输入数据
11
+ with open(mesh_file.name, "rb") as f:
12
+ mesh_data = f.read()
13
+
14
+ # 处理图像
15
+ images = {}
16
+ for img_file in image_files:
17
+ img_name = os.path.basename(img_file.name)
18
+ with open(img_file.name, "rb") as f:
19
+ images[img_name] = f.read()
20
+
21
+ # 处理姿态数据
22
+ poses = {}
23
+ for img_file, pose_text in zip(image_files, pose_texts):
24
+ img_name = os.path.basename(img_file.name)
25
+ poses[img_name] = pose_text
26
+
27
+ # 运行纹理重建
28
+ mesh_bytes, texture_bytes = tex.run_texturing(
29
+ mesh_data=mesh_data,
30
+ images=images,
31
+ intrinsics=intrinsics_text,
32
+ poses=poses,
33
+ texture_resolution=2048,
34
+ fill_holes=True
35
+ )
36
+
37
+ # 保存结果到临时文件
38
+ with open("textured_mesh.obj", "wb") as f:
39
+ f.write(mesh_bytes)
40
+
41
+ with open("texture.png", "wb") as f:
42
+ f.write(texture_bytes)
43
+
44
+ return "textured_mesh.obj", "texture.png", "纹理重建成功!"
45
+
46
+ except Exception as e:
47
+ return None, None, f"处理失败: {str(e)}"
48
+
49
+ # 创建Gradio界面
50
+ with gr.Blocks(title="TexRecon 纹理重建工具") as demo:
51
+ gr.Markdown("# TexRecon 纹理重建工具")
52
+ gr.Markdown("上传网格模型、图像序列和相机参数,生成带纹理的3D模型")
53
+
54
+ with gr.Row():
55
+ with gr.Column(scale=1):
56
+ mesh_file = gr.File(label="网格模型 (.ply)", file_types=[".ply"])
57
+ image_files = gr.File(label="图像序列", file_types=[".jpg", ".png"], file_count="multiple")
58
+
59
+ intrinsics_text = gr.Textbox(
60
+ label="相机内参",
61
+ placeholder="输入相机内参数据",
62
+ lines=5
63
+ )
64
+
65
+ pose_texts = gr.Textbox(
66
+ label="相机姿态 (每个图像一个)",
67
+ placeholder="输入相机姿态数据,每个图像一行",
68
+ lines=5
69
+ )
70
+
71
+ process_btn = gr.Button("开始纹理重建")
72
+
73
+ with gr.Column(scale=1):
74
+ output_mesh = gr.File(label="带纹理的模型")
75
+ output_texture = gr.Image(label="纹理贴图")
76
+ status_text = gr.Textbox(label="状态")
77
+
78
+ process_btn.click(
79
+ fn=process_texturing,
80
+ inputs=[mesh_file, image_files, intrinsics_text, pose_texts],
81
+ outputs=[output_mesh, output_texture, status_text]
82
+ )
83
+
84
+ if __name__ == "__main__":
85
+ demo.launch()
86
+
pytexrecon.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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可执行文件,请确保已编译")
24
+
25
+ self._check_texrecon()
26
+
27
+ # 创建临时目录用于处理中间文件
28
+ self.temp_dir = tempfile.TemporaryDirectory()
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是否可执行"""
105
+ try:
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")
143
+ intrinsics_path = os.path.join(self.work_dir, "intrinsics.txt")
144
+ poses_dir = os.path.join(self.work_dir, "poses")
145
+ output_dir = os.path.join(self.work_dir, "output")
146
+
147
+ os.makedirs(images_dir, exist_ok=True)
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,
178
+ "--poses", poses_dir,
179
+ "--output", output_dir,
180
+ "--resolution", str(texture_resolution),
181
+ "--padding", str(padding)
182
+ ]
183
+
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
+
206
+ with open(texture_path, "rb") as f:
207
+ texture_bytes = f.read()
208
+
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
+
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=3.0
2
+ numpy
3
+ tempfile
4
+