图像滤镜处理算法:柔化、光照、放大镜、哈哈镜

上一篇介绍了图像处理中的 灰度、黑白、底片、浮雕 这篇文章我们继续介绍柔化、光照、放大镜、哈哈镜 效果。

本文的最后提供了完整的Android工程下载,图像处理部分主要采用JNI,算法使用C实现,因为在开发过程中发现使用Java来进行数值处理时,速度绝对是不堪忍受的。现在代码中依然保留了一些Java的滤镜算法接口,即滤镜名称如果分为形如”浮雕”和”浮雕J”的,则前者使用Java实现,后者是C实现,各位可以比较一下两种实现的性能,也能对JNI的高性能有一个直观的认识。

程序提供了一些简单的滤镜效果,运行效果如下:

1 柔化算法
柔化算法的效果是让图片的每一个点与周围点的颜色更平滑,算法原理很简单,就是针对每一个像素,将其颜色值置为周围8个点加上自身的RGB的平均值。不过这样处理后的效果不是很明显,可以采用高斯模糊算法,能获取更好的效果。

原图如下:

采用平均值法的柔化算法处理结果如下:

2 光照
有时需要在照片中增加一个光源这样的效果,如何实现呢?

首先我们设定一个光源中心坐标和光照强度,然后以此光源和图片边缘的最短距离为半径,依次为每个点的RGB增加一个同样的值,当然,图片上的点距离光源中心越远,则增加的颜色值越小,通过这样的方法,就能够实现光照的效果了。

原图如下:

光照处理效果如下:

3 放大镜
前面介绍过通过坐标变换矩阵来缩放、旋转图片的方法,这里通过直接操作图像数据来实现图像的局部放大功能。

假如我们定义放大镜的坐标为(CenterX,CenterY),半径为Radius,而放大倍数为M = 2,那么其实就是将原图中的坐标为(CenterX,CenterY)、半径为Radius/M的区域的图像放大到放大镜覆盖的区域即可,算法其实很简单,对图片上的每一个点(X,Y),求其与(CenterX,CenterY)的距离Distance,若Distance < Radius,则取原图中坐标为(X/M,Y/M)的像素的颜色值作为新的颜色值。

处理效果如下:

代码如下:

jintArray Java_com_spore_meitu_jni_ImageUtilEngine_toFangdajing
  (JNIEnv* env,jobject thiz, jintArray buf, jint width, jint height,jint centerX, jint centerY, jint radius, jfloat multiple)
{
    jint * cbuf;
    cbuf = (*env)->GetIntArrayElements(env, buf, 0);
    int newSize = width * height;
    jint rbuf\[newSize\]; // 新图像像素值
    float xishu = multiple;
    int real_radius = (int)(radius / xishu);
    int i = 0, j = 0;
    for (i = 0; i < width; i++)
    {
        for (j = 0; j < height; j++)
        {
            int curr_color = cbuf\[j * width + i\];
            int pixR = red(curr_color);
            int pixG = green(curr_color);
            int pixB = blue(curr_color);
            int pixA = alpha(curr_color);
            int newR = pixR;
            int newG = pixG;
            int newB = pixB;
            int newA = pixA;
            int distance = (int) ((centerX - i) * (centerX - i) + (centerY - j) * (centerY - j));
            if (distance < radius * radius)
            {
                // 图像放大效果
                int src_x = (int)((float)(i - centerX) / xishu + centerX);
                int src_y = (int)((float)(j - centerY) / xishu + centerY);
                int src_color = cbuf\[src_y * width + src_x\];
                newR = red(src_color);
                newG = green(src_color);
                newB = blue(src_color);
                newA = alpha(src_color);
            }
            newR = min(255, max(0, newR));
            newG = min(255, max(0, newG));
            newB = min(255, max(0, newB));
            newA = min(255, max(0, newA));
            int modif_color = ARGB(newA, newR, newG, newB);
            rbuf\[j * width + i\] = modif_color;
        }
    }
    jintArray result = (*env)->NewIntArray(env, newSize);
    (*env)->SetIntArrayRegion(env, result, 0, newSize, rbuf);
    (*env)->ReleaseIntArrayElements(env, buf, cbuf, 0);
    return result;
}

4 哈哈镜
哈哈镜就是各种千奇百怪的扭曲效果,其实上面的放大镜不是真正的放大镜效果,大家都知道使用凹凸镜时看到的图像其实是有扭曲效果的,因为放大镜并不是一个平滑的镜面,而是越靠经中心的点放大倍数越大,靠近边缘的点放大倍数越小,这样就形成了扭曲的效果,这个效果和哈哈镜比较类似。

于是,我们可以改进上面的放大镜算法来实现一个简单的哈哈镜的扭曲效果,注意放大算法中这两行代码:

int src_x = (int)((float)(i - centerX) / xishu + centerX);
int src_y = (int)((float)(j - centerY) / xishu + centerY);

这就是计算新的颜色值与原图中像素对应坐标的,从这里可以看出,这种方式是简单的根据放大系数等比例的缩放坐标值,即Y = X / M,由于是一元的关系,因此只能实现平滑的缩放的效果,我们应该使用一种二元或者其他的计算方法,使得放大系数与距离成反比。

这是我想到的一种比较简单的方法,是通过抛物线的二项表达式推导出来的,当然,其实效果也不是那里理想,不过好歹能看出扭曲的效果了。不得不感慨当年高数应该好好学习的啊!

将上面的代码替换成下面:

// 放大镜的凹凸效果
int src_x = (int) ((float) (i - centerX) / xishu);
int src_y = (int) ((float) (j - centerY) / xishu);
src_x = (int)(src_x * (sqrt(distance) / real_radius));
src_y = (int)(src_y * (sqrt(distance) / real_radius));
src_x = src_x + centerX;
src_y = src_y + centerY;

现在处理后的效果如下:

下载地址