From 93632600a79347d767c06d6b8b3378398bac6f19 Mon Sep 17 00:00:00 2001 From: wTechArtist Date: Wed, 18 Jun 2025 09:57:28 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E5=90=88=E5=B9=B6README=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E5=88=A0=E9=99=A4=E5=86=97=E4=BD=99=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 193 ++++++-------------- VGGT_FIX_SUMMARY.md | 116 ------------ VGGT_INTEGRATION_SUMMARY.md | 140 --------------- __init__.py | 9 +- comfyui_vggt_nodes.py | 5 +- glb_point_cloud_processor.py | 336 +++++++++++++++++++++++++++++++++++ 6 files changed, 400 insertions(+), 399 deletions(-) delete mode 100644 VGGT_FIX_SUMMARY.md delete mode 100644 VGGT_INTEGRATION_SUMMARY.md create mode 100755 glb_point_cloud_processor.py diff --git a/README.md b/README.md index faccd04..d8940a4 100644 --- a/README.md +++ b/README.md @@ -1,178 +1,93 @@ -# VGGT ComfyUI 集成 +# ComfyUI VVL VideoCamera Advanced - 环境安装指南 -在 ComfyUI 中集成 Facebook Research 的 VGGT(Visual Geometry Grounded Transformer)模型,实现原生的多视图3D重建、相机姿态估计、深度预测和轨迹跟踪功能。 - -## 功能特性 - -- ✅ **原生VGGT算法**: 直接使用官方VGGT推理流程 -- ✅ **多视图3D重建**: 支持1-200帧图像序列输入 -- ✅ **相机姿态估计**: 自动估计相机内外参数 -- ✅ **深度图预测**: 生成高质量密集深度图 -- ✅ **点云重建**: 基于深度和姿态信息重建3D点云 -- ✅ **原生结果输出**: 保留所有原生Tensor数据 -- ✅ **自动模型管理**: 支持本地缓存和HuggingFace自动下载 +这是一个集成了VGGT(Visual Geometry Grounded Transformer)和GLB点云处理功能的ComfyUI自定义节点包。 ## 安装 -### 1. 自动安装(推荐) +### 方法1: 自动安装(推荐) ```bash cd custom_nodes/ComfyUI_VVL_VideoCamera_Advanced python install.py ``` -### 2. 手动安装 +### 方法2: 手动安装 ```bash -# 安装VGGT专用依赖 +# 1. 安装VGGT专用依赖 pip install -r requirements_vggt.txt -# 创建模型目录 +# 2. 创建模型目录 mkdir -p models/vggt + +# 3. 安装GLB处理依赖 +pip install trimesh scikit-learn ``` -## 使用方法 - -### 基本工作流 - -1. **加载模型**: 使用 `VGGT 模型加载器` 节点 - - 选择设备(auto/cuda/cpu) - - 选择模型版本(VGGT-1B) - -2. **输入图像**: 连接图像序列到 `VGGT 多输入重建` 节点 - - 支持1-200帧图像 - - 自动预处理到336x336分辨率 - -3. **配置参数**: - - `confidence_threshold`: 置信度阈值(0-100%) - - `show_cameras`: 是否显示相机位置 - - `mask_*`: 各种遮罩选项 - -4. **获取结果**: - - `raw_result`: 原生VGGT推理结果(包含所有Tensor) - - `intrinsics_json`: 相机内参JSON - - `trajectory_preview`: 轨迹预览图像 - - `poses_json`: 相机姿态JSON - - `model_3d_path`: 3D模型文件路径 - -### 节点说明 - -#### VGGT 模型加载器 -- **输入**: 设备选择、模型版本 -- **输出**: VGGT模型实例 -- **功能**: 自动下载和缓存模型权重 - -#### VGGT 多输入重建 -- **输入**: VGGT模型、图像序列、配置参数 -- **输出**: 原生结果、JSON数据、预览图像、3D模型 -- **功能**: 执行完整的VGGT推理流程 - -## 模型管理 - -### 支持的模型 - -| 模型名称 | 参数量 | 文件大小 | HuggingFace Hub | -|---------|--------|----------|-----------------| -| VGGT-1B | 1B | ~4.7GB | facebook/VGGT-1B | - -### 模型存储 - -模型权重存储在 `models/vggt/` 目录: -- 自动下载:首次使用时从HuggingFace Hub下载 -- 本地缓存:下载后自动缓存,避免重复下载 -- 手动放置:可手动将 `vggt_1b.pt` 放入该目录 - -## 技术架构 - -### 核心组件 +## 依赖要求 +### VGGT核心依赖 ``` -ComfyUI Framework -├── VVL_VideoCamera_Advanced/ -│ ├── vggt/ # VGGT原生代码 -│ ├── vggt_native_interface.py # 原生接口封装 -│ ├── vggt_model_loader.py # 模型加载管理 -│ ├── comfyui_vggt_nodes.py # ComfyUI节点 -│ └── visual_util.py # 可视化工具 +torch>=2.0.0 +torchvision +transformers +accelerate +huggingface_hub +numpy +pillow ``` -### 推理流程 +### GLB处理依赖 +``` +trimesh # GLB文件读写(必需) +scikit-learn # 离群点检测(可选) +numpy # 数值计算 +``` -1. **图像预处理**: 使用VGGT官方预处理函数 -2. **特征聚合**: `model.aggregator(images)` -3. **多分支预测**: - - 相机姿态: `model.camera_head()` - - 深度图: `model.depth_head()` - - 点云: `model.point_head()` - - 轨迹跟踪: `model.track_head()` (可选) -4. **结果后处理**: 保留原生Tensor + 生成可视化 +## 验证安装 -## 性能优化 +运行测试脚本验证安装是否成功: -### 内存使用 +```bash +python test_vggt_integration.py +``` -| 输入帧数 | GPU内存 | 推理时间 | -|---------|---------|----------| -| 1 | 1.88GB | 0.04s | -| 10 | 3.63GB | 0.14s | -| 50 | 11.41GB | 1.04s | -| 100 | 21.15GB | 3.12s | -| 200 | 40.63GB | 8.75s | - -### 优化建议 - -- 使用Flash Attention 3(如果可用) -- 启用混合精度推理(FP16) -- 根据GPU内存调整输入帧数 -- 使用CUDA设备获得最佳性能 +如果看到 "🎉 所有测试通过!" 消息,说明安装成功。 ## 故障排除 ### 常见问题 -1. **模型下载失败** - - 检查网络连接 - - 手动下载模型文件到 `models/vggt/` +1. **"trimesh库不可用"错误** + ```bash + pip install trimesh + ``` -2. **内存不足** +2. **"No module named 'vggt'"错误** + - 确保所有 `__init__.py` 文件存在 + - 重新运行 `python install.py` + +3. **模型下载失败** + - 检查网络连接 + - 模型会自动从HuggingFace下载到 `models/vggt/` 目录 + +4. **内存不足** - 减少输入图像数量 - 使用CPU推理 - 关闭其他GPU程序 -3. **依赖缺失** - - 运行 `python install.py` 重新安装 - - 检查Python环境和包版本 +### 模型存储 -### 日志调试 +模型权重自动存储在 `ComfyUI/models/vggt/` 目录: +- 首次使用时自动从HuggingFace Hub下载 +- 本地缓存避免重复下载 +- 可手动放置模型文件到该目录 -启用详细日志: -```python -import logging -logging.getLogger('vvl_vggt_nodes').setLevel(logging.DEBUG) -``` +## 系统要求 -## 开发说明 +- Python 3.8+ +- PyTorch 2.0+ +- CUDA(推荐,CPU也支持) +- 至少8GB内存(GPU推理需要更多显存) -### 扩展节点 - -参考 `vggt.md` 技术规范文档,可以基于原生接口开发新的专用节点: -- 深度估计专用节点 -- 相机姿态估计专用节点 -- 点跟踪专用节点 - -### 贡献指南 - -1. 遵循技术规范文档 -2. 保持原生VGGT API兼容性 -3. 添加适当的错误处理和日志 -4. 更新文档和测试 - -## 许可证 - -本项目遵循原始VGGT项目的许可证条款。 - -## 致谢 - -- [Facebook Research VGGT](https://github.com/facebookresearch/vggt) -- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) -- VGGT论文作者和贡献者 \ No newline at end of file +安装完成后即可在ComfyUI中使用VGGT和GLB处理节点。 \ No newline at end of file diff --git a/VGGT_FIX_SUMMARY.md b/VGGT_FIX_SUMMARY.md deleted file mode 100644 index eeecfc4..0000000 --- a/VGGT_FIX_SUMMARY.md +++ /dev/null @@ -1,116 +0,0 @@ -# VGGT ComfyUI集成修复总结 - -## 问题诊断 - -❌ **初始错误**: `RuntimeError: VGGT not available: No module named 'vggt'` - -这个错误表明虽然VGGT代码存在于 `vggt/` 目录中,但Python无法将其识别为一个可导入的模块包。 - -## 根本原因 - -🔍 **缺失的 `__init__.py` 文件** - -Python需要 `__init__.py` 文件来识别目录为模块包。检查发现: -- ✅ `vggt/heads/track_modules/__init__.py` - 存在 -- ✅ `vggt/dependency/__init__.py` - 存在 -- ✅ `vggt/layers/__init__.py` - 存在 -- ❌ `vggt/__init__.py` - **缺失** (根目录) -- ❌ `vggt/models/__init__.py` - **缺失** -- ❌ `vggt/utils/__init__.py` - **缺失** -- ❌ `vggt/heads/__init__.py` - **缺失** - -## 修复措施 - -✅ **创建缺失的 `__init__.py` 文件** - -```bash -# 创建的文件: -custom_nodes/ComfyUI_VVL_VideoCamera_Advanced/vggt/__init__.py -custom_nodes/ComfyUI_VVL_VideoCamera_Advanced/vggt/models/__init__.py -custom_nodes/ComfyUI_VVL_VideoCamera_Advanced/vggt/utils/__init__.py -custom_nodes/ComfyUI_VVL_VideoCamera_Advanced/vggt/heads/__init__.py -``` - -每个文件都包含适当的模块文档字符串,使Python能够正确识别这些目录为模块包。 - -## 验证结果 - -✅ **所有导入测试通过** - -```python -# 测试通过的导入: -from vggt.models.vggt import VGGT # ✓ 成功 -from vggt.utils.load_fn import load_and_preprocess_images # ✓ 成功 -from vggt_model_loader import VVLVGGTLoader # ✓ 成功 -from vggt_native_interface import VGGTNativeInterface # ✓ 成功 -from comfyui_vggt_nodes import VGGTMultiInputNode # ✓ 成功 -``` - -✅ **节点创建测试通过** - -``` -VGGTLoader输入类型: ['device', 'vggt_model'] -VGGTMultiInputNode输入类型: ['vggt_model', 'images'] -VGGTMultiInputNode输出类型: ('RAW_VGGT_RESULT', 'STRING', 'IMAGE', 'STRING', 'STRING') -``` - -✅ **模型信息获取正常** - -``` -VGGT可用性: True -模型目录: /home/game-netease/.cache/comfyui/models/vggt -本地模型: {'VGGT-1B': False} -``` - -## 解决方案效果 - -🎉 **完全修复**: ComfyUI现在可以正确识别和使用VGGT节点 - -### 修复前: -``` -RuntimeError: VGGT not available: No module named 'vggt' -``` - -### 修复后: -``` -🎉 所有测试通过!VGGT节点可以正常工作了! -``` - -## 技术细节 - -### Python模块系统要求 -- Python需要 `__init__.py` 文件来将目录识别为包 -- 缺少根目录的 `__init__.py` 会导致整个包无法导入 -- 子目录的 `__init__.py` 也是必需的 - -### 文件结构 (修复后) -``` -vggt/ -├── __init__.py # ✅ 新增 -├── models/ -│ ├── __init__.py # ✅ 新增 -│ └── vggt.py -├── utils/ -│ ├── __init__.py # ✅ 新增 -│ └── load_fn.py -├── heads/ -│ ├── __init__.py # ✅ 新增 -│ └── track_modules/ -└── ... -``` - -## 后续使用 - -现在用户可以在ComfyUI中正常使用VGGT节点: - -1. **加载模型**: 使用 `VGGT 模型加载器` 节点 -2. **处理图像**: 连接图像序列到 `VGGT 多输入重建` 节点 -3. **获得结果**: 原生VGGT结果 + 格式化输出 - -## 总结 - -✅ **问题完全解决**: 通过添加缺失的 `__init__.py` 文件,VGGT模块现在可以正确导入 -✅ **功能完整**: 所有原生VGGT功能都可以在ComfyUI中使用 -✅ **测试验证**: 所有组件都经过测试验证正常工作 - -这是一个简单但关键的修复,确保了Python模块系统能够正确识别和导入VGGT包结构。 \ No newline at end of file diff --git a/VGGT_INTEGRATION_SUMMARY.md b/VGGT_INTEGRATION_SUMMARY.md deleted file mode 100644 index a7536e0..0000000 --- a/VGGT_INTEGRATION_SUMMARY.md +++ /dev/null @@ -1,140 +0,0 @@ -# VGGT ComfyUI集成重构完成总结 - -## 重构概况 - -✅ **重构成功完成**!已根据最新的 vggt.md 技术规范和官方 Facebook Research VGGT 仓库,成功重构代码以实现原生的 VGGT 算法集成。 - -## 重构重点 - -### 1. 原生VGGT算法实现 -- ✅ 创建了 `vggt_native_interface.py`,实现完整的原生VGGT推理流程 -- ✅ 严格按照官方API:`aggregator → camera_head → depth_head → point_head → track_head` -- ✅ 使用官方预处理函数和工具函数 -- ✅ 支持混合精度推理(FP16) - -### 2. 输入输出规范化 -- ✅ 输入改为仅接受图片序列(移除内部视频预处理) -- ✅ 输出同时包含原生结果和格式化结果 -- ✅ 原生Tensor数据完整保留 -- ✅ 支持1-200帧图像序列 - -### 3. 模型管理优化 -- ✅ 模型文件存储在 `ComfyUI/models/vggt/` 目录 -- ✅ 本地存在则直接加载,否则自动从HuggingFace下载 -- ✅ 支持模型缓存和设备管理 -- ✅ 断点续传和错误恢复 - -### 4. 依赖管理 -- ✅ 创建了 `requirements_vggt.txt` 专用依赖文件 -- ✅ 包含所有核心依赖,注释掉可能有编译问题的可选依赖 -- ✅ 版本锁定以避免兼容性问题 - -## 文件结构 - -``` -ComfyUI_VVL_VideoCamera_Advanced/ -├── vggt/ # VGGT原生代码(已存在) -├── vggt_native_interface.py # ✅ 新增:原生接口封装 -├── vggt_model_loader.py # ✅ 更新:优化模型加载 -├── comfyui_vggt_nodes.py # ✅ 重构:使用原生接口 -├── requirements_vggt.txt # ✅ 新增:专用依赖 -├── install.py # ✅ 新增:自动安装脚本 -├── test_vggt_integration.py # ✅ 新增:集成测试 -├── README.md # ✅ 新增:使用文档 -├── VGGT_INTEGRATION_SUMMARY.md # ✅ 新增:本总结文档 -├── vggt.md # ✅ 更新:技术规范 -└── __init__.py # ✅ 更新:节点注册 -``` - -## 核心组件 - -### VGGTNativeInterface -- 完整的VGGT推理管道 -- 选择性推理支持 -- 官方工具函数集成 - -### VGGTImageProcessor -- 官方预处理函数优先 -- 备用预处理方案 -- 遮罩应用支持 - -### VGGTResultProcessor -- 原生结果保留 -- 格式化输出生成 -- JSON序列化支持 - -### VGGTMultiInputNode -- 图片序列输入(必需) -- 原生结果输出(首位) -- 配置参数支持 - -### VVLVGGTLoader -- 自动模型下载 -- 本地缓存管理 -- 设备自动选择 - -## 测试验证 - -✅ **所有集成测试通过**: -- 依赖检查: ✓ 通过 -- 模型目录: ✓ 通过 -- VGGT模型加载: ✓ 通过 -- 原生接口: ✓ 通过 -- ComfyUI节点: ✓ 通过 - -## 安装使用 - -### 自动安装 -```bash -cd custom_nodes/ComfyUI_VVL_VideoCamera_Advanced -python install.py -``` - -### 验证安装 -```bash -python test_vggt_integration.py -``` - -### 在ComfyUI中使用 -1. 使用 `VGGT 模型加载器` 加载模型 -2. 连接图像序列到 `VGGT 多输入重建` 节点 -3. 获得原生VGGT结果和格式化输出 - -## 技术特性 - -### 性能优化 -- 混合精度推理(FP16) -- 模型缓存机制 -- 内存使用监控 -- 批处理支持 - -### 错误处理 -- 完整的异常捕获 -- 优雅的错误恢复 -- 详细的日志记录 -- 用户友好的错误信息 - -### 兼容性 -- PyTorch 2.5.1+ 支持 -- CUDA 和 CPU 推理 -- ComfyUI 工作流集成 -- 官方VGGT API兼容 - -## 后续扩展 - -根据 vggt.md 技术规范,可以进一步开发: -- 深度估计专用节点 -- 相机姿态估计专用节点 -- 点跟踪专用节点 -- 可选依赖集成(flash-attn、pytorch3d等) - -## 总结 - -🎉 **重构成功完成**!现在 ComfyUI 中已集成了完全原生的 VGGT 算法,用户可以: - -1. **使用原生VGGT算法**进行多视图3D重建 -2. **获得原生Tensor结果**用于后续处理 -3. **享受自动化模型管理**无需手动下载 -4. **体验优化的性能**和稳定性 - -该集成严格遵循 Facebook Research VGGT 的官方实现,确保了算法的原始性和准确性。 \ No newline at end of file diff --git a/__init__.py b/__init__.py index 2b9fab3..b9e37a4 100644 --- a/__init__.py +++ b/__init__.py @@ -5,17 +5,20 @@ ComfyUI VVL Video Camera Advanced from .comfyui_vggt_nodes import VGGTMultiInputNode from .vggt_model_loader import VVLVGGTLoader +from .glb_point_cloud_processor import GLBPointCloudProcessor # 节点类映射 NODE_CLASS_MAPPINGS = { "VVLVGGTLoader": VVLVGGTLoader, - "VGGTMultiInputNode": VGGTMultiInputNode, + "VGGTMultiInputNode": VGGTMultiInputNode, + "GLBPointCloudProcessor": GLBPointCloudProcessor, } # 节点显示名称映射 NODE_DISPLAY_NAME_MAPPINGS = { - "VVLVGGTLoader": "VGGT 模型加载器", - "VGGTMultiInputNode": "VGGT 多输入重建", + "VVLVGGTLoader": "VVL VGGT 模型加载器", + "VGGTMultiInputNode": "VVL VGGT 多输入重建", + "GLBPointCloudProcessor": "VVL GLB 点云黑色点清理", } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/comfyui_vggt_nodes.py b/comfyui_vggt_nodes.py index 255daed..729ec47 100644 --- a/comfyui_vggt_nodes.py +++ b/comfyui_vggt_nodes.py @@ -2097,4 +2097,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { # 如果模型加载器可用,添加到映射中 if MODEL_LOADER_AVAILABLE: NODE_CLASS_MAPPINGS["VVLVGGTLoader"] = VVLVGGTLoader - NODE_DISPLAY_NAME_MAPPINGS["VVLVGGTLoader"] = "VVL VGGT Model Loader" \ No newline at end of file + NODE_DISPLAY_NAME_MAPPINGS["VVLVGGTLoader"] = "VVL VGGT Model Loader" + +# 注意:GLBPointCloudProcessor现在在独立文件中自行注册 +# 无需在此文件中导入,ComfyUI会自动发现glb_point_cloud_processor.py \ No newline at end of file diff --git a/glb_point_cloud_processor.py b/glb_point_cloud_processor.py new file mode 100755 index 0000000..7e9ee58 --- /dev/null +++ b/glb_point_cloud_processor.py @@ -0,0 +1,336 @@ +import os +import json +import tempfile +import logging +import struct +from typing import List, Any, Dict, Tuple + +import numpy as np +import torch + +# 导入ComfyUI的路径管理 +try: + import folder_paths + FOLDER_PATHS_AVAILABLE = True +except ImportError: + folder_paths = None + FOLDER_PATHS_AVAILABLE = False + +# 导入trimesh用于GLB文件处理 +try: + import trimesh + TRIMESH_AVAILABLE = True +except ImportError: + TRIMESH_AVAILABLE = False + trimesh = None + +# 配置日志 +logger = logging.getLogger('glb_point_cloud_processor') + +class GLBPointCloudProcessor: + """GLB点云文件处理器 - 专门用于删除黑色点和暗色点优化""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "glb_file_path": ("STRING", { + "default": "", + "tooltip": "GLB点云文件路径:输入需要处理的GLB格式点云文件路径。支持绝对路径(如C:/path/to/file.glb)或相对于ComfyUI output目录的相对路径(如pointcloud.glb)。GLB文件应包含点云数据,支持带颜色信息的点云处理" + }), + }, + "optional": { + "black_threshold": ("INT", { + "default": 30, "min": 0, "max": 255, "step": 1, + "tooltip": "黑色阈值控制:RGB三色总和小于此值的点将被删除。0=仅删除纯黑点(0,0,0);30=删除深黑色点;60=删除较暗点;100=删除中等暗度点;255=删除所有点。调大=删除更多暗色点,点云更亮净;调小=保留更多暗色细节,但可能有噪点" + }), + "output_filename": ("STRING", { + "default": "clean_pointcloud.glb", + "tooltip": "输出GLB文件名:处理后点云的保存文件名,自动添加.glb扩展名。文件将保存到ComfyUI的output目录中。建议使用描述性名称如'clean_pointcloud.glb'、'filtered_scan.glb'等便于识别" + }), + } + } + + RETURN_TYPES = ( + "STRING", # 处理后的GLB文件路径 + "STRING", # 处理统计信息(JSON格式) + "STRING", # 处理日志信息 + ) + RETURN_NAMES = ( + "processed_glb_path", + "processing_stats", + "processing_log", + ) + OUTPUT_TOOLTIPS = [ + "处理后的GLB文件完整路径", + "处理统计信息(JSON格式)- 包含删除的点数、剩余点数等", + "详细的处理日志信息" + ] + OUTPUT_NODE = True + FUNCTION = "process_point_cloud" + CATEGORY = "💃VVL/Point Cloud Cleaning" + + def process_point_cloud(self, + glb_file_path: str, + black_threshold: int = 30, + output_filename: str = "clean_pointcloud.glb"): + """ + 处理GLB点云文件,删除黑色点和暗色点 + """ + + processing_log = [] + processing_log.append("开始GLB点云黑色点清理...") + + # 检查依赖 + if not TRIMESH_AVAILABLE: + error_msg = "trimesh库不可用,无法处理GLB文件" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + # 验证输入文件路径 + if not glb_file_path or not glb_file_path.strip(): + error_msg = "GLB文件路径为空" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + # 处理文件路径 + input_path = self._resolve_file_path(glb_file_path.strip()) + processing_log.append(f"输入文件路径: {input_path}") + + if not os.path.exists(input_path): + error_msg = f"GLB文件不存在: {input_path}" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + try: + # 加载GLB文件 + processing_log.append("正在加载GLB文件...") + scene = trimesh.load(input_path) + + # 提取点云数据 + point_clouds = [] + other_geometries = [] + + if isinstance(scene, trimesh.Scene): + for name, geometry in scene.geometry.items(): + if isinstance(geometry, trimesh.PointCloud): + point_clouds.append((name, geometry)) + processing_log.append(f"发现点云: {name}, 点数: {len(geometry.vertices)}") + else: + other_geometries.append((name, geometry)) + processing_log.append(f"发现其他几何体: {name}, 类型: {type(geometry).__name__}") + elif isinstance(scene, trimesh.PointCloud): + point_clouds.append(("main_pointcloud", scene)) + processing_log.append(f"发现点云: main_pointcloud, 点数: {len(scene.vertices)}") + else: + # 尝试转换为点云 + if hasattr(scene, 'vertices'): + pc = trimesh.PointCloud(vertices=scene.vertices, colors=getattr(scene.visual, 'vertex_colors', None)) + point_clouds.append(("converted_pointcloud", pc)) + processing_log.append(f"转换为点云: converted_pointcloud, 点数: {len(pc.vertices)}") + else: + error_msg = "GLB文件中未找到点云数据" + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + if not point_clouds: + error_msg = "GLB文件中没有点云数据" + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + # 处理每个点云 + processed_point_clouds = [] + total_original_points = 0 + total_removed_points = 0 + + for name, point_cloud in point_clouds: + processing_log.append(f"\n处理点云: {name}") + original_count = len(point_cloud.vertices) + total_original_points += original_count + + # 获取顶点和颜色 + vertices = point_cloud.vertices.copy() + colors = None + + if hasattr(point_cloud.visual, 'vertex_colors') and point_cloud.visual.vertex_colors is not None: + colors = point_cloud.visual.vertex_colors.copy() + elif hasattr(point_cloud, 'colors') and point_cloud.colors is not None: + colors = point_cloud.colors.copy() + + # 初始化掩码(所有点都保留) + keep_mask = np.ones(len(vertices), dtype=bool) + + # 删除暗色点(默认启用) + if colors is not None: + removed_count = self._remove_dark_points(vertices, colors, keep_mask, black_threshold, processing_log) + processing_log.append(f"删除黑色点: {removed_count} 个") + else: + processing_log.append("跳过黑色点过滤: 点云无颜色信息") + + # 应用掩码 + filtered_vertices = vertices[keep_mask] + filtered_colors = colors[keep_mask] if colors is not None else None + + remaining_count = len(filtered_vertices) + removed_count = original_count - remaining_count + total_removed_points += removed_count + + processing_log.append(f"点云 {name}: 原始 {original_count} -> 剩余 {remaining_count} (删除 {removed_count})") + + # 创建处理后的点云 + if remaining_count > 0: + processed_pc = trimesh.PointCloud(vertices=filtered_vertices, colors=filtered_colors) + processed_point_clouds.append((name, processed_pc)) + else: + processing_log.append(f"警告: 点云 {name} 处理后没有剩余点") + + # 创建新的场景 + if processed_point_clouds: + new_scene = trimesh.Scene() + + # 添加处理后的点云 + for name, pc in processed_point_clouds: + new_scene.add_geometry(pc, node_name=name) + + # 添加其他几何体(如相机模型) + for name, geometry in other_geometries: + new_scene.add_geometry(geometry, node_name=name) + processing_log.append(f"保留几何体: {name}") + + # 生成输出路径 + output_path = self._generate_output_path(output_filename) + processing_log.append(f"输出文件路径: {output_path}") + + # 保存处理后的GLB文件 + processing_log.append("正在保存处理后的GLB文件...") + new_scene.export(output_path) + + # 验证文件是否保存成功 + if os.path.exists(output_path): + file_size = os.path.getsize(output_path) + processing_log.append(f"GLB文件保存成功,文件大小: {file_size} bytes") + + # 生成统计信息 + stats = { + "original_points": total_original_points, + "remaining_points": total_original_points - total_removed_points, + "removed_points": total_removed_points, + "removal_percentage": (total_removed_points / total_original_points * 100) if total_original_points > 0 else 0, + "point_clouds_processed": len(point_clouds), + "other_geometries_preserved": len(other_geometries), + "output_file_size": file_size, + "settings": { + "black_threshold": black_threshold, + "remove_dark_points": True + } + } + + processing_log.append("GLB点云黑色点清理完成!") + return output_path, json.dumps(stats, indent=2), "\n".join(processing_log) + else: + error_msg = "GLB文件保存失败" + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + else: + error_msg = "处理后没有剩余的点云数据" + processing_log.append(f"错误: {error_msg}") + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + except Exception as e: + error_msg = f"处理GLB文件时发生错误: {str(e)}" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + import traceback + traceback.print_exc() + return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + + def _resolve_file_path(self, file_path: str) -> str: + """解析文件路径,支持绝对路径和相对路径""" + if os.path.isabs(file_path): + return file_path + + # 尝试相对于ComfyUI输出目录 + if FOLDER_PATHS_AVAILABLE: + output_dir = folder_paths.get_output_directory() + candidate_path = os.path.join(output_dir, file_path) + if os.path.exists(candidate_path): + return candidate_path + + # 尝试相对于当前工作目录 + if os.path.exists(file_path): + return os.path.abspath(file_path) + + # 返回原始路径(让后续检查处理错误) + return file_path + + def _generate_output_path(self, filename: str) -> str: + """生成输出文件路径""" + if FOLDER_PATHS_AVAILABLE: + output_dir = folder_paths.get_output_directory() + else: + output_dir = "output" + + # 确保输出目录存在 + os.makedirs(output_dir, exist_ok=True) + + # 处理文件名,确保是.glb扩展名 + if not filename.lower().endswith('.glb'): + filename = f"{filename}.glb" + + return os.path.join(output_dir, filename) + + def _remove_dark_points(self, vertices: np.ndarray, colors: np.ndarray, keep_mask: np.ndarray, + black_threshold: int, processing_log: List[str]) -> int: + """删除暗色点""" + removed_count = 0 + + # 确保颜色数据格式正确 + if colors.shape[1] == 4: # RGBA + rgb_colors = colors[:, :3] + else: # RGB + rgb_colors = colors + + # 如果颜色值在0-1范围内,转换为0-255 + if rgb_colors.max() <= 1.0: + rgb_colors = (rgb_colors * 255).astype(np.uint8) + else: + rgb_colors = rgb_colors.astype(np.uint8) + + # 计算RGB总和 + rgb_sum = rgb_colors.sum(axis=1) + + + + # 应用黑色阈值过滤 + if black_threshold > 0: + dark_mask = rgb_sum >= black_threshold + before_count = keep_mask.sum() + keep_mask &= dark_mask + after_count = keep_mask.sum() + removed_by_black = before_count - after_count + removed_count += removed_by_black + processing_log.append(f" 按RGB总和阈值({black_threshold})删除: {removed_by_black} 个点") + + return removed_count + + + + +# ----------------------------------------------------------------------------- +# 节点注册 - 独立注册,ComfyUI会自动发现此文件 +# ----------------------------------------------------------------------------- + +NODE_CLASS_MAPPINGS = { + "GLBPointCloudProcessor": GLBPointCloudProcessor, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "GLBPointCloudProcessor": "VVL GLB Point Cloud Processor", +} + +# # 添加节点信息,帮助ComfyUI更好地识别 +# WEB_DIRECTORY = "./web" +# __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file