[小脚本] 基于opencv 的绿幕抠图

Posted 长虹剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[小脚本] 基于opencv 的绿幕抠图相关的知识,希望对你有一定的参考价值。

网上有一些 基于 opencv-python 的绿幕抠图算法,大多比较简单,只写明了最简单的原理,比如就是选择指定范围的颜色,然后在这个范围内的就抠掉。

但是简单的这样有一些问题,就是比如:
1)有些区域会抠出洞
2)边缘扣不干净,而且会存在锯齿状结果。

解决方案:
1)使用图像闭运算
2)需要求出一个离散数值作为抠图通道权重,而非 0 / 1。 可以用颜色的距离作为这个权重,这样权重在边缘应该会呈现过渡分布。

但是解决方案1),会带来一个问题,就是如果绿幕正好是个小洞(比如OK的手指),这样会带来问题。

最终变做实验边写代码,就有了下面我整理的代码,其实也是作为参考,可能对于不同的任务还得继续调
代码中主要看 get_mask_v3
思路是找到绿幕分割边界地方(mask_b),这部分应该选择过度更平滑的get_mask,容易去掉绿色,也不容易出现锯齿。具体实现是使用三级mask检测,具体可以看代码。

from _chj.comm.pic import *
from _chj.comm.video import *
os.chdir( os.path.split( os.path.realpath( sys.argv[0] ) )[0] )
# rbg: rgb(0, 216, 0)

def main():
    f1_base()

def f1_base():
    cls_w_rgb = Cls_video()
    cls_w_a = Cls_video()
    cls_w_rgb.task_comm( 1, ["tmp/res_rgb", 30, None] )
    cls_w_a.task_comm( 1, ["tmp/res_a", 30, None] )
    cls_read = Cls_video_read().init_files(["a.mp4"]) 
   
    while True:
        st, imgs = cls_read.read_imgs()
        
        if st: break
        img = imgs[0]
        mask = get_mask_v3(img)
        #cv.imwrite("tmp/m.jpg", mask)
        img = get_mask_img(img, mask, 0)
        
        exit()
        cls_w_rgb.task_comm(2, img)
        cls_w_a.task_comm(2, mask)
    
    cls_w_rgb.ffmpeg_aug="-shortest -b:v 2000k"
    cls_w_a.ffmpeg_aug="-shortest -b:v 200k"
    cls_w_rgb.task_comm(3) 
    cls_w_a.task_comm(3) 
    print(122)

def img_open(img, size=5, it=3):
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size))  # 矩形结构
    open = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel, iterations=it)
    return open

def img_close(img, size=5, it=3):
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size))  # 矩形结构
    close = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=it)
    return close

def img_dilate(img, size=5, it=3):
    k = np.ones((size, size), dtype=np.uint8)
    return cv2.dilate(img, k, it)

def img_erode(img, size=5, it=3):
    k = np.ones((size, size), dtype=np.uint8)
    return cv2.erode(img, k, it)

# 非绿幕为1
def get_mask(img, mmin, mmax, minGreen):
    #, minGreen = np.array([0, 255, 0]), maxGreen = np.array([0, 255, 0])
    #hsv = cv2.cvtColor(opencv, cv2.COLOR_RGB2HSV)
    #mask = cv2.inRange(img, minGreen, maxGreen)
    
    dis = np.sqrt( np.power(img - minGreen, 2).mean(-1) )
    #dis = np.power(img - minGreen, 2).sum(-1) #.mean(-1)
    #dis = np.abs(img - minGreen).sum(-1) #.mean(-1)
    
    #mmin, mmax = 5, 400 # 后面这个参数越大,绿幕越小,但是实际上内部已经被平滑了
    dis[ dis<mmin ] = mmin
    dis-=mmin
    dis = dis / mmax
    dis[ dis>1 ] = 1
    ids=dis*255
    #print(dis[50:60, 50:60])
    mask = (dis*255).astype(np.uint8)
    
    #showimg(mask)
    #mask = img_open( mask, 5, 2 )
    ##mask = img_close( mask, 3, 3 )
    #showimg(mask)
    return mask


def get_mask_img(img, mask, isshow=0):
    mr = mask / 255.0
    img = mr[...,np.newaxis] * img + (1-mr)[...,np.newaxis]
        #img[ mask.astype(np.bool_) ] = 0
    img = img.astype(np.uint8)
    if isshow:
        showimg(img)
    return img

# 调试 v1 时想到的
def get_mask_v2_1(img):
    color = np.array([0, 255, 0] )
    mask = get_mask( img, 5, 400, color ) # 需要调的
    mask = img_open( mask, 5, 2 )
    
    mask4 = get_mask( img, 5, 20, color ) # 真正要的参数
    mask4 = img_open( mask4, 3, 2 ) # 去掉一些噪声
   
    mask1 = img_erode(mask4, 5, 5)
    mask2 = img_dilate(mask4, 5, 5)
    
    mask3 = mask1 != mask2 # 找到边界的地方,这些地方用更加缓和的mask
    for e in [mask4, mask, mask1, mask2, mask3.astype(np.uint8)*255]:
        get_mask_img(img, e, 1)
    
    #mask5 = img_close( mask, 5, 2 )
    mask4[mask3] = mask[mask3]
    #mask4[mask1==255] = mask5[ mask1==255 ]
    #mask4 = img_close( mask4, 3, 2 ) # 防止中间有洞
    get_mask_img(img, mask4, 1)
    #exit()
    return mask4

# 1)要考虑过渡
# 2)要想使得能检测出更多的绿色范围,那么 get_mask 的第三个参数非常重要
# 3)考虑到第三个参数太大虽然检测的绿色多,但是容易引起错误,
#       因此需要确定一个边界,对于边界上的用第三个值大些的, 而边界通过 膨胀与腐蚀确定,但是应该时通过第三个值小的确定,
#  但是值太小,有些绿色检测不出来,所里这里是个矛盾点。
# 可能更合理的是采用三个 mask,即第三个值分级

def get_mask_v2(img):
    color = np.array([0, 255, 0] )
    mask = get_mask( img, 3, 150, color ) # 需要调的
    mask = img_open( mask, 5, 2 )
    
    mask4 = get_mask( img, 3, 30, color ) # 真正要的参数
    mask4[mask4!=255] = 0
    mask4 = img_open( mask4, 3, 2 ) # 去掉一些噪声
   
    mask1 = img_erode(mask4, 7, 5)
    mask2 = img_dilate(mask4, 7, 5)
    
    mask3 = mask1 != mask2 # 找到边界的地方,这些地方用更加缓和的mask
    for e in [mask4, mask, mask1, mask2, mask3.astype(np.uint8)*255]:
        get_mask_img(img, e, 1)
    
    mask5 = img_close( mask, 5, 2 ) # 这个可选
    mask4[mask3] = mask[mask3]
    mask4[mask1==255] = mask5[ mask1==255 ]
    #mask4 = img_close( mask4, 3, 2 ) # 防止中间有洞
    get_mask_img(img, mask4, 1)
    #exit()
    return mask4
    
def get_mask_v3(img):
    color = np.array([0, 255, 0] )
    mask1 = get_mask( img, 3, 150, color ) # 用于检测更多的边缘绿色
    mask2 = get_mask( img, 3, 50, color ) # 用于确定边界
    mask3 = get_mask( img, 3, 20, color ) # 把纯绿色的去掉
    
    mask1 = img_open( mask1, 5, 2 )
    mask2 = img_open( mask2, 5, 2 )
    mask3 = img_open( mask3, 5, 2 )
    
    #mask2[mask2!=255] = 0 # 可选
    maskb1 = img_erode(mask2, 7, 5)
    maskb2 = img_dilate(mask2, 7, 5)
    maskb = maskb1 != maskb2
    
    #for e in [mask4, mask, mask1, mask2, mask3.astype(np.uint8)*255]:
    #    get_mask_img(img, e, 1)
    
    mask5 = img_close( mask1, 5, 2 ) # 这个可选
    mask3[maskb] = mask1[maskb]
    mask3[maskb1==255] = mask5[ maskb1==255 ]
    #mask4 = img_close( mask4, 3, 2 ) # 防止中间有洞
    get_mask_img(img, mask3, 1)
    #exit()
    return mask3

if __name__=="__main__": main()


以上是关于[小脚本] 基于opencv 的绿幕抠图的主要内容,如果未能解决你的问题,请参考以下文章

[小脚本] 基于opencv 的绿幕抠图

我的OpenGL学习进阶之旅 OpenGL ES 实现 绿幕抠图 以及 替换绿幕背景的功能

我的OpenGL学习进阶之旅 OpenGL ES 实现 绿幕抠图 以及 替换绿幕背景的功能

AI目标分割能力,无需绿幕即可实现快速视频抠图

学习OpenCV4教你替换绿幕背景

AE抠像-绿幕抠像技术-Primatte Keyer 视频教程 https://www.cgaee.com