半径无关快速高斯模糊实现(附完整C代码)

之前,俺也发过不少快速高斯模糊算法.

俺一般认为,只要处理一千六百万像素彩色图片,在2.2GHz的CPU上单核单线程超过1秒的算法,都是不快的.

之前发的几个算法,在俺2.2GHz的CPU上耗时都会超过1秒.

而众所周知,快速高斯模糊有很多实现方法:

1.FIR (Finite impulse response)

https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A

2.SII (Stacked integral images)

http://dx.doi.org/10.1109/ROBOT.2010.5509400

http://arxiv.org/abs/1107.4958

3.Vliet-Young-Verbeek (Recursive filter)

http://dx.doi.org/10.1016/0165-1684(95)00020-E

http://dx.doi.org/10.1109/ICPR.1998.711192

4.DCT (Discrete Cosine Transform)

http://dx.doi.org/10.1109/78.295213

5.box (Box filter)

http://dx.doi.org/10.1109/TPAMI.1986.4767776

6.AM(Alvarez, Mazorra)

http://www.jstor.org/stable/2158018

7.Deriche (Recursive filter)

http://hal.inria.fr/docs/00/07/47/78/PDF/RR-1893.pdf

8.ebox (Extended Box)

http://dx.doi.org/10.1007/978-3-642-24785-9_38

9.IIR (Infinite Impulse Response)

https://software.intel.com/zh-cn/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions

10.FA (Fast Anisotropic)

http://mathinfo.univ-reims.fr/IMG/pdf/Fast_Anisotropic_Gquss_Filtering_-_GeusebroekECCV02.pdf

......

实现高斯模糊的方法虽然很多,但是作为算法而言,核心关键是简单高效.

目前俺经过实测,IIR是兼顾效果以及性能的不错的方法,也是半径无关(即模糊不同强度耗时基本不变)的实现.

英特尔官方实现的这份(需要翻墙):

IIR Gaussian Blur Filter Implementation using Intel® Advanced Vector Extensions [PDF 513KB] source: gaussian_blur.cpp [36KB]

采用了英特尔处理器的流(SIMD)指令,算法处理速度极其惊人.

俺写算法追求干净整洁,高效简单,换言之就是不采用任何硬件加速方案,实现简单高效,以适应不同硬件环境.

故基于英特尔这份代码,俺对其进行了改写以及优化.

最终在俺2.20GHz的CPU上,单核单线程,不采用流(SIMD)指令,达到了,处理一千六百万像素的彩色照片仅需700毫秒左右.

按照惯例,还是贴个效果图比较直观.

之前也有网友问过这个算法的实现问题.

想了想,还是将代码共享出来,供大家参考学习.

完整代码:

void CalGaussianCoeff(float sigma, float * a0, float * a1, float * a2, float * a3, float * b1, float * b2, float * cprev, float * cnext) {
    float alpha, lamma, k;

    if (sigma < 0.5f)
        sigma = 0.5f;
    alpha = (float)exp((0.726) * (0.726)) / sigma;
    lamma = (float)exp(-alpha);
    *b2 = (float)exp(-2 * alpha);
    k = (1 - lamma) * (1 - lamma) / (1 + 2 * alpha * lamma - (*b2));
    *a0 = k; *a1 = k * (alpha - 1) * lamma;
    *a2 = k * (alpha + 1) * lamma;
    *a3 = -k * (*b2);
    *b1 = -2 * lamma;
    *cprev = (*a0 + *a1) / (1 + *b1 + *b2);
    *cnext = (*a2 + *a3) / (1 + *b1 + *b2);
}

void gaussianHorizontal(unsigned char * bufferPerLine, unsigned char * lpRowInitial, unsigned char  * lpColumn, int width, int height, int Channels, int Nwidth, float a0a1, float a2a3, float b1b2, float  cprev, float cnext)
{
    int HeightStep = Channels*height;
    int WidthSubOne = width - 1;
    if (Channels == 3)
    {
        float prevOut[3];
        prevOut[0] = (lpRowInitial[0] * cprev);
        prevOut[1] = (lpRowInitial[1] * cprev);
        prevOut[2] = (lpRowInitial[2] * cprev);
        for (int x = 0; x < width; ++x) {
            prevOut[0] = ((lpRowInitial[0] * (a0a1)) - (prevOut[0] * (b1b2)));
            prevOut[1] = ((lpRowInitial[1] * (a0a1)) - (prevOut[1] * (b1b2)));
            prevOut[2] = ((lpRowInitial[2] * (a0a1)) - (prevOut[2] * (b1b2)));
            bufferPerLine[0] = prevOut[0];
            bufferPerLine[1] = prevOut[1];
            bufferPerLine[2] = prevOut[2];
            bufferPerLine += Channels;
            lpRowInitial += Channels;
        }
        lpRowInitial -= Channels;
        lpColumn += HeightStep * WidthSubOne;
        bufferPerLine -= Channels;
        prevOut[0] = (lpRowInitial[0] * cnext);
        prevOut[1] = (lpRowInitial[1] * cnext);
        prevOut[2] = (lpRowInitial[2] * cnext);

        for (int x = WidthSubOne; x >= 0; --x) {
            prevOut[0] = ((lpRowInitial[0] * (a2a3)) - (prevOut[0] * (b1b2)));
            prevOut[1] = ((lpRowInitial[1] * (a2a3)) - (prevOut[1] * (b1b2)));
            prevOut[2] = ((lpRowInitial[2] * (a2a3)) - (prevOut[2] * (b1b2)));
            bufferPerLine[0] += prevOut[0];
            bufferPerLine[1] += prevOut[1];
            bufferPerLine[2] += prevOut[2];
            lpColumn[0] = bufferPerLine[0];
            lpColumn[1] = bufferPerLine[1];
            lpColumn[2] = bufferPerLine[2];
            lpRowInitial -= Channels;
            lpColumn -= HeightStep;
            bufferPerLine -= Channels;
        }
    }
    else if (Channels == 4)
    {
        float prevOut[4];

        prevOut[0] = (lpRowInitial[0] * cprev);
        prevOut[1] = (lpRowInitial[1] * cprev);
        prevOut[2] = (lpRowInitial[2] * cprev);
        prevOut[3] = (lpRowInitial[3] * cprev);
        for (int x = 0; x < width; ++x) {
            prevOut[0] = ((lpRowInitial[0] * (a0a1)) - (prevOut[0] * (b1b2)));
            prevOut[1] = ((lpRowInitial[1] * (a0a1)) - (prevOut[1] * (b1b2)));
            prevOut[2] = ((lpRowInitial[2] * (a0a1)) - (prevOut[2] * (b1b2)));
            prevOut[3] = ((lpRowInitial[3] * (a0a1)) - (prevOut[3] * (b1b2)));

            bufferPerLine[0] = prevOut[0];
            bufferPerLine[1] = prevOut[1];
            bufferPerLine[2] = prevOut[2];
            bufferPerLine[3] = prevOut[3];
            bufferPerLine += Channels;
            lpRowInitial += Channels;
        }
        lpRowInitial -= Channels;
        lpColumn += HeightStep * WidthSubOne;
        bufferPerLine -= Channels;

        prevOut[0] = (lpRowInitial[0] * cnext);
        prevOut[1] = (lpRowInitial[1] * cnext);
        prevOut[2] = (lpRowInitial[2] * cnext);
        prevOut[3] = (lpRowInitial[3] * cnext);

        for (int x = WidthSubOne; x >= 0; --x) {
            prevOut[0] = ((lpRowInitial[0] * a2a3) - (prevOut[0] * b1b2));
            prevOut[1] = ((lpRowInitial[1] * a2a3) - (prevOut[1] * b1b2));
            prevOut[2] = ((lpRowInitial[2] * a2a3) - (prevOut[2] * b1b2));
            prevOut[3] = ((lpRowInitial[3] * a2a3) - (prevOut[3] * b1b2));
            bufferPerLine[0] += prevOut[0];
            bufferPerLine[1] += prevOut[1];
            bufferPerLine[2] += prevOut[2];
            bufferPerLine[3] += prevOut[3];
            lpColumn[0] = bufferPerLine[0];
            lpColumn[1] = bufferPerLine[1];
            lpColumn[2] = bufferPerLine[2];
            lpColumn[3] = bufferPerLine[3];
            lpRowInitial -= Channels;
            lpColumn -= HeightStep;
            bufferPerLine -= Channels;
        }
    }
    else if (Channels == 1)
    {
        float prevOut = (lpRowInitial[0] * cprev);

        for (int x = 0; x < width; ++x) {
            prevOut = ((lpRowInitial[0] * (a0a1)) - (prevOut  * (b1b2)));
            bufferPerLine[0] = prevOut;
            bufferPerLine += Channels;
            lpRowInitial += Channels;
        }
        lpRowInitial -= Channels;
        lpColumn += HeightStep*WidthSubOne;
        bufferPerLine -= Channels;

        prevOut = (lpRowInitial[0] * cnext);

        for (int x = WidthSubOne; x >= 0; --x) {
            prevOut = ((lpRowInitial[0] * a2a3) - (prevOut  * b1b2));
            bufferPerLine[0] += prevOut;
            lpColumn[0] = bufferPerLine[0];
            lpRowInitial -= Channels;
            lpColumn -= HeightStep;
            bufferPerLine -= Channels;
        }
    }
}

void gaussianVertical(unsigned char * bufferPerLine, unsigned char * lpRowInitial, unsigned char * lpColInitial, int height, int width, int Channels, float a0a1, float a2a3, float b1b2, float  cprev, float  cnext) {

    int WidthStep = Channels*width;
    int HeightSubOne = height - 1;
    if (Channels == 3)
    {
        float prevOut[3];
        prevOut[0] = (lpRowInitial[0] * cprev);
        prevOut[1] = (lpRowInitial[1] * cprev);
        prevOut[2] = (lpRowInitial[2] * cprev);

        for (int y = 0; y < height; y++) {
            prevOut[0] = ((lpRowInitial[0] * a0a1) - (prevOut[0] * b1b2));
            prevOut[1] = ((lpRowInitial[1] * a0a1) - (prevOut[1] * b1b2));
            prevOut[2] = ((lpRowInitial[2] * a0a1) - (prevOut[2] * b1b2));
            bufferPerLine[0] = prevOut[0];
            bufferPerLine[1] = prevOut[1];
            bufferPerLine[2] = prevOut[2];
            bufferPerLine += Channels;
            lpRowInitial += Channels;
        }
        lpRowInitial -= Channels;
        bufferPerLine -= Channels;
        lpColInitial += WidthStep * HeightSubOne;
        prevOut[0] = (lpRowInitial[0] * cnext);
        prevOut[1] = (lpRowInitial[1] * cnext);
        prevOut[2] = (lpRowInitial[2] * cnext);
        for (int y = HeightSubOne; y >= 0; y--) {
            prevOut[0] = ((lpRowInitial[0] * a2a3) - (prevOut[0] * b1b2));
            prevOut[1] = ((lpRowInitial[1] * a2a3) - (prevOut[1] * b1b2));
            prevOut[2] = ((lpRowInitial[2] * a2a3) - (prevOut[2] * b1b2));
            bufferPerLine[0] += prevOut[0];
            bufferPerLine[1] += prevOut[1];
            bufferPerLine[2] += prevOut[2];
            lpColInitial[0] = bufferPerLine[0];
            lpColInitial[1] = bufferPerLine[1];
            lpColInitial[2] = bufferPerLine[2];
            lpRowInitial -= Channels;
            lpColInitial -= WidthStep;
            bufferPerLine -= Channels;
        }
    }
    else if (Channels == 4)
    {
        float prevOut[4];

        prevOut[0] = (lpRowInitial[0] * cprev);
        prevOut[1] = (lpRowInitial[1] * cprev);
        prevOut[2] = (lpRowInitial[2] * cprev);
        prevOut[3] = (lpRowInitial[3] * cprev);

        for (int y = 0; y < height; y++) {
            prevOut[0] = ((lpRowInitial[0] * a0a1) - (prevOut[0] * b1b2));
            prevOut[1] = ((lpRowInitial[1] * a0a1) - (prevOut[1] * b1b2));
            prevOut[2] = ((lpRowInitial[2] * a0a1) - (prevOut[2] * b1b2));
            prevOut[3] = ((lpRowInitial[3] * a0a1) - (prevOut[3] * b1b2));
            bufferPerLine[0] = prevOut[0];
            bufferPerLine[1] = prevOut[1];
            bufferPerLine[2] = prevOut[2];
            bufferPerLine[3] = prevOut[3];
            bufferPerLine += Channels;
            lpRowInitial += Channels;
        }
        lpRowInitial -= Channels;
        bufferPerLine -= Channels;
        lpColInitial += WidthStep*HeightSubOne;
        prevOut[0] = (lpRowInitial[0] * cnext);
        prevOut[1] = (lpRowInitial[1] * cnext);
        prevOut[2] = (lpRowInitial[2] * cnext);
        prevOut[3] = (lpRowInitial[3] * cnext);
        for (int y = HeightSubOne; y >= 0; y--) {
            prevOut[0] = ((lpRowInitial[0] * a2a3) - (prevOut[0] * b1b2));
            prevOut[1] = ((lpRowInitial[1] * a2a3) - (prevOut[1] * b1b2));
            prevOut[2] = ((lpRowInitial[2] * a2a3) - (prevOut[2] * b1b2));
            prevOut[3] = ((lpRowInitial[3] * a2a3) - (prevOut[3] * b1b2));
            bufferPerLine[0] += prevOut[0];
            bufferPerLine[1] += prevOut[1];
            bufferPerLine[2] += prevOut[2];
            bufferPerLine[3] += prevOut[3];
            lpColInitial[0] = bufferPerLine[0];
            lpColInitial[1] = bufferPerLine[1];
            lpColInitial[2] = bufferPerLine[2];
            lpColInitial[3] = bufferPerLine[3];
            lpRowInitial -= Channels;
            lpColInitial -= WidthStep;
            bufferPerLine -= Channels;
        }
    }
    else if (Channels == 1)
    {
        float prevOut = 0;
        prevOut = (lpRowInitial[0] * cprev);
        for (int y = 0; y < height; y++) {
            prevOut = ((lpRowInitial[0] * a0a1) - (prevOut * b1b2));
            bufferPerLine[0] = prevOut;
            bufferPerLine += Channels;
            lpRowInitial += Channels;
        }
        lpRowInitial -= Channels;
        bufferPerLine -= Channels;
        lpColInitial += WidthStep*HeightSubOne;
        prevOut = (lpRowInitial[0] * cnext);
        for (int y = HeightSubOne; y >= 0; y--) {
            prevOut = ((lpRowInitial[0] * a2a3) - (prevOut * b1b2));
            bufferPerLine[0] += prevOut;
            lpColInitial[0] = bufferPerLine[0];
            lpRowInitial -= Channels;
            lpColInitial -= WidthStep;
            bufferPerLine -= Channels;
        }
    }
}
//本人博客:http://tntmonks.cnblogs.com/ 转载请注明出处.
void  GaussianBlurFilter(unsigned char * input, unsigned char * output, int Width, int Height, int Stride, float GaussianSigma) {

    int Channels = Stride / Width;
    float a0, a1, a2, a3, b1, b2, cprev, cnext;

    CalGaussianCoeff(GaussianSigma, &a0, &a1, &a2, &a3, &b1, &b2, &cprev, &cnext);

    float a0a1 = (a0 + a1);
    float a2a3 = (a2 + a3);
    float b1b2 = (b1 + b2); 

    int bufferSizePerThread = (Width > Height ? Width : Height) * Channels;
    unsigned char * bufferPerLine = (unsigned char*)malloc(bufferSizePerThread);
    unsigned char * tempData = (unsigned char*)malloc(Height * Stride);
    if (bufferPerLine == NULL || tempData == NULL)
    {
        if (tempData)
        {
            free(tempData);
        }
        if (bufferPerLine)
        {
            free(bufferPerLine);
        }
        return;
    }
    for (int y = 0; y < Height; ++y) {
        unsigned char * lpRowInitial = input + Stride * y;
        unsigned char * lpColInitial = tempData + y * Channels;
        gaussianHorizontal(bufferPerLine, lpRowInitial, lpColInitial, Width, Height, Channels, Width, a0a1, a2a3, b1b2, cprev, cnext);
    }
    int HeightStep = Height*Channels;
    for (int x = 0; x < Width; ++x) {
        unsigned char * lpColInitial = output + x*Channels;
        unsigned char * lpRowInitial = tempData + HeightStep * x;
        gaussianVertical(bufferPerLine, lpRowInitial, lpColInitial, Height, Width, Channels, a0a1, a2a3, b1b2, cprev, cnext);
    }

    free(bufferPerLine);
    free(tempData);
}

调用方法:

  GaussianBlurFilter(输入图像数据,输出图像数据,宽度,高度,通道数,强度)

  注:支持通道数分别为 1 ,3 ,4.

关于IIR相关知识,参阅 百度词条 "IIR数字滤波器"

http://baike.baidu.com/view/3088994.htm

天下武功,唯快不破。 本文只是抛砖引玉一下,若有其他相关问题或者需求也可以邮件联系俺探讨。

邮箱地址是: gaozhihan@vip.qq.com

题外话:

很多网友一直推崇使用opencv,opencv的确十分强大,但是若是想要有更大的发展空间以及创造力.

还是要一步一个脚印去实现一些最基本的算法,扎实的基础才是构建上层建筑的基本条件.

俺目前只是把opencv当资料库来看,并不认为opencv可以用于绝大多数的商业项目.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据小魔方

数据地图多图层对象的颜色标度重叠问题解决方案

一篇旧文,解决一个困扰已经的小技术问题,权当是学习ggplot2以来的整理回顾与查漏补缺。 ---- 今天这一篇是昨天推送的基础上进行了进一步的深化,主要讲如何...

3775
来自专栏天天P图攻城狮

终端图像处理系列 - 图像混合模式的Shader实现

在图像处理应用中,将两张或者多张图片混合显示是非常常见的一种操作,应用场景包括但不限于:加水印、标签,插入画中画,遮盖等等...

1.1K17
来自专栏阿凯的Excel

帕累托图(主次图)绘制方法(Excel绘制图表系列课程)

有朋友让我快点、马上、立刻、最先分享帕累托图的绘制方法。什么是帕累托图?主要想表达何种含义呢?让我们慢慢聊。 帕累托图(Pareto chart)由来 是以意大...

3135
来自专栏数据小魔方

上帝视角——给世界一个特写~

最近在研究使用R包制作动画图表,也就是类似GIF动图,感觉很有趣,也是动态图表呈现的一个非常独特的领域,刚刚研究出了些成果,今天这篇分享使用GIF动画+球型投影...

3506
来自专栏数据小魔方

R语言可视化——图表美化与套用主题(上)

ggplot函数所制作的图表默认设计风格虽然经过设计师的精雕细琢,但是并不是尽如人意。 毕竟在这个大众审美水平水平越来越高的时代里,企业中的各式报告也强调与企业...

4055
来自专栏量子位

三分钟训练眼球追踪术,AI就知道你在盯着哪个妹子 | TensorFlow.js代码

如果有个眼球追踪AI,加上人脸识别,或许就能在被老板盯上的瞬间,进入奋力工作模式。

8943
来自专栏RESTART POiNTER

Web H5视频滤镜的“百搭”解决方案——WebGL着色器

视频滤镜,顾名思义,是在视频素材上duang特效的一种操作。 随着H5页面越做越炫酷的趋势,单一的视频播放已经不能满足我们的需求,视频滤镜在Web页面上的应用越...

1.9K3
来自专栏数据小魔方

R语言可视化——地图填充与散点图图层叠加

今天跟大家分享关于如何在地图图层上添加散点图。 散点图需要精确的经纬度信息才能在叠加的图层上进行映射,因此我们选用中国省级轮廓地图以及各省省会城市的经纬度进行案...

5147
来自专栏落影的专栏

OpenGL ES实践教程(六)全景视频获取焦点

教程 OpenGL ES实践教程1-Demo01-AVPlayer OpenGL ES实践教程2-Demo02-摄像头采集数据和渲染 OpenGL ES实践...

3195
来自专栏拂晓风起

Flash:利用Bitmapdata,ColorTransform,DrawPath,制造绚丽效果

1102

扫码关注云+社区

领取腾讯云代金券