美文网首页
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