文章目录
- 前言
- 色度抠图技术(Chroma Keying)
- 基本原理
- 数据准备
- 代码实现
- 性能分析
- 代码优化
- 优化后的速度
前言
现阶段绿幕抠图有很多种方式,比如色度抠图(Chroma Keying)、亮度抠图(Luma Keying)、色差抠图(Color Difference Keying),甚至还有绿幕抠图的模型。不同方式的绿幕抠图效果和效率各有差异,在使用时应该根据的自己的业务需求进行选择。
本文主要介绍色度抠图技术
文末有完整代码以及优化后的代码
色度抠图技术(Chroma Keying)
基本原理
色度抠图技术的工作原理基于颜色空间的差异。它通常将输入的图像从RGB(红、绿、蓝)色彩空间转换到更适合于颜色分离的色彩空间,如HSV(色调、饱和度、亮度)或YUV(亮度、色度U、色度V)。然后,系统分析图像中的每个像素,并将其与预设的“键控颜色”(通常是绿色或蓝色)进行比较。通过计算像素和预设颜色的距离或者相似度来分割前景和后景。(后续的代码实践里面会讲解)
数据准备
准备的绿幕图片前景中不能出现和绿幕背景相同的颜色,不然会被当作背景替换掉。
拍摄绿幕图片时,尽量避免绿幕反光导致人物边缘出现绿光的现象。
背景的颜色均匀且无杂物
代码实现
原图
图片色彩空间转换,前景和后景的分离(掩码图片)
import cv2
import time
import numpy as np
image_path="图片路径"
# 加载图片
image = cv2.imread(image_path) # image的数据类型是numpy.ndarray,其实就是将每一像素点转换成了[b,g,r]
# 转换到HSV颜色空间
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 后续需要根据这个进行分离
# 如果抠图不理想,可以调整一下色彩的范围
lower_green = np.array([35, 43, 46]) # hsv 绿色最小值
upper_green = np.array([77, 255, 255]) # hsv 绿色最大值
# 根据给定的颜色范围创建掩码图片
mask = cv2.inRange(hsv_image, lower_green, upper_green)
# 显示掩码图片,缩放展示cv2.resize(mask.copy(), (image.shape[1] // 2, image.shape[0] // 2))
cv2.imshow('mask', mask)
掩码图片
白色的部分就是绿幕的部分,黑色的部分就是其他的颜色
掩码反转背景替换
# 反转掩码,因为我们最后需要的是黑色的部分,所以需要将黑色和白色进行交换
mask_inv = cv2.bitwise_not(mask)
# 使用掩码提取前景
fg = cv2.bitwise_and(image, image, mask=mask_inv)
背景替换
background_path = '背景图片路径'
background = cv2.imread(background_path)
# 调整背景大小以匹配前景
bg = cv2.resize(background, (image.shape[1], image.shape[0]))
result = bg.copy() # 复制一份,也可以不复制,直接在原图上进行修改
# 利用numpy的向量加速,其实就是将前景所对应的像素点替换到背景所对应的像素点上
result[mask_inv == 255] = fg[mask_inv == 255]
cv2.imshow('Green Screen Keying', result)
第一版代码
import cv2
import time
import numpy as np
def green_screen_keying(image_path, background_path):
# 读取图像
image = cv2.imread(image_path)
background = cv2.imread(background_path)
t1 = time.time()
# 转换到HSV颜色空间
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
print(type(hsv_image))
lower_green = np.array([35, 43, 46])
upper_green = np.array([77, 255, 255])
mask = cv2.inRange(hsv_image, lower_green, upper_green)
cv2.imshow('mask', cv2.resize(mask.copy(), (image.shape[1] // 2, image.shape[0] // 2)))
cv2.imwrite("mask.jpg", mask)
# 反转掩码
mask_inv = cv2.bitwise_not(mask)
# 使用掩码提取前景
fg = cv2.bitwise_and(image, image, mask=mask_inv)
# 调整背景大小以匹配前景
cv2.imwrite("fg.jpg", fg)
bg = cv2.resize(background, (image.shape[1], image.shape[0]))
result = bg.copy()
# print(mask_inv) # 掩膜只有黑色和白色
# 利用numpy的向量加速
result[mask_inv == 255] = fg[mask_inv == 255]
# 显示结果
t2 = time.time()
print(t2 - t1)
cv2.imshow('Green Screen Keying', cv2.resize(result.copy(), (image.shape[1] // 3, image.shape[0] // 3)))
cv2.imwrite("result.jpg", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 使用示例
green_screen_keying('../img/girl.png', '../img/7869190.jpg')
性能分析
电脑配置
系统:window 10
CPU:AMD Ryzen 5 4600H with Radeon Graphics 3.00 GHz
内存:32G,DDR4 3200MHz
电脑配置比较旧,新款的电脑测出的速度应该要更快。
将上面的代码一些无关的显示和存储操作删除以后,经过多次的测试发现替换一张图片1080x1920的图片大概需要0.078s,其中result[mask_inv == 255] = fg[mask_inv == 255]
这一步占了0.055s,占耗时的70%,是一个拖慢效率主主要的步骤。
result[mask_inv == 255] = fg[mask_inv == 255]
实际上这个操作已经避免了循环遍历,利用了numpy本身的特性,效率已经算是够快了(numpy中可能还有更快的方式,欢迎大佬来指点),但是如果我们处理的是一个视频流或直播,需要一种更加高效的方式处理视频流,来提高效率或者保证视频帧的稳定产生。
代码优化
废话不多说直接上代码
import time
import cv2
import numpy as np
def green_screen_keying(image_path, background_path):
image = cv2.imread(image_path)
background = cv2.imread(background_path)
t1 = time.time()
bg = cv2.resize(background, (image.shape[1], image.shape[0]))
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_green = np.array([36, 25, 25])
upper_green = np.array([70, 255, 255])
# 创建掩码
mask = cv2.inRange(hsv_image, lower_green, upper_green)
# 反转掩码
mask_inv = cv2.bitwise_not(mask)
# 使用掩码提取前景,只保留非绿色的部分
fg = cv2.bitwise_and(image, image, mask=mask_inv)
# 使用掩码提取背景,只保留原图片中绿色部分
background_masked = cv2.bitwise_and(bg, bg, mask=mask)
# 将两个图片进行或操作分别保留前景和背景中存在的部分,
result = cv2.bitwise_or(fg, background_masked)
t2 = time.time()
print(t2-t1)
green_screen_keying('../img/girl.png', '../img/7869190.jpg')```
前面关于图片掩码的处理都是一样的,主要的区别就是后面前景图片和后景图片融合的部分。
主要改变就是下面的部分,我们利用掩码和反转掩码分别在前景图片和后景图片中取出需要保留的部分,然后在利用opencv
的bitwise_or
将两个图片进行融合。其实就是或操,图片中被舍弃的部分是“黑色”,两张图片中取非“黑色”部分
# 使用掩码提取前景,只保留非绿色的部分
fg = cv2.bitwise_and(image, image, mask=mask_inv)
# 使用掩码提取背景,只保留原图片中绿色部分
background_masked = cv2.bitwise_and(bg, bg, mask=mask) #
# 将两个图片进行或操作,分别保留前景和背景中存在的部分,
result = cv2.bitwise_or(fg, background_masked)
优化后的速度
一张1080x1920的图片替换绿幕所花费的时间是0.013s,处理一张图片的耗时大概是原来的16.7%,速度是原来的6倍,优化的效果算是比较理想的。
关于背景替换后出现边缘锯齿状的情况,有几个解决方向
- 一个是在拍摄绿幕的时候避免主体内容收到绿光的污染
- 提高原始素材的分辨率,更大的素材像素点就更多,即使出现锯齿状,缩放图片以后也在一定程度上缓解。
- 优化算法,使用opencv一些’膨胀’、’腐蚀‘的形态学操作,还可以使用一些 ’高斯模糊‘,’边缘检测‘等方式
各位看官看官还有更优的方式,欢迎指点。如果对你有所帮助,希望您能点个赞支持一下。