insta360-3dgs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- insta360_3dgs-0.1.0/.gitignore +16 -0
- insta360_3dgs-0.1.0/.python-version +1 -0
- insta360_3dgs-0.1.0/LICENSE +21 -0
- insta360_3dgs-0.1.0/PKG-INFO +127 -0
- insta360_3dgs-0.1.0/README.md +101 -0
- insta360_3dgs-0.1.0/pyproject.toml +52 -0
- insta360_3dgs-0.1.0/skill/SKILL.md +20 -0
- insta360_3dgs-0.1.0/skill/scripts/bootstrap.py +252 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/__init__.py +4 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/__main__.py +7 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/_dispatch.py +54 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/cli.py +191 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/config.py +108 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/expand_cubemap.py +209 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/export_pointcloud.py +61 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/extract_frames.py +44 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/preview.py +133 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/run_sfm.py +90 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/sdk_stitch.py +114 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/train.py +137 -0
- insta360_3dgs-0.1.0/src/insta360_3dgs/verify.py +157 -0
- insta360_3dgs-0.1.0/uv.lock +126 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FemtoRhythm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: insta360-3dgs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Insta360 .insv -> 全景 3D Gaussian Splatting 点云流水线 (MediaSDK 拼接 + 等矩形 SfM + 3DGS)
|
|
5
|
+
Project-URL: Homepage, https://github.com/FemtoRhythm/insta360-3dgs
|
|
6
|
+
Project-URL: Repository, https://github.com/FemtoRhythm/insta360-3dgs
|
|
7
|
+
Author: FemtoRhythm
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: 3dgs,colmap,gaussian-splatting,insta360,panorama,point-cloud
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: pillow>=9.0
|
|
22
|
+
Requires-Dist: plyfile>=0.8
|
|
23
|
+
Requires-Dist: scipy>=1.10
|
|
24
|
+
Requires-Dist: tqdm>=4.64
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# insta360-3dgs
|
|
28
|
+
|
|
29
|
+
把 Insta360 拍出来的 `.insv` 双鱼眼视频,自动重建成全景 3D Gaussian Splatting,最后导出一个标准的 `x,y,z + rgb` 点云(PLY),可以直接扔进 CloudCompare、MeshLab 这类工具看。
|
|
30
|
+
|
|
31
|
+
## 它做了什么
|
|
32
|
+
|
|
33
|
+
Insta360 的双镜头是两颗鱼眼分开存的(`_00_` 前镜头、`_10_` 后镜头),要变成能用的 3D 结果,得一路处理:
|
|
34
|
+
|
|
35
|
+
1. 用官方 MediaSDK 把两颗鱼眼拼成一张 360° 等矩形视频
|
|
36
|
+
2. 抽帧
|
|
37
|
+
3. 用 COLMAP 的等矩形相机模型做位姿重建
|
|
38
|
+
4. 把每帧全景展开成 6 个共光心的透视面,喂给 3DGS
|
|
39
|
+
5. 训练高斯模型
|
|
40
|
+
6. 导出标准点云
|
|
41
|
+
|
|
42
|
+
### 为什么先做等矩形 SfM
|
|
43
|
+
|
|
44
|
+
全景图每个像素对应一个方向,光心在球心。如果图省事直接把 6 个 cube 面当成独立相机去做 SfM,共光心的面会被误当成自由相机,凭空多出伪基线,位姿会乱。所以先在等矩形空间用 EQUIRECTANGULAR 相机模型跑一遍 SfM 拿到平滑的相机轨迹,再基于这个轨迹展开 6 个透视面,保证每一帧的 6 个面光心都重合在同一点。
|
|
45
|
+
|
|
46
|
+
## 依赖
|
|
47
|
+
|
|
48
|
+
分两类:轻量依赖交给 uv 装,重型依赖复用机器上已经配好的环境。
|
|
49
|
+
|
|
50
|
+
| 依赖 | 用途 | 说明 |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `uv` | Python 环境管理 | `uv sync` 装轻量依赖 |
|
|
53
|
+
| `ffmpeg` | 抽帧 | 系统 PATH 里可用即可 |
|
|
54
|
+
| Insta360 MediaSDK | 拼接 `.insv` | 官方 SDK,手动放好 `MediaSDKTest.exe` |
|
|
55
|
+
| gaussian-splatting | 训练 | 复用已有 `.venv`(含 torch CUDA + 编译好的 `diff-gaussian-rasterization` / `simple-knn`) |
|
|
56
|
+
| `pycolmap`(cp311) | 等矩形 SfM | 预编译包目录 |
|
|
57
|
+
|
|
58
|
+
## 安装
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/FemtoRhythm/insta360-3dgs
|
|
62
|
+
cd insta360-3dgs
|
|
63
|
+
uv sync
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 配置外部依赖
|
|
67
|
+
|
|
68
|
+
项目通过环境变量找重型依赖,默认路径都假设它们和仓库同级(`../`)。对不上的话用环境变量覆盖:
|
|
69
|
+
|
|
70
|
+
| 环境变量 | 默认值 | 说明 |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| `INSTA360_3DGS_MEDIASDK_EXE` | `../sdk/MediaSDK/bin/MediaSDKTest.exe` | MediaSDK 拼接程序 |
|
|
73
|
+
| `INSTA360_3DGS_GS_DIR` | `../20260823_1/gaussian-splatting` | gaussian-splatting 目录 |
|
|
74
|
+
| `INSTA360_3DGS_PYLIBS` | `../20260823_1/pylibs` | pycolmap 预编译包 |
|
|
75
|
+
| `INSTA360_3DGS_RUNTIME_PYTHON` | `<GS_DIR>/.venv/Scripts/python.exe` | 跑重型阶段的解释器 |
|
|
76
|
+
|
|
77
|
+
## 使用
|
|
78
|
+
|
|
79
|
+
一条命令跑完,输入可以是单个 `.insv` 文件,也可以是装了一堆 `.insv` 的目录:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv run insta360-3dgs run --input VID_20260823_215314_00_006.insv --output out/
|
|
83
|
+
# 或整个目录,训练完顺手做质量评估
|
|
84
|
+
uv run insta360-3dgs run --input ./raw_clips --output out/ --verify
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
想单独跑某一步也行,每个子命令都是幂等的(产物已存在就直接跳过):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv run insta360-3dgs stitch --input ./raw_clips --output out/stitched
|
|
91
|
+
uv run insta360-3dgs extract --input out/stitched/xxx.mp4 --output out/
|
|
92
|
+
uv run insta360-3dgs sfm --output out/ --threads 8
|
|
93
|
+
uv run insta360-3dgs cubemap --output out/
|
|
94
|
+
uv run insta360-3dgs train --output out/ --iterations 30000
|
|
95
|
+
uv run insta360-3dgs export --output out/ --iteration 30000
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
另外两个可选的子命令:`verify` 算 PSNR/SSIM/L1,`preview` 渲染全景图和原图对比。
|
|
99
|
+
|
|
100
|
+
## 常用参数
|
|
101
|
+
|
|
102
|
+
| 参数 | 默认 | 说明 |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `--stitch-type` | `optflow` | 拼接算法:`template` / `optflow` / `dynamicstitch` / `aistitch` |
|
|
105
|
+
| `--output-size` | `3840x1920` | 拼接分辨率 `WxH` |
|
|
106
|
+
| `--fps` | `0.5` | 抽帧频率 |
|
|
107
|
+
| `--width` | `2880` | 全景帧宽度(高度 = 宽度 / 2) |
|
|
108
|
+
| `--iterations` | `30000` | 3DGS 训练迭代数 |
|
|
109
|
+
| `--threads` | `8` | SfM 线程数 |
|
|
110
|
+
|
|
111
|
+
## 输出结构
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
<output>/
|
|
115
|
+
stitched/ 拼接出的等矩形 mp4
|
|
116
|
+
frames/ 抽出的全景帧
|
|
117
|
+
sfm/ 等矩形 SfM(结果在 sfm/0/)
|
|
118
|
+
cubemap/ cube 面训练数据(images/ + sparse/0/)
|
|
119
|
+
model/ 3DGS 训练输出
|
|
120
|
+
pointcloud.ply 最终点云(x,y,z + rgb)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 已知问题
|
|
124
|
+
|
|
125
|
+
- 双镜头文件必须成对(`_00_` 前 + `_10_` 后),只传一个会拼不完整;传目录时会自动配对。
|
|
126
|
+
- 训练阶段内部会切到 gaussian-splatting 目录,`--output` 记得传绝对路径(CLI 已经帮你处理了)。
|
|
127
|
+
- 想重跑某一步,删掉对应的输出子目录就行,比如 `rm -rf out/model` 再跑一次 train。
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# insta360-3dgs
|
|
2
|
+
|
|
3
|
+
把 Insta360 拍出来的 `.insv` 双鱼眼视频,自动重建成全景 3D Gaussian Splatting,最后导出一个标准的 `x,y,z + rgb` 点云(PLY),可以直接扔进 CloudCompare、MeshLab 这类工具看。
|
|
4
|
+
|
|
5
|
+
## 它做了什么
|
|
6
|
+
|
|
7
|
+
Insta360 的双镜头是两颗鱼眼分开存的(`_00_` 前镜头、`_10_` 后镜头),要变成能用的 3D 结果,得一路处理:
|
|
8
|
+
|
|
9
|
+
1. 用官方 MediaSDK 把两颗鱼眼拼成一张 360° 等矩形视频
|
|
10
|
+
2. 抽帧
|
|
11
|
+
3. 用 COLMAP 的等矩形相机模型做位姿重建
|
|
12
|
+
4. 把每帧全景展开成 6 个共光心的透视面,喂给 3DGS
|
|
13
|
+
5. 训练高斯模型
|
|
14
|
+
6. 导出标准点云
|
|
15
|
+
|
|
16
|
+
### 为什么先做等矩形 SfM
|
|
17
|
+
|
|
18
|
+
全景图每个像素对应一个方向,光心在球心。如果图省事直接把 6 个 cube 面当成独立相机去做 SfM,共光心的面会被误当成自由相机,凭空多出伪基线,位姿会乱。所以先在等矩形空间用 EQUIRECTANGULAR 相机模型跑一遍 SfM 拿到平滑的相机轨迹,再基于这个轨迹展开 6 个透视面,保证每一帧的 6 个面光心都重合在同一点。
|
|
19
|
+
|
|
20
|
+
## 依赖
|
|
21
|
+
|
|
22
|
+
分两类:轻量依赖交给 uv 装,重型依赖复用机器上已经配好的环境。
|
|
23
|
+
|
|
24
|
+
| 依赖 | 用途 | 说明 |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `uv` | Python 环境管理 | `uv sync` 装轻量依赖 |
|
|
27
|
+
| `ffmpeg` | 抽帧 | 系统 PATH 里可用即可 |
|
|
28
|
+
| Insta360 MediaSDK | 拼接 `.insv` | 官方 SDK,手动放好 `MediaSDKTest.exe` |
|
|
29
|
+
| gaussian-splatting | 训练 | 复用已有 `.venv`(含 torch CUDA + 编译好的 `diff-gaussian-rasterization` / `simple-knn`) |
|
|
30
|
+
| `pycolmap`(cp311) | 等矩形 SfM | 预编译包目录 |
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/FemtoRhythm/insta360-3dgs
|
|
36
|
+
cd insta360-3dgs
|
|
37
|
+
uv sync
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 配置外部依赖
|
|
41
|
+
|
|
42
|
+
项目通过环境变量找重型依赖,默认路径都假设它们和仓库同级(`../`)。对不上的话用环境变量覆盖:
|
|
43
|
+
|
|
44
|
+
| 环境变量 | 默认值 | 说明 |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `INSTA360_3DGS_MEDIASDK_EXE` | `../sdk/MediaSDK/bin/MediaSDKTest.exe` | MediaSDK 拼接程序 |
|
|
47
|
+
| `INSTA360_3DGS_GS_DIR` | `../20260823_1/gaussian-splatting` | gaussian-splatting 目录 |
|
|
48
|
+
| `INSTA360_3DGS_PYLIBS` | `../20260823_1/pylibs` | pycolmap 预编译包 |
|
|
49
|
+
| `INSTA360_3DGS_RUNTIME_PYTHON` | `<GS_DIR>/.venv/Scripts/python.exe` | 跑重型阶段的解释器 |
|
|
50
|
+
|
|
51
|
+
## 使用
|
|
52
|
+
|
|
53
|
+
一条命令跑完,输入可以是单个 `.insv` 文件,也可以是装了一堆 `.insv` 的目录:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv run insta360-3dgs run --input VID_20260823_215314_00_006.insv --output out/
|
|
57
|
+
# 或整个目录,训练完顺手做质量评估
|
|
58
|
+
uv run insta360-3dgs run --input ./raw_clips --output out/ --verify
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
想单独跑某一步也行,每个子命令都是幂等的(产物已存在就直接跳过):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv run insta360-3dgs stitch --input ./raw_clips --output out/stitched
|
|
65
|
+
uv run insta360-3dgs extract --input out/stitched/xxx.mp4 --output out/
|
|
66
|
+
uv run insta360-3dgs sfm --output out/ --threads 8
|
|
67
|
+
uv run insta360-3dgs cubemap --output out/
|
|
68
|
+
uv run insta360-3dgs train --output out/ --iterations 30000
|
|
69
|
+
uv run insta360-3dgs export --output out/ --iteration 30000
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
另外两个可选的子命令:`verify` 算 PSNR/SSIM/L1,`preview` 渲染全景图和原图对比。
|
|
73
|
+
|
|
74
|
+
## 常用参数
|
|
75
|
+
|
|
76
|
+
| 参数 | 默认 | 说明 |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| `--stitch-type` | `optflow` | 拼接算法:`template` / `optflow` / `dynamicstitch` / `aistitch` |
|
|
79
|
+
| `--output-size` | `3840x1920` | 拼接分辨率 `WxH` |
|
|
80
|
+
| `--fps` | `0.5` | 抽帧频率 |
|
|
81
|
+
| `--width` | `2880` | 全景帧宽度(高度 = 宽度 / 2) |
|
|
82
|
+
| `--iterations` | `30000` | 3DGS 训练迭代数 |
|
|
83
|
+
| `--threads` | `8` | SfM 线程数 |
|
|
84
|
+
|
|
85
|
+
## 输出结构
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
<output>/
|
|
89
|
+
stitched/ 拼接出的等矩形 mp4
|
|
90
|
+
frames/ 抽出的全景帧
|
|
91
|
+
sfm/ 等矩形 SfM(结果在 sfm/0/)
|
|
92
|
+
cubemap/ cube 面训练数据(images/ + sparse/0/)
|
|
93
|
+
model/ 3DGS 训练输出
|
|
94
|
+
pointcloud.ply 最终点云(x,y,z + rgb)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 已知问题
|
|
98
|
+
|
|
99
|
+
- 双镜头文件必须成对(`_00_` 前 + `_10_` 后),只传一个会拼不完整;传目录时会自动配对。
|
|
100
|
+
- 训练阶段内部会切到 gaussian-splatting 目录,`--output` 记得传绝对路径(CLI 已经帮你处理了)。
|
|
101
|
+
- 想重跑某一步,删掉对应的输出子目录就行,比如 `rm -rf out/model` 再跑一次 train。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "insta360-3dgs"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Insta360 .insv -> 全景 3D Gaussian Splatting 点云流水线 (MediaSDK 拼接 + 等矩形 SfM + 3DGS)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "FemtoRhythm" }]
|
|
9
|
+
keywords = ["insta360", "3dgs", "gaussian-splatting", "colmap", "panorama", "point-cloud"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Topic :: Multimedia :: Graphics :: 3D Modeling",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"numpy>=1.24",
|
|
22
|
+
"scipy>=1.10",
|
|
23
|
+
"pillow>=9.0",
|
|
24
|
+
"plyfile>=0.8",
|
|
25
|
+
"tqdm>=4.64",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/FemtoRhythm/insta360-3dgs"
|
|
30
|
+
Repository = "https://github.com/FemtoRhythm/insta360-3dgs"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
insta360-3dgs = "insta360_3dgs.cli:main"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/insta360_3dgs"]
|
|
41
|
+
|
|
42
|
+
# 说明: torch(CUDA) / pycolmap / 已编译的 diff-gaussian-rasterization、simple-knn
|
|
43
|
+
# 属于"重型运行时依赖", 默认复用已有的已编译环境 (见 config.py 与 README)。
|
|
44
|
+
# 如需在 uv 环境内自行安装 torch(CUDA), 取消下面注释并按需选择 CUDA 索引:
|
|
45
|
+
#
|
|
46
|
+
# [tool.uv.sources]
|
|
47
|
+
# torch = { index = "pytorch-cu128" }
|
|
48
|
+
#
|
|
49
|
+
# [[tool.uv.index]]
|
|
50
|
+
# name = "pytorch-cu128"
|
|
51
|
+
# url = "https://download.pytorch.org/whl/cu128"
|
|
52
|
+
# explicit = true
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: insta360-3dgs
|
|
3
|
+
description: 'Insta360 .insv 双鱼眼视频到全景 3D Gaussian Splatting 点云的重建流水线。基于 MediaSDK 拼接 + pycolmap 等矩形 SfM + 3DGS 训练。'
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# insta360-3dgs
|
|
9
|
+
|
|
10
|
+
把 Insta360 的 `.insv` 双鱼眼视频重建为全景 3DGS 点云。源码单一真源在 GitHub 仓库 `FemtoRhythm/insta360-3dgs`,本 skill 用引导脚本定位/拉取源码并运行,不内嵌源码。
|
|
11
|
+
|
|
12
|
+
## 用法
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
python <skill目录>/scripts/bootstrap.py check # 诊断环境
|
|
16
|
+
python <skill目录>/scripts/bootstrap.py setup # 拉取源码 + uv sync
|
|
17
|
+
python <skill目录>/scripts/bootstrap.py run --input <xx.insv或目录> --output <输出目录>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
完整的流水线说明、环境变量、排错都在仓库的 README 里。
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""insta360-3dgs skill 引导脚本。
|
|
4
|
+
|
|
5
|
+
职责:
|
|
6
|
+
check 诊断环境(源码 / uv / ffmpeg / MediaSDK / gaussian-splatting /
|
|
7
|
+
pycolmap / runtime python / GPU)
|
|
8
|
+
setup 准备源码(定位或 git clone)并 uv sync 安装轻量依赖
|
|
9
|
+
run 注入外部依赖路径后运行端到端流水线(.insv -> 全景 3DGS -> 点云)
|
|
10
|
+
|
|
11
|
+
仅依赖 Python 标准库,可在 Python 3.8+ 直接运行。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
GITHUB_REPO = "https://github.com/FemtoRhythm/insta360-3dgs"
|
|
24
|
+
|
|
25
|
+
# 本机已知部署位置,作为「自动识别」的候选路径;可自行增删。
|
|
26
|
+
_KNOWN_PROJECT_CANDIDATES = [
|
|
27
|
+
Path(r"d:\Documents\WorkSpace\python\i2\20260828_3\insta360-3dgs"),
|
|
28
|
+
]
|
|
29
|
+
_KNOWN_MEDIASDK_CANDIDATES = [
|
|
30
|
+
Path(r"d:\Documents\WorkSpace\python\i2\20260828_3\sdk\MediaSDK\bin\MediaSDKTest.exe"),
|
|
31
|
+
]
|
|
32
|
+
_KNOWN_GS_DIR_CANDIDATES = [
|
|
33
|
+
Path(r"d:\Documents\WorkSpace\python\i2\20260828_3\20260823_1\gaussian-splatting"),
|
|
34
|
+
]
|
|
35
|
+
_KNOWN_PYLIBS_CANDIDATES = [
|
|
36
|
+
Path(r"d:\Documents\WorkSpace\python\i2\20260828_3\20260823_1\pylibs"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------------------------------- #
|
|
41
|
+
# 定位
|
|
42
|
+
# --------------------------------------------------------------------------- #
|
|
43
|
+
def _is_project(path: Path) -> bool:
|
|
44
|
+
return (path / "pyproject.toml").is_file() and (path / "src" / "insta360_3dgs").is_dir()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def find_project() -> Path | None:
|
|
48
|
+
"""按优先级定位源码项目根:环境变量 -> cwd -> 已知候选。"""
|
|
49
|
+
env = os.environ.get("INSTA360_3DGS_PROJECT_DIR")
|
|
50
|
+
if env and _is_project(Path(env)):
|
|
51
|
+
return Path(env)
|
|
52
|
+
|
|
53
|
+
cwd = Path.cwd()
|
|
54
|
+
if _is_project(cwd):
|
|
55
|
+
return cwd
|
|
56
|
+
|
|
57
|
+
for p in _KNOWN_PROJECT_CANDIDATES:
|
|
58
|
+
if _is_project(p):
|
|
59
|
+
return p
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _first_existing(candidates: list[Path]) -> Path | None:
|
|
64
|
+
for p in candidates:
|
|
65
|
+
if p.exists():
|
|
66
|
+
return p
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _runtime_python_candidates(gs_dir: Path) -> list[Path]:
|
|
71
|
+
return [gs_dir / ".venv" / "Scripts" / "python.exe", # Windows
|
|
72
|
+
gs_dir / ".venv" / "bin" / "python"] # POSIX
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def resolve_deps(project: Path | None) -> dict[str, Path | None]:
|
|
76
|
+
"""解析四个外部依赖路径。优先级:环境变量 > config 默认推导 > 已知候选。"""
|
|
77
|
+
parent = project.parent if project else Path.cwd().parent
|
|
78
|
+
|
|
79
|
+
def _pick(name: str, default: list[Path], known: list[Path]) -> Path | None:
|
|
80
|
+
env = os.environ.get(name)
|
|
81
|
+
if env:
|
|
82
|
+
return Path(env)
|
|
83
|
+
return _first_existing(default + known)
|
|
84
|
+
|
|
85
|
+
gs_dir = _pick(
|
|
86
|
+
"INSTA360_3DGS_GS_DIR",
|
|
87
|
+
[parent / "20260823_1" / "gaussian-splatting"],
|
|
88
|
+
_KNOWN_GS_DIR_CANDIDATES,
|
|
89
|
+
)
|
|
90
|
+
return {
|
|
91
|
+
"mediasdk_exe": _pick(
|
|
92
|
+
"INSTA360_3DGS_MEDIASDK_EXE",
|
|
93
|
+
[parent / "sdk" / "MediaSDK" / "bin" / "MediaSDKTest.exe"],
|
|
94
|
+
_KNOWN_MEDIASDK_CANDIDATES,
|
|
95
|
+
),
|
|
96
|
+
"gs_dir": gs_dir,
|
|
97
|
+
"pylibs": _pick(
|
|
98
|
+
"INSTA360_3DGS_PYLIBS",
|
|
99
|
+
[parent / "20260823_1" / "pylibs"],
|
|
100
|
+
_KNOWN_PYLIBS_CANDIDATES,
|
|
101
|
+
),
|
|
102
|
+
"runtime_python": (
|
|
103
|
+
Path(os.environ["INSTA360_3DGS_RUNTIME_PYTHON"])
|
|
104
|
+
if os.environ.get("INSTA360_3DGS_RUNTIME_PYTHON")
|
|
105
|
+
else _first_existing(_runtime_python_candidates(gs_dir) if gs_dir else [])
|
|
106
|
+
),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# --------------------------------------------------------------------------- #
|
|
111
|
+
# check
|
|
112
|
+
# --------------------------------------------------------------------------- #
|
|
113
|
+
def _ok(b: bool) -> str:
|
|
114
|
+
return "OK " if b else "MISS"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def cmd_check(args: argparse.Namespace) -> int:
|
|
118
|
+
project = find_project()
|
|
119
|
+
deps = resolve_deps(project)
|
|
120
|
+
|
|
121
|
+
uv = shutil.which("uv")
|
|
122
|
+
ffmpeg = shutil.which("ffmpeg")
|
|
123
|
+
git = shutil.which("git")
|
|
124
|
+
nvidia_smi = shutil.which("nvidia-smi")
|
|
125
|
+
|
|
126
|
+
print("=== insta360-3dgs 环境诊断 ===")
|
|
127
|
+
print(f"[{_ok(project is not None)}] 项目源码 {project or '未找到'}")
|
|
128
|
+
print(f"[{_ok(uv is not None)}] uv {uv or ''}")
|
|
129
|
+
print(f"[{_ok(ffmpeg is not None)}] ffmpeg {ffmpeg or ''}")
|
|
130
|
+
print(f"[{_ok(git is not None)}] git {git or ''}")
|
|
131
|
+
print(f"[{_ok(nvidia_smi is not None)}] NVIDIA GPU {nvidia_smi or ''}")
|
|
132
|
+
print(f"[{_ok(deps['mediasdk_exe'] is not None)}] MediaSDKTest.exe {deps['mediasdk_exe'] or ''}")
|
|
133
|
+
print(f"[{_ok(deps['gs_dir'] is not None)}] gaussian-splatting {deps['gs_dir'] or ''}")
|
|
134
|
+
print(f"[{_ok(deps['pylibs'] is not None)}] pylibs(pycolmap) {deps['pylibs'] or ''}")
|
|
135
|
+
print(f"[{_ok(deps['runtime_python'] is not None)}] runtime python {deps['runtime_python'] or ''}")
|
|
136
|
+
|
|
137
|
+
missing = [
|
|
138
|
+
("项目源码", project is None),
|
|
139
|
+
("uv", uv is None),
|
|
140
|
+
("ffmpeg", ffmpeg is None),
|
|
141
|
+
("MediaSDKTest.exe", deps["mediasdk_exe"] is None),
|
|
142
|
+
("gaussian-splatting", deps["gs_dir"] is None),
|
|
143
|
+
("pylibs(pycolmap)", deps["pylibs"] is None),
|
|
144
|
+
("runtime python", deps["runtime_python"] is None),
|
|
145
|
+
]
|
|
146
|
+
miss_list = [name for name, is_miss in missing if is_miss]
|
|
147
|
+
if miss_list:
|
|
148
|
+
print(f"\n缺失项: {', '.join(miss_list)}")
|
|
149
|
+
print("提示: 运行 `python bootstrap.py setup` 可自动准备源码与轻量依赖;"
|
|
150
|
+
"重型依赖(MediaSDK/gaussian-splatting/pycolmap)需预先放置并用环境变量指定。")
|
|
151
|
+
return 1
|
|
152
|
+
print("\n环境就绪,可运行: python bootstrap.py run --input <xx.insv> --output <out>")
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# --------------------------------------------------------------------------- #
|
|
157
|
+
# setup
|
|
158
|
+
# --------------------------------------------------------------------------- #
|
|
159
|
+
def cmd_setup(args: argparse.Namespace) -> int:
|
|
160
|
+
project = find_project()
|
|
161
|
+
if project is None:
|
|
162
|
+
dest = Path(os.environ.get("INSTA360_3DGS_PROJECT_DIR", Path.home() / "insta360-3dgs"))
|
|
163
|
+
if not shutil.which("git"):
|
|
164
|
+
print("错误: 未安装 git,无法 clone 源码。请先安装 git 或用 INSTA360_3DGS_PROJECT_DIR 指定已有源码。")
|
|
165
|
+
return 1
|
|
166
|
+
print(f"克隆源码: {GITHUB_REPO} -> {dest}")
|
|
167
|
+
subprocess.run(["git", "clone", GITHUB_REPO, str(dest)], check=True)
|
|
168
|
+
project = dest
|
|
169
|
+
|
|
170
|
+
print(f"项目目录: {project}")
|
|
171
|
+
uv = shutil.which("uv")
|
|
172
|
+
if not uv:
|
|
173
|
+
print("错误: 未安装 uv,无法同步依赖。请先安装 uv (https://docs.astral.sh/uv/)。")
|
|
174
|
+
return 1
|
|
175
|
+
print("同步轻量依赖: uv sync")
|
|
176
|
+
subprocess.run(["uv", "sync"], cwd=project, check=True)
|
|
177
|
+
|
|
178
|
+
# 校验外部依赖
|
|
179
|
+
deps = resolve_deps(project)
|
|
180
|
+
for key, label in [("mediasdk_exe", "MediaSDKTest.exe"),
|
|
181
|
+
("gs_dir", "gaussian-splatting"),
|
|
182
|
+
("pylibs", "pylibs(pycolmap)"),
|
|
183
|
+
("runtime_python", "runtime python")]:
|
|
184
|
+
if deps[key] is None:
|
|
185
|
+
print(f"警告: 未检测到 {label},请放置后用对应 INSTA360_3DGS_* 环境变量指定。")
|
|
186
|
+
print("setup 完成。")
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# --------------------------------------------------------------------------- #
|
|
191
|
+
# run
|
|
192
|
+
# --------------------------------------------------------------------------- #
|
|
193
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
194
|
+
project = find_project()
|
|
195
|
+
if project is None:
|
|
196
|
+
print("错误: 未找到项目源码。请先运行 `python bootstrap.py setup`。")
|
|
197
|
+
return 1
|
|
198
|
+
|
|
199
|
+
deps = resolve_deps(project)
|
|
200
|
+
env = os.environ.copy()
|
|
201
|
+
mapping = {
|
|
202
|
+
"INSTA360_3DGS_MEDIASDK_EXE": deps["mediasdk_exe"],
|
|
203
|
+
"INSTA360_3DGS_GS_DIR": deps["gs_dir"],
|
|
204
|
+
"INSTA360_3DGS_PYLIBS": deps["pylibs"],
|
|
205
|
+
"INSTA360_3DGS_RUNTIME_PYTHON": deps["runtime_python"],
|
|
206
|
+
}
|
|
207
|
+
for k, v in mapping.items():
|
|
208
|
+
if v is None:
|
|
209
|
+
print(f"错误: 依赖缺失 {k},请先运行 `python bootstrap.py check` 诊断。")
|
|
210
|
+
return 1
|
|
211
|
+
env[k] = str(v)
|
|
212
|
+
|
|
213
|
+
argv = ["uv", "run", "insta360-3dgs", "run",
|
|
214
|
+
"--input", *args.input, "--output", str(Path(args.output).resolve())]
|
|
215
|
+
if args.iterations:
|
|
216
|
+
argv += ["--iterations", str(args.iterations)]
|
|
217
|
+
if args.verify:
|
|
218
|
+
argv += ["--verify"]
|
|
219
|
+
|
|
220
|
+
print("==>", " ".join(argv))
|
|
221
|
+
subprocess.run(argv, cwd=project, env=env, check=True)
|
|
222
|
+
print(f"\n完成! 点云输出: {Path(args.output).resolve() / 'pointcloud.ply'}")
|
|
223
|
+
return 0
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# --------------------------------------------------------------------------- #
|
|
227
|
+
# 入口
|
|
228
|
+
# --------------------------------------------------------------------------- #
|
|
229
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
230
|
+
parser = argparse.ArgumentParser(prog="bootstrap.py", description="insta360-3dgs skill 引导脚本")
|
|
231
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
232
|
+
|
|
233
|
+
sub.add_parser("check", help="诊断环境").set_defaults(func=cmd_check)
|
|
234
|
+
sub.add_parser("setup", help="准备源码与轻量依赖").set_defaults(func=cmd_setup)
|
|
235
|
+
|
|
236
|
+
p_run = sub.add_parser("run", help="运行端到端流水线")
|
|
237
|
+
p_run.add_argument("--input", required=True, nargs="+", help="一个或多个 .insv 文件或目录")
|
|
238
|
+
p_run.add_argument("--output", required=True, help="输出目录")
|
|
239
|
+
p_run.add_argument("--iterations", type=int, default=None, help="3DGS 训练迭代数")
|
|
240
|
+
p_run.add_argument("--verify", action="store_true", help="训练后质量评估")
|
|
241
|
+
p_run.set_defaults(func=cmd_run)
|
|
242
|
+
|
|
243
|
+
return parser
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def main() -> int:
|
|
247
|
+
args = build_parser().parse_args()
|
|
248
|
+
return args.func(args)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
sys.exit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""重型阶段重执行。
|
|
3
|
+
|
|
4
|
+
uv 环境只装轻量依赖,sfm/train 这些需要 torch/pycolmap 的阶段交给
|
|
5
|
+
gaussian-splatting/.venv 里的解释器跑,并注入 PYTHONPATH 指向本项目 src/。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from . import config
|
|
15
|
+
|
|
16
|
+
# src/ 目录 (insta360_3dgs 包的父目录)
|
|
17
|
+
_SRC_DIR = str(Path(__file__).resolve().parent.parent)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _absolutize(args: list[str]) -> list[str]:
|
|
21
|
+
"""把 `--output <path>` 转成绝对路径。
|
|
22
|
+
|
|
23
|
+
train/verify 等阶段会在模块加载时 os.chdir 到 gaussian-splatting 目录,
|
|
24
|
+
若这里传相对路径, 后续 `output/cubemap` 会被解析到错误位置。统一转绝对路径规避。
|
|
25
|
+
"""
|
|
26
|
+
out: list[str] = []
|
|
27
|
+
i = 0
|
|
28
|
+
while i < len(args):
|
|
29
|
+
out.append(args[i])
|
|
30
|
+
if args[i] == "--output" and i + 1 < len(args):
|
|
31
|
+
out.append(str(Path(args[i + 1]).resolve()))
|
|
32
|
+
i += 1
|
|
33
|
+
i += 1
|
|
34
|
+
return out
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_stage(module: str, args: list[str]) -> None:
|
|
38
|
+
python = config.runtime_python()
|
|
39
|
+
if not Path(python).exists():
|
|
40
|
+
raise SystemExit(
|
|
41
|
+
f"运行时解释器不存在: {python}\n"
|
|
42
|
+
f"请检查 gaussian-splatting/.venv 是否已创建, 或用环境变量 "
|
|
43
|
+
f"INSTA360_3DGS_RUNTIME_PYTHON 指定含 torch(CUDA)+pycolmap 的解释器")
|
|
44
|
+
|
|
45
|
+
env = os.environ.copy()
|
|
46
|
+
env["PYTHONPATH"] = _SRC_DIR + os.pathsep + env.get("PYTHONPATH", "")
|
|
47
|
+
|
|
48
|
+
command = [python, "-m", f"insta360_3dgs.{module}", *_absolutize(args)]
|
|
49
|
+
print(f"\n==> {' '.join(command)}", flush=True)
|
|
50
|
+
subprocess.run(command, env=env, check=True)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
sys.exit("此模块不应直接运行, 请通过 cli 调用")
|