本文为稀土技能社区首发签约文章,30天内制止转载,30天后未获授权制止转载,侵权必究!

一、前语

在许多视频对话软件中,都能够挑选视频的布景。其原理便是将人像抠出来,把非人像部分替换。而大多数软件是切换图片布景,或许是动图布景。利用图像切割技能,能够完结更复杂的布景替换,最终成果就像电影特效。

许多特效拍摄会使用绿幕布景,而图像切割技能能够在不使用绿幕的情况下到达类似的效果,不过相较绿幕要差一些。

今日咱们要做的便是使用开源的人像切割项目来完结视频布景替换的操作。咱们会在原有项目的基础上持续开发。

二、项目介绍

2.1 项目运行

今日咱们要使用的是一个视频转绿幕的项目。项目地址:github.com/PeterL1n/Ro…

首先需求下载项目:

git clone https://github.com/PeterL1n/RobustVideoMatting.git

下载完结后,安装对应的依靠:

cd RobustVideoMatting
pip install -r requirements_inference.txt

然后下载对应的模型:github.com/PeterL1n/Ro…

能够挑选rvm_mobilenetv3.pth、rvm_resnet50.pth其中一个,放在项目目录下。

然后在项目下创立一个Python文件,写入下面的代码:

import torch
from model import MattingNetwork
from inference import convert_video
# 挑选mobilenetv3或resnet50,对应前面下载的两个模型
model = MattingNetwork('mobilenetv3').eval().cuda()  # or "resnet50"
model.load_state_dict(torch.load('rvm_mobilenetv3.pth'))
convert_video(
    model,                           # 模型
    input_source='input.mp4',        # 视频或许图片目录
    output_type='video',             # 选"video"或"png_sequence"
    output_composition='com.mp4',    # 输出路径
    output_alpha="pha.mp4"           # 预测的alpha通道
    output_video_mbps=4,             
    downsample_ratio=None,          
    seq_chunk=12,                 
)

运行上面代码就能够完结视频抠人像的操作,输出的com.mp4是一个绿幕视频,pha.mp4是一个是非视频(人像为白,布景为黑)。下面是一些比如:

使用图像分割技术实现视频特效

2.2 界说图片抠图和视频抠图函数

在项目中,没有供给自己抠图的代码,所以咱们自己编写对应的代码。项目中model.MattingNetwork便是一个人像切割网络,咱们调用它的前向传达办法,输入图片,它就会回来抠取的alpha通道,代码如下:

import cv2
import torch  
from PIL import Image  
from torchvision.transforms import transforms  
from model import MattingNetwork  
device = "cuda" if torch.cuda.is_available() else "cpu"  
# 加载模型  
segmentor = MattingNetwork('resnet50').eval().cuda()  
segmentor.load_state_dict(torch.load('rvm_resnet50.pth'))  
def human_segment(model, image):  
    src = (transforms.PILToTensor()(image) / 255.)[None]  
    src = src.to(device)  
    # 抠图  
    with torch.no_grad():  
        fgr, pha, *rec = model(src)  
        segmented = torch.cat([src.cpu(), pha.cpu()], dim=1).squeeze(0).permute(1, 2, 0).numpy()  
        segmented = cv2.normalize(segmented, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)  
        return Image.fromarray(segmented)  
human_segment(segmentor, Image.open('xscn.jpg')).show()

现在只需求调用human_segment函数就能完结抠图操作了。而抠视频的操作也十分简单,在项目下供给了下面的代码:

from torch.utils.data import DataLoader
from torchvision.transforms import ToTensor
from inference_utils import VideoReader, VideoWriter
reader = VideoReader('input.mp4', transform=ToTensor())
writer = VideoWriter('output.mp4', frame_rate=30)
bgr = torch.tensor([.47, 1, .6]).view(3, 1, 1).cuda()  # Green background.
rec = [None] * 4                                       # Initial recurrent states.
downsample_ratio = 0.25                                # Adjust based on your video.
with torch.no_grad():
    for src in DataLoader(reader):                     # RGB tensor normalized to 0 ~ 1.
        fgr, pha, *rec = model(src.cuda(), *rec, downsample_ratio)  # Cycle the recurrent states.
        com = fgr * pha + bgr * (1 - pha)              # Composite to green background. 
        writer.write(com)                              # Write frame.

咱们也能够自己编写代码用于视频转化,这儿的操作便是逐帧读取,然后调用human_segment函数。这儿写一个示例代码:

capture = cv2.VideoCapture("input.mp4")
while True:  
    ret, frame = capture.read()  
    if not ret:  
        break  
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  
    result = human_segment(segmentor, Image.fromarray(image))  
    result = cv2.cvtColor(np.array(result), cv2.COLOR_RGB2BGR)  
    cv2.imshow("result", result)  
    cv2.waitKey(10)  
    cv2.destroyAllWindows()

不过上述代码显现时无法显现透明效果。

三、视频布景切换

视频布景切换的思路能够分为下面几个步骤:

  1. 读取人像视频帧和布景视频帧
  2. 对每一帧进行抠图
  3. 把抠出来的人像与布景混合
  4. 把混合成果写入视频

下面咱们一步步来完结。其中1、2步咱们已经完结了,下面要做的便是3、4两个步骤。

3.1 png图片切换布景

第三步能够看做是给png图片换布景的操作,能够把这一步完结为一个函数,代码如下:

from PIL import Image
def change_background(image, background):  
    w, h = image.size  
    background = background.resize((w, h))  
    background.paste(image, (0, 0), image)  
    return background

其中image和background都是Pillow图片。

3.2 写入视频

最后便是写入视频的操作了,这个能够用OpenCV完结,代码如下:

# 读取人像视频和布景视频
capture = cv2.VideoCapture("input.mp4")  
capture_background = cv2.VideoCapture('background.mp4')  
# 获取画面巨细  
fps = capture.get(cv2.CAP_PROP_FPS)  
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))  
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))  
size = (width, height)  
# 写入视频  
fourcc = cv2.VideoWriter_fourcc(*'mp4v')  
out = cv2.VideoWriter('output.mp4', fourcc, fps, size)  
frames = min(capture.get(cv2.CAP_PROP_FRAME_COUNT), capture_background.get(cv2.CAP_PROP_FRAME_COUNT))  
bar = tqdm(total=frames)
while True:  
    ret1, frame1 = capture.read()  
    ret2, frame2 = capture_background.read()  
    # 如果有一个视频完毕了,则完毕
    if not ret1 or not ret2:  
        break  
    image = cv2.cvtColor(frame1, cv2.COLOR_BGR2RGB)  
    segmented = human_segment(segmentor, Image.fromarray(image))  
    background = Image.fromarray(cv2.cvtColor(frame2, cv2.COLOR_BGR2RGB))  
    changed = change_background(segmented, background)  
    changed = cv2.cvtColor(np.array(changed), cv2.COLOR_RGB2BGR)  
    out.write(changed)  
    bar.update(1)  
out.release()

上面的代码便是完结了本章开端提到的四个步骤,最后会输出一个视频。

在代码中,读取了人像和布景视频,并且不要求两个视频长度相同。程序会主动选取较短的视频作为输出视频的时长。

在代码中有一部分BGR转RBG、cv转Pillow的操作,这部分代码还能做一些改善。