基于​Segment-and-Track Anything与ProPainter实现视频一键目标移除与一键祛除水印

news2024/11/14 15:14:54

一、 ProPainter

1.算法简介

ProPainter是由新加坡南洋理工大学(Nanyang Technological University)的S-Lab团队开发的一款视频修复工具。它融合了图像和特征修复的优势,以及高效的Transformer技术,旨在提供高质量的视频修复效果,同时保持高效性。
ProPainter包含以下功能:

  1. 对象去除:能够轻松去除视频中的不需要的对象。
  2. 水印删除:可用于删除视频中的水印,提高视觉质量。
  3. 视频内容完整性修复:能够修复损坏的视频内容,使其看起来 完整和连贯。

2.项目部署

想对ProPainter有更多了解或者想部署ProPainter项目的可以我之前的博客:
一键智能视频编辑与视频修复算法——ProPainter源码解析与部署

3.项目局限性

ProPainter当前开源的代码只有视频移除对象部分的源码,但在移除对象之前,要生成mask图,ProPainter不提供生成mask图像的代码,生成mask图像的代码要借助目标分割与目标追踪。
比如我要移动掉桌子中间的投影仪,那要借助Segment-and-Track Anything对目标进行分割与追踪,然后每一帧都生成mask图像:
在这里插入图片描述
生成的mask图像:
在这里插入图片描述

二、Segment-and-Track Anything

1.算法简介

“Segment-and-Track Anything” 是由浙江大学 ReLER 实验室开发的一款多功能视频分割和目标跟踪模型,它深度整合了 SAM(Segment Anything Model)和视频分割技术,使其能够高效地跟踪视频中的目标,并支持多种交互方式(如点、画笔和文字输入)。

在这个基础上,SAM-Track 实现了多个传统视频分割任务的统一,使其能够一键分割和追踪任意视频中的任意目标,将传统视频分割技术推向通用视频分割领域。SAM-Track 在复杂场景下表现出卓越的性能,即使在单一GPU卡上也能高质量地稳定跟踪数百个目标。

SAM-Track 模型基于 ECCV’22 VOT Workshop 四个赛道的冠军方案 DeAOT。DeAOT 是一种高效的多目标视频对象分割模型,在提供首帧物体标注的情况下,可以对视频的其余帧中的物体进行追踪分割。DeAOT 使用一种识别机制,将一个视频中的多个目标嵌入到同一高维空间中,从而实现对多个物体的同时跟踪。DeAOT 在多物体追踪方面的速度表现媲美其他专注于单个物体追踪的 VOS 方法。此外,通过基于分层 Transformer 的传播机制,DeAOT 更好地整合了长时序和短时序信息,表现出卓越的追踪性能。然而,DeAOT 需要参考帧的标注来初始化,为了提高方便性,SAM-Track 利用了图像分割领域的明星模型 SAM,以获取高质量的参考帧标注信息。SAM 凭借出色的零样本迁移能力以及多种交互方式,使 SAM-Track 能够为 DeAOT 高效获取高质量的参考帧标注信息。

虽然 SAM 模型在图像分割领域表现出色,但它无法输出语义标签,并且文本提示也无法有效地支持 Referring Object Segmentation 以及其他依赖深层语义理解的任务。因此,SAM-Track 模型进一步集成了 Grounding DINO,实现了高精度的语言引导视频分割。Grounding DINO 是一种开放集合目标检测模型,具备出色的语言理解能力。

2.项目部署

可参考之前的博客:
​Segment-and-Track Anything——通用智能视频分割、目标追踪、编辑算法解读与源码部署

三、项目整合

1.目标分割与追踪

把Segment-and-Track Anything和ProPainter整合在一起之后,实现目标分割与目标追踪。
目标分割与目标追踪:

def tracking_objects_in_video(SegTracker, input_video, input_img_seq=None, frame_num=0):

    if input_video is not None:
        video_name = os.path.basename(input_video).split('.')[0]
    else:
        return None, None

    # create dir to save result
    tracking_result_dir = f'{os.path.join(os.path.dirname(__file__), "output", f"{video_name}")}'
    create_dir(tracking_result_dir)

    io_args = {
        'tracking_result_dir': tracking_result_dir,
        'output_mask_dir': f'{tracking_result_dir}/{video_name}_masks',
        'output_masked_frame_dir': f'{tracking_result_dir}/{video_name}_masked_frames',
        'output_video': f'{tracking_result_dir}/{video_name}_seg.mp4',  # keep same format as input video
        # 'output_gif': f'{tracking_result_dir}/{video_name}_seg.gif',
    }

    return video_type_input_tracking(SegTracker, input_video, io_args, video_name, frame_num)


def video_type_input_tracking(SegTracker, input_video, io_args, video_name, frame_num=0):

    pred_list = []
    masked_pred_list = []

    # source video to segment
    cap = cv2.VideoCapture(input_video)
    fps = cap.get(cv2.CAP_PROP_FPS)

    if frame_num > 0:
        output_mask_name = sorted([img_name for img_name in os.listdir(io_args['output_mask_dir'])])
        output_masked_frame_name = sorted([img_name for img_name in os.listdir(io_args['output_masked_frame_dir'])])

        for i in range(0, frame_num):
            cap.read()
            pred_list.append(
                np.array(Image.open(os.path.join(io_args['output_mask_dir'], output_mask_name[i])).convert('P')))
            masked_pred_list.append(
                cv2.imread(os.path.join(io_args['output_masked_frame_dir'], output_masked_frame_name[i])))

    # create dir to save predicted mask and masked frame
    if frame_num == 0:
        if os.path.isdir(io_args['output_mask_dir']):
            # os.system(f"rm -r {io_args['output_mask_dir']}")
            pass
        if os.path.isdir(io_args['output_masked_frame_dir']):
            # os.system(f"rm -r {io_args['output_masked_frame_dir']}")
            pass
    output_mask_dir = io_args['output_mask_dir']
    create_dir(io_args['output_mask_dir'])
    create_dir(io_args['output_masked_frame_dir'])

    torch.cuda.empty_cache()
    gc.collect()
    sam_gap = SegTracker.sam_gap
    frame_idx = 0

    with torch.cuda.amp.autocast():
        while cap.isOpened():
            ret, frame = cap.read()
            if not ret:
                break
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

            if frame_idx == 0:
                pred_mask = SegTracker.first_frame_mask
                torch.cuda.empty_cache()
                gc.collect()
            elif (frame_idx % sam_gap) == 0:
                seg_mask = SegTracker.seg(frame)
                torch.cuda.empty_cache()
                gc.collect()
                track_mask = SegTracker.track(frame)
                # find new objects, and update tracker with new objects
                new_obj_mask = SegTracker.find_new_objs(track_mask, seg_mask)
                save_prediction(new_obj_mask, output_mask_dir, str(frame_idx + frame_num).zfill(5) + '_new.png')
                pred_mask = track_mask + new_obj_mask
                # segtracker.restart_tracker()
                SegTracker.add_reference(frame, pred_mask)
            else:
                pred_mask = SegTracker.track(frame, update_memory=True)
            torch.cuda.empty_cache()
            gc.collect()

            save_prediction(pred_mask, output_mask_dir, str(frame_idx + frame_num).zfill(5) + '.png')
            pred_list.append(pred_mask)

            print("processed frame {}, obj_num {}".format(frame_idx + frame_num, SegTracker.get_obj_num()), end='\r')
            frame_idx += 1
        cap.release()
        print('\nfinished')

    ##################
    # Visualization
    ##################

    # draw pred mask on frame and save as a video
    cap = cv2.VideoCapture(input_video)
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    out = cv2.VideoWriter(io_args['output_video'], fourcc, fps, (width, height))

    frame_idx = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break

        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        pred_mask = pred_list[frame_idx]
        masked_frame = draw_mask(frame, pred_mask)
        cv2.imwrite(f"{io_args['output_masked_frame_dir']}/{str(frame_idx).zfill(5)}.png", masked_frame[:, :, ::-1])

        masked_pred_list.append(masked_frame)
        masked_frame = cv2.cvtColor(masked_frame, cv2.COLOR_RGB2BGR)
        out.write(masked_frame)

        print('frame {} writed'.format(frame_idx), end='\r')
        frame_idx += 1
    out.release()
    cap.release()
    print("\n{} saved".format(io_args['output_video']))
    print('\nfinished')

    # manually release memory (after cuda out of memory)
    del SegTracker
    torch.cuda.empty_cache()
    gc.collect()

    return io_args['output_video']

在这里插入图片描述
执行之后,在项目根目录的output目录下生成mask图:
在这里插入图片描述

2. 目标移除

得到mask图之后就可以使用ProPainter进行视频目标移除:

def remove_watermark(input_video):
    print("开始祛除目标")
    # print('cwd', os.getcwd())
    root_path = os.getcwd()

    os.chdir(os.path.join(root_path,'ProPainter'))

    python_exe = resolve_relative_path(os.path.join(root_path,'env/python.exe'))
    inference = resolve_relative_path(os.path.join(root_path,'ProPainter/inference_propainter.py'))

    video_name = os.path.basename(input_video).split('.')[0].split('_')[0]
    output_base_path = resolve_relative_path('./output/')
    output_path = f'{output_base_path}/{video_name}/'
    mask = f'{output_path}/{video_name}_masks/'

    command = f'{python_exe} {inference} --video {input_video} --mask {mask}  --output {output_path} --fp16 --subvideo_length 50'
    print(command)
    result = subprocess.run(command, shell=True)

    if result.returncode != 0:
        error_message = result.stderr.decode('utf-8', 'ignore')
        print(f"错误 {error_message}")
    else:
        print("成功")
    file_name = input_video.split('\\')[-1].split('.')[0]
    print(file_name)
    os.chdir(resolve_relative_path('./'))
    print('cwd', os.getcwd())
    return output_path + '/' + file_name + '/' + 'inpaint_out' + '.mp4'
    # return input_video

在这里插入图片描述

四、项目源码

1.项目配置

我使用的硬件环境是GPU是3080,在目前项目只能处理短视频,对输入视频的尺寸也有限制,输入的尺寸过大会出现GPU内存不够用的现象,输入的视频太长,超过1分钟的视频,会出现卡死的现象。

2.项目源码

为了运行方便,这里把项目打包成一个包,下载之后直接运行,不用安装任何环境,但要在GPU下使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1122680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SolidWorks模型导入到Gazebo中

首先建立好solidworks模型,然后另存为stl格式, 导出为STL文件时,文件名最好不要是中文,并且要将后缀STL改为stl,否则Gazebo无法识别 这是我创建好的机器人充电桩模型: 尺寸是单位是mm: 135mm …

C语言程序的翻译环境和执行环境

目录 一、概述:翻译环境、执行环境 1.翻译环境 2.执行环境(运行环境) 二、详述翻译环境——编译环境、链接环境 1.编译环境 2.链接环境 三、详述编译过程——预编译、编译、汇编 1.预编译(预处理) 2.编译&…

JVM 堆外内存查看方法

JVM 堆外内存查看方法 概述 是否曾经想过为什么Java应用程序通过众所周知的*-Xms和-Xmx调整标志消耗的内存比指定的数量大得多 ?由于各种原因和可能的优化,JVM可能会分配额外的本机内存。这些额外的分配最终可能使消耗的内存超出-Xmx* 限制。在本教程中…

第6周 .NET

好嘛!本来以为上周SQL Server环境配置等已经够恶心了,没想到这周又得去搞所谓的Microsoft Visual Studio 2005了。 首先非常离谱的是,这个Microsoft Visual Studio 2005如果就是指Visual Studio 2005,那么已经是8年前的老的不行的…

【技能树笔记】网络篇——练习题解析(八)

目录 前言 一、LAN技术 1.1 堆叠与集群 1.2 MSTP的特点 二、WAN技术 2.1 PPP链路建立 2.2 PPPoE 2.3 组播 2.3.1 组播的IP 2.3.2 组播分发树 2.3.3 组播协议 三、IPv6基础 3.1 IPv6地址 3.2 IPv6协议 3.3 IPv6过渡技术 总结 🌈嗨!我是Filotimo__&#x1…

快速拿下 AI Prompt 工程师证书攻略!

Datawhale干货 贡献者:许文豪、司玉鑫、甘元琦 Prompt 是 AI 2.0 时代打开大模型能力的金钥匙,它能够大大的提高工作效率。 如果把大语言模型 (LLM,Large Language Model) 具象成一个的员工,那 Prompt 提示词则好比是你给员工下的…

留意差距:弥合网络安全基础设施的挑战

您最近一直在关注日益增加的网络威胁吗?如果您发现自己沉浸在 IT 或技术中,那么您可能会永远追求与时俱进。每天都会出现新的漏洞,这对保持消息灵通提出了巨大的挑战。 构建和维护能够应对复杂攻击者的网络安全基础设施所面临的挑战是真实存…

idea的debug调试

目录 断点条件设置(condition) 断点表达式(evaluate expression) 断点回退(reset frame) 断点条件设置(condition) 条件断点,一般是满足我们设置的某个条件时,debug断点才会生效。这种条件断点设置,我们一般用在多重循环中。 这儿我们以li…

codeforces (C++ In Love )

题目: 翻译: 思路: 1、在一个集合中有多组线段,如果有不相交的两组线段,则输出YES,否则输出NO。 2、每次操纵可以选择增加一组线段或者删除一组线段后,输出YES或者NO。 3、用flag标记该线段是否…

数据结构: map与set的简单实现

目录 map与set的模拟实现 1.基本框架 2.模拟实现map与set所需要做的事 1.使用模板 , 达到泛性编程 2.比较问题 3.迭代器 RBTree中: operator operator-- 4.map [ ] 的实现 5.使用普通迭代器构造const迭代器 效果 map与set的模拟实现 1.基本框架 map set 2.模拟实…

nodejs+vue旅行社网站系统-计算机毕业设计

激发了管理人员的创造性与主动性,对旅行社网站系统而言非常有利。 模块包括主界面,首页、个人中心、用户管理、景点分类管理、旅游景点管理、景点购票管理、酒店信息管理、酒店预定管理、旅游路线管理、系统管理等进行相应的操作。 目 录 摘 要 I ABST…

力扣刷题 day52:10-22

1.数组拆分 给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。 返回该 最大总和 。 方法一:排序 #方法一:排序 def arrayPai…

【计算机网络】UDP的报文结构和注意事项

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的协议,它在传输层中提供了简单、不可靠的数据传输服务。与TCP(Transmission Control Protocol,传输控制协议)不同,UDP不需要建立连接&…

驱动开发3 ioctl函数的使用+3个实例(不传递第三个参数、第三个参数为整型、第三个参数为地址)

开发板:stm32mp157aaa(Cortex-A7*2 Cortex-M4*1)开发环境:vscode、串口工具 1 引入ioctl函数的意义 linux操作系统中有意将数据的读写和读写功能的选择分别交给不同的函数去完成。就让read/write函数只进行数据的读写即可&#x…

手把手创建属于自己的ASP.NET Croe Web API项目

第一步:创建项目的时候选择ASP.NET Croe Web API 点击下一步,然后配置: 下一步:

Openssl数据安全传输平台007:共享内存及代码的实现 ——待完善项目具体代码和逻辑

文章目录 0. 代码仓库1. 使用流程案例代码: 2. API解析2.1 创建或打开一块共享内存区2.2 将当前进程和共享内存关联到一起2.3 将共享内存和当前进程分离2.4 共享内存操作 -( 删除共享内存 ) 3. 思考问题3. ftok函数4. 共享内存API封装-以本项…

rust学习——栈、堆、所有权

文章目录 栈、堆、所有权栈(Stack)与堆(Heap)栈堆性能区别所有权与堆栈 所有权原则变量作用域所有权与函数返回值与作用域 栈、堆、所有权 栈(Stack)与堆(Heap) 栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于…

互联网Java工程师面试题·Java 面试篇·第五弹

目录 79、适配器模式和装饰器模式有什么区别? 80、适配器模式和代理模式之前有什么不同? 81、什么是模板方法模式? 82、什么时候使用访问者模式? 83、什么时候使用组合模式? 84、继承和组合之间有什么不同&#…

【BIGRU预测】基于双向门控循环单元的多变量时间序列预测(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

改进YOLO系列 | YOLOv5/v7 引入 Dynamic Snake Convolution | 动态蛇形卷积

准确分割拓扑管状结构,如血管和道路,在各个领域中至关重要,可以确保下游任务的准确性和效率。然而,许多因素使任务复杂化,包括细小的局部结构和可变的全局形态。在这项工作中,我们注意到管状结构的特殊性,并利用这一知识来引导我们的DSCNet,以在三个阶段同时增强感知:…