美文网首页
iOS视觉-- (12) OpenGL ES+GLSL实现口红和

iOS视觉-- (12) OpenGL ES+GLSL实现口红和

作者: 桀骜不驯的搬砖者 | 来源:发表于2022-12-13 16:21 被阅读0次

前面我们学习了大眼和瘦脸技巧,接下来我们来学习口红和腮红。口红和腮红实现原理是一致的。下边只会对口红的实现进行分析。
借鉴博客:《Android 美颜类相机开发汇总》第六章 Android OpenGLES 美妆定制实现
借鉴项目:AwemeLike
效果展示:

效果图.gif

实现的方法,我们很容易想到就是把唇印纹理绘制到嘴巴的位置就可以了。
那么实现的步骤就像下面一样:

图1

第一步不做详细介绍就是视频帧的渲染而已。重点是第二步的实现。接下来我们进行第二步的解析。

  • 1. 如何定位嘴巴的位置?

//手动对齐
tCoordinates[i2+0] = GLfloat((faceTextureCoordinates[i2+0] * 1280 - Float(imageBounds.origin.x)) / Float(imageBounds.size.width))
这里相当于嘴巴👄纹理在1280x1280,对应的纹理坐标转换

//x = (1280 - 262.5) / 2 = 508.75 - 7.5(由于图片中心向右偏移6px = 3pt * 2.5) = 501.25
//y = (1280 - 167.5) / 2 = 556.25� //手动对齐
let mouthImageBounds = CGRect(x: 501.25, y: 710, width: 262.5, height: 167.5) //w/h = 1.567164 scale = 2.5 原图:105/67


部分核心代码

        let size = 111 * 2
        var tempPoint: [GLfloat] = [GLfloat].init(repeating: 0, count: size)
        var index = 0
        for i in 0..<faceInfo.landmarks.count {
            let point = faceInfo.landmarks[i].cgPointValue
            tempPoint[i*2+0] = GLfloat(point.x * 2 - 1)
            tempPoint[i*2+1] = GLfloat(point.y * 2 - 1)

            index += 2
            if (index == size) {
                break
            }
        }

        //注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致
        let position = glGetAttribLocation(mouthFaceMarkupProgram, "position")
        glEnableVertexAttribArray(GLuint(position))
        glVertexAttribPointer(GLuint(position), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 0, tempPoint)


        //----处理纹理数据-------
        let tSize = 111 * 2
        let pointCount = faceTextureCoordinates.count / 2
        var tCoordinates: [GLfloat] = [GLfloat].init(repeating: 0, count: tSize)
        for i in 0..<pointCount {
            //手动对齐
            tCoordinates[i*2+0] = GLfloat((faceTextureCoordinates[i*2+0] * 1280 - Float(imageBounds.origin.x)) / Float(imageBounds.size.width))
            tCoordinates[i*2+1] = GLfloat((faceTextureCoordinates[i*2+1] * 1280 - Float(imageBounds.origin.y)) / Float(imageBounds.size.height))
        }
        let textCoord = glGetAttribLocation(mouthFaceMarkupProgram, "inputTextureCoordinate")
        //设置合适的格式从buffer里面读取数据
        glEnableVertexAttribArray(GLuint(textCoord))
        glVertexAttribPointer(GLuint(textCoord), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 0, tCoordinates)

        glActiveTexture(GLenum(GL_TEXTURE0))
        glBindTexture(GLenum(GL_TEXTURE_2D), bgTexture)//mouthFaceMarkupTexture CVOpenGLESTextureGetName(texture!)
        glUniform1i(glGetUniformLocation(self.mouthFaceMarkupProgram, "inputImageTexture"), 0) //单个纹理可以不用设置

        glActiveTexture(GLenum(GL_TEXTURE3))
        glBindTexture(GLenum(GL_TEXTURE_2D), effectTexture)
        glUniform1i(glGetUniformLocation(self.mouthFaceMarkupProgram, "inputImageTexture2"), 3) //单个纹理可以不用设置

        glDrawElements(GLenum(GL_TRIANGLES), GLsizei(faceIndexs.count), GLenum(GL_UNSIGNED_INT), faceIndexs)

人脸识别SDK会返回面部特征顶点后,然后绘制出人脸的位置和口红的位置。



这里会看见脸的部分还是黑色的,我们还要绘制上摄像头。如下着色器代码:

precision mediump float;
varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;

uniform float intensity;
uniform int blendMode;

float blendHardLight(float base, float blend) {
    return blend<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend));
}

vec3 blendHardLight(vec3 base, vec3 blend) {
    return vec3(blendHardLight(base.r,blend.r),blendHardLight(base.g,blend.g),blendHardLight(base.b,blend.b));
}

float blendSoftLight(float base, float blend) {
    return (blend<0.5)?(base+(2.0*blend-1.0)*(base-base*base)):(base+(2.0*blend-1.0)*(sqrt(base)-base));
}
vec3 blendSoftLight(vec3 base, vec3 blend) {
    return vec3(blendSoftLight(base.r,blend.r),blendSoftLight(base.g,blend.g),blendSoftLight(base.b,blend.b));
}

vec3 blendMultiply(vec3 base, vec3 blend) {
    return base*blend;
}

float blendOverlay(float base, float blend) {
    return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend));
}
vec3 blendOverlay(vec3 base, vec3 blend) {
    return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b));
}

vec3 blendFunc(vec3 base, vec3 blend, int blendMode) {
    if (blendMode == 0) {
        return blend;
    } else if (blendMode == 15) {
        return blendMultiply(base, blend);
    } else if (blendMode == 17) {
        return blendOverlay(base, blend);
    } else if (blendMode == 22) {
        return blendHardLight(base, blend);
    }
    return blend;
}

void main()
{
   vec4 fgColor = texture2D(inputImageTexture2, textureCoordinate);//这里是口红/腮红纹理
   fgColor = fgColor * intensity;
   vec4 bgColor = texture2D(inputImageTexture, vec2(textureCoordinate2.x, textureCoordinate2.y));//视频帧纹理
   if (fgColor.a == 0.0) {//透明的部分返回视频帧纹理
       gl_FragColor = bgColor;
       return;
   }
   
   //两种纹理混合叠加后的效果
   vec3 color = blendFunc(bgColor.rgb, clamp(fgColor.rgb * (1.0 / fgColor.a), 0.0, 1.0), blendMode);
//    color = color * intensity;
   //
   gl_FragColor = vec4(bgColor.rgb * (1.0 - fgColor.a) + color.rgb * fgColor.a, 1.0);
}

AwemeLike项目,作者使用的是OpenGL Blend方式 + shader Blend方式进行实现。在GPUImageFaceMarkupFilter文件中, 如下图

但是在实现中发现只有人脸部分,而其他都是黑色。后面不行。想到用之前的方式:前一个的输出作为下一个的输入,和之前大眼瘦脸一样的方式。如下图

但是这里绘制同样的套路绘制腮红的时候。图像反了。。修改了一下片元着色器代码。有两份差不多一样的代码,就是做了一个翻转操作。
到此就结束。难点在于位置对应和混合操作

本文Demo: 码云GitHub

相关文章

网友评论

      本文标题:iOS视觉-- (12) OpenGL ES+GLSL实现口红和

      本文链接:https://www.haomeiwen.com/subject/ogwqqdtx.html