[小脚本] 基于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 的绿幕抠图的主要内容,如果未能解决你的问题,请参考以下文章
我的OpenGL学习进阶之旅 OpenGL ES 实现 绿幕抠图 以及 替换绿幕背景的功能