Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Metal视频处理——绿幕视频合成

Metal视频处理——绿幕视频合成

原创
作者头像
落影
发布于 2018-09-16 03:43:07
发布于 2018-09-16 03:43:07
4.7K00
代码可运行
举报
文章被收录于专栏:落影的专栏落影的专栏
运行总次数:0
代码可运行

前言

Metal入门教程总结

Metal图像处理——直方图均衡化

本文介绍如何用Metal把一个带绿幕的视频和一个普通视频进行合并。

正文

绿幕视频合成可以分为两步,首先是把视频读取成视频帧并做好对齐,其次是做两个图像的合成。

首先是从正常视频里面读取一帧图像,如下:

正常视频的截图
正常视频的截图

其次是从绿幕视频里面读取一帧图像,如下:

绿幕视频的截图
绿幕视频的截图

最后用Metal把两个图像进行合成,效果预览:

效果
效果

如何把绿色的背景替换成新的图像?

把两个图像拉伸到同样大小再对齐,然后把每个绿色的像素点替换成另外一个图像的颜色,便实现了绿色背景的替换。

绿幕图像
绿幕图像

核心过程是确定替换时机。

RGB、YUV、HSV颜色空间的替换方案大同小异,这里以YUV颜色空间为例,解释其具体的过程。

1、计算绿色rgb(0.0, 1.0, 0.0)的YUV表示

根据具体的转换公式,把RGB的颜色转换成YUV颜色,这里先把要替换的绿色转换成maskYUV。(转换公式见附录)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    constant float3 greenMaskColor = float3(0.0, 1.0, 0.0); // 过滤掉绿色的
  
    float maskY = 0.257 * greenMaskColor.r + 0.504 * greenMaskColor.g + 0.098 * greenMaskColor.b;
    float maskU = -0.148 * greenMaskColor.r - 0.291 * greenMaskColor.g + 0.439 * greenMaskColor.b;
    float maskV = 0.439 * greenMaskColor.r - 0.368 * greenMaskColor.g - 0.071 * greenMaskColor.b;
    float3 maskYUV = float3(maskY, maskU, maskV) + float3(16.0 / 255.0, 0.5, 0.5);
2、把带绿幕的图像从RGB转成YUV

视频图像是从cpu传递到gpu,格式是kCVPixelFormatType_420YpCbCr8BiPlanarFullRange

所以读取出来是yuv的纹理,需要通过yuv=>rgb的转换矩阵进行处理,得到rgb的颜色值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 绿幕视频读取出来的图像,yuv颜色空间
    float3 greenVideoYUV = float3(greenTextureY.sample(textureSampler, input.textureCoordinate).r,
                              greenTextureUV.sample(textureSampler, input.textureCoordinate).rg);
    // yuv转成rgb
    float3 greenVideoRGB = convertMatrix->matrix * (greenVideoYUV + convertMatrix->offset);
3、把正常的图像从RGB转成YUV

这个过程同步骤2,得到正常的图像(不带绿幕),用于第四步时替换绿色背景。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 正常视频读取出来的图像,yuv颜色空间
    float3 normalVideoYUV = float3(normalTextureY.sample(textureSampler, input.textureCoordinate).r,
                             normalTextureUV.sample(textureSampler, input.textureCoordinate).rg);
    // yuv转成rgb
    float3 normalVideoRGB = convertMatrix->matrix * (normalVideoYUV + convertMatrix->offset);
4、计算替换值,混合两个图像

现在我们有三个YUV的属性maskYUVgreenVideoYUVnormalVideoYUV,我们希望在greenVideoYUV接近maskYUV的时候,把greenVideoYUV的值替换成normalVideoYUV,完成我们的替换效果。

Y是亮度值,UV是色度值,比较时只需关注色度值。

引入函数:float smoothstep(float start, float end, float parameter)

起点start和终点end指定最小值和最大值,parameter为与start、end比较的值。

parameter<start,返回 0。

parameter>end,返回 1。

start<parameter<end,返回值(0, 1),越接近边界值变换越平稳。

我们用distance算出maskYUV.yz和greenVideoYUV.yz的差距,如果小于0.1证明两个颜色值很接近(在样例这里就是接近绿色),我们用normalVideoRGB替换掉该颜色值;如果大于0.3证明两个颜色值差别很大,我们保留greenVideoRGB的颜色值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 计算需要替换的值
    float blendValue = smoothstep(0.1, 0.3, distance(maskYUV.yz, greenVideoYUV.yz));
    // 混合两个图像
    return float4(mix(normalVideoRGB, greenVideoRGB, blendValue), 1.0); // blendValue=0,表示接近绿色,取normalColor;

综合上面的步骤,我们得到最终的fragment shader:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constant float3 greenMaskColor = float3(0.0, 1.0, 0.0); // 过滤掉绿色的

fragment float4
samplingShader(RasterizerData input [[stage_in]], // stage_in表示这个数据来自光栅化。(光栅化是顶点处理之后的步骤,业务层无法修改)
               texture2d<float> greenTextureY [[ texture(LYFragmentTextureIndexGreenTextureY) ]], // texture表明是纹理数据,LYFragmentTextureIndexGreenTextureY是索引
               texture2d<float> greenTextureUV [[ texture(LYFragmentTextureIndexGreenTextureUV) ]], // texture表明是纹理数据,LYFragmentTextureIndexGreenTextureUV是索引
               texture2d<float> normalTextureY [[ texture(LYFragmentTextureIndexNormalTextureY) ]], // texture表明是纹理数据,LYFragmentTextureIndexNormalTextureY是索引
               texture2d<float> normalTextureUV [[ texture(LYFragmentTextureIndexNormalTextureUV) ]], // texture表明是纹理数据,LYFragmentTextureIndexNormalTextureUV是索引
               constant LYConvertMatrix *convertMatrix [[ buffer(LYFragmentInputIndexMatrix) ]]) //buffer表明是缓存数据,LYFragmentInputIndexMatrix是索引
{
    constexpr sampler textureSampler (mag_filter::linear,
                                      min_filter::linear); // sampler是采样器
    
    /*
     From RGB to YUV

     Y = 0.299R + 0.587G + 0.114B
     U = 0.492 (B-Y)
     V = 0.877 (R-Y)
     
     上面是601
     
     下面是601-fullrange
     */
    float maskY = 0.257 * greenMaskColor.r + 0.504 * greenMaskColor.g + 0.098 * greenMaskColor.b;
    float maskU = -0.148 * greenMaskColor.r - 0.291 * greenMaskColor.g + 0.439 * greenMaskColor.b;
    float maskV = 0.439 * greenMaskColor.r - 0.368 * greenMaskColor.g - 0.071 * greenMaskColor.b;
    float3 maskYUV = float3(maskY, maskU, maskV) + float3(16.0 / 255.0, 0.5, 0.5);
    // 绿幕视频读取出来的图像,yuv颜色空间
    float3 greenVideoYUV = float3(greenTextureY.sample(textureSampler, input.textureCoordinate).r,
                              greenTextureUV.sample(textureSampler, input.textureCoordinate).rg);
    // yuv转成rgb
    float3 greenVideoRGB = convertMatrix->matrix * (greenVideoYUV + convertMatrix->offset);
    // 正常视频读取出来的图像,yuv颜色空间
    float3 normalVideoYUV = float3(normalTextureY.sample(textureSampler, input.textureCoordinate).r,
                             normalTextureUV.sample(textureSampler, input.textureCoordinate).rg);
    // yuv转成rgb
    float3 normalVideoRGB = convertMatrix->matrix * (normalVideoYUV + convertMatrix->offset);
    // 计算需要替换的值
    float blendValue = smoothstep(0.1, 0.3, distance(maskYUV.yz, greenVideoYUV.yz));
    // 混合两个图像
    return float4(mix(normalVideoRGB, greenVideoRGB, blendValue), 1.0); // blendValue=0,表示接近绿色,取normalColor;
}
遇到的问题

颜色转换异常。

demo中用到两次转换,分别是shader中maskColor从rgb转yuv和还有读取的图像从yuv转rgb。

其中yuv转rgb的矩阵在GPUImage中可参考:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 设置好转换的矩阵
    matrix_float3x3 kColorConversion601FullRangeMatrix = (matrix_float3x3){
        (simd_float3){1.0,    1.0,    1.0},
        (simd_float3){0.0,    -0.343, 1.765},
        (simd_float3){1.4,    -0.711, 0.0},
    };
    
    vector_float3 kColorConversion601FullRangeOffset = (vector_float3){ -(16.0/255.0), -0.5, -0.5}; // 这个是偏移

rgb转yuv的时候,经过一番查找,终于在rgb和yuv颜色空间的转换找到:

颜色转换矩阵
颜色转换矩阵

注意上述的16、128在shader中的处理要除以255。

总结

绿幕视频合成的实现很顺利,只在计算转换后的颜色值差异时有所疑惑,也顺利解决。故此文章不多赘述,如有疑问直接看源码。

还有文章中没有提及的视频的加载、Metal的相关处理详见demoGithub地址

附录

rgb和yuv颜色空间的转换

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Metal入门教程(五)视频渲染
Metal入门教程(一)图片绘制 Metal入门教程(二)三维变换 Metal入门教程(三)摄像头采集渲染 Metal入门教程(四)灰度计算
落影
2018/07/20
4.5K0
iOS AVDemo(13):视频渲染,用 Metal 渲染丨音视频工程示例
iOS/Android 客户端开发同学如果想要开始学习音视频开发,最丝滑的方式是对音视频基础概念知识有一定了解后,再借助 iOS/Android 平台的音视频能力上手去实践音视频的采集 → 编码 → 封装 → 解封装 → 解码 → 渲染过程,并借助音视频工具来分析和理解对应的音视频数据。
关键帧
2022/06/13
1.1K0
iOS AVDemo(13):视频渲染,用 Metal 渲染丨音视频工程示例
Metal图像处理——直方图均衡化
首先,我们用直方图来表示一张图像:横坐标代表的是颜色值,纵坐标代表的是该颜色值在图像中出现次数。
落影
2018/08/31
1.6K0
Metal图像处理——颜色查找表(Color Lookup Table)
一张1024x1024的普通图片,是由1024 * 1024=1048576个像素点组成,每个像素点包括RGBA共32bit,常见的图像处理是对相邻像素点颜色、像素点本身颜色做处理。 在对像素点本身颜色做处理的情况下,需要把某个颜色映射成另外一个颜色,比如说把颜色rgb(0.2, 0.3, 0.4) * colorMatrix = rgb(0.1, 0.2, 0.3),可以使用shader实现这个颜色转变对图片进行处理。但实际过程中的颜色映射计算过程可能会更加复杂,并且会有很多冗余运算(比如我们对相同的颜色会有重复的运算),我们希望用空间换取时间,把相同颜色值的运算结果缓存下来。
落影
2018/10/08
2.5K0
Metal图像处理——颜色查找表(Color Lookup Table)
Metal入门教程总结
本文介绍Metal和Metal Shader Language,以及Metal和OpenGL ES的差异性,也是实现入门教程的心得总结。
落影
2018/08/21
5.3K0
Metal入门教程总结
OpenGL YUV 和 RGB 图像转换出现偏色问题怎么解决?
早上知识星球里的一位同学,遇到 yuv2rgb 偏色问题,这个问题比较典型,今天展开说一下。
字节流动
2023/11/17
1.3K0
OpenGL YUV 和 RGB 图像转换出现偏色问题怎么解决?
Metal入门教程(二)三维变换
上一篇的教程介绍了如何绘制一张图片,这次的目标是把图片显示到3D物体上,并进行三维变换。
落影
2018/07/01
1.7K3
Metal入门教程(二)三维变换
IOS – OpenGL ES 指定颜色抠图 GPUImageChromaKeyFilter
GPUImageChromaKeyFilter 属于 GPUImage 颜色处理相关,用来处理图片指定颜色抠图
猿说编程[Python和C]
2023/03/23
6060
谈拾取摄像机拍摄景物的颜色转化为指定颜色Demo心得
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
bering
2019/12/02
5340
Metal入门教程(七)天空盒全景
Metal入门教程(一)图片绘制 Metal入门教程(二)三维变换 Metal入门教程(三)摄像头采集渲染 Metal入门教程(四)灰度计算 Metal入门教程(五)视频渲染 Metal入门教程(六)边界检测
落影
2018/08/04
2K0
Metal入门教程(六)边界检测
Metal入门教程(一)图片绘制 Metal入门教程(二)三维变换 Metal入门教程(三)摄像头采集渲染 Metal入门教程(四)灰度计算 Metal入门教程(五)视频渲染
落影
2018/07/29
1.6K0
Metal入门教程(一)图片绘制
这里是一篇Metal新手教程,先定个小目标:把绘制一张图片到屏幕上。 Metal系列教程的代码地址; OpenGL ES系列教程在这里;
落影
2018/06/24
3.5K0
Android OpenGL ES(八) - 简单实现绿幕抠图
这里的关键是,判断颜色的范围。这里简单的认定 g>140.0 && r<128.0 && b<128.0 时为绿色。当是绿色的时候,就将其颜色换成白色。同时alpha值设置为0.0
deep_sadness
2018/10/15
3K0
滤镜之LUT
滤镜基本是相机或者图像处理软件中的标配功能,它能对图像实现各种特殊效果,比如iPhone中的滤镜功能:
雪月清
2020/06/23
2.1K0
iOS GPUImage源码解读(一)
最近在不断学习、使用的过程中,有了更深刻的理解,特来写一篇源码解读的文章详细介绍下核心代码的具体实现。
天天P图攻城狮
2018/02/01
7.2K14
iOS GPUImage源码解读(一)
音视频开发之旅(39)- 高斯模糊实现与优化
我们在平时的开发中模糊是非常常用的技能,在android中有java的开源方案,也有RenderScript方案,今天我们来学习实践通过OpenGL如何实现高斯模糊。 在工作中用到的高斯模糊,也只是做到基本的简单实用,为什么能实现以及是否可以性能优化点提升速度降低内存,之前都欠考虑。 通过这篇我们来学习高斯模糊的原理、实现以及优化,我们的旅程开启。
音视频开发之旅
2021/03/27
2.1K1
音视频开发之旅(39)- 高斯模糊实现与优化
移形换影 - 短视频色彩特效背后的故事
本文介绍了腾讯云短视频(UGSV)众多视频特效中的一种,如果是视频文件,显然要交给计算机自动解决,如何做到呢?
腾讯视频云终端团队
2018/06/15
23.2K6
移形换影 - 短视频色彩特效背后的故事
如何渲染最原始的yuv视频数据?
  我们在用纹理增加细节那篇文章中提到过,要将图片渲染在屏幕上,首先要拿到图片的像素数组数据,然后将像素数组数据通过纹理单元传递到片段着色器中,最后通过纹理采样函数将纹理中对应坐标的颜色值采样出来,然后给最终的片段赋予颜色值。现在换成了yuv视频,我们应该如何处理呢?因为最终的片段颜色值是RGBA格式的,而我们的视频是YUV格式的,所以我们需要做一个转化:即将YUV转化为RGBA。
故乡的樱花开了
2024/02/27
3920
如何渲染最原始的yuv视频数据?
IOS – OpenGL ES 调节图像色彩替换 GPUImageFalseColorFilter
GPUImageFalseColorFilter 属于 GPUImage 颜色处理相关,用来处理图片色彩替换,分别指定用什么颜色代替图像的暗部和亮色区域。默认值为(0.0,0.0,0.5)和(1.0,0.0,0.0),shader 源码如下:
猿说编程[Python和C]
2023/03/21
5070
IOS – OpenGL ES 调节图像色彩替换 GPUImageFalseColorFilter
数字视频基础知识
一、光和颜色 1 光和颜色 可见光是波长在380 nm~780 nm 之间的电磁波,我们看到的大多数光不是 一种波长的光,而是由许多不同波长的光组合成的。如果光源由单波长组成,就 称为单色光源。该光源具有能量,也称强度。实际中,只有极少数光源是单色的, 大多数光源是由不同波长组成,每个波长的光具有自身的强度。这称为光源的光 谱分析。 颜色是视觉系统对可见光的感知结果。研究表明,人的视网膜有对红、绿、 蓝颜色敏感程度不同的三种锥体细胞。红、绿和蓝三种锥体细胞对不同频率的光 的感知程度不同,对不同亮度的感知程度也不同。 自然界中的任何一种颜色都可以由R,G,B 这3 种颜色值之和来确定,以这 三种颜色为基色构成一个RGB 颜色空间。
lcyw
2022/06/10
8300
数字视频基础知识
相关推荐
Metal入门教程(五)视频渲染
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验