File size: 34,436 Bytes
777e649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411a33c
777e649
 
 
 
 
 
 
 
 
 
1dd77e9
 
fbb1647
1dd77e9
 
777e649
 
 
378f315
 
34d6249
 
378f315
34d6249
378f315
34d6249
378f315
 
777e649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fbccfd3
777e649
fbccfd3
777e649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e13bbb8
777e649
 
 
 
 
 
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
# -*- coding: utf-8 -*-

import argparse
import gradio
import os
import torch
import numpy as np
import tempfile
import functools
import trimesh
import copy
from scipy.spatial.transform import Rotation

from dust3r.inference import inference, load_model
from dust3r.image_pairs import make_pairs
from dust3r.utils.image import load_images, rgb, resize_images
from dust3r.utils.device import to_numpy
from dust3r.viz import add_scene_cam, CAM_COLORS, OPENGL, pts3d_to_trimesh, cat_meshes
from dust3r.cloud_opt import global_aligner, GlobalAlignerMode
from SAM2.sam2.build_sam import build_sam2_video_predictor
import matplotlib.pyplot as plt

import shutil
import json
from PIL import Image
import math
import cv2

plt.ion()


# 添加 sam2 模块路径
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), 'SAM2'))

torch.backends.cuda.matmul.allow_tf32 = True  # for gpu >= Ampere and pytorch >= 1.12
batch_size = 1



#import subprocess
#import sys
# 先构建扩展模块
#subprocess.check_call([sys.executable, "setup.py", "build_ext", "--inplace"])
# 安装项目到开发模式
#subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-e', '.'])


########################## 引入grounding_dino #############################
from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection
def get_mask_from_grounding_dino(video_dir, ann_frame_idx, ann_obj_id, input_text):
    # init grounding dino model from huggingface
    model_id = "IDEA-Research/grounding-dino-tiny"
    device = "cuda" if torch.cuda.is_available() else "cpu"
    processor = AutoProcessor.from_pretrained(model_id)
    grounding_model = AutoModelForZeroShotObjectDetection.from_pretrained(model_id).to(device)

    # setup the input image and text prompt for SAM 2 and Grounding DINO
    # VERY important: text queries need to be lowercased + end with a dot


    """
    Step 2: Prompt Grounding DINO and SAM image predictor to get the box and mask for specific frame
    """
    # prompt grounding dino to get the box coordinates on specific frame
    frame_names = [
        p for p in os.listdir(video_dir)
        if os.path.splitext(p)[-1] in [".jpg", ".jpeg", ".JPG", ".JPEG"]
    ]
    # frame_names.sort(key=lambda p: os.path.splitext(p)[0])
    img_path = os.path.join(video_dir, frame_names[ann_frame_idx])
    image = Image.open(img_path)


    # run Grounding DINO on the image
    inputs = processor(images=image, text=input_text, return_tensors="pt").to(device)
    with torch.no_grad():
        outputs = grounding_model(**inputs)

    results = processor.post_process_grounded_object_detection(
        outputs,
        inputs.input_ids,
        box_threshold=0.25,
        text_threshold=0.3,
        target_sizes=[image.size[::-1]]
    )
    return results[0]["boxes"], results[0]["labels"]

def get_masks_from_grounded_sam2(h, w, predictor, video_dir, input_text):

    inference_state = predictor.init_state(video_path=video_dir)
    predictor.reset_state(inference_state)

    ann_frame_idx = 0  # the frame index we interact with
    ann_obj_id = 1  # give a unique id to each object we interact with (it can be any integers)
    print("Running Groundding DINO......")
    input_boxes, OBJECTS = get_mask_from_grounding_dino(video_dir, ann_frame_idx, ann_obj_id, input_text)
    print("Groundding DINO run over!")
    if(len(OBJECTS) < 1):
        raise gradio.Error("The images you input do not contain the target in '{}'".format(input_text))
    # 给第一个帧输入由grounding_dino输出的boxes作为prompts
    for object_id, (label, box) in enumerate(zip(OBJECTS, input_boxes)):
        _, out_obj_ids, out_mask_logits = predictor.add_new_points_or_box(
            inference_state=inference_state,
            frame_idx=ann_frame_idx,
            obj_id=ann_obj_id,
            box=box,
        )
        break #只加入第一个box


    # sam2获取所有帧的分割结果
    video_segments = {}  # video_segments contains the per-frame segmentation results
    for out_frame_idx, out_obj_ids, out_mask_logits in predictor.propagate_in_video(inference_state):
        video_segments[out_frame_idx] = {
            out_obj_id: (out_mask_logits[i] > 0.0).cpu().numpy()
            for i, out_obj_id in enumerate(out_obj_ids)
        }

    resize_mask = resize_mask_to_img(video_segments, w, h)
    return resize_mask


def handle_uploaded_files(uploaded_files, target_folder):
    # 创建目标文件夹
    if not os.path.exists(target_folder):
        os.makedirs(target_folder)

    # 遍历上传的文件,移动到目标文件夹
    for file in uploaded_files:
        file_path = file.name  # 文件的临时路径
        file_name = os.path.basename(file_path)  # 文件名
        target_path = os.path.join(target_folder, file_name)
        shutil.copy2(file_path, target_path)
        print("copy images from {} to {}".format(file_path, target_path))

    return target_folder
def show_mask(mask, ax, random_color=False):
    if random_color:
        color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)
    else:
        color = np.array([30 / 255, 144 / 255, 255 / 255, 0.6])
    h, w = mask.shape[-2:]
    mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)
    ax.imshow(mask_image)

def show_mask_sam2(mask, ax, obj_id=None, random_color=False):
    if random_color:
        color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)
    else:
        cmap = plt.get_cmap("tab10")
        cmap_idx = 0 if obj_id is None else obj_id
        color = np.array([*cmap(cmap_idx)[:3], 0.6])
    h, w = mask.shape[-2:]
    mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)
    ax.imshow(mask_image)
def show_points(coords, labels, ax, marker_size=375):
    pos_points = coords[labels == 1]
    neg_points = coords[labels == 0]
    ax.scatter(pos_points[:, 0], pos_points[:, 1], color='green', marker='*', s=marker_size, edgecolor='white',
               linewidth=1.25)
    ax.scatter(neg_points[:, 0], neg_points[:, 1], color='red', marker='*', s=marker_size, edgecolor='white',
               linewidth=1.25)

def show_box(box, ax):
    x0, y0 = box[0], box[1]
    w, h = box[2] - box[0], box[3] - box[1]
    ax.add_patch(plt.Rectangle((x0, y0), w, h, edgecolor='green', facecolor=(0, 0, 0, 0), lw=2))



def get_args_parser():
    parser = argparse.ArgumentParser()
    parser_url = parser.add_mutually_exclusive_group()
    parser_url.add_argument("--local_network", action='store_true', default=False,
                            help="make app accessible on local network: address will be set to 0.0.0.0")
    parser_url.add_argument("--server_name", type=str, default="0.0.0.0", help="server url, default is 127.0.0.1")
    parser.add_argument("--image_size", type=int, default=512, choices=[512, 224], help="image size")
    parser.add_argument("--server_port", type=int, help=("will start gradio app on this port (if available). "
                                                         "If None, will search for an available port starting at 7860."),
                        default=None)
    parser.add_argument("--weights", type=str, default="./checkpoints/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth", required=False, help="path to the model weights")
    parser.add_argument("--device", type=str, default='cpu', help="pytorch device")
    parser.add_argument("--tmp_dir", type=str, default="./", help="value for tempfile.tempdir")
    return parser


# 将渲染的3D保存到outfile路径
def _convert_scene_output_to_glb(outdir, imgs, pts3d, mask, focals, cams2world, cam_size=0.05,
                                 cam_color=None, as_pointcloud=False, transparent_cams=False):
    assert len(pts3d) == len(mask) <= len(imgs) <= len(cams2world) == len(focals)
    pts3d = to_numpy(pts3d)
    imgs = to_numpy(imgs)
    focals = to_numpy(focals)
    cams2world = to_numpy(cams2world)

    scene = trimesh.Scene()

    # full pointcloud
    if as_pointcloud:
        pts = np.concatenate([p[m] for p, m in zip(pts3d, mask)])
        col = np.concatenate([p[m] for p, m in zip(imgs, mask)])
        pct = trimesh.PointCloud(pts.reshape(-1, 3), colors=col.reshape(-1, 3))
        scene.add_geometry(pct)
    else:
        meshes = []
        for i in range(len(imgs)):
            meshes.append(pts3d_to_trimesh(imgs[i], pts3d[i], mask[i]))
        mesh = trimesh.Trimesh(**cat_meshes(meshes))
        scene.add_geometry(mesh)

    # add each camera
    for i, pose_c2w in enumerate(cams2world):
        if isinstance(cam_color, list):
            camera_edge_color = cam_color[i]
        else:
            camera_edge_color = cam_color or CAM_COLORS[i % len(CAM_COLORS)]
        add_scene_cam(scene, pose_c2w, camera_edge_color,
                      None if transparent_cams else imgs[i], focals[i],
                      imsize=imgs[i].shape[1::-1], screen_width=cam_size)

    rot = np.eye(4)
    rot[:3, :3] = Rotation.from_euler('y', np.deg2rad(180)).as_matrix()
    scene.apply_transform(np.linalg.inv(cams2world[0] @ OPENGL @ rot))
    outfile = os.path.join(outdir, 'scene.glb')
    print('(exporting 3D scene to', outfile, ')')
    scene.export(file_obj=outfile)
    return outfile


def get_3D_model_from_scene(outdir, scene, sam2_masks, min_conf_thr=3, as_pointcloud=False, mask_sky=False,
                            clean_depth=False, transparent_cams=False, cam_size=0.05):
    """
    extract 3D_model (glb file) from a reconstructed scene
    """
    if scene is None:
        return None
    # post processes
    if clean_depth:
        scene = scene.clean_pointcloud()
    if mask_sky:
        scene = scene.mask_sky()

    # get optimized values from scene
    rgbimg = scene.imgs

    focals = scene.get_focals().cpu()
    cams2world = scene.get_im_poses().cpu()
    # 3D pointcloud from depthmap, poses and intrinsics
    pts3d = to_numpy(scene.get_pts3d())
    scene.min_conf_thr = float(scene.conf_trf(torch.tensor(min_conf_thr)))
    msk = to_numpy(scene.get_masks())

    assert len(msk) == len(sam2_masks)
    # 将sam2输出的mask 和 dust3r输出的置信度阈值筛选后的msk取交集
    for i in range(len(sam2_masks)):
        msk[i] = np.logical_and(msk[i], sam2_masks[i])

    return _convert_scene_output_to_glb(outdir, rgbimg, pts3d, msk, focals, cams2world, as_pointcloud=as_pointcloud,
                                        transparent_cams=transparent_cams, cam_size=cam_size) # 置信度和SAM2 mask的交集

# 将视频分割成固定帧数
def video_to_frames_fix(video_path, output_folder, frame_interval=10, target_fps=6):
    """
    将视频转换为图像帧,并保存为 JPEG 文件。
    frame_interval:保存帧的步长
    target_fps: 目标帧率(每秒保存的帧数)
    """

    # 确保输出文件夹存在
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    # 打开视频文件
    cap = cv2.VideoCapture(video_path)
    # 获取视频总帧数
    frames_num = cap.get(cv2.CAP_PROP_FRAME_COUNT)

    # 计算动态帧间隔
    frame_interval = math.ceil(frames_num / target_fps)
    print(f"总帧数: {frames_num} FPS, 动态帧间隔: 每隔 {frame_interval} 帧保存一次.")
    frame_count = 0
    saved_frame_count = 0
    success, frame = cap.read()

    file_list = []
    # 逐帧读取视频
    while success:
        if frame_count % frame_interval == 0:
            # 每隔 frame_interval 帧保存一次
            frame_filename = os.path.join(output_folder, f"frame_{saved_frame_count:04d}.jpg")
            cv2.imwrite(frame_filename, frame)
            file_list.append(frame_filename)
            saved_frame_count += 1
        frame_count += 1
        success, frame = cap.read()

    # 释放视频捕获对象
    cap.release()
    print(f"视频处理完成,共保存了 {saved_frame_count} 帧到文件夹 '{output_folder}'.")
    return file_list


def video_to_frames(video_path, output_folder, frame_interval=10, target_fps = 2):
    """
    将视频转换为图像帧,并保存为 JPEG 文件。
    frame_interval:保存帧的步长
    target_fps: 目标帧率(每秒保存的帧数)
    """

    # 确保输出文件夹存在
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    # 打开视频文件
    cap = cv2.VideoCapture(video_path)
    # 获取视频的实际帧率
    actual_fps = cap.get(cv2.CAP_PROP_FPS)

    # 获取视频总帧数
    frames_num = cap.get(cv2.CAP_PROP_FRAME_COUNT)

    # 计算动态帧间隔
    # frame_interval = math.ceil(actual_fps / target_fps)
    print(f"实际帧率: {actual_fps} FPS, 动态帧间隔: 每隔 {frame_interval} 帧保存一次.")
    frame_count = 0
    saved_frame_count = 0
    success, frame = cap.read()

    file_list = []
    # 逐帧读取视频
    while success:
        if frame_count % frame_interval == 0:
            # 每隔 frame_interval 帧保存一次
            frame_filename = os.path.join(output_folder, f"frame_{saved_frame_count:04d}.jpg")
            cv2.imwrite(frame_filename, frame)
            file_list.append(frame_filename)
            saved_frame_count += 1
        frame_count += 1
        success, frame = cap.read()

    # 释放视频捕获对象
    cap.release()
    print(f"视频处理完成,共保存了 {saved_frame_count} 帧到文件夹 '{output_folder}'.")
    return file_list

def overlay_mask_on_image(image, mask, color=[0, 1, 0], alpha=0.5):
    """
    将mask融合在image上显示。
    返回融合后的图片 (H, W, 3)
    """

    # 创建一个与image相同尺寸的全黑图像
    mask_colored = np.zeros_like(image)

    # 将mask为True的位置赋值为指定颜色
    mask_colored[mask] = color

    # 将彩色掩码与原图像叠加
    overlay = cv2.addWeighted(image, 1 - alpha, mask_colored, alpha, 0)

    return overlay
def get_reconstructed_video(sam2, outdir, model, device, image_size, image_mask, video_dir, schedule, niter, min_conf_thr,
                            as_pointcloud, mask_sky, clean_depth, transparent_cams, cam_size,
                            scenegraph_type, winsize, refid, input_text):
    target_dir = os.path.join(outdir, 'frames_video')
    file_list = video_to_frames_fix(video_dir, target_dir)
    scene, outfile, imgs = get_reconstructed_scene(sam2, outdir, model, device, image_size, image_mask, file_list, schedule, niter, min_conf_thr,
                            as_pointcloud, mask_sky, clean_depth, transparent_cams, cam_size,
                            scenegraph_type, winsize, refid, target_dir, input_text)
    return scene, outfile, imgs

def get_reconstructed_image(sam2, outdir, model, device, image_size, image_mask, filelist, schedule, niter, min_conf_thr,
                            as_pointcloud, mask_sky, clean_depth, transparent_cams, cam_size,
                            scenegraph_type, winsize, refid, input_text):
    target_folder = handle_uploaded_files(filelist, os.path.join(outdir, 'uploaded_images'))
    scene, outfile, imgs = get_reconstructed_scene(sam2, outdir, model, device, image_size, image_mask, filelist, schedule, niter, min_conf_thr,
                            as_pointcloud, mask_sky, clean_depth, transparent_cams, cam_size,
                            scenegraph_type, winsize, refid, target_folder, input_text)
    return scene, outfile, imgs
def get_reconstructed_scene(sam2, outdir, model, device, image_size, image_mask, filelist, schedule, niter, min_conf_thr,
                            as_pointcloud, mask_sky, clean_depth, transparent_cams, cam_size,
                            scenegraph_type, winsize, refid, images_folder, input_text=None):
    """
    from a list of images, run dust3rWithSam2 inference, global aligner.
    then run get_3D_model_from_scene
    """
    imgs = load_images(filelist, size=image_size)
    img_size = imgs[0]["true_shape"]
    for img in imgs[1:]:
        if not np.equal(img["true_shape"], img_size).all():
            raise gradio.Error("Please ensure that the images you enter are of the same size")

    if len(imgs) == 1:
        imgs = [imgs[0], copy.deepcopy(imgs[0])]
        imgs[1]['idx'] = 1
    if scenegraph_type == "swin":
        scenegraph_type = scenegraph_type + "-" + str(winsize)
    elif scenegraph_type == "oneref":
        scenegraph_type = scenegraph_type + "-" + str(refid)





    pairs = make_pairs(imgs, scene_graph=scenegraph_type, prefilter=None, symmetrize=True)
    output = inference(pairs, model, device, batch_size=batch_size)

    mode = GlobalAlignerMode.PointCloudOptimizer if len(imgs) > 2 else GlobalAlignerMode.PairViewer
    scene = global_aligner(output, device=device, mode=mode)
    lr = 0.01

    if mode == GlobalAlignerMode.PointCloudOptimizer:
        loss = scene.compute_global_alignment(init='mst', niter=niter, schedule=schedule, lr=lr)



    # also return rgb, depth and confidence imgs
    # depth is normalized with the max value for all images
    # we apply the jet colormap on the confidence maps
    rgbimg = scene.imgs
    depths = to_numpy(scene.get_depthmaps())
    confs = to_numpy([c for c in scene.im_conf])
    cmap = plt.get_cmap('jet')
    depths_max = max([d.max() for d in depths])
    depths = [d / depths_max for d in depths]
    confs_max = max([d.max() for d in confs])
    confs = [cmap(d / confs_max) for d in confs]

    # TODO 调用SAM2获取masks
    h, w = rgbimg[0].shape[:-1]
    masks = None
    if not input_text or input_text.isspace(): # input_text 为空串
        masks = get_masks_from_sam2(h, w, sam2, images_folder)
    else:
        masks = get_masks_from_grounded_sam2(h, w, sam2, images_folder, input_text)  # gd-sam2


    imgs = []
    for i in range(len(rgbimg)):
        imgs.append(rgbimg[i])
        imgs.append(rgb(depths[i]))
        imgs.append(rgb(confs[i]))
        imgs.append(overlay_mask_on_image(rgbimg[i], masks[i])) # mask融合原图,展示SAM2的分割效果

    # TODO 基于SAM2的mask过滤DUST3R的3D重建模型
    outfile = get_3D_model_from_scene(outdir, scene, masks, min_conf_thr, as_pointcloud, mask_sky,
                                      clean_depth, transparent_cams, cam_size)
    return scene, outfile, imgs


def resize_mask_to_img(masks, target_width, target_height):
    frame_mask = []
    origin_size = masks[0][1].shape # 1表示object id
    for frame, objects_mask in masks.items(): # 每个frame和该frame对应的分割结果
        # 每个frame可能包含多个object对应的mask
        masks = list(objects_mask.values())
        if not masks: # masks为空,即当前frame不包含object
            frame_mask.append(np.ones(origin_size, dtype=bool))
        else: # 将当前frame包含的所有object的mask取并集
            union_mask = masks[0]
            for mask in masks[1:]:
                union_mask = np.logical_or(union_mask, mask)
            frame_mask.append(union_mask)
    resized_mask = []
    for mask in frame_mask:
        mask_image = Image.fromarray(mask.squeeze(0).astype(np.uint8) * 255)
        resized_mask_image = mask_image.resize((target_width, target_height), Image.NEAREST)
        resized_mask.append(np.array(resized_mask_image) > 0)

    return resized_mask

def get_masks_from_sam2(h, w, predictor, video_dir):

    inference_state = predictor.init_state(video_path=video_dir)
    predictor.reset_state(inference_state)


    # 给一个帧添加points
    points = np.array([[360, 550], [340, 400]], dtype=np.float32)
    labels = np.array([1, 1], dtype=np.int32)


    _, out_obj_ids, out_mask_logits = predictor.add_new_points(
        inference_state=inference_state,
        frame_idx=0,
        obj_id=1,
        points=points,
        labels=labels,
    )

    # sam2获取所有帧的分割结果
    video_segments = {}  # video_segments contains the per-frame segmentation results
    for out_frame_idx, out_obj_ids, out_mask_logits in predictor.propagate_in_video(inference_state):
        video_segments[out_frame_idx] = {
            out_obj_id: (out_mask_logits[i] > 0.0).cpu().numpy()
            for i, out_obj_id in enumerate(out_obj_ids)
        }

    resize_mask = resize_mask_to_img(video_segments, w, h)
    return resize_mask

def set_scenegraph_options(inputfiles, winsize, refid, scenegraph_type):
    num_files = len(inputfiles) if inputfiles is not None else 1
    max_winsize = max(1, (num_files - 1) // 2)
    if scenegraph_type == "swin":
        winsize = gradio.Slider(label="Scene Graph: Window Size", value=max_winsize,
                                minimum=1, maximum=max_winsize, step=1, visible=True)
        refid = gradio.Slider(label="Scene Graph: Id", value=0, minimum=0,
                              maximum=num_files - 1, step=1, visible=False)
    elif scenegraph_type == "oneref":
        winsize = gradio.Slider(label="Scene Graph: Window Size", value=max_winsize,
                                minimum=1, maximum=max_winsize, step=1, visible=False)
        refid = gradio.Slider(label="Scene Graph: Id", value=0, minimum=0,
                              maximum=num_files - 1, step=1, visible=True)
    else:
        winsize = gradio.Slider(label="Scene Graph: Window Size", value=max_winsize,
                                minimum=1, maximum=max_winsize, step=1, visible=False)
        refid = gradio.Slider(label="Scene Graph: Id", value=0, minimum=0,
                              maximum=num_files - 1, step=1, visible=False)
    return winsize, refid

def process_images(imagesList):
    return None

def process_videos(video):
    return None

def upload_images_listener(image_size, file_list):
    if len(file_list) == 1:
        raise gradio.Error("Please enter images from at least two different views.")
    print("Uploading image[0] to ImageMask:")
    img_0 = load_images([file_list[0]], image_size)
    i1 = img_0[0]['img'].squeeze(0)
    rgb_img = rgb(i1)
    return rgb_img
def upload_video_listener(image_size, video_dir):
    cap = cv2.VideoCapture(video_dir)
    success, frame = cap.read() # 第一帧
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    Image_frame = Image.fromarray(rgb_frame)
    resized_frame = resize_images([Image_frame], image_size)
    i1 = resized_frame[0]['img'].squeeze(0)
    rgb_img = rgb(i1)
    return rgb_img

def main_demo(sam2, tmpdirname, model, device, image_size, server_name, server_port):

    # functools.partial解析:https://blog.csdn.net/wuShiJingZuo/article/details/135018810
    recon_fun_image_demo = functools.partial(get_reconstructed_image,sam2, tmpdirname, model, device,
                                  image_size)

    recon_fun_video_demo = functools.partial(get_reconstructed_video, sam2, tmpdirname, model, device,
                                  image_size)

    upload_files_fun = functools.partial(upload_images_listener,image_size)
    upload_video_fun = functools.partial(upload_video_listener, image_size)
    with gradio.Blocks() as demo1:
        scene = gradio.State(None)
        gradio.HTML('<h1 style="text-align: center;">DUST3R With SAM2: Segmenting Everything In 3D</h1>')
        gradio.HTML("""<h2 style="text-align: center;">
          <a href='https://arxiv.org/abs/2304.03284' target='_blank' rel='noopener'>[paper]</a>
          <a href='https://github.com/baaivision/Painter' target='_blank' rel='noopener'>[code]</a>
        </h2>""")
        gradio.HTML("""
          <div style="text-align: center;">
            <h2 style="text-align: center;">DUSt3R can unify various 3D vision tasks and set new SoTAs on monocular/multi-view depth estimation as well as relative pose estimation.</h2>
          </div>
          """)
        gradio.set_static_paths(paths=["static/images/"])
        project_path = "static/images/project.gif"
        gradio.HTML(f"""
              <div align='center' >
              <img src="/file={project_path}"  width='720px'>
              </div>
              """)
        gradio.HTML("<p> \
            <strong>DUST3R+SAM2: One touch for any segmentation in a video.</strong> <br>\
            Choose an example below &#128293; &#128293;  &#128293; <br>\
            Or, upload by yourself: <br>\
            1. Upload a video to be tested to 'video'. If failed, please check the codec, we recommend h.264 by default. <br>2. Upload a prompt image to 'prompt' and draw <strong>a point or line on the target</strong>.  <br>\
            <br> \
            💎 SAM segments the target with any point or scribble, then SegGPT segments the whole video. <br>\
            💎 Examples below were never trained and are randomly selected for testing in the wild. <br>\
            💎 Current UI interface only unleashes a small part of the capabilities of SegGPT, i.e., 1-shot case. <br> \
                            Note: we only take the first 16 frames for the demo.    \
            </p>")

        with gradio.Column():
            with gradio.Row():
                inputfiles = gradio.File(file_count="multiple")
                with gradio.Column():
                    image_mask = gradio.ImageMask(image_mode="RGB", type="numpy", brush=gradio.Brush(),
                                                  label="prompt (提示图)", transforms=(), width=600, height=450)
                    input_text = gradio.Textbox(info="please enter object here", label="Text Prompt")
            with gradio.Row():
                schedule = gradio.Dropdown(["linear", "cosine"],
                                           value='linear', label="schedule", info="For global alignment!")
                niter = gradio.Number(value=300, precision=0, minimum=0, maximum=5000,
                                      label="num_iterations", info="For global alignment!")
                scenegraph_type = gradio.Dropdown(["complete", "swin", "oneref"],
                                                  value='complete', label="Scenegraph",
                                                  info="Define how to make pairs",
                                                  interactive=True)
                winsize = gradio.Slider(label="Scene Graph: Window Size", value=1,
                                        minimum=1, maximum=1, step=1, visible=False)
                refid = gradio.Slider(label="Scene Graph: Id", value=0, minimum=0, maximum=0, step=1, visible=False)

            run_btn = gradio.Button("Run")

            with gradio.Row():
                # adjust the confidence threshold
                min_conf_thr = gradio.Slider(label="min_conf_thr", value=3.0, minimum=1.0, maximum=20, step=0.1)
                # adjust the camera size in the output pointcloud
                cam_size = gradio.Slider(label="cam_size", value=0.05, minimum=0.001, maximum=0.1, step=0.001)
            with gradio.Row():
                as_pointcloud = gradio.Checkbox(value=False, label="As pointcloud")
                # two post process implemented
                mask_sky = gradio.Checkbox(value=False, label="Mask sky")
                clean_depth = gradio.Checkbox(value=True, label="Clean-up depthmaps")
                transparent_cams = gradio.Checkbox(value=False, label="Transparent cameras")

            outmodel = gradio.Model3D()
            outgallery = gradio.Gallery(label='rgb,depth,confidence,mask', columns=4, height="100%")

            inputfiles.upload(upload_files_fun, inputs=inputfiles, outputs=image_mask)

            run_btn.click(fn=recon_fun_image_demo,  # 调用get_reconstructed_image即DUST3R模型
                          inputs=[image_mask, inputfiles, schedule, niter, min_conf_thr, as_pointcloud,
                                  mask_sky, clean_depth, transparent_cams, cam_size,
                                  scenegraph_type, winsize, refid, input_text],
                          outputs=[scene, outmodel, outgallery])




        # ## ****************************  video  *******************************************************
    with gradio.Blocks() as demo2:
        gradio.HTML('<h1 style="text-align: center;">DUST3R With SAM2: Segmenting Everything In 3D</h1>')
        gradio.HTML("""<h2 style="text-align: center;">
          <a href='https://arxiv.org/abs/2304.03284' target='_blank' rel='noopener'>[paper]</a>
          <a href='https://github.com/baaivision/Painter' target='_blank' rel='noopener'>[code]</a>
        </h2>""")
        gradio.HTML("""
          <div style="text-align: center;">
            <h2 style="text-align: center;">DUSt3R can unify various 3D vision tasks and set new SoTAs on monocular/multi-view depth estimation as well as relative pose estimation.</h2>
          </div>
          """)
        gradio.set_static_paths(paths=["static/images/"])
        project_path = "static/images/project.gif"
        gradio.HTML(f"""
              <div align='center' >
              <img src="/file={project_path}"  width='720px'>
              </div>
              """)
        gradio.HTML("<p> \
            <strong>DUST3R+SAM2: One touch for any segmentation in a video.</strong> <br>\
            Choose an example below &#128293; &#128293;  &#128293; <br>\
            Or, upload by yourself: <br>\
            1. Upload a video to be tested to 'video'. If failed, please check the codec, we recommend h.264 by default. <br>2. Upload a prompt image to 'prompt' and draw <strong>a point or line on the target</strong>.  <br>\
            <br> \
            💎 SAM segments the target with any point or scribble, then SegGPT segments the whole video. <br>\
            💎 Examples below were never trained and are randomly selected for testing in the wild. <br>\
            💎 Current UI interface only unleashes a small part of the capabilities of SegGPT, i.e., 1-shot case. <br> \
                            Note: we only take the first 16 frames for the demo.    \
            </p>")

        with gradio.Column():
            with gradio.Row():
                input_video = gradio.Video(width=600, height=600)
                with gradio.Column():
                    image_mask = gradio.ImageMask(image_mode="RGB", type="numpy", brush=gradio.Brush(),
                                                  label="prompt (提示图)", transforms=(), width=600, height=450)
                    input_text = gradio.Textbox(info="please enter object here", label="Text Prompt")


            with gradio.Row():
                schedule = gradio.Dropdown(["linear", "cosine"],
                                           value='linear', label="schedule", info="For global alignment!")
                niter = gradio.Number(value=300, precision=0, minimum=0, maximum=5000,
                                      label="num_iterations", info="For global alignment!")
                scenegraph_type = gradio.Dropdown(["complete", "swin", "oneref"],
                                                  value='complete', label="Scenegraph",
                                                  info="Define how to make pairs",
                                                  interactive=True)
                winsize = gradio.Slider(label="Scene Graph: Window Size", value=1,
                                        minimum=1, maximum=1, step=1, visible=False)
                refid = gradio.Slider(label="Scene Graph: Id", value=0, minimum=0, maximum=0, step=1, visible=False)

            run_btn = gradio.Button("Run")

            with gradio.Row():
                # adjust the confidence threshold
                min_conf_thr = gradio.Slider(label="min_conf_thr", value=3.0, minimum=1.0, maximum=20, step=0.1)
                # adjust the camera size in the output pointcloud
                cam_size = gradio.Slider(label="cam_size", value=0.05, minimum=0.001, maximum=0.1, step=0.001)
            with gradio.Row():
                as_pointcloud = gradio.Checkbox(value=False, label="As pointcloud")
                # two post process implemented
                mask_sky = gradio.Checkbox(value=False, label="Mask sky")
                clean_depth = gradio.Checkbox(value=True, label="Clean-up depthmaps")
                transparent_cams = gradio.Checkbox(value=False, label="Transparent cameras")

            outmodel = gradio.Model3D()
            outgallery = gradio.Gallery(label='rgb,depth,confidence,mask', columns=4, height="100%")

            input_video.upload(upload_video_fun, inputs=input_video, outputs=image_mask)

            run_btn.click(fn=recon_fun_video_demo,  # 调用get_reconstructed_scene即DUST3R模型
                          inputs=[image_mask, input_video, schedule, niter, min_conf_thr, as_pointcloud,
                                  mask_sky, clean_depth, transparent_cams, cam_size,
                                  scenegraph_type, winsize, refid, input_text],
                          outputs=[scene, outmodel, outgallery])

    app = gradio.TabbedInterface([demo1, demo2], ["3d rebuilding by images", "3d rebuilding by video"])
    app.launch(share=False, server_name=server_name, server_port=server_port)


# TODO 修改bug:
#在项目的一次启动中,上传的多组图片在点击run后,会保存在同一个临时文件夹中,
# 这样后面再上传其他场景的图片时,不同场景下的图片会存在于一个文件夹中,
# 不同场景的图片导致分割与重建错误

## 目前构思的解决:在文件夹下再基于创建一个文件夹存放不同场景的图片,可以基于时间命名该文件夹


if __name__ == '__main__':
    parser = get_args_parser()
    args = parser.parse_args()

    if args.tmp_dir is not None:
        tmp_path = args.tmp_dir
        os.makedirs(tmp_path, exist_ok=True)
        tempfile.tempdir = tmp_path

    if args.server_name is not None:
        server_name = args.server_name
    else:
        server_name = '0.0.0.0' if args.local_network else '127.0.0.1'

    # DUST3R
    model = load_model(args.weights, args.device)
    # SAM2
    # 加载模型
    sam2_checkpoint = "./SAM2/checkpoints/sam2_hiera_large.pt"
    model_cfg = "sam2_hiera_l.yaml"
    sam2 = build_sam2_video_predictor(model_cfg, sam2_checkpoint)
    # dust3rWithSam2 will write the 3D model inside tmpdirname
    with tempfile.TemporaryDirectory(suffix='dust3r_gradio_demo') as tmpdirname: # DUST3R生成的3D .glb 文件所在的文件夹名称
        print('Outputing stuff in', tmpdirname)
        main_demo(sam2, tmpdirname, model, args.device, args.image_size, server_name, args.server_port)