【Android 美颜相机】第十八天:GPUImageChromaKeyBlendFilter 解析

GPUImageChromaKeyBlendFilter 代码全解析

本文将逐行解析 GPUImageChromaKeyBlendFilter.java 代码,涵盖代码注释、模块功能、核心逻辑及实际使用方式,该类是 Android 平台基于 OpenGL ES 2.0 实现的色度键混合滤镜(绿幕抠图),属于 GPUImage 开源框架的核心滤镜之一。

完整代码(逐行注释版)

java 复制代码
/*
 * Copyright (C) 2018 CyberAgent, Inc.
 * 版权声明:归属CyberAgent公司,2018年发布
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 授权协议:基于Apache 2.0开源许可证
 * you may not use this file except in compliance with the License.
 * 使用限制:除非遵守许可证条款,否则禁止使用本文件
 * You may obtain a copy of the License at
 * 许可证获取地址:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * 免责声明:除非法律要求或书面协议约定,否则
 * distributed under the License is distributed on an "AS IS" BASIS,
 * 本软件按"原样"分发,不附带任何担保
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 无明示或暗示的担保(包括适销性、特定用途适配性等)
 * See the License for the specific language governing permissions and
 * 详细权限与限制请查阅许可证文本
 * limitations under the License.
 */

// 声明包名:归属CyberAgent的GPUImage滤镜模块
package jp.co.cyberagent.android.gpuimage.filter;

// 导入OpenGL ES 2.0核心API,用于操作着色器、统一变量等底层GL逻辑
import android.opengl.GLES20;

/**
 * Selectively replaces a color in the first image with the second image
 * 类功能描述:选择性将第一张图片中的指定颜色区域替换为第二张图片的内容(绿幕抠图核心逻辑)
 */
public class GPUImageChromaKeyBlendFilter extends GPUImageTwoInputFilter {
    // 定义色度键混合的片段着色器(GLSL代码),运行在GPU上逐像素计算
    public static final String CHROMA_KEY_BLEND_FRAGMENT_SHADER = " precision highp float;\n" +
            // 声明浮点精度为高精度,保证颜色计算无失真
            " \n" +
            " varying highp vec2 textureCoordinate;\n" +
            // 顶点着色器传递的第一个输入纹理坐标(待抠图的绿幕图)
            " varying highp vec2 textureCoordinate2;\n" +
            // 顶点着色器传递的第二个输入纹理坐标(替换的背景图)
            "\n" +
            " uniform float thresholdSensitivity;\n" +
            // 统一变量:颜色匹配阈值(控制抠图的严格程度)
            " uniform float smoothing;\n" +
            // 统一变量:平滑度(控制抠图边缘的渐变过渡)
            " uniform vec3 colorToReplace;\n" +
            // 统一变量:需要替换的目标颜色(RGB归一化值)
            " uniform sampler2D inputImageTexture;\n" +
            // 统一变量:第一个输入纹理的采样器(关联绿幕图纹理ID)
            " uniform sampler2D inputImageTexture2;\n" +
            // 统一变量:第二个输入纹理的采样器(关联背景图纹理ID)
            " \n" +
            " void main()\n" +
            // 片段着色器主函数(每个像素执行一次)
            " {\n" +
            "     vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n" +
            // 采样绿幕图当前像素的RGBA颜色
            "     vec4 textureColor2 = texture2D(inputImageTexture2, textureCoordinate2);\n" +
            // 采样背景图当前像素的RGBA颜色
            "     \n" +
            // 计算目标替换颜色的YCrCb分量(亮度+色度,抠图核心:只比较色度避免亮度干扰)
            "     float maskY = 0.2989 * colorToReplace.r + 0.5866 * colorToReplace.g + 0.1145 * colorToReplace.b;\n" +
            // 目标颜色的亮度(Y):ITU-R BT.601标准转换公式
            "     float maskCr = 0.7132 * (colorToReplace.r - maskY);\n" +
            // 目标颜色的红色差(Cr)
            "     float maskCb = 0.5647 * (colorToReplace.b - maskY);\n" +
            // 目标颜色的蓝色差(Cb)
            "     \n" +
            // 计算绿幕图当前像素的YCrCb分量
            "     float Y = 0.2989 * textureColor.r + 0.5866 * textureColor.g + 0.1145 * textureColor.b;\n" +
            // 当前像素的亮度(Y)
            "     float Cr = 0.7132 * (textureColor.r - Y);\n" +
            // 当前像素的红色差(Cr)
            "     float Cb = 0.5647 * (textureColor.b - Y);\n" +
            // 当前像素的蓝色差(Cb)
            "     \n" +
            // 计算混合权重:判断当前像素是否属于目标颜色,决定替换程度
            "     float blendValue = 1.0 - smoothstep(thresholdSensitivity, thresholdSensitivity + smoothing, distance(vec2(Cr, Cb), vec2(maskCr, maskCb)));\n" +
            /*
             * 核心逻辑拆解:
             * 1. distance:计算当前像素与目标颜色的Cr/Cb(色度)欧氏距离
             * 2. smoothstep:将距离映射到[阈值, 阈值+平滑度]区间,生成0~1的平滑值
             * 3. 1.0 - 结果:距离越近(越接近目标颜色),blendValue越接近1(完全替换为背景图)
             */
            "     gl_FragColor = mix(textureColor, textureColor2, blendValue);\n" +
            // 按权重混合像素:mix(a,b,t),t=0取a(绿幕图),t=1取b(背景图)
            " }";

    // GLSL统一变量的位置句柄(用于Java层向GPU传递参数)
    private int thresholdSensitivityLocation; // 阈值灵敏度变量句柄
    private int smoothingLocation; // 平滑度变量句柄
    private int colorToReplaceLocation; // 目标颜色变量句柄

    // 滤镜默认参数
    private float thresholdSensitivity = 0.4f; // 阈值灵敏度默认值
    private float smoothing = 0.1f; // 平滑度默认值
    private float[] colorToReplace = new float[]{0.0f, 1.0f, 0.0f}; // 默认替换绿色(RGB归一化)

    // 无参构造方法
    public GPUImageChromaKeyBlendFilter() {
        super(CHROMA_KEY_BLEND_FRAGMENT_SHADER);
        // 调用父类构造方法:传入自定义片段着色器,顶点着色器复用父类双输入纹理实现
    }

    @Override
    public void onInit() {
        // 重写父类初始化方法:GL程序创建后调用
        super.onInit();
        // 获取GLSL统一变量的位置句柄(通过变量名关联)
        thresholdSensitivityLocation = GLES20.glGetUniformLocation(getProgram(), "thresholdSensitivity");
        smoothingLocation = GLES20.glGetUniformLocation(getProgram(), "smoothing");
        colorToReplaceLocation = GLES20.glGetUniformLocation(getProgram(), "colorToReplace");
    }

    @Override
    public void onInitialized() {
        // 重写父类方法:GL程序初始化完成后调用
        super.onInitialized();
        // 设置默认参数到GPU的统一变量中
        setSmoothing(smoothing);
        setThresholdSensitivity(thresholdSensitivity);
        setColorToReplace(colorToReplace[0], colorToReplace[1], colorToReplace[2]);
    }

    /**
     * The degree of smoothing controls how gradually similar colors are replaced in the image
     * The default value is 0.1
     * 平滑度Setter:控制相似颜色的替换渐变程度,值越大边缘越自然
     */
    public void setSmoothing(final float smoothing) {
        this.smoothing = smoothing; // 更新本地参数
        setFloat(smoothingLocation, this.smoothing); // 传递给GPU的统一变量
    }

    /**
     * The threshold sensitivity controls how similar pixels need to be colored to be replaced
     * The default value is 0.3
     * 阈值灵敏度Setter:控制像素颜色与目标颜色的相似度要求,值越小越严格
     */
    public void setThresholdSensitivity(final float thresholdSensitivity) {
        this.thresholdSensitivity = thresholdSensitivity; // 更新本地参数
        setFloat(thresholdSensitivityLocation, this.thresholdSensitivity); // 传递给GPU
    }

    /**
     * The color to be replaced is specified using individual red, green, and blue components (normalized to 1.0).
     * The default is green: (0.0, 1.0, 0.0).
     * 目标替换颜色Setter:通过归一化RGB值(0~1)指定要抠除的颜色
     *
     * @param redComponent   红色分量(0~1)
     * @param greenComponent 绿色分量(0~1)
     * @param blueComponent  蓝色分量(0~1)
     */
    public void setColorToReplace(float redComponent, float greenComponent, float blueComponent) {
        colorToReplace = new float[]{redComponent, greenComponent, blueComponent}; // 更新本地颜色
        setFloatVec3(colorToReplaceLocation, colorToReplace); // 传递RGB向量给GPU
    }
}

代码模块功能解析

1. 基础层:版权与依赖导入

  • 版权声明:遵循 Apache 2.0 许可证,明确使用权限;
  • 包导入:jp.co.cyberagent.android.gpuimage.filter 是 GPUImage 框架的滤镜核心包,android.opengl.GLES20 是 Android 操作 OpenGL ES 2.0 的核心 API,用于与 GPU 通信。

2. 核心层:类与继承关系

  • 类名:GPUImageChromaKeyBlendFilter(色度键混合滤镜);
  • 继承:GPUImageTwoInputFilter(双输入纹理滤镜基类),支持同时处理"绿幕图(待抠图)"和"背景图(替换图)"两张输入图片,是实现抠图的基础。

3. 核心逻辑:GLSL 片段着色器

片段着色器是滤镜的核心(运行在 GPU 上),核心逻辑如下:

  • 色彩空间转换:将 RGB 转换为 YCrCb(亮度+色度),仅比较色度(Cr/Cb)避免亮度干扰,是抠图的关键优化;
  • 混合权重计算 :通过 distance 计算当前像素与目标颜色的色度距离,结合 smoothstep 生成 0~1 的混合权重;
  • 像素混合 :通过 mix 按权重混合两张图的像素,实现"目标颜色区域替换为背景图"。

4. 交互层:成员变量与生命周期

  • 统一变量句柄thresholdSensitivityLocation 等变量存储 GLSL 统一变量的索引,是 Java 层向 GPU 传参的"桥梁";
  • 默认参数:预设绿色为抠除颜色、阈值 0.4、平滑度 0.1,适配常规绿幕场景;
  • 生命周期方法
    • onInit():GL 程序创建后,获取统一变量句柄;
    • onInitialized():GL 程序初始化完成后,设置默认参数到 GPU。

5. 配置层:Setter 方法

提供三个核心参数的配置接口,支持动态调整抠图效果:

  • setSmoothing:调整抠图边缘的平滑度,值越大边缘过渡越自然(避免锯齿);
  • setThresholdSensitivity:调整颜色匹配的严格程度,值越小仅"极接近目标颜色"的像素会被替换;
  • setColorToReplace:支持自定义抠除颜色(如蓝幕可传 0.0f, 0.0f, 1.0f)。

着色器(绿幕抠图)工作原理

该着色器的执行规则是为图像的每个像素执行一次main函数,通过「颜色识别→权重计算→像素混合」的三步核心逻辑完成抠图,全程在GPU上并行执行,效率远高于CPU端的抠图计算,适合移动端图片/视频的实时抠图场景。

下面先附上逐行精准注释版代码,再分阶段拆解其完整工作原理,并解析核心参数的作用。

逐行精准注释版代码

c 复制代码
// 声明浮点精度为高精度,保证颜色计算的准确性,避免抠图出现色彩失真
precision highp float;

// 从顶点着色器传递的纹理坐标(易变变量,经光栅化插值后逐像素唯一)
// 对应第一个输入纹理:待抠图的绿幕/蓝幕图(inputImageTexture)
varying highp vec2 textureCoordinate;
// 对应第二个输入纹理:替换的背景图(inputImageTexture2)
varying highp vec2 textureCoordinate2;

// 统一变量:颜色匹配阈值灵敏度(Java层可动态调整,控制抠图的颜色匹配严格程度)
uniform float thresholdSensitivity;
// 统一变量:边缘平滑度(Java层可动态调整,控制抠图边缘的渐变过渡效果,避免锯齿)
uniform float smoothing;
// 统一变量:需要抠除/替换的目标颜色(RGB三维向量,值为0~1的归一化值,如绿色是(0.0,1.0,0.0))
uniform vec3 colorToReplace;
// 统一变量:2D纹理采样器,关联第一个输入纹理(绿幕图)的ID,用于采样像素颜色
uniform sampler2D inputImageTexture;
// 统一变量:2D纹理采样器,关联第二个输入纹理(背景图)的ID
uniform sampler2D inputImageTexture2;

// 片段着色器主函数:每个像素执行一次,是抠图逻辑的核心入口
void main()
{
    // 步骤1:采样两个纹理的当前像素颜色
    // 从绿幕图中采样当前像素的RGBA颜色(vec4:r/g/b/a,均为0~1)
    vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    // 从背景图中采样当前像素的RGBA颜色(与绿幕图像素位置一一对应)
    vec4 textureColor2 = texture2D(inputImageTexture2, textureCoordinate2);
    
    // 步骤2:将「目标替换颜色」从RGB色彩空间转换为YCrCb色彩空间
    // 计算目标颜色的亮度分量Y(ITU-R BT.601标准转换公式,数字图像通用)
    float maskY = 0.2989 * colorToReplace.r + 0.5866 * colorToReplace.g + 0.1145 * colorToReplace.b;
    // 计算目标颜色的红色差分量Cr(色度分量,反映红色与亮度的差值)
    float maskCr = 0.7132 * (colorToReplace.r - maskY);
    // 计算目标颜色的蓝色差分量Cb(色度分量,反映蓝色与亮度的差值)
    float maskCb = 0.5647 * (colorToReplace.b - maskY);
    
    // 步骤3:将「绿幕图当前像素颜色」也转换为YCrCb色彩空间
    // 计算当前像素的亮度分量Y
    float Y = 0.2989 * textureColor.r + 0.5866 * textureColor.g + 0.1145 * textureColor.b;
    // 计算当前像素的红色差分量Cr
    float Cr = 0.7132 * (textureColor.r - Y);
    // 计算当前像素的蓝色差分量Cb
    float Cb = 0.5647 * (textureColor.b - Y);
    
    // 步骤4:计算混合权重blendValue(核心:判断当前像素是否为目标颜色,决定替换程度)
    float blendValue = 1.0 - smoothstep(thresholdSensitivity, thresholdSensitivity + smoothing, distance(vec2(Cr, Cb), vec2(maskCr, maskCb)));
    
    // 步骤5:按权重混合两个像素颜色,输出最终像素颜色
    // mix(a, b, t):GLSL内置混合函数,t=0取a(绿幕图),t=1取b(背景图),0~1之间平滑过渡
    gl_FragColor = mix(textureColor, textureColor2, blendValue);
}

分阶段拆解核心工作原理

整个着色器的抠图逻辑分为5个核心阶段 ,环环相扣,最终实现"精准抠除目标颜色、自然替换背景"的效果,其中YCrCb色彩空间转换混合权重计算是最关键的两个环节。

阶段1:纹理采样------获取两个纹理的当前像素颜色

这是抠图的基础,通过GLSL内置的texture2D(sampler, uv)函数,根据顶点着色器传递并插值后的纹理坐标,分别从**绿幕图(inputImageTexture)背景图(inputImageTexture2)**中采样出当前像素的RGBA颜色,得到两个变量:

  • textureColor:绿幕图当前像素的颜色(待判断是否为目标抠除颜色);
  • textureColor2:背景图当前像素的颜色(若绿幕图当前像素是目标颜色,则用该颜色替换)。

关键细节 :两个纹理的采样坐标textureCoordinatetextureCoordinate2是一一对应的,保证绿幕图的某个像素位置,会精准替换为背景图相同位置的像素,避免背景错位。

阶段2:色彩空间转换------RGB → YCrCb(抠图的核心优化)

这是整个抠图逻辑的灵魂步骤,也是为什么不用直接RGB颜色比较、而要先转换色彩空间的关键。

2.1 为什么要转换为YCrCb?

RGB色彩空间的红、绿、蓝三个分量是相互关联的 ,且受亮度影响极大。比如绿幕图中,同一绿色因光照不均(亮部/暗部),RGB值会相差很大,直接用RGB比较会导致"同色不同值",抠图不精准(比如暗部的绿色没被抠除)。

YCrCb色彩空间 将颜色拆分为三个相互独立的分量,完美解决了亮度干扰的问题:

  • Y(Luminance):亮度分量,反映像素的明暗程度;
  • Cr(Chrominance Red) :红色差分量,反映红色与亮度的差值(色度分量);
  • Cb(Chrominance Blue) :蓝色差分量,反映蓝色与亮度的差值(色度分量)。

其中,Cr和Cb是色度分量,仅代表颜色的"色相",与亮度无关 ------绿幕图中,无论绿色是亮是暗,其Cr和Cb值基本保持不变。因此,通过比较Cr和Cb值来判断是否为目标颜色,能彻底避免光照不均导致的抠图不精准问题,这是工业级绿幕抠图的标准做法。

2.2 转换公式说明

代码中使用的是ITU-R BT.601标准转换公式,这是数字图像(如JPG/PNG/视频)中RGB转YCrCb的通用公式,系数是经过标准化的,保证转换的准确性:

  • 亮度Y:Y = 0.2989*R + 0.5866*G + 0.1145*B(绿色对亮度的贡献最大,红色次之,蓝色最小,符合人眼视觉特性);
  • 红色差Cr:Cr = 0.7132*(R - Y)(提取红色与亮度的差值,隔离亮度影响);
  • 蓝色差Cb:Cb = 0.5647*(B - Y)(提取蓝色与亮度的差值,隔离亮度影响)。

代码中分别对**目标替换颜色(colorToReplace)绿幕图当前像素颜色(textureColor)**执行该转换,得到各自的YCrCb分量,为后续的颜色匹配做准备。

阶段3:色度距离计算------判断当前像素是否为目标颜色

转换为YCrCb后,完全忽略亮度分量Y ,仅通过色度分量Cr和Cb 来判断当前像素是否为目标颜色,核心是计算当前像素的Cr/Cb目标颜色的Cr/Cb 之间的欧氏距离

代码中通过GLSL内置的distance函数实现:

glsl 复制代码
distance(vec2(Cr, Cb), vec2(maskCr, maskCb))
  • vec2(Cr, Cb):绿幕图当前像素的色度坐标(仅保留Cr和Cb,形成二维向量);
  • vec2(maskCr, maskCb):目标替换颜色的色度坐标;
  • distance(a, b):计算两个二维向量之间的欧氏距离,距离越小,说明当前像素的色度与目标颜色的色度越相似,也就是当前像素越接近要抠除的颜色。

举个例子:如果当前像素是纯绿色(目标颜色),其Cr/Cb与目标颜色的Cr/Cb完全一致,距离为0;如果当前像素是红色,其Cr/Cb与目标颜色相差极大,距离会很大。

阶段4:混合权重计算------生成0~1的平滑替换权重

得到色度距离后,需要将其转换为0~1之间的混合权重blendValue ,这个权重决定了当前像素"保留绿幕图"还是"替换为背景图",代码中通过smoothstep函数实现核心转换,再做反向计算:

glsl 复制代码
float blendValue = 1.0 - smoothstep(thresholdSensitivity, thresholdSensitivity + smoothing, distance(...));

先拆解两个核心GLSL内置函数,再讲权重的含义:

4.1 smoothstep:生成平滑的0~1过渡值

smoothstep(min, max, x)是GLSL的平滑步长函数 ,核心作用是将值x映射到[min, max]区间,并生成0~1之间的平滑插值,规则如下:

  • x ≤ min:返回0.0;
  • x ≥ max:返回1.0;
  • min < x < max:返回0~1之间的平滑值(采用三次插值,过渡更自然,无生硬突变)。
4.2 1.0 - 反向计算:匹配抠图的权重逻辑

结合smoothstep和反向计算,blendValue的最终规则为:

色度距离情况 smoothstep返回值 blendValue值 抠图逻辑(混合规则)
距离 ≤ 阈值(极相似) 0.0 1.0 完全替换为背景图(mix取textureColor2)
距离 ≥ 阈值+平滑度(完全不相似) 1.0 0.0 完全保留绿幕图(mix取textureColor)
阈值 < 距离 < 阈值+平滑度(相似但非纯目标色) 0~1的平滑值 1~0的平滑值 平滑混合绿幕图和背景图(边缘过渡)
4.3 两个核心参数的作用(thresholdSensitivity/smoothing)

这两个Java层可动态调整的参数,直接控制抠图的精度边缘自然度,也是该滤镜的可配置核心:

  • thresholdSensitivity(阈值灵敏度) :控制颜色匹配的严格程度。值越小,仅"色度距离极近(极接近目标颜色)"的像素会被判定为抠除区域,抠图更精准;值越大,会将"相似度一般"的像素也判定为抠除区域,可能导致误抠非目标颜色。
  • smoothing(平滑度) :控制抠图边缘的过渡范围 。值越大,[阈值, 阈值+平滑度]的区间越宽,边缘的混合过渡越自然,避免抠图边缘出现锯齿、生硬的断层;值越小,边缘过渡越窄,抠图边缘越锐利,但容易出现锯齿。

举个例子 :若thresholdSensitivity=0.4smoothing=0.1,则色度距离在00.4的像素会完全替换为背景,0.40.5的像素会平滑混合,0.5以上的像素完全保留绿幕图。

阶段5:像素混合与输出------最终实现抠图换背景

这是抠图的最后一步,通过GLSL内置的mix函数,根据计算出的blendValue权重,平滑混合绿幕图和背景图的当前像素颜色,最终赋值给OpenGL内置的输出变量gl_FragColor,作为当前像素的最终显示颜色。

glsl 复制代码
gl_FragColor = mix(textureColor, textureColor2, blendValue);

mix(a, b, t)是GLSL的线性混合函数,核心公式为:

复制代码
mix_result = a * (1 - t) + b * t

结合抠图的权重逻辑,最终效果为:

  • blendValue=1.0(纯目标颜色):mix_result = textureColor*0 + textureColor2*1 = textureColor2 → 完全替换为背景图;
  • blendValue=0.0(非目标颜色):mix_result = textureColor*1 + textureColor2*0 = textureColor → 完全保留绿幕图;
  • blendValue=0.5(边缘像素):mix_result = textureColor*0.5 + textureColor2*0.5 → 绿幕图和背景图各占50%,实现自然的边缘过渡。

最终,所有像素执行完上述逻辑后,绿幕图中的目标颜色区域会被精准抠除并替换为背景图,非目标颜色区域则完整保留,实现完美的抠图换背景效果。

核心参数的调优建议

在实际使用中,通过Java层调整thresholdSensitivitysmoothing两个参数,能适配不同的绿幕/蓝幕素材,达到最佳抠图效果,通用调优建议如下:

  1. 阈值灵敏度(thresholdSensitivity):初始值设为0.4,若抠图不彻底(目标颜色没抠干净),适当增大(如0.5);若出现误抠(非目标颜色被替换),适当减小(如0.3);
  2. 平滑度(smoothing):初始值设为0.1,若抠图边缘有锯齿、生硬,适当增大(如0.2);若边缘过度模糊、丢失细节,适当减小(如0.05);
  3. 目标颜色(colorToReplace) :默认是绿色(0.0,1.0,0.0),若为蓝幕抠图,改为蓝色(0.0,0.0,1.0)即可,也可根据实际素材的颜色微调(如偏黄的绿幕,适当降低绿色分量、提高红色分量)。

整体工作流程总结

该色度键混合片段着色器的完整抠图流程,可概括为6个核心步骤,每个像素独立执行,GPU并行计算保证实时性:

  1. 采样:从绿幕图和背景图中采样当前像素的RGBA颜色;
  2. 转换:将目标替换颜色和绿幕图当前像素颜色,从RGB转换为YCrCb色彩空间,分离亮度和色度;
  3. 提取:忽略亮度分量Y,仅保留两个颜色的色度分量Cr和Cb;
  4. 测距:计算当前像素与目标颜色的Cr/Cb欧氏距离,判断颜色相似度;
  5. 加权 :通过smoothstep和反向计算,将距离转换为0~1的平滑混合权重blendValue
  6. 混合 :通过mix函数按权重混合两个像素颜色,输出最终抠图后的像素颜色。

该实现是移动端工业级绿幕抠图 的经典方案,兼顾了抠图精度边缘自然度,且基于GPU并行计算,能实现图片/视频的实时抠图,广泛应用于直播、短视频、图片编辑等移动端场景。

实际使用步骤(Android 项目)

1. 依赖引入

首先在项目 build.gradle 引入 GPUImage 库:

gradle 复制代码
dependencies {
    implementation 'jp.co.cyberagent.android:gpuimage:2.1.0'
}

2. 核心使用代码

java 复制代码
// 1. 初始化GPUImage(关联显示控件)
GPUImage gpuImage = new GPUImage(context);
GLSurfaceView glSurfaceView = findViewById(R.id.gl_surface_view);
gpuImage.setGLSurfaceView(glSurfaceView);

// 2. 加载输入图片:绿幕图(待抠图)、背景图(替换图)
Bitmap greenScreenBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.green_screen_photo);
Bitmap backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_photo);

// 3. 创建抠图滤镜实例
GPUImageChromaKeyBlendFilter chromaKeyFilter = new GPUImageChromaKeyBlendFilter();

// 4. 自定义参数(可选,默认值也可直接使用)
chromaKeyFilter.setThresholdSensitivity(0.3f); // 更严格的颜色匹配
chromaKeyFilter.setSmoothing(0.2f); // 更自然的边缘过渡
chromaKeyFilter.setColorToReplace(0.0f, 1.0f, 0.0f); // 确认抠除绿色

// 5. 设置滤镜与输入图片
gpuImage.setFilter(chromaKeyFilter);
gpuImage.setImage(greenScreenBitmap); // 设置主纹理(绿幕图)
((GPUImageTwoInputFilter) gpuImage.getFilter()).setSecondInput(backgroundBitmap); // 设置次纹理(背景图)

// 6. 渲染并显示
gpuImage.requestRender();

3. 注意事项

  • 图片尺寸:两张输入图片建议尺寸一致,避免拉伸变形;
  • 参数调优:阈值过小会导致抠图不彻底,过大则会误替换非目标颜色;平滑度过大则边缘模糊,过小则边缘有锯齿;
  • 性能优化:避免频繁修改参数(每次修改都会触发 GPU 统一变量更新),建议一次性配置完成。

总结

GPUImageChromaKeyBlendFilter 是 Android 平台高性能绿幕抠图的经典实现,核心是通过 OpenGL ES 2.0 将抠图逻辑转移到 GPU 执行(相比 CPU 抠图效率提升数倍)。

其核心设计思路是"色度空间分离+平滑混合",既保证了抠图精度,又兼顾了边缘自然度,是移动端音视频、图片编辑场景中绿幕抠图的首选方案。

相关推荐
00后程序员张4 小时前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
陈天伟教授5 小时前
人工智能应用-机器视觉:AI 鉴伪 06.人脸控制技术
人工智能·神经网络·数码相机·生成对抗网络·dnn
悠哉清闲6 小时前
不同车型drawable不同
android·开发语言
00后程序员张9 小时前
在 iOS 设备上同时监控 CPU、GPU 与内存的方法
android·ios·小程序·https·uni-app·iphone·webview
测试_AI_一辰9 小时前
项目实践笔记 9:打卡/日报Agent项目Bug 修改与稳定性收口(v1.0)
android·开发语言·人工智能·功能测试·ai编程·ab测试
马 孔 多 在下雨9 小时前
Kotlin协程进阶王炸之作-Kotlin的协程到底是什么
android·开发语言·kotlin
冬奇Lab9 小时前
【Kotlin系列15】多平台开发实战:一次编写,多端运行
android·开发语言·kotlin
Dxy12393102169 小时前
告别默认排序:MySQL自定义排序的“炼金术”
android·数据库·mysql
请叫我大虾10 小时前
发现一个jdk中ArrayList的小BUG
android·java·bug
一起养小猫10 小时前
Flutter for OpenHarmony 实战:双控制系统实现(按钮+键盘)
android·flutter·计算机外设·harmonyos