最近参加超越杯发生了一段小插曲,主办方要求提交白底的团队介绍VCR,然而之前没有注意到这点,就找了个风景优美的地方拍摄,直到截止前才发现大事不妙。

此时自然而然地就想到抠图了!然而这次的视频背景花里胡哨。

我询问了各路图像大佬,得到的回复往往是几个paper标题,或者几个不明觉厉的词汇,至于直接能拿来用解决燃眉之急的,实在没有。。。

于是我决定从问题本质出发,视频抠图最直接的思路不外乎三步走:

1.从视频转换为一帧帧的图像

2.将每帧图像分别进行人像分离

3.将处理后的每帧图像合并回新视频

其中13两步显然是没有难度的,只要找到对应的视频、图像库。

第2步就是难点、瓶颈所在了。

市面上的人像分割api众多,例如百度的aip、旷视的Face++、国外火爆的remove.bg。

我还是选择了网络稳定,每日允许调用50000次的百度aip,倘若每秒25帧,意味着可以每日处理长度为33.33分钟的视频!


当然如果有离线版api(可以推荐我一下)或者自己训练好的神经网络,肯定是更佳的选择。

下面开始写代码啦

首先是视频转图片部分


import


经过这一步转换,我们约2分钟的测试视频被神奇地转为


这1819张独立的图片


需要一定地存储空间哦~

接下来,关键的一步到了!对每张图片进行抠图处理。


import cv2
import base64
import numpy as np

from aip import AipBodyAnalysis


def do_split_figure(i, color_new = [255, 255, 255]):
    imgfile = 'imgs' + str(i) + '.jpg'
    ori_img = cv2.imread(imgfile)
    height, width, _ = ori_img.shape

    with open(imgfile, 'rb') as fp:
        img_info = fp.read()

    seg_res = client.bodySeg(img_info)
    labelmap = base64.b64decode(seg_res['labelmap'])
    nparr = np.fromstring(labelmap, np.uint8)
    labelimg = cv2.imdecode(nparr, 1)  # 不是0 0 0就是1 1 1的抠图结果矩阵
    labelimg = cv2.resize(labelimg, (width, height), interpolation=cv2.INTER_NEAREST)

    all_one = np.where(labelimg > -1, 1, labelimg)
    #mask_img = np.where(labelimg == 1, 255, labelimg)  # 如果是人的区域rgb都弄成255,否则都是0显示黑色
    # maskfile = imgfile.replace('.jpg', '_mask.png')
    # cv2.imwrite(maskfile, mask_img)

    labelimg = cv2.blur(labelimg, (9, 9))
    sum = labelimg * ori_img + (all_one - labelimg)*color_new

    res_imgfile = 'imgsres' + str(i) + '.jpg'
    result = cv2.resize(sum, None, fx=1, fy=1, interpolation=cv2.INTER_NEAREST)

    cv2.imwrite(res_imgfile, result)

    print(imgfile, 'Done.')

    pass


# 在百度云中申请,每天各接口有 50000 次调用限制.
APP_ID = '****'
API_KEY = '****'
SECRET_KEY = '****'  # '****' 这个key不能给别人看

client = AipBodyAnalysis(APP_ID, API_KEY, SECRET_KEY)

num_img = 1820  # 这个先手动写吧
i_start = 1  # 262

for i in range(num_img):
    if i >= i_start - 1:
        while True:
            bfinish = True
            try:
                do_split_figure(i + 1)
            except:
                bfinish = False
            if bfinish:
                break


只需要调整numimg值为图片总数,就能自动进行抠图啦!

可以在调用do_split_figure函数时指定结果图片的背景色,默认是白底。

如果想看抠图的mask结果,可以将保存mask_img相关的几行代码取消注释。

人像分割得到的mask

值得注意的是,在得到0/1结果矩阵后,需要进行边缘羽化处理,这里我用了简单的cv2.blur,大家如果有更好的方法可以告诉我。

然后就是

sum = labelimg * ori_img + (all_one - labelimg)*color_new

以blur之后的矩阵为比例,将前景色和背景色填上去啦!

最后,激动人心的时刻到了!只需要再将这些图片转回视频就行了。


import cv2
from cv2 import VideoWriter, VideoWriter_fourcc, imread, resize
import os

img_root = "imgsres"
# Edit each frame's appearing time!
fps = 25
fourcc = VideoWriter_fourcc(*"MJPG")
# 分辨率自己看着写
videoWriter = cv2.VideoWriter("TestVideoRes.avi", fourcc, fps, (1920, 1080))

im_names = os.listdir(img_root)
for im_name in range(len(im_names)):
    frame = cv2.imread(img_root + str(im_name+1) + '.jpg')
    print(im_name)
    videoWriter.write(frame)

videoWriter.release()


我们来看一下效果吧。。。

https://www.zhihu.com/video/1103039471712329728


不能说有多棒,但思路就是这样的,如果要改进可以从选择更好的人像分割API或者使用本地训练好的神经网络入手。

视频帧之间也是有联系的,如果追求更好的效果也应该考虑进去,不过就比较专业了。