相机之廋脸

相机之使用OpenGL预览
相机之使用OpenGL拍照
相机之使用OpenGL录像
相机之为录像添加音频
相机之大眼
相机之贴纸
相机之口红
相机之美颜磨皮
相机之LUT滤镜

相机廋脸效果

瘦脸效果实现:将指定圆形区域内的像素按照一定的规则进行整体偏移,越靠近圆心,像素偏移的强度越大,示意图:


jialing.jpeg

片段着色器

precision highp float;

varying vec2 v_TextureCoord;
uniform sampler2D vTexture;

// 圆半径
uniform float leftRadius;
// 拉伸中心坐标
uniform vec2 leftOrigin;
// 拉伸目标坐标
uniform vec2 leftTarget;

uniform float rightRadius;
uniform vec2 rightOrigin;
uniform vec2 rightTarget;

// textureCoord:采样坐标;originPosition:拉伸中心坐标;targetPosition:拉伸目标坐标;radius:拉伸半径;curve:拉伸算法参数
vec2 stretch(vec2 textureCoord, vec2 originPosition, vec2 targetPosition, float radius, float curve){
    // 存储当前采样点到应该采样的点的偏移值
    vec2 offset = vec2(0.0);
    // 应该采样的点
    vec2 result = vec2(0.0);
    // 偏移的方向
    vec2 direction = targetPosition - originPosition;
    // 采样坐标距离拉伸中心坐标越近,偏移越远
    float infect = distance(textureCoord, originPosition) / radius;
    // 放大infect的影响,默认curve为1,即不放大影响,这个值越大,拉伸到指定点越圆润,越小越尖
    infect = pow(infect, curve);
    infect = 1.0 - infect;
    infect = clamp(infect, 0.0, 1.0);
    // 要偏移的值
    offset = direction * infect;
    result = textureCoord - offset;
    return result;
}

void main(){
    vec2 stretchedCoord = stretch(v_TextureCoord, leftOrigin, leftTarget, leftRadius, 1.0);
    stretchedCoord = stretch(stretchedCoord, rightOrigin, rightTarget, rightRadius, 1.0);
    gl_FragColor = texture2D(vTexture, stretchedCoord);
}

着色器程序

class FaceLiftFilter(context: Context, width: Int, height: Int) :
    FboFilter(context, R.raw.base_vertex, R.raw.face_lift_frag, width, height) {
    private var faceLiftRatio: Float = 0f
    private lateinit var matrix: FloatArray
    private var facePosition: FloatArray = FloatArray(84 * 2)

    // 圆的半径,只有在圆内才会进行变形
    private val leftRadiusLocation = GLES20.glGetUniformLocation(mProgram, "leftRadius")

    // 拉伸中心坐标,相当于PS中使用液化功能时,鼠标点下的地方
    private val leftOriginLocation = GLES20.glGetUniformLocation(mProgram, "leftOrigin")

    // 拉伸目标坐标,相当于PS中使用液化功能时,鼠标抬起的地方
    private val leftTargetLocation = GLES20.glGetUniformLocation(mProgram, "leftTarget")

    private val rightRadiusLocation = GLES20.glGetUniformLocation(mProgram, "rightRadius")
    private val rightOriginLocation = GLES20.glGetUniformLocation(mProgram, "rightOrigin")
    private val rightTargetLocation = GLES20.glGetUniformLocation(mProgram, "rightTarget")

    // 左太阳穴索引
    private val leftTempleIndex = 63

    // 右太阳穴索引
    private val rightTempleIndex = 62

    // 下巴索引
    private val chinIndex = 64

    override fun onDrawInFBO(textureId: Int) {

        // 先将textureId的图像画到这一个FBO中
        //激活纹理单元0
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        //将textureId纹理绑定到纹理单元0
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
        //将纹理单元0传给vTexture,告诉vTexture采样器从纹理单元0读取数据
        GLES20.glUniform1i(vTexture, 0)
        //在textureId纹理上画出图像
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)

        // 右太阳穴
        val rightTemple = getPos(rightTempleIndex)
        // 左太阳穴
        val leftTemple = getPos(leftTempleIndex)
        // 下巴
        val chin = getPos(chinIndex)

        // 圆的半径
        var leftRadius = sqrt(
            pow(chin[0] - leftTemple[0], 2f) + pow(chin[1] - leftTemple[1], 2f)
        ) / 2f
        GLES20.glUniform1f(leftRadiusLocation, leftRadius)
        var rightRadius = sqrt(
            pow(chin[0] - rightTemple[0], 2f) + pow(chin[1] - rightTemple[1], 2f)
        ) / 2f
        GLES20.glUniform1f(rightRadiusLocation, rightRadius)

        // 左边鼠标最远的抬起坐标,即左边太阳穴与下巴中点
        val leftMaxUp = floatArrayOf(
            (leftTemple[0] + chin[0]) / 2f,
            (leftTemple[1] + chin[1]) / 2f
        )
        // 右边鼠标最远的抬起坐标,即右边太阳穴与下巴中点
        val rightMaxUp = floatArrayOf(
            (rightTemple[0] + chin[0]) / 2f,
            (rightTemple[1] + chin[1]) / 2f
        )

        // 左边鼠标点下坐标,为左边太阳穴 绕着 左边最远的抬起坐标,逆时针旋转 90
        val leftDown = floatArrayOf(
            leftMaxUp[0] - (leftTemple[1] - leftMaxUp[1]) * sin(90f),
            leftMaxUp[1] + (leftTemple[0] - leftMaxUp[0]) * sin(90f)
        )
        GLES20.glUniform2f(leftOriginLocation, leftDown[0], leftDown[1])
        // 右边鼠标点下坐标,为下巴 绕着 右边最远的抬起坐标,逆时针旋转 90
        val rightDown = floatArrayOf(
            rightMaxUp[0] - (chin[1] - rightMaxUp[1]) * sin(90f),
            rightMaxUp[1] + (chin[0] - rightMaxUp[0]) * sin(90f)
        )
        GLES20.glUniform2f(rightOriginLocation, rightDown[0], rightDown[1])

        // 以右边点下坐标为坐标系,y轴和右边鼠标最远的抬起坐标的夹角
        val rightTheta = atan(abs(rightDown[0] - rightMaxUp[0]) / abs(rightDown[1] - rightMaxUp[1]))
        // 以左边点下坐标为坐标系,y轴和左边鼠标最远的抬起坐标的夹角
        val leftTheta = atan(abs(leftDown[0] - leftMaxUp[0]) / abs(leftDown[1] - leftMaxUp[1]))

        // 左边抬起坐标
        val leftUp = floatArrayOf(
            leftDown[0] + leftRadius * faceLiftRatio * sin(leftTheta),
            leftDown[1] + leftRadius * faceLiftRatio * cos(leftTheta)
        )
        GLES20.glUniform2f(leftTargetLocation, leftUp[0], leftUp[1])

        // 右边抬起坐标
        val rightUp = floatArrayOf(
            rightDown[0] - rightRadius * faceLiftRatio * sin(rightTheta),
            rightDown[1] + rightRadius * faceLiftRatio * cos(rightTheta)
        )
        GLES20.glUniform2f(rightTargetLocation, rightUp[0], rightUp[1])

        //解除绑定
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
    }

    /**
     * 获取纹理坐标系中,index索引对应的位置
     * @param index Int
     * @return FloatArray
     */
    private fun getPos(index: Int): FloatArray {
        val pos =
            floatArrayOf(facePosition[index * 2], facePosition[index * 2 + 1], 0f, 1f)
        val posResult = FloatArray(4)
        // 因为facePosition中的人脸数据是向左侧着的,因此位置信息需要旋转90度
        Matrix.multiplyMV(posResult, 0, matrix, 0, pos, 0)
        // 现在坐标是在归一化坐标系中的值,而OpenGL程序中是在texture2D函数中使用,需要转换为纹理坐标
        posResult[0] = (posResult[0] + 1f) / 2f
        posResult[1] = (posResult[1] + 1f) / 2f
        return posResult
    }

    /**
     * 更新人脸顶点位置
     * @param facePosition FloatArray
     */
    fun setFacePosition(facePosition: FloatArray) {
        this.facePosition = facePosition
    }

    /**
     * 设置瘦脸程度
     * @param ratio Float
     */
    fun setFaceLiftRatio(ratio: Float) {
        // 因为faceLiftRatio过大,会变得很畸形,因此在这里控制faceLiftRatio在[0,1/4]之间
        faceLiftRatio = ratio / 4f
    }

    /**
     * 设置变换矩阵,否则人脸位置是旋转90的
     * @param matrix FloatArray
     */
    fun setUniforms(matrix: FloatArray) {
        this.matrix = matrix
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352