Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
|
@@ -682,6 +682,7 @@ def run_model(
|
|
| 682 |
filter_white_bg=False,
|
| 683 |
enable_segmentation=False,
|
| 684 |
text_prompt=DEFAULT_TEXT_PROMPT,
|
|
|
|
| 685 |
):
|
| 686 |
"""
|
| 687 |
Run the MapAnything model + optional segmentation
|
|
@@ -689,12 +690,14 @@ def run_model(
|
|
| 689 |
global model
|
| 690 |
import torch
|
| 691 |
|
|
|
|
| 692 |
print(f"Processing images from {target_dir}")
|
| 693 |
|
| 694 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 695 |
device = torch.device(device)
|
| 696 |
|
| 697 |
# Initialize MapAnything model
|
|
|
|
| 698 |
if model is None:
|
| 699 |
model = initialize_mapanything_model(high_level_config, device)
|
| 700 |
else:
|
|
@@ -704,10 +707,12 @@ def run_model(
|
|
| 704 |
|
| 705 |
# Load segmentation models if enabled
|
| 706 |
if enable_segmentation:
|
|
|
|
| 707 |
load_grounding_dino_model(device)
|
| 708 |
load_sam_model(device)
|
| 709 |
|
| 710 |
# Load images
|
|
|
|
| 711 |
print("Loading images...")
|
| 712 |
image_folder_path = os.path.join(target_dir, "images")
|
| 713 |
views = load_images(image_folder_path)
|
|
@@ -717,12 +722,14 @@ def run_model(
|
|
| 717 |
raise ValueError("No images found. Check your upload.")
|
| 718 |
|
| 719 |
# Run model inference
|
|
|
|
| 720 |
print("Running inference...")
|
| 721 |
outputs = model.infer(
|
| 722 |
views, apply_mask=apply_mask, mask_edges=True, memory_efficient_inference=False
|
| 723 |
)
|
| 724 |
|
| 725 |
# Convert predictions
|
|
|
|
| 726 |
predictions = {}
|
| 727 |
extrinsic_list = []
|
| 728 |
intrinsic_list = []
|
|
@@ -768,6 +775,7 @@ def run_model(
|
|
| 768 |
predictions["final_mask"] = np.stack(final_mask_list, axis=0)
|
| 769 |
|
| 770 |
# Process visualization data
|
|
|
|
| 771 |
processed_data = process_predictions_for_visualization(
|
| 772 |
predictions, views, high_level_config, filter_black_bg, filter_white_bg
|
| 773 |
)
|
|
@@ -775,6 +783,7 @@ def run_model(
|
|
| 775 |
# Segmentation processing
|
| 776 |
segmented_glb = None
|
| 777 |
if enable_segmentation and grounding_dino_model is not None:
|
|
|
|
| 778 |
print("\n🎯 Starting segmentation...")
|
| 779 |
print(f"🔍 Detection prompt: {text_prompt[:100]}...")
|
| 780 |
|
|
@@ -782,6 +791,8 @@ def run_model(
|
|
| 782 |
all_view_masks = []
|
| 783 |
|
| 784 |
for view_idx, ref_image in enumerate(images_list):
|
|
|
|
|
|
|
| 785 |
print(f"\n📸 Processing view {view_idx + 1}/{len(images_list)}...")
|
| 786 |
|
| 787 |
if ref_image.dtype != np.uint8:
|
|
@@ -810,17 +821,21 @@ def run_model(
|
|
| 810 |
|
| 811 |
# Match objects across views
|
| 812 |
if any(len(dets) > 0 for dets in all_view_detections):
|
|
|
|
| 813 |
object_id_map, unique_objects = match_objects_across_views(all_view_detections)
|
| 814 |
|
| 815 |
# Generate segmented mesh
|
|
|
|
| 816 |
segmented_glb = create_multi_view_segmented_mesh(
|
| 817 |
processed_data, all_view_detections, all_view_masks,
|
| 818 |
object_id_map, unique_objects, target_dir
|
| 819 |
)
|
| 820 |
|
| 821 |
# Cleanup
|
|
|
|
| 822 |
torch.cuda.empty_cache()
|
| 823 |
|
|
|
|
| 824 |
return predictions, processed_data, segmented_glb
|
| 825 |
|
| 826 |
|
|
@@ -1079,17 +1094,17 @@ def gradio_demo(
|
|
| 1079 |
show_cam=True,
|
| 1080 |
filter_black_bg=False,
|
| 1081 |
filter_white_bg=False,
|
| 1082 |
-
conf_thres=3.0,
|
| 1083 |
apply_mask=True,
|
| 1084 |
show_mesh=True,
|
| 1085 |
enable_segmentation=False,
|
| 1086 |
text_prompt=DEFAULT_TEXT_PROMPT,
|
| 1087 |
-
|
| 1088 |
):
|
| 1089 |
"""执行重建"""
|
| 1090 |
if not os.path.isdir(target_dir) or target_dir == "None":
|
| 1091 |
return None, None, "❌ 未找到有效的目标目录,请先上传文件", None, None, None, None, None, None, None, None, None
|
| 1092 |
|
|
|
|
| 1093 |
start_time = time.time()
|
| 1094 |
gc.collect()
|
| 1095 |
torch.cuda.empty_cache()
|
|
@@ -1103,14 +1118,16 @@ def gradio_demo(
|
|
| 1103 |
all_files = [f"{i}: {filename}" for i, filename in enumerate(all_files)]
|
| 1104 |
frame_filter_choices = ["All"] + all_files
|
| 1105 |
|
|
|
|
| 1106 |
print("运行 MapAnything 模型...")
|
| 1107 |
with torch.no_grad():
|
| 1108 |
predictions, processed_data, segmented_glb = run_model(
|
| 1109 |
target_dir, apply_mask, True, filter_black_bg, filter_white_bg,
|
| 1110 |
-
enable_segmentation, text_prompt
|
| 1111 |
)
|
| 1112 |
|
| 1113 |
# 保存预测结果
|
|
|
|
| 1114 |
prediction_save_path = os.path.join(target_dir, "predictions.npz")
|
| 1115 |
np.savez(prediction_save_path, **predictions)
|
| 1116 |
|
|
@@ -1118,6 +1135,7 @@ def gradio_demo(
|
|
| 1118 |
frame_filter = "All"
|
| 1119 |
|
| 1120 |
# 生成 GLB 文件名
|
|
|
|
| 1121 |
glbfile = os.path.join(
|
| 1122 |
target_dir,
|
| 1123 |
f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}.glb",
|
|
@@ -1131,20 +1149,21 @@ def gradio_demo(
|
|
| 1131 |
mask_black_bg=filter_black_bg,
|
| 1132 |
mask_white_bg=filter_white_bg,
|
| 1133 |
as_mesh=show_mesh,
|
| 1134 |
-
conf_percentile=conf_thres,
|
| 1135 |
)
|
| 1136 |
glbscene.export(file_obj=glbfile)
|
| 1137 |
|
| 1138 |
# 清理内存
|
|
|
|
| 1139 |
del predictions
|
| 1140 |
gc.collect()
|
| 1141 |
torch.cuda.empty_cache()
|
| 1142 |
|
| 1143 |
end_time = time.time()
|
| 1144 |
print(f"总耗时: {end_time - start_time:.2f}秒")
|
| 1145 |
-
log_msg = f"✅ 重建成功 ({len(all_files)} 帧)"
|
| 1146 |
|
| 1147 |
# Populate visualization tabs
|
|
|
|
| 1148 |
depth_vis, normal_vis, measure_img, measure_pts = populate_visualization_tabs(
|
| 1149 |
processed_data
|
| 1150 |
)
|
|
@@ -1153,6 +1172,8 @@ def gradio_demo(
|
|
| 1153 |
depth_selector, normal_selector, measure_selector = update_view_selectors(
|
| 1154 |
processed_data
|
| 1155 |
)
|
|
|
|
|
|
|
| 1156 |
|
| 1157 |
return (
|
| 1158 |
glbfile,
|
|
@@ -1428,7 +1449,6 @@ def update_visualization(
|
|
| 1428 |
frame_filter,
|
| 1429 |
show_cam,
|
| 1430 |
is_example,
|
| 1431 |
-
conf_thres=None,
|
| 1432 |
filter_black_bg=False,
|
| 1433 |
filter_white_bg=False,
|
| 1434 |
show_mesh=True,
|
|
@@ -1459,7 +1479,6 @@ def update_visualization(
|
|
| 1459 |
mask_black_bg=filter_black_bg,
|
| 1460 |
mask_white_bg=filter_white_bg,
|
| 1461 |
as_mesh=show_mesh,
|
| 1462 |
-
conf_percentile=conf_thres,
|
| 1463 |
)
|
| 1464 |
glbscene.export(file_obj=glbfile)
|
| 1465 |
|
|
@@ -1578,7 +1597,7 @@ def load_example_scene(scene_name, examples_dir="examples"):
|
|
| 1578 |
break
|
| 1579 |
|
| 1580 |
if selected_scene is None:
|
| 1581 |
-
return None, None, None,
|
| 1582 |
|
| 1583 |
file_objects = []
|
| 1584 |
for image_path in selected_scene["image_files"]:
|
|
@@ -1587,7 +1606,6 @@ def load_example_scene(scene_name, examples_dir="examples"):
|
|
| 1587 |
target_dir, image_paths = handle_uploads(file_objects, 1.0)
|
| 1588 |
|
| 1589 |
return (
|
| 1590 |
-
None,
|
| 1591 |
None,
|
| 1592 |
target_dir,
|
| 1593 |
image_paths,
|
|
@@ -1656,35 +1674,45 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
|
|
| 1656 |
with gr.Column(scale=1, min_width=300):
|
| 1657 |
gr.Markdown("### 📤 输入")
|
| 1658 |
|
| 1659 |
-
|
| 1660 |
-
|
| 1661 |
-
|
| 1662 |
-
|
| 1663 |
-
|
| 1664 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
| 1676 |
-
|
| 1677 |
-
|
|
|
|
|
|
|
| 1678 |
|
| 1679 |
image_gallery = gr.Gallery(
|
| 1680 |
label="图片预览", columns=3, height=350,
|
| 1681 |
show_download_button=True, object_fit="contain", preview=True
|
| 1682 |
)
|
| 1683 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1684 |
with gr.Row():
|
| 1685 |
submit_btn = gr.Button("🚀 开始重建", variant="primary", scale=2)
|
| 1686 |
clear_btn = gr.ClearButton(
|
| 1687 |
-
[
|
| 1688 |
value="🗑️ 清空", scale=1
|
| 1689 |
)
|
| 1690 |
|
|
@@ -1755,18 +1783,14 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
|
|
| 1755 |
max_lines=1
|
| 1756 |
)
|
| 1757 |
|
| 1758 |
-
# 高级选项(
|
| 1759 |
-
with gr.Accordion("⚙️ 高级选项", open=
|
| 1760 |
with gr.Row(equal_height=False):
|
| 1761 |
with gr.Column(scale=1, min_width=300):
|
| 1762 |
gr.Markdown("#### 可视化参数")
|
| 1763 |
frame_filter = gr.Dropdown(
|
| 1764 |
choices=["All"], value="All", label="显示帧"
|
| 1765 |
)
|
| 1766 |
-
conf_thres = gr.Slider(
|
| 1767 |
-
minimum=0, maximum=100, value=0, step=0.1,
|
| 1768 |
-
label="置信度阈值(百分位)"
|
| 1769 |
-
)
|
| 1770 |
show_cam = gr.Checkbox(label="显示相机", value=True)
|
| 1771 |
show_mesh = gr.Checkbox(label="显示网格", value=True)
|
| 1772 |
filter_black_bg = gr.Checkbox(label="过滤黑色背景", value=False)
|
|
@@ -1821,7 +1845,7 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
|
|
| 1821 |
scene_img.select(
|
| 1822 |
fn=lambda name=scene["name"]: load_example_scene(name),
|
| 1823 |
outputs=[
|
| 1824 |
-
reconstruction_output,
|
| 1825 |
target_dir_output, image_gallery, log_output
|
| 1826 |
]
|
| 1827 |
)
|
|
@@ -1839,32 +1863,110 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
|
|
| 1839 |
)
|
| 1840 |
|
| 1841 |
# 上传文件自动更新
|
| 1842 |
-
def update_gallery_on_unified_upload(
|
| 1843 |
-
if not
|
| 1844 |
-
return None, None, None
|
| 1845 |
-
|
| 1846 |
-
all_files = []
|
| 1847 |
-
if files_video:
|
| 1848 |
-
all_files.append(files_video)
|
| 1849 |
-
if files_images:
|
| 1850 |
-
all_files.extend(files_images)
|
| 1851 |
-
target_dir, image_paths = handle_uploads(all_files, interval)
|
| 1852 |
return (
|
| 1853 |
-
None,
|
| 1854 |
target_dir,
|
| 1855 |
image_paths,
|
| 1856 |
"✅ 上传完成,点击「开始重建」进行 3D 处理",
|
| 1857 |
)
|
| 1858 |
|
| 1859 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1860 |
fn=update_gallery_on_unified_upload,
|
| 1861 |
-
inputs=[
|
| 1862 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1863 |
)
|
| 1864 |
-
|
| 1865 |
-
|
| 1866 |
-
|
| 1867 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1868 |
)
|
| 1869 |
|
| 1870 |
# 重建按钮
|
|
@@ -1878,7 +1980,7 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
|
|
| 1878 |
fn=gradio_demo,
|
| 1879 |
inputs=[
|
| 1880 |
target_dir_output, frame_filter, show_cam,
|
| 1881 |
-
filter_black_bg, filter_white_bg,
|
| 1882 |
apply_mask_checkbox, show_mesh,
|
| 1883 |
enable_segmentation, text_prompt
|
| 1884 |
],
|
|
@@ -1896,12 +1998,12 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
|
|
| 1896 |
clear_btn.add([reconstruction_output, segmented_output, log_output])
|
| 1897 |
|
| 1898 |
# 可视化参数实时更新
|
| 1899 |
-
for component in [frame_filter, show_cam,
|
| 1900 |
component.change(
|
| 1901 |
fn=update_visualization,
|
| 1902 |
inputs=[
|
| 1903 |
target_dir_output, frame_filter, show_cam, is_example,
|
| 1904 |
-
|
| 1905 |
],
|
| 1906 |
outputs=[reconstruction_output, log_output]
|
| 1907 |
)
|
|
|
|
| 682 |
filter_white_bg=False,
|
| 683 |
enable_segmentation=False,
|
| 684 |
text_prompt=DEFAULT_TEXT_PROMPT,
|
| 685 |
+
progress=gr.Progress(),
|
| 686 |
):
|
| 687 |
"""
|
| 688 |
Run the MapAnything model + optional segmentation
|
|
|
|
| 690 |
global model
|
| 691 |
import torch
|
| 692 |
|
| 693 |
+
progress(0, desc="🔧 初始化设备...")
|
| 694 |
print(f"Processing images from {target_dir}")
|
| 695 |
|
| 696 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 697 |
device = torch.device(device)
|
| 698 |
|
| 699 |
# Initialize MapAnything model
|
| 700 |
+
progress(0.05, desc="📥 加载 MapAnything 模型...")
|
| 701 |
if model is None:
|
| 702 |
model = initialize_mapanything_model(high_level_config, device)
|
| 703 |
else:
|
|
|
|
| 707 |
|
| 708 |
# Load segmentation models if enabled
|
| 709 |
if enable_segmentation:
|
| 710 |
+
progress(0.1, desc="🎯 加载分割模型...")
|
| 711 |
load_grounding_dino_model(device)
|
| 712 |
load_sam_model(device)
|
| 713 |
|
| 714 |
# Load images
|
| 715 |
+
progress(0.15, desc="📷 加载图片...")
|
| 716 |
print("Loading images...")
|
| 717 |
image_folder_path = os.path.join(target_dir, "images")
|
| 718 |
views = load_images(image_folder_path)
|
|
|
|
| 722 |
raise ValueError("No images found. Check your upload.")
|
| 723 |
|
| 724 |
# Run model inference
|
| 725 |
+
progress(0.2, desc=f"🚀 运行 3D 重建 ({len(views)} 张图片)...")
|
| 726 |
print("Running inference...")
|
| 727 |
outputs = model.infer(
|
| 728 |
views, apply_mask=apply_mask, mask_edges=True, memory_efficient_inference=False
|
| 729 |
)
|
| 730 |
|
| 731 |
# Convert predictions
|
| 732 |
+
progress(0.5, desc="🔄 处理预测结果...")
|
| 733 |
predictions = {}
|
| 734 |
extrinsic_list = []
|
| 735 |
intrinsic_list = []
|
|
|
|
| 775 |
predictions["final_mask"] = np.stack(final_mask_list, axis=0)
|
| 776 |
|
| 777 |
# Process visualization data
|
| 778 |
+
progress(0.6, desc="🎨 准备可视化数据...")
|
| 779 |
processed_data = process_predictions_for_visualization(
|
| 780 |
predictions, views, high_level_config, filter_black_bg, filter_white_bg
|
| 781 |
)
|
|
|
|
| 783 |
# Segmentation processing
|
| 784 |
segmented_glb = None
|
| 785 |
if enable_segmentation and grounding_dino_model is not None:
|
| 786 |
+
progress(0.65, desc="🎯 开始物体分割...")
|
| 787 |
print("\n🎯 Starting segmentation...")
|
| 788 |
print(f"🔍 Detection prompt: {text_prompt[:100]}...")
|
| 789 |
|
|
|
|
| 791 |
all_view_masks = []
|
| 792 |
|
| 793 |
for view_idx, ref_image in enumerate(images_list):
|
| 794 |
+
progress(0.65 + (view_idx / len(images_list)) * 0.2,
|
| 795 |
+
desc=f"🔍 检测视图 {view_idx + 1}/{len(images_list)}...")
|
| 796 |
print(f"\n📸 Processing view {view_idx + 1}/{len(images_list)}...")
|
| 797 |
|
| 798 |
if ref_image.dtype != np.uint8:
|
|
|
|
| 821 |
|
| 822 |
# Match objects across views
|
| 823 |
if any(len(dets) > 0 for dets in all_view_detections):
|
| 824 |
+
progress(0.85, desc="🔗 匹配跨视图物体...")
|
| 825 |
object_id_map, unique_objects = match_objects_across_views(all_view_detections)
|
| 826 |
|
| 827 |
# Generate segmented mesh
|
| 828 |
+
progress(0.9, desc="🏗️ 生成分割3D模型...")
|
| 829 |
segmented_glb = create_multi_view_segmented_mesh(
|
| 830 |
processed_data, all_view_detections, all_view_masks,
|
| 831 |
object_id_map, unique_objects, target_dir
|
| 832 |
)
|
| 833 |
|
| 834 |
# Cleanup
|
| 835 |
+
progress(0.95, desc="🧹 清理内存...")
|
| 836 |
torch.cuda.empty_cache()
|
| 837 |
|
| 838 |
+
progress(1.0, desc="✅ 完成!")
|
| 839 |
return predictions, processed_data, segmented_glb
|
| 840 |
|
| 841 |
|
|
|
|
| 1094 |
show_cam=True,
|
| 1095 |
filter_black_bg=False,
|
| 1096 |
filter_white_bg=False,
|
|
|
|
| 1097 |
apply_mask=True,
|
| 1098 |
show_mesh=True,
|
| 1099 |
enable_segmentation=False,
|
| 1100 |
text_prompt=DEFAULT_TEXT_PROMPT,
|
| 1101 |
+
progress=gr.Progress(),
|
| 1102 |
):
|
| 1103 |
"""执行重建"""
|
| 1104 |
if not os.path.isdir(target_dir) or target_dir == "None":
|
| 1105 |
return None, None, "❌ 未找到有效的目标目录,请先上传文件", None, None, None, None, None, None, None, None, None
|
| 1106 |
|
| 1107 |
+
progress(0, desc="🔄 准备重建...")
|
| 1108 |
start_time = time.time()
|
| 1109 |
gc.collect()
|
| 1110 |
torch.cuda.empty_cache()
|
|
|
|
| 1118 |
all_files = [f"{i}: {filename}" for i, filename in enumerate(all_files)]
|
| 1119 |
frame_filter_choices = ["All"] + all_files
|
| 1120 |
|
| 1121 |
+
progress(0.05, desc="🚀 运行 MapAnything 模型...")
|
| 1122 |
print("运行 MapAnything 模型...")
|
| 1123 |
with torch.no_grad():
|
| 1124 |
predictions, processed_data, segmented_glb = run_model(
|
| 1125 |
target_dir, apply_mask, True, filter_black_bg, filter_white_bg,
|
| 1126 |
+
enable_segmentation, text_prompt, progress
|
| 1127 |
)
|
| 1128 |
|
| 1129 |
# 保存预测结果
|
| 1130 |
+
progress(0.92, desc="💾 保存预测结果...")
|
| 1131 |
prediction_save_path = os.path.join(target_dir, "predictions.npz")
|
| 1132 |
np.savez(prediction_save_path, **predictions)
|
| 1133 |
|
|
|
|
| 1135 |
frame_filter = "All"
|
| 1136 |
|
| 1137 |
# 生成 GLB 文件名
|
| 1138 |
+
progress(0.93, desc="🏗️ 生成原始3D模型...")
|
| 1139 |
glbfile = os.path.join(
|
| 1140 |
target_dir,
|
| 1141 |
f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}.glb",
|
|
|
|
| 1149 |
mask_black_bg=filter_black_bg,
|
| 1150 |
mask_white_bg=filter_white_bg,
|
| 1151 |
as_mesh=show_mesh,
|
|
|
|
| 1152 |
)
|
| 1153 |
glbscene.export(file_obj=glbfile)
|
| 1154 |
|
| 1155 |
# 清理内存
|
| 1156 |
+
progress(0.96, desc="🧹 清理内存...")
|
| 1157 |
del predictions
|
| 1158 |
gc.collect()
|
| 1159 |
torch.cuda.empty_cache()
|
| 1160 |
|
| 1161 |
end_time = time.time()
|
| 1162 |
print(f"总耗时: {end_time - start_time:.2f}秒")
|
| 1163 |
+
log_msg = f"✅ 重建成功 ({len(all_files)} 帧,耗时 {end_time - start_time:.1f}秒)"
|
| 1164 |
|
| 1165 |
# Populate visualization tabs
|
| 1166 |
+
progress(0.98, desc="🎨 生成可视化...")
|
| 1167 |
depth_vis, normal_vis, measure_img, measure_pts = populate_visualization_tabs(
|
| 1168 |
processed_data
|
| 1169 |
)
|
|
|
|
| 1172 |
depth_selector, normal_selector, measure_selector = update_view_selectors(
|
| 1173 |
processed_data
|
| 1174 |
)
|
| 1175 |
+
|
| 1176 |
+
progress(1.0, desc="✅ 全部完成!")
|
| 1177 |
|
| 1178 |
return (
|
| 1179 |
glbfile,
|
|
|
|
| 1449 |
frame_filter,
|
| 1450 |
show_cam,
|
| 1451 |
is_example,
|
|
|
|
| 1452 |
filter_black_bg=False,
|
| 1453 |
filter_white_bg=False,
|
| 1454 |
show_mesh=True,
|
|
|
|
| 1479 |
mask_black_bg=filter_black_bg,
|
| 1480 |
mask_white_bg=filter_white_bg,
|
| 1481 |
as_mesh=show_mesh,
|
|
|
|
| 1482 |
)
|
| 1483 |
glbscene.export(file_obj=glbfile)
|
| 1484 |
|
|
|
|
| 1597 |
break
|
| 1598 |
|
| 1599 |
if selected_scene is None:
|
| 1600 |
+
return None, None, None, "❌ 场景未找到"
|
| 1601 |
|
| 1602 |
file_objects = []
|
| 1603 |
for image_path in selected_scene["image_files"]:
|
|
|
|
| 1606 |
target_dir, image_paths = handle_uploads(file_objects, 1.0)
|
| 1607 |
|
| 1608 |
return (
|
|
|
|
| 1609 |
None,
|
| 1610 |
target_dir,
|
| 1611 |
image_paths,
|
|
|
|
| 1674 |
with gr.Column(scale=1, min_width=300):
|
| 1675 |
gr.Markdown("### 📤 输入")
|
| 1676 |
|
| 1677 |
+
unified_upload = gr.File(
|
| 1678 |
+
file_count="multiple",
|
| 1679 |
+
label="上传视频或图片",
|
| 1680 |
+
interactive=True,
|
| 1681 |
+
file_types=["image", "video"],
|
| 1682 |
+
)
|
| 1683 |
+
|
| 1684 |
+
with gr.Row():
|
| 1685 |
+
s_time_interval = gr.Slider(
|
| 1686 |
+
minimum=0.1, maximum=5.0, value=1.0, step=0.1,
|
| 1687 |
+
label="视频采样时间间隔(每x秒取一帧)",
|
| 1688 |
+
interactive=True,
|
| 1689 |
+
visible=True,
|
| 1690 |
+
scale=3,
|
| 1691 |
+
)
|
| 1692 |
+
resample_btn = gr.Button(
|
| 1693 |
+
"重新采样视频",
|
| 1694 |
+
visible=False,
|
| 1695 |
+
variant="secondary",
|
| 1696 |
+
scale=1,
|
| 1697 |
+
)
|
| 1698 |
|
| 1699 |
image_gallery = gr.Gallery(
|
| 1700 |
label="图片预览", columns=3, height=350,
|
| 1701 |
show_download_button=True, object_fit="contain", preview=True
|
| 1702 |
)
|
| 1703 |
|
| 1704 |
+
|
| 1705 |
+
clear_uploads_btn = gr.ClearButton(
|
| 1706 |
+
[unified_upload, image_gallery],
|
| 1707 |
+
value="清空上传",
|
| 1708 |
+
variant="secondary",
|
| 1709 |
+
size="sm",
|
| 1710 |
+
)
|
| 1711 |
+
|
| 1712 |
with gr.Row():
|
| 1713 |
submit_btn = gr.Button("🚀 开始重建", variant="primary", scale=2)
|
| 1714 |
clear_btn = gr.ClearButton(
|
| 1715 |
+
[unified_upload, target_dir_output, image_gallery],
|
| 1716 |
value="🗑️ 清空", scale=1
|
| 1717 |
)
|
| 1718 |
|
|
|
|
| 1783 |
max_lines=1
|
| 1784 |
)
|
| 1785 |
|
| 1786 |
+
# 高级选项(默认打开)
|
| 1787 |
+
with gr.Accordion("⚙️ 高级选项", open=True):
|
| 1788 |
with gr.Row(equal_height=False):
|
| 1789 |
with gr.Column(scale=1, min_width=300):
|
| 1790 |
gr.Markdown("#### 可视化参数")
|
| 1791 |
frame_filter = gr.Dropdown(
|
| 1792 |
choices=["All"], value="All", label="显示帧"
|
| 1793 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1794 |
show_cam = gr.Checkbox(label="显示相机", value=True)
|
| 1795 |
show_mesh = gr.Checkbox(label="显示网格", value=True)
|
| 1796 |
filter_black_bg = gr.Checkbox(label="过滤黑色背景", value=False)
|
|
|
|
| 1845 |
scene_img.select(
|
| 1846 |
fn=lambda name=scene["name"]: load_example_scene(name),
|
| 1847 |
outputs=[
|
| 1848 |
+
reconstruction_output,
|
| 1849 |
target_dir_output, image_gallery, log_output
|
| 1850 |
]
|
| 1851 |
)
|
|
|
|
| 1863 |
)
|
| 1864 |
|
| 1865 |
# 上传文件自动更新
|
| 1866 |
+
def update_gallery_on_unified_upload(files, interval):
|
| 1867 |
+
if not files:
|
| 1868 |
+
return None, None, None
|
| 1869 |
+
target_dir, image_paths = handle_uploads(files, interval)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1870 |
return (
|
|
|
|
| 1871 |
target_dir,
|
| 1872 |
image_paths,
|
| 1873 |
"✅ 上传完成,点击「开始重建」进行 3D 处理",
|
| 1874 |
)
|
| 1875 |
|
| 1876 |
+
def show_resample_button(files):
|
| 1877 |
+
"""仅当上传的文件包含视频时显示重新采样按钮"""
|
| 1878 |
+
if not files:
|
| 1879 |
+
return gr.update(visible=False)
|
| 1880 |
+
|
| 1881 |
+
# 检查是否有视频文件
|
| 1882 |
+
video_extensions = [
|
| 1883 |
+
".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp",
|
| 1884 |
+
]
|
| 1885 |
+
has_video = False
|
| 1886 |
+
|
| 1887 |
+
for file_data in files:
|
| 1888 |
+
if isinstance(file_data, dict) and "name" in file_data:
|
| 1889 |
+
file_path = file_data["name"]
|
| 1890 |
+
else:
|
| 1891 |
+
file_path = str(file_data)
|
| 1892 |
+
|
| 1893 |
+
file_ext = os.path.splitext(file_path)[1].lower()
|
| 1894 |
+
if file_ext in video_extensions:
|
| 1895 |
+
has_video = True
|
| 1896 |
+
break
|
| 1897 |
+
|
| 1898 |
+
return gr.update(visible=has_video)
|
| 1899 |
+
|
| 1900 |
+
def resample_video_with_new_interval(files, new_interval, current_target_dir):
|
| 1901 |
+
"""使用新的滑块值重新采样视频"""
|
| 1902 |
+
if not files:
|
| 1903 |
+
return (
|
| 1904 |
+
current_target_dir,
|
| 1905 |
+
None,
|
| 1906 |
+
"没有可重新采样的文件。",
|
| 1907 |
+
gr.update(visible=False),
|
| 1908 |
+
)
|
| 1909 |
+
|
| 1910 |
+
# 检查是否有视频需要重新采样
|
| 1911 |
+
video_extensions = [
|
| 1912 |
+
".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp",
|
| 1913 |
+
]
|
| 1914 |
+
has_video = any(
|
| 1915 |
+
os.path.splitext(
|
| 1916 |
+
str(file_data["name"] if isinstance(file_data, dict) else file_data)
|
| 1917 |
+
)[1].lower()
|
| 1918 |
+
in video_extensions
|
| 1919 |
+
for file_data in files
|
| 1920 |
+
)
|
| 1921 |
+
|
| 1922 |
+
if not has_video:
|
| 1923 |
+
return (
|
| 1924 |
+
current_target_dir,
|
| 1925 |
+
None,
|
| 1926 |
+
"未找到视频进行重新采样。",
|
| 1927 |
+
gr.update(visible=False),
|
| 1928 |
+
)
|
| 1929 |
+
|
| 1930 |
+
# 清理旧的目标目录
|
| 1931 |
+
if (
|
| 1932 |
+
current_target_dir
|
| 1933 |
+
and current_target_dir != "None"
|
| 1934 |
+
and os.path.exists(current_target_dir)
|
| 1935 |
+
):
|
| 1936 |
+
shutil.rmtree(current_target_dir)
|
| 1937 |
+
|
| 1938 |
+
# 使用新间隔处理文件
|
| 1939 |
+
target_dir, image_paths = handle_uploads(files, new_interval)
|
| 1940 |
+
|
| 1941 |
+
return (
|
| 1942 |
+
target_dir,
|
| 1943 |
+
image_paths,
|
| 1944 |
+
f"视频已使用 {new_interval}秒 间隔重新采样。点击「开始重建」进行 3D 处理。",
|
| 1945 |
+
gr.update(visible=False),
|
| 1946 |
+
)
|
| 1947 |
+
|
| 1948 |
+
unified_upload.change(
|
| 1949 |
fn=update_gallery_on_unified_upload,
|
| 1950 |
+
inputs=[unified_upload, s_time_interval],
|
| 1951 |
+
outputs=[target_dir_output, image_gallery, log_output]
|
| 1952 |
+
).then(
|
| 1953 |
+
fn=show_resample_button,
|
| 1954 |
+
inputs=[unified_upload],
|
| 1955 |
+
outputs=[resample_btn],
|
| 1956 |
)
|
| 1957 |
+
|
| 1958 |
+
# 滑块改变时显示重新采样按钮(仅当已上传文件时)
|
| 1959 |
+
s_time_interval.change(
|
| 1960 |
+
fn=show_resample_button,
|
| 1961 |
+
inputs=[unified_upload],
|
| 1962 |
+
outputs=[resample_btn],
|
| 1963 |
+
)
|
| 1964 |
+
|
| 1965 |
+
# 处理重新采样按钮点击
|
| 1966 |
+
resample_btn.click(
|
| 1967 |
+
fn=resample_video_with_new_interval,
|
| 1968 |
+
inputs=[unified_upload, s_time_interval, target_dir_output],
|
| 1969 |
+
outputs=[target_dir_output, image_gallery, log_output, resample_btn],
|
| 1970 |
)
|
| 1971 |
|
| 1972 |
# 重建按钮
|
|
|
|
| 1980 |
fn=gradio_demo,
|
| 1981 |
inputs=[
|
| 1982 |
target_dir_output, frame_filter, show_cam,
|
| 1983 |
+
filter_black_bg, filter_white_bg,
|
| 1984 |
apply_mask_checkbox, show_mesh,
|
| 1985 |
enable_segmentation, text_prompt
|
| 1986 |
],
|
|
|
|
| 1998 |
clear_btn.add([reconstruction_output, segmented_output, log_output])
|
| 1999 |
|
| 2000 |
# 可视化参数实时更新
|
| 2001 |
+
for component in [frame_filter, show_cam, show_mesh]:
|
| 2002 |
component.change(
|
| 2003 |
fn=update_visualization,
|
| 2004 |
inputs=[
|
| 2005 |
target_dir_output, frame_filter, show_cam, is_example,
|
| 2006 |
+
filter_black_bg, filter_white_bg, show_mesh
|
| 2007 |
],
|
| 2008 |
outputs=[reconstruction_output, log_output]
|
| 2009 |
)
|