File size: 9,281 Bytes
f813a93
 
 
22c90d8
f813a93
 
22c90d8
 
f813a93
 
 
22c90d8
f813a93
662e51a
 
f813a93
 
 
 
 
 
 
 
22c90d8
 
f813a93
22c90d8
 
 
f813a93
 
 
 
22c90d8
 
f813a93
 
 
 
22c90d8
f813a93
22c90d8
 
f813a93
22c90d8
 
 
f813a93
22c90d8
 
f813a93
22c90d8
 
f813a93
22c90d8
f813a93
22c90d8
 
f813a93
 
22c90d8
f813a93
22c90d8
 
f813a93
 
22c90d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f813a93
22c90d8
 
f813a93
22c90d8
 
f813a93
 
22c90d8
f813a93
 
22c90d8
f813a93
22c90d8
 
f813a93
 
22c90d8
 
 
 
 
f813a93
 
22c90d8
f813a93
22c90d8
 
 
 
 
f813a93
22c90d8
f813a93
 
 
 
 
 
 
22c90d8
 
f813a93
 
22c90d8
f813a93
22c90d8
f813a93
 
22c90d8
 
 
 
f813a93
 
 
22c90d8
f813a93
 
 
 
 
 
 
 
 
 
 
22c90d8
f813a93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22c90d8
f813a93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22c90d8
 
f813a93
 
 
22c90d8
f813a93
 
 
22c90d8
f813a93
 
 
 
 
 
 
 
 
22c90d8
 
 
 
f813a93
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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()