观念高斯模糊与优化算法,Java达成高斯模糊和图像的长空卷积

高斯模糊(加泰罗尼亚语:Gaussian Blur),也叫高斯平滑,是在Adobe
Photoshop、GIMP以及Paint.NET等图像处理软件山东中国广播公司大应用的处理效果,平日用它来减弱图像噪声以及降低细节层次。那种歪曲技术生成的图像,其视觉效果仿佛经过三个半透明显示屏在察看图像,那与画面焦外成像效果散景以及普通照明阴影中的效果都醒目例外。高斯平滑也用于总结机视觉算法中的预先处理阶段,以提升图像在分歧期比较例大小下的图像效果(参见尺度空间表示以及尺度空间实现)。
从数学的角度来看,图像的高斯模糊进程正是图像与正态分布做卷积。由高满堂态分布又叫作高斯分布,所以那项技艺就叫作高斯模糊。图像与圆圈方框模糊做卷积将会转移更准确的焦外成像效果。由于高斯函数的傅立叶变换是其它2个高斯函数,所以高斯模糊对于图像来说正是3个低通滤波器。

高斯模糊

高斯模糊(立陶宛(Lithuania)语:Gaussian Blur),也叫高斯平滑,是在Adobe
Photoshop、GIMP以及Paint.NET等图像处理软件广东中国广播集团泛使用的处理效果,平日用它来压缩图像杂讯以及降低细节层次。那种歪曲技术生成的图像,其视觉效果就像经过四个半透明显示屏在察看图像,那与画面焦外成像效果散景以及常见照明阴影中的效果都强烈分歧。高斯平滑也用于计算机视觉算法中的预先处理阶段,以增长图像在不相同期相比重大小下的图像效果。
从数学的角度来看,图像的高斯模糊进度正是图像与正态分布做卷积。由柳盈瑄态分布又叫作高斯分布,所以那项技能就叫作高斯模糊。图像与圆圈方框模糊做卷积将会扭转更精确的焦外成像效果。由于高斯函数的傅立叶变换是此外三个高斯函数,所以高斯模糊对于图像来说正是1个低通滤波器。

高斯模糊使用了高斯的正态分布的密度函数,总计图像中各个像素的转换。

葡萄娱乐场 1

gaussian-function.png

依照壹维高斯函数,能够推导得到贰维高斯函数:

葡萄娱乐场 2

二维高斯函数.png

葡萄娱乐场 3

二维的正太分布.png

其中r是张冠李戴半径,r^二 = x^贰 +
y^贰,σ是正态分布的正规不是。在二维空间中,那些公式生成的曲面包车型客车等高线是从宗旨开始呈正态分布的同心圆。分布不为零的像素组成的卷积矩阵与原有图像做变换。各类像素的值都是周边相邻像素值的加权平均。原始像素的值有最大的高斯分布值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也进一步小。那样举行模糊处理比其余的均匀模糊滤波器越来越高地保存了边缘效果。

其实,在iOS上完毕高斯模糊是件很不难的事宜。早在iOS 5.0就有了Core
Image的API,而且在CoreImage.framework库中,提供了汪洋的滤镜达成。

+(UIImage *)coreBlurImage:(UIImage *)image withBlurNumber:(CGFloat)blur 
{ 
    CIContext *context = [CIContext contextWithOptions:nil]; 
    CIImage *inputImage= [CIImage imageWithCGImage:image.CGImage]; 
    //设置filter
    CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"]; 
    [filter setValue:inputImage forKey:kCIInputImageKey];
    [filter setValue:@(blur) forKey: @"inputRadius"]; 
    //模糊图片
    CIImage *result=[filter valueForKey:kCIOutputImageKey]; 
    CGImageRef outImage=[context createCGImage:result fromRect:[result extent]];       
    UIImage *blurImage=[UIImage imageWithCGImage:outImage];           
    CGImageRelease(outImage); 
    return blurImage;
}

在Android上贯彻高斯模糊也得以接纳原生的API—–RenderScript,可是供给Android的API是1七上述,约等于Android
4.二本子。

    /**
     * 使用RenderScript实现高斯模糊的算法
     * @param bitmap
     * @return
     */
    public Bitmap blur(Bitmap bitmap){
        //Let's create an empty bitmap with the same size of the bitmap we want to blur
        Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        //Instantiate a new Renderscript
        RenderScript rs = RenderScript.create(getApplicationContext());
        //Create an Intrinsic Blur Script using the Renderscript
        ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        //Create the Allocations (in/out) with the Renderscript and the in/out bitmaps
        Allocation allIn = Allocation.createFromBitmap(rs, bitmap);
        Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);
        //Set the radius of the blur: 0 < radius <= 25
        blurScript.setRadius(20.0f);
        //Perform the Renderscript
        blurScript.setInput(allIn);
        blurScript.forEach(allOut);
        //Copy the final bitmap created by the out Allocation to the outBitmap
        allOut.copyTo(outBitmap);
        //recycle the original bitmap
        bitmap.recycle();
        //After finishing everything, we destroy the Renderscript.
        rs.destroy();

        return outBitmap;

    }

咱俩付出的图像框架cv4j也提供了1个滤镜来促成高斯模糊。

GaussianBlurFilter filter = new GaussianBlurFilter();
filter.setSigma(10);

RxImageData.bitmap(bitmap).addFilter(filter).into(image2);

葡萄娱乐场 4

使用RenderScript达成高斯模糊.png

葡萄娱乐场 5

利用cv四j达成高斯模糊.png

能够见见,cv4j贯彻的高斯模糊跟RenderScript达成的法力一样。

里头,GaussianBlurFilter的代码如下:

public class GaussianBlurFilter implements CommonFilter {
    private float[] kernel;
    private double sigma = 2;
    ExecutorService mExecutor;
    CompletionService<Void> service;

    public GaussianBlurFilter() {
        kernel = new float[0];
    }

    public void setSigma(double a) {
        this.sigma = a;
    }

    @Override
    public ImageProcessor filter(final ImageProcessor src){
        final int width = src.getWidth();
        final int height = src.getHeight();
        final int size = width*height;
        int dims = src.getChannels();
        makeGaussianKernel(sigma, 0.002, (int)Math.min(width, height));

        mExecutor = TaskUtils.newFixedThreadPool("cv4j",dims);
        service = new ExecutorCompletionService<>(mExecutor);

        // save result
        for(int i=0; i<dims; i++) {

            final int temp = i;
            service.submit(new Callable<Void>() {
                public Void call() throws Exception {
                    byte[] inPixels = src.toByte(temp);
                    byte[] temp = new byte[size];
                    blur(inPixels, temp, width, height); // H Gaussian
                    blur(temp, inPixels, height, width); // V Gaussain
                    return null;
                }
            });
        }

        for (int i = 0; i < dims; i++) {
            try {
                service.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        mExecutor.shutdown();
        return src;
    }

    /**
     * <p> here is 1D Gaussian        , </p>
     *
     * @param inPixels
     * @param outPixels
     * @param width
     * @param height
     */
    private void blur(byte[] inPixels, byte[] outPixels, int width, int height)
    {
        int subCol = 0;
        int index = 0, index2 = 0;
        float sum = 0;
        int k = kernel.length-1;
        for(int row=0; row<height; row++) {
            int c = 0;
            index = row;
            for(int col=0; col<width; col++) {
                sum = 0;
                for(int m = -k; m< kernel.length; m++) {
                    subCol = col + m;
                    if(subCol < 0 || subCol >= width) {
                        subCol = 0;
                    }
                    index2 = row * width + subCol;
                    c = inPixels[index2] & 0xff;
                    sum += c * kernel[Math.abs(m)];
                }
                outPixels[index] = (byte)Tools.clamp(sum);
                index += height;
            }
        }
    }

    public void makeGaussianKernel(final double sigma, final double accuracy, int maxRadius) {
        int kRadius = (int)Math.ceil(sigma*Math.sqrt(-2*Math.log(accuracy)))+1;
        if (maxRadius < 50) maxRadius = 50;         // too small maxRadius would result in inaccurate sum.
        if (kRadius > maxRadius) kRadius = maxRadius;
        kernel = new float[kRadius];
        for (int i=0; i<kRadius; i++)               // Gaussian function
            kernel[i] = (float)(Math.exp(-0.5*i*i/sigma/sigma));
        double sum;                                 // sum over all kernel elements for normalization
        if (kRadius < maxRadius) {
            sum = kernel[0];
            for (int i=1; i<kRadius; i++)
                sum += 2*kernel[i];
        } else
            sum = sigma * Math.sqrt(2*Math.PI);

        for (int i=0; i<kRadius; i++) {
            double v = (kernel[i]/sum);
            kernel[i] = (float)v;
        }
        return;
    }
}

 

空中卷积

②维卷积在图像处理中会常常遇上,图像处理中用到的大多是二维卷积的离散格局。

葡萄娱乐场 6

2维卷积的离散方式.png

以下是cv4j完成的种种卷积效果。

葡萄娱乐场 7

各类卷积效果壹.png

葡萄娱乐场 8

各样卷积效果二.png

cv4j
近来支撑如下的空间卷积滤镜

filter 名称 作用
ConvolutionHVFilter 卷积 模糊或者降噪
MinMaxFilter 最大最小值滤波 去噪声
SAPNoiseFilter 椒盐噪声 增加噪声
SharpFilter 锐化 增强
MedimaFilter 中值滤波 去噪声
LaplasFilter 拉普拉斯 提取边缘
FindEdgeFilter 寻找边缘 梯度提取
SobelFilter 梯度 获取x、y方向的梯度提取
VarianceFilter 方差滤波 高通滤波
MaerOperatorFilter 马尔操作 高通滤波
USMFilter USM 增强

高斯模糊是1种图像模糊滤波器,它用正态分布估测计算图像中各类像素的变换。N维空间正态分布方程为

总结

cv4j
gloomyfish和自家1块儿付出的图像处理库,最近还处于早期的版本。

现阶段一度完成的法力:

葡萄娱乐场 9

cv4j.png

这周,我们对
cv4j
做了较大的调动,对全体架构实行了优化。还添加了半空中卷积功用(图片增强、锐化、模糊等等)。接下来,大家会做二值图像的辨析(腐蚀、膨胀、开闭操作、概略提取等等)

葡萄娱乐场 10

在贰维空间定义为

葡萄娱乐场 11

其中r是指鹿为马半径
葡萄娱乐场 12),σ是正态分布的专业不是。在2维空间中,这一个公式生成的曲面包车型地铁等高线是从中央开端呈正态分布的同心圆。分布不为零的像素组成的卷积矩阵与原本图像做变换。每种像素的值都是相近相邻像素值的加权平均。原始像素的值有最大的高斯分布值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也越来越小。那样进行模糊处理比其余的均匀模糊滤波器更加高地保留了边缘效果,参见尺度空间完结

辩驳上来讲,图像中每点的分布都不为零,这也正是说每种像素的测算都亟需包涵整幅图像。在实际上利用中,在测算高斯函数的离散近似时,在大概③σ距离之外的像素都足以当作不起成效,这几个像素的总结也就足以忽略。常常,图像处理程序只须求总计葡萄娱乐场 13的矩阵就能够确定保障相关像素影响。对于边界上的点,日常选取复制周围的点到另一面再开始展览加权平均运算。

除外圆形对称之外,高斯模糊也能够在2维图像上对四个独立的壹维空间分别开展总括,那叫作线性可分。那约等于,使用二维矩阵变换得到的效率也足以经过在档次方向实行1维高斯矩阵变换加上竖直方向的一维高斯矩阵变换得到。从计算的角度来看,那是一项实用的特色,因为这样只要求葡萄娱乐场 14次计算,而不可分的矩阵则须要葡萄娱乐场 15次计算,其中葡萄娱乐场 16,葡萄娱乐场 17是急需举行滤波的图像的维数,葡萄娱乐场 18葡萄娱乐场 19是滤波器的维数。

对一幅图像进行频仍接连高斯模糊的效益与三回越来越大的高斯模糊能够发生相同的机能,大的高斯模糊的半径是所用多少个高斯模糊半径平方和的平方根。例如,使用半径分别为六和八的两遍高斯模糊变换获得的职能同样1遍半径为拾的高斯模糊效果,葡萄娱乐场 20。依据那几个涉及,使用五个一而再较小的高斯模糊处理不会比单个高斯较大拍卖时间要少。

在减小图像尺寸的场子常常利用高斯模糊。在进行欠采集样品的时候,平日在采集样品在此以前对图像实行低通滤波处理。那样就能够确认保障在采集样品图像中不会冒出虚假的反复消息。高斯模糊有很好的特色,如未有鲜明的境界,那样就不会在滤波图像中形成震荡。

如上资料摘自维基百科(高斯模糊词条):

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

那么具体怎么样贯彻呢?

代码献上:

inline int* buildGaussKern(int winSize, int sigma)
{
    int wincenter, x;
    float   sum = 0.0f;
    wincenter = winSize / 2;
    float *kern = (float*)malloc(winSize*sizeof(float));
    int *ikern = (int*)malloc(winSize*sizeof(int));
    float SQRT_2PI = 2.506628274631f;
    float sigmaMul2PI = 1.0f / (sigma * SQRT_2PI);
    float divSigmaPow2 = 1.0f / (2.0f * sigma * sigma);
    for (x = 0; x < wincenter + 1; x++)
    {
        kern[wincenter - x] = kern[wincenter + x] = exp(-(x * x)* divSigmaPow2) * sigmaMul2PI;
        sum += kern[wincenter - x] + ((x != 0) ? kern[wincenter + x] : 0.0);
    }
    sum = 1.0f / sum;
    for (x = 0; x < winSize; x++)
    {
        kern[x] *= sum;
        ikern[x] = kern[x] * 256.0f;
    }
    free(kern);
    return ikern;
}

void GaussBlur(unsigned char*  pixels, unsigned int    width, unsigned int  height, unsigned  int channels, int sigma)
{
    width = 3 * width;
    if ((width % 4) != 0) width += (4 - (width % 4));

    unsigned int  winsize = (1 + (((int)ceil(3 * sigma)) * 2));
    int *gaussKern = buildGaussKern(winsize, sigma);
    winsize *= 3;
    unsigned int  halfsize = winsize / 2;

    unsigned char *tmpBuffer = (unsigned char*)malloc(width * height* sizeof(unsigned char));

    for (unsigned int h = 0; h < height; h++)
    {
        unsigned int  rowWidth = h * width;

        for (unsigned int w = 0; w < width; w += channels)
        {
            unsigned int rowR = 0;
            unsigned int rowG = 0;
            unsigned int rowB = 0;
            int * gaussKernPtr = gaussKern;
            int whalfsize = w + width - halfsize;
            unsigned int  curPos = rowWidth + w;
            for (unsigned int k = 1; k < winsize; k += channels)
            {
                unsigned int  pos = rowWidth + ((k + whalfsize) % width);
                int fkern = *gaussKernPtr++;
                rowR += (pixels[pos] * fkern);
                rowG += (pixels[pos + 1] * fkern);
                rowB += (pixels[pos + 2] * fkern);
            }

            tmpBuffer[curPos] = ((unsigned char)(rowR >> 8));
            tmpBuffer[curPos + 1] = ((unsigned char)(rowG >> 8));
            tmpBuffer[curPos + 2] = ((unsigned char)(rowB >> 8));

        }
    }
    winsize /= 3;
    halfsize = winsize / 2;
    for (unsigned int w = 0; w < width; w++)
    {
        for (unsigned int h = 0; h < height; h++)
        {
            unsigned    int col_all = 0;
            int hhalfsize = h + height - halfsize;
            for (unsigned int k = 0; k < winsize; k++)
            {
                col_all += tmpBuffer[((k + hhalfsize) % height)* width + w] * gaussKern[k];
            }
            pixels[h * width + w] = (unsigned char)(col_all >> 8);
        }
    }
    free(tmpBuffer);
    free(gaussKern); 
}

备注:

之于原始算法,我做了有些小改变,重若是为了缅想一小点性质上的题材。

奇迹会写太多注释反而显示啰嗦,所以将就着看哈。

那份代码,实地衡量速度尤其不佳,处理一张5000×3000在半径大小五左右都要耗费时间10来秒至几10秒不等,实在难以承受。

由于速度的题目,网上就有好多优化算法的贯彻。

事先本人也发过1篇《连忙高斯模糊算法葡萄娱乐场,》,在同等条件下,这么些算法已经比如经济法快上十数倍。

是因为那份代码实在麻烦阅读学习,所以,笔者对其进展了特其他调整和优化。

void GaussianBlur(unsigned char* img,  unsigned int width, unsigned int height, unsigned int channels, unsigned int radius)
{
    radius = min(max(1, radius), 248);
    unsigned int kernelSize = 1 + radius * 2;
    unsigned int* kernel = (unsigned int*)malloc(kernelSize* sizeof(unsigned int));
    memset(kernel, 0, kernelSize* sizeof(unsigned int));
    int(*mult)[256] = (int(*)[256])malloc(kernelSize * 256 * sizeof(int));
    memset(mult, 0, kernelSize * 256 * sizeof(int));

    int xStart = 0;
    int yStart = 0;
    width = xStart + width - max(0, (xStart + width) - width);
    height = yStart + height - max(0, (yStart + height) - height);
    int imageSize = width*height;
    int widthstep = width*channels;
    if (channels == 3 || channels == 4)
    {
        unsigned char *    CacheImg = nullptr;
        CacheImg = (unsigned char *)malloc(sizeof(unsigned char) * imageSize * 6);
        if (CacheImg == nullptr) return;
        unsigned char *    rCache = CacheImg;
        unsigned char *    gCache = CacheImg + imageSize;
        unsigned char *    bCache = CacheImg + imageSize * 2;
        unsigned char *    r2Cache = CacheImg + imageSize * 3;
        unsigned char *    g2Cache = CacheImg + imageSize * 4;
        unsigned char *    b2Cache = CacheImg + imageSize * 5;
        int sum = 0;
        for (int K = 1; K < radius; K++){
            unsigned int szi = radius - K;
            kernel[radius + K] = kernel[szi] = szi*szi;
            sum += kernel[szi] + kernel[szi];
            for (int j = 0; j < 256; j++){
                mult[radius + K][j] = mult[szi][j] = kernel[szi] * j;
            }
        }
        kernel[radius] = radius*radius;
        sum += kernel[radius];
        for (int j = 0; j < 256; j++){
            mult[radius][j] = kernel[radius] * j;
        }
        for (int Y = 0; Y < height; ++Y) {
            unsigned char*     LinePS = img + Y*widthstep;
            unsigned char*     LinePR = rCache + Y*width;
            unsigned char*     LinePG = gCache + Y*width;
            unsigned char*     LinePB = bCache + Y*width;
            for (int X = 0; X < width; ++X) {
                int     p2 = X*channels;
                LinePR[X] = LinePS[p2];
                LinePG[X] = LinePS[p2 + 1];
                LinePB[X] = LinePS[p2 + 2];
            }
        }
        int kernelsum = 0;
        for (int K = 0; K < kernelSize; K++){
            kernelsum += kernel[K];
        }
        float fkernelsum = 1.0f / kernelsum;
        for (int Y = yStart; Y < height; Y++){
            int heightStep = Y * width;
            unsigned char*     LinePR = rCache + heightStep;
            unsigned char*     LinePG = gCache + heightStep;
            unsigned char*     LinePB = bCache + heightStep;
            for (int X = xStart; X < width; X++){
                int cb = 0;
                int cg = 0;
                int cr = 0;
                for (int K = 0; K < kernelSize; K++){
                    unsigned    int     readPos = ((X - radius + K + width) % width);
                    int * pmult = mult[K];
                    cr += pmult[LinePR[readPos]];
                    cg += pmult[LinePG[readPos]];
                    cb += pmult[LinePB[readPos]];
                }
                unsigned int p = heightStep + X;
                r2Cache[p] = cr* fkernelsum;
                g2Cache[p] = cg* fkernelsum;
                b2Cache[p] = cb* fkernelsum;
            }
        }
        for (int X = xStart; X < width; X++){
            int WidthComp = X*channels;
            int WidthStep = width*channels;
            unsigned char*     LinePS = img + X*channels;
            unsigned char*     LinePR = r2Cache + X;
            unsigned char*     LinePG = g2Cache + X;
            unsigned char*     LinePB = b2Cache + X;
            for (int Y = yStart; Y < height; Y++){
                int cb = 0;
                int cg = 0;
                int cr = 0;
                for (int K = 0; K < kernelSize; K++){
                    unsigned int   readPos = ((Y - radius + K + height) % height) * width;
                    int * pmult = mult[K];
                    cr += pmult[LinePR[readPos]];
                    cg += pmult[LinePG[readPos]];
                    cb += pmult[LinePB[readPos]];
                }
                int    p = Y*WidthStep;
                LinePS[p] = (unsigned char)(cr * fkernelsum);
                LinePS[p + 1] = (unsigned char)(cg * fkernelsum);
                LinePS[p + 2] = (unsigned char)(cb* fkernelsum);


            }
        }
        free(CacheImg);
    }
    else if (channels == 1)
    {
        unsigned char *    CacheImg = nullptr;
        CacheImg = (unsigned char *)malloc(sizeof(unsigned char) * imageSize * 2);
        if (CacheImg == nullptr) return;
        unsigned char *    rCache = CacheImg;
        unsigned char *    r2Cache = CacheImg + imageSize;

        int sum = 0;
        for (int K = 1; K < radius; K++){
            unsigned int szi = radius - K;
            kernel[radius + K] = kernel[szi] = szi*szi;
            sum += kernel[szi] + kernel[szi];
            for (int j = 0; j < 256; j++){
                mult[radius + K][j] = mult[szi][j] = kernel[szi] * j;
            }
        }
        kernel[radius] = radius*radius;
        sum += kernel[radius];
        for (int j = 0; j < 256; j++){
            mult[radius][j] = kernel[radius] * j;
        }
        for (int Y = 0; Y < height; ++Y) {
            unsigned char*     LinePS = img + Y*widthstep;
            unsigned char*     LinePR = rCache + Y*width;
            for (int X = 0; X < width; ++X) {
                LinePR[X] = LinePS[X];
            }
        }
        int kernelsum = 0;
        for (int K = 0; K < kernelSize; K++){
            kernelsum += kernel[K];
        }
        float fkernelsum = 1.0f / kernelsum;
        for (int Y = yStart; Y < height; Y++){
            int heightStep = Y * width;
            unsigned char*     LinePR = rCache + heightStep;
            for (int X = xStart; X < width; X++){
                int cb = 0;
                int cg = 0;
                int cr = 0;
                for (int K = 0; K < kernelSize; K++){
                    unsigned    int     readPos = ( (X - radius + K+width)%width);
                    int * pmult = mult[K];
                    cr += pmult[LinePR[readPos]];
                }
                unsigned int p = heightStep + X;
                r2Cache[p] = cr * fkernelsum;
            }
        }
        for (int X = xStart; X < width; X++){
            int WidthComp = X*channels;
            int WidthStep = width*channels;
            unsigned char*     LinePS = img + X*channels;
            unsigned char*     LinePR = r2Cache + X;
            for (int Y = yStart; Y < height; Y++){
                int cb = 0;
                int cg = 0;
                int cr = 0;
                for (int K = 0; K < kernelSize; K++){
                    unsigned int   readPos = ((Y - radius + K+height)%height) * width;
                    int * pmult = mult[K];
                    cr += pmult[LinePR[readPos]];
                }
                int    p = Y*WidthStep;
                LinePS[p] = (unsigned char)(cr* fkernelsum);
            }
        }
        free(CacheImg);
    } 
    free(kernel);
    free(mult);
}

  当中有部分算法优化技术,想来也能起到有些投砾引珠的功能。

贴个效益图:

葡萄娱乐场 21

本文只是投石问路一下,若有此外连锁难题要么须要也足以邮件联系自个儿切磋。

 邮箱地址是:

gaozhihan@vip.qq.com