iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第1张图片

       笔者之前发表的音视频文章,有图像的处理,音频的重采样等等,都属于入门级别。通过阅读它们,读者能对音视频有了了解。可在G‍itee上面回顾。

       2023 年,笔者将整理下 关于 OpenGLES 的实验室系列 并进行发表。首先为读者带来2D篇的系列,它大多是 x y 坐标,不涉及 z 坐标,所以用 2D篇。内容上,它不对 OpenGLES 的基础知识进行细说与讨论。但如果对 OpenGLES 不了解或者了解一点,仍可通过本实验室系列了解 OpenGLES。它旨在激起读者的兴趣,扩展到实际的应用上。总的来说,这些实验 & Demo 将是额外的,即对基础学习的补充,通过这些它们的实践和运用,能让读者进一步了解 OpenGLES 。

前言

       本次实验室带来的是《OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图》。

       如果读者还记得之前其他作者发过的一篇文章《如何实现图片的扭曲效果,窗帘效果及仿真水波纹效果,修图技术之瘦身瘦脸效果的实现(android-drawBitmapMesh)》,是介绍 Android 的 drawBitmapMesh,可以快速实现图像扭曲效果的API。那时笔者看完后,想想 iOS 也可以有,基于 OpenGLES 封装出类似的 API。因此有了本次实验 & Demo。

Demo

       通过手势从脸部边缘向内滑动,Demo 效果比较一般,因为网格的细粒度不够,所以拉拽影响区域大,导致变形夸张。Demo 使用的人物图片是从《Android:修图技术之瘦脸效果的实现(drawBitmapMesh) 》 里面的,如有侵权可以告知笔者删除。

       Git 地址:QHDrawBitmapMeshMan: iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图

实验

效果

       这是瘦脸修图前后效果(不得不说修图真的是个技术活):

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第2张图片

修图前

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第3张图片

修图后

结构图

       native 层进行 mash 的变换计算,导入 OpenGLES 后进行转换后再渲染。

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第4张图片

绘制网格效果

       绘制出 10 * 10 的网格

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第5张图片

绘制网格代码

// 计算网格的顶点在 view 上的实际坐标
for (int i = 0; i < h + 1; i++) {
    float fy = bmHeight * i / h;
    for (int j = 0; j < w + 1; j++) {
        float fx = bmWidth * j / w;
        [verts addObject:@(fx)];
        [verts addObject:@(fy)];
    }
}

// CG 绘制网格
- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
}

指定网格效果

       指定 第1 和 第97 网格,输出固定颜色区域

       这是直观的调试编写的 GLSL 的方法,因为 Xcode 是无法对 GLSL 进行断点调试 和 打印日志,所以通过固定颜色来进行 debug 判断。

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第6张图片

在 shader 里面进行判断

// idx 为 100 个网格的编号
// 由于是 10 * 10,所以下面指定格子进行测试
if (idx == 97) {// 将 97 号网格的纹理都绘制成 蓝色
    gl_FragColor = vec4(0, 0, 1, 1);
}
else if (idx == 1) {// 将 1 号网格的纹理都绘制成 绿色
    gl_FragColor = vec4(0, 1, 0, 1);
}
else {// 其网格的绘制成原始纹素
    gl_FragColor = vec4(texture2D(inputImageTexture, v_texcoord).rgb, 1);
}

扭曲手势及扭曲网格算法

       使用扭曲手势的起点和终点,满足范围的网格点就行比例偏移,这样初始的正矩形就会变成不规则矩形。

- (void)warp2Start:(CGPoint)sp end:(CGPoint)ep {

    NSMutableArray *v = self.verts;
    CGFloat width = self.frame.size.width;
    CGFloat height = self.frame.size.height;
    float w = width/R;
    float h = height/R2;
    // 满足条件的区域半径
    CGFloat r = MAX(w, h) + 0.5/*敏感值*/;

    float dse_x = ep.x - sp.x;
    float des_y = ep.y - sp.y;
    // 求出滑动的直线距离
    float des = sqrtf(dse_x*dse_x + des_y*des_y);

    for (int i = 0; i < v.count; i += 2) {
        float x = [v[i] floatValue];
        float y = [v[i + 1] floatValue];
        
        // 边角区域不修改,保证图像依然是矩形
        if (x == 0 || y == 0 || x == width || y == height) {
            continue;
        }
        else {
            float xx = x - sp.x;
            float yy = y - sp.y;
            // 该点是否在满足的区域,先判断是否在矩形区域,再判断圆形区域
            if (fabsf(xx) <= r && fabsf(yy) <= r) {
                float dxy = sqrtf(xx*xx + yy*yy);
                if (dxy <= r) {
                    // 满足条件的计算出偏移值,并控制偏移值的比例(最大值为半个区域距离)
                    float dr = (r * r - dxy);
                    float e = dr * dr / ((dr + des * des) * (dr + des * des));
                    float tx = MIN(e * dse_x, w/2);
                    float ty = MIN(e * des_y, h/2);
                    v[i] = @(x + tx);
                    v[i + 1] = @(y + ty);
                }
            }
        }
    }
    
    // ... ...
}

网格扭曲效果

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第7张图片

扭曲后指定网格效果

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第8张图片

透视变换

       由于指定算法兼容不规则矩形,所以通用

       这就是《iOS:OpenGLES 实验室之2D篇 第一弹 の 智能弹幕》提到的算法,可以对任意四边形继续判断,可以返回跳到 Shader 章节进行回顾。

mediump float a;
mediump float b;
mediump float c;
mediump float d;//分别存四个向量的计算结果;
a = (x[1] - x[0])*(fy - y[0]) - (y[1] - y[0])*(fx - x[0]);
b = (x[2] - x[1])*(fy - y[1]) - (y[2] - y[1])*(fx - x[1]);
c = (x[3] - x[2])*(fy - y[2]) - (y[3] - y[2])*(fx - x[2]);
d = (x[0] - x[3])*(fy - y[3]) - (y[0] - y[3])*(fx - x[3]);
if ((a >= 0.0 && b >= 0.0 && c >= 0.0 && d >= 0.0) || (a <= 0.0 && b <= 0.0 && c <= 0.0 && d <= 0.0)) {
    idx = i + j * w;
    break;
}

       透视变换算法是网上移植修改——《【图像处理】透视变换 Perspective Transformation》,其关键是用 八个点坐标构建一组八个的方程组后求出透视变化的 3*3 矩阵。

std::tuple PerspectiveTransform::transform(float x, float y) {
    float denominator = a13 * x + a23 * y + a33;
    float xx = (a11 * x + a21 * y + a31) / denominator;
    float yy = (a12 * x + a22 * y + a32) / denominator;
    return std::make_tuple(xx, yy);
}

       计算出透视矩阵后传入到 shader 进行转换

mediump mat3 pt = h_pt[idx];

mediump float x = v_texcoord.x;
mediump float y = v_texcoord.y;
mediump float denominator = pt[0][2] * x + pt[1][2] * y + pt[2][2];
mediump float xx = (pt[0][0] * x + pt[1][0] * y + pt[2][0]) / denominator;
mediump float yy = (pt[0][1] * x + pt[1][1] * y + pt[2][1]) / denominator;

gl_FragColor = vec4(texture2D(inputImageTexture, vec2(xx, yy)).rgb, 1);

仿射变换

       这里用仿射变换来侧面聊聊变换的推导过程,仿射变换也可以看做是一种特殊的透视变换(z轴方向不变),所以这里只需使用 2x2 矩阵。如下是求导过程:

iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图_第9张图片

       最终目标是求出一个逆矩阵,知道 原始4顶点 & 目标4顶点,通过它们求得矩阵,最终获取逆矩阵,此步骤(即1-5)都在 CPU 上执行,当手势滑动改变的网格就会有产生一个新的逆矩阵

       步骤6是在 GPU,用求得的逆矩阵来将变换的 x,y 映射回来的 x,y,从而得到真正需要渲染的纹素。

       以上是通过仿射变换来简单模拟和解说《【图像处理】透视变换 Perspective Transformation》的推导,透视变换是求3x3矩阵。

原始数据

       这里附上将 png 解码 rgba 的 ffmpeg 指令,这样直接传入给 Shader。因为 jpg / png 是编码后的图片,给 Shader 使用的需要原始数据 rgb / yuv,所以还要解码。而 Apple 内置的 Image 有快速转化 textureId 的高级 API,读者可以自行学习。该指令也会在下一个实验被应用到

ffmpeg -i test00.png -vcodec rawvideo -pix_fmt rgb24 head.rgb
或
ffmpeg -i test00.png -vcodec rawvideo -pix_fmt rgba head_rgba.rgb

最后

       原理简单点说就是:

       微观上,将单个像素点的纹理值用其他纹理值替换,从而到达宏观上,整体的改变,而这里的改变就是瘦脸。读者也可以往外拉,哈哈。这里的重点就是 OpenGLES 的整个流程编程,GLSL 的编写,还有透视矩阵的应用。

       感谢各位读者,那就下个实验,再见啦!

       欢迎读者回顾本系列的文章:

  • 《iOS:OpenGLES 实验室之2D篇 第一弹 の 智能弹幕

链接

  • 《Gitee》:

    https://gitee.com/chenqihui

  • 《如何实现图片的扭曲效果,窗帘效果及仿真水波纹效果,修图技术之瘦身瘦脸效果的实现(android-drawBitmapMesh)》:https://mp.weixin.qq.com/s/qqoBLsjU69YpWjkzBGgM_w

  • 《Android:修图技术之瘦脸效果的实现(drawBitmapMesh)》:https://www.jianshu.com/p/51d8dd99d27d

  • 《QHDrawBitmapMeshMan: iOS:OpenGLES 实验室之2D篇 第二弹 の 瘦脸修图》:

    https://gitee.com/chenqihui/qhdraw-bitmap-mesh-man

  • 《【图像处理】透视变换 Perspective Transformation》: 

    https://blog.csdn.net/xiaowei_cqu/article/details/26471527

你可能感兴趣的:(ios)