1.图像卷积滤波与高斯模糊,图像的高斯模糊进程即使图像与正态遍布做卷积

高斯模糊(法语:Gaussian Blur),也叫高斯平滑,是在Adobe
Photoshop、GIMP以及Paint.NET等图像管理软件浙江中国广播公司小运用的拍卖成效,平常用它来收缩图像噪声以及裁减细节等级次序。这种歪曲才具转移的图像,其视觉效果仿佛经过多少个半透明荧屏在察看图像,那与画面焦外成像效果散景以及常见照明阴影中的效果都刚强例外。高斯平滑也用于计算机视觉算法中的预先处理阶段,以巩固图像在不相同期比较例大小下的图像效果(参见尺度空间表示以及尺度空间达成)。
从数学的角度来看,图像的高斯模糊进程尽管图像与正态遍及做卷积。由李晓明态分布又叫作高斯布满,所以那项能力就叫作高斯模糊。图像与圆圈方框模糊做卷积将会扭转特别正确的焦外成像效果。由于高斯函数的傅立叶转换是其他贰个高斯函数,所以高斯模糊对于图像来讲正是贰个低通滤波器。

高斯模糊

高斯模糊(土耳其(Turkey)语:Gaussian Blur),也叫高斯平滑,是在Adobe
Photoshop、GIMP以及Paint.NET等图像管理软件江苏中国广播公司大选拔的管理效果,经常用它来缩短图像杂讯以及裁减细节档次。这种歪曲技能生成的图像,其视觉效果仿佛经过三个半透明显示屏在考查图像,那与画面焦外成像效果散景以及普通照明阴影中的效果都分明分化。高斯平滑也用于计算机视觉算法中的预先管理阶段,以升高图像在区别比重大小下的图像效果。
从数学的角度来看,图像的高斯模糊进度便是图像与正态布满做卷积。由刘和平态遍及又叫作高斯布满,所以那项手艺就叫作高斯模糊。图像与圆圈方框模糊做卷积将会转变特别正确的焦外成像效果。由于高斯函数的傅立叶转变是别的三个高斯函数,所以高斯模糊对于图像来讲正是叁个低通滤波器。

高斯模糊使用了高斯的正态分布的密度函数,计算图像中种种像素的改造。

图片 1

gaussian-function.png

基于一维高斯函数,能够推导得到二维高斯函数:

图片 2

二维高斯函数.png

图片 3

二维的正太分布.png

其中r是模糊半径,r^2 = x^2 +
y^2,σ是正态布满的正儿八经不是。在二维空间中,那个公式生成的曲面包车型客车等高线是从大旨开端呈正态布满的同心圆。分布不为零的像素组成的卷积矩阵与原来图像做调换。每一种像素的值都是四周相邻像素值的加权平均。原始像素的值有最大的高斯布满值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也愈加小。那样实行模糊管理比别的的平均模糊滤波器更加高地保存了边缘效果。

其实,在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是17上述,也便是Android
4.2版本。

    /**
     * 使用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也提供了一个滤镜来促成高斯模糊。

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

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

图片 4

采取RenderScript达成高斯模糊.png

图片 5

应用cv4j完毕高斯模糊.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

二维卷积的离散格局.png

以下是cv4j福寿康宁的种种卷积效果。

图片 7

各个卷积效果1.png

图片 8

各个卷积效果2.png

cv4j
前段时间支持如下的空间卷积滤镜

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

目录大纲

1.图像卷积滤波与高斯模糊
2.高斯模糊完结与优化
3.RenderScript的介绍与运用

高斯模糊是一种图像模糊滤波器,它用正态分布总计图像中种种像素的变换。N维空间正态遍及方程为

总结

cv4j
gloomyfish和本人三头付出的图像管理库,近日还处于早先时代的版本。

前段时间早已达成的效力:

图片 9

cv4j.png

这周,我们对
cv4j
做了十分的大的调节,对总体架构实行了优化。还抬高了上空卷积功效(图片巩固、锐化、模糊等等)。接下来,我们会做二值图像的剖判(腐蚀、膨胀、开闭操作、概略提取等等)

一.图像卷积滤波与高斯模糊

图片 10

1.1 图像卷积滤波

对此滤波来讲,它可以说是图像管理最基本的法子,能够生出非常的多不等的成效。以下图来说

图片 11

图中矩阵分别为二维原图像素矩阵,二维的图像滤波矩阵(也称为卷积核,上边讲到滤波器和卷积核都以同个概念),以及最后滤波后的新像素图。对于原图像的每二个像素点,总括它的圈子像素和滤波器矩阵的对应成分的成就,然后加起来,作为当下主导像素地点的值,那样就产生了滤波的经过了。

可以观看,一个原图像通过一定的卷积核管理后就可以调换为另八个图像了。而对此滤波器来说,也可能有早晚的平整须要的。


  • 滤波器的深浅应该是奇数,那样它才有二个着力,举个例子3×3,5×5仍然7×7。有宗旨了,也可能有了半径的可以称作,举个例子5×5轻重的核的半径正是2。

  • 滤波器矩阵全数的要素之和必要求对等1,那是为了确认保障滤波前后图像的亮度保持不改变。当然了,那不是硬性供给了。

  • 假若滤波器矩阵全部因素之和超过1,那么滤波后的图像就能比原图像越来越亮,反之,假若低于1,那么获得的图像就能变暗。如果和为0,图像不会变黑,但也会相当暗。

  • 对于滤波后的结构,恐怕会现出负数只怕高于255的数值。对这种情形,大家将他们平昔截断到0和255之间就能够。对于负数,也得以取相对值。

在二维空间定义为

1.2 卷积核一些用法

既然如此知道滤波器能够用来对原图进行操作,那么,有没有一部分比较实际的例子。文中卷积核相关图片来自网络

图片 12

1.2.1 空卷积核

图片 13

能够见见,这么些滤波器啥也从不做,获得的图像和原图是一样的。因为唯有中央点的值是1。邻域点的权值都以0,对滤波后的取值未有别的影响。

其中r是模糊半径
图片 14),σ是正态遍布的规范不是。在二维空间中,这几个公式生成的曲面包车型地铁等高线是从中央开头呈正态布满的同心圆。布满不为零的像素组成的卷积矩阵与原来图像做转换。种种像素的值都以四周相邻像素值的加权平均。原始像素的值有最大的高斯布满值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也进一步小。那样进行模糊管理比其余的动态平衡模糊滤波器更加高地保存了边缘效果,参见尺度空间实现

1.2.2 图像锐化滤波器

图像的锐化和边缘检查评定很像,首先找到边缘,然后把边缘加到原本的图像上面,这样就加剧了图像的边缘,使图像看起来更为咄咄逼人了。这两侧操作统一同来便是锐化滤波器了,也正是在边缘检查实验滤波器的根基上,再在着力的职分加1,那样滤波后的图像就能够和原始的图像具备同样的亮度了,但是会愈加尖锐。

图片 15

咱们把核加大,就足以获取更精细的锐化效果

图片 16

辩解上来说,图像中每点的分布都不为零,那也正是说每种像素的测算都亟待包涵整幅图像。在实际利用中,在测算高斯函数的离散近似时,在大约3σ距离之外的像素都足以作为不起作用,那么些像素的测算也就足以忽略。平常,图像管理程序只须求计算图片 17的矩阵就足以确定保障相关像素影响。对于边界上的点,通常使用复制左近的点到另一面再开展加权平均运算。

1.2.3 浮雕

浮雕滤波器能够给图像一种3D阴影的功力。只要将基本一边的像素减去另一面包车型大巴像素就可以了。这时候,像素值有不小希望是负数,大家将负数当成阴影,将正数当成光,然后大家对结果图像加上128的偏移。那时候,图像大部分就成为棕褐了。
上边是45度的浮雕滤波器

图片 18

我们只要加大滤波器,就可以获得进一步夸张的成效了

图片 19

而外圆形对称之外,高斯模糊也能够在二维图像上对八个单身的一维空间分别开展总结,那叫作线性可分。那也算得,使用二维矩阵转变获得的法力也足以由此在档期的顺序方向拓展一维高斯矩阵转换加上竖直方向的一维高斯矩阵调换获得。从总计的角度来看,那是一项一蹴而就的风味,因为那样只供给图片 20次计算,而不可分的矩阵则必要图片 21次计算,其中图片 22,图片 23是内需实行滤波的图像的维数,图片 24图片 25是滤波器的维数。

1.2.4 均值模糊

作者们得以将日前像素和它的四邻域的像素一同取平均,然后再除以5,可能直接在滤波器的5个地点取0.2的值就可以,如下图:

图片 26

能够看出,这几个模糊依然比较温和的,大家能够把滤波器变大,那样就能够变得狠毒了:注意要将和再除以13.

图片 27

能够观察均值模糊也能够变成让图片模糊,可是它的模糊不是很平整,不平易重要在于距离宗旨点相当的远的点与离开大旨点比较近的所带的权重值同样,产生的模糊效果同样
而想要做到平滑,让权重值跟随宗旨点地点距离差别而差异,则足以采用正态分布(中间大,两端小)那几个特点来兑现。

对一幅图像实行频仍老是高斯模糊的法力与二回更加大的高斯模糊能够生出同样的机能,大的高斯模糊的半径是所用三个高斯模糊半径平方和的平方根。比方,使用半径分别为6和8的两遍高斯模糊转换得到的效率同样贰次半径为10的高斯模糊效果,图片 28。依据这几个涉及,使用多少个三番五次非常的小的高斯模糊管理不会比单个高斯非常大拍卖时间要少。

1.3 高斯模糊

有了前面的文化,我们掌握假设要想完毕高斯模糊的特色,则需求经过创设对应的权重矩阵来进展滤波。

在削减图像尺寸的场馆日常采纳高斯模糊。在张开欠采样的时候,平常在采集样品在此以前对图像举办低通滤波管理。那样就足以确定保障在采集样品图像中不会油但是生虚假的反复音讯。高斯模糊有很好的特征,如未有显明的分界,这样就不会在滤波图像中产生震荡。

1.3.1 正态遍及

图片 29

正态布满

正态布满中,越左近主题点,取值越大,越远远地离开中央,取值越小。
计量平均值的时候,我们只需求将”中央点”作为原点,别的点依照其在正态曲线上的职分,分配权重,就足以博得三个加权平均值。正态分布分明是一种可取的权重分配情势。

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

1.3.2 高斯函数

什么展现出正态遍布?则要求选拔高等函大数来兑现。
地点的正态遍布是一维的,而对于图像都以二维的,所以大家供给二维的正态布满。

图片 30

正态布满的密度函数叫做”高斯函数”(Gaussian function)。它的一维情势是:

图片 31

image.png

内部,μ是x的均值,σ是x的方差。因为总括平均值的时候,中央点便是原点,所以μ等于0。

图片 32

image.png

依据一维高斯函数,能够推导得到二维高斯函数:

图片 33

image.png

有了那些函数 ,就足以计算每种点的权重了。

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

1.3.3 获取权重矩阵

要是大旨点的坐标是(0,0),那么相差它近年来的8个点的坐标如下:

图片 34

更远的点依此类推。
为了总计权重矩阵,须要设定σ的值。假定σ=1.5,则模糊半径为1的权重矩阵如下:

图片 35

那9个点的权重总和卓殊0.4787147,固然只总括那9个点的加权平均,还必得让它们的权重之和卓殊1,因而地点9个值还要分别除以0.4787147,得到末了的权重矩阵。

图片 36

除以总值那一个进度也称之为”归一难题“
指标是让滤镜的权重总值相当于1。不然的话,使用总值超越1的滤镜会让图像偏亮,小于1的滤镜会让图像偏暗。

那正是说具体什么贯彻呢?

1.3.4 总结模糊值

有了权重矩阵,就能够总括高斯模糊的值了。
即便现存9个像素点,灰度值(0-255)如下:

图片 37

各种点乘以自个儿的权重值:

图片 38

得到

图片 39

将那9个值加起来,便是中央点的高斯模糊的值。
对全体一点点重复那几个历程,就取得了高斯模糊后的图像。对于彩色图片来讲,则需求对HighlanderGB两个通道分别做高斯模糊。

代码献上:

1.3.5 边界值难点

既是是依靠权重矩阵来开展管理的

图片 40

image.png

假设三个点处于边界,相近未有足够的点,怎么做?

  • ① 对称管理,正是把已部分点拷贝到另一面包车型大巴呼应地方,模拟出完整的矩阵。

  • 赋0,想象图疑似最为长的图像的一片段,除了咱们给定值的有的,其他部分的像素值都以0

  • 赋边界值,想象图疑似非常制长,不过默许赋值的不是0而是对应边界点的值
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); 
}

二. 高斯模糊达成与优化

知道原理之后则足以做越来越完结,从Android上的话。

备注:

2.1 创设权重矩阵

 public static double[][] getMatrix(int radius){
        //根据radius创建权重矩阵.
        int size = 2 * radius + 1;
        double[][] matrix = new double[size][size];
        double sigama = (double) radius / 3;
        double sigamaDouble = 2 * sigama * sigama;
        double sigamaPi = Math.PI * sigamaDouble;
        int row = 0;
        double sum = 0;
        for(int i = -radius ; i <= radius ; i++){
            int line = 0;
            for(int j = -radius ; j <= radius ; j++){
                double x = i * i;
                double y = j * j;
                matrix[row][line] = Math.exp(-(x + y)/sigamaDouble)/sigamaPi;
                sum += matrix[row][line];
                line++;
            }
            row++;
        }
        //归一
        for(int i = 0 ; i < size ; i++){
            for(int j = 0 ; j < size ; j++){
                matrix[i][j] /= sum;
            }
        }
        return matrix;
    }

对此第5行sigama的估量,参谋正态布满曲线图,能够知道 3σ
距离以外的点,权重已经不屑一提了。反推就可以见道当模糊半径为r时,取σ为 r/3
是三个比较适当的取值。

之于原始算法,作者做了一部分小改动,主假诺为了思量一丢丢属性上的主题材料。

2.2 计算

        //获取权重矩阵
        double[][] matrix = getMatrix(radius);
        int width  = scaleBitmap.getWidth();
        int height = scaleBitmap.getHeight();
        int[] currentPixels = new int[width * height];
        scaleBitmap.getPixels(currentPixels, 0, width, 0, 0, width, height)
for(int i = 0 ; i < width ; i++){
            for(int j = 0; j < height ; j++){
                int red = 0;
                int green = 0;
                int blue = 0;
                int x = i - radius;
                int y = j - radius;
                //先不处理边界值
                if(x >0 && y > 0 && (i+radius < width && j+radius < height)) {
                        for (int tempI = -radius; tempI <= radius; tempI++) {
                            for (int tempJ = -radius; tempJ <= radius; tempJ++) {
                                int color = currentPixels[(j + tempJ) * width + i + tempI];
                                red += (int) (Color.red(color) * matrix[tempI + radius][tempJ + radius]);
                                green += (int) (Color.green(color) * matrix[tempI + radius][tempJ + radius]);
                                blue += (int) (Color.blue(color) * matrix[tempI + radius][tempJ + radius]);
                            }
                        }
                        int color = currentPixels[j * width + i];
                        currentPixels[j * width + i] = Color.rgb(red, green, blue);
                    }
                }
            }
        }

此地currentPixels则是图像的像素矩阵。获取到的则是图片的像素的color,而由此Color对应的章程则能够转成对应的SportageGB格局,
也足以直接

red = (p & 0xff0000) >> 16;
green = (p & 0x00ff00) >> 8;
blue = (p & 0x0000ff);

当你跑程序后,你会等到嫌疑世界。手提式有线话机一些低等手提式无线话机来讲,手提式有线电话机天性是硬伤。并且上边包车型客车算法是最最最菜的算法,跑起来大概要几十秒以致几分钟。

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

2.3 优化

优化上得以分为很各样,一种是从图片像素上的优化,一种是算法的优化,另一种是调用层的优化,从java层改为jni层实现

那份代码,实地衡量速度极其不好,管理一张四千x三千在半径大小5左右都要耗费时间十来秒至几十秒不等,实在麻烦承受。

2.3.1 图片像素上的优化

前边能够看到算法的首要循环在于width,height,radius,那么能够从减弱像素点,相当于削减图片上入手。既然模糊后的图纸比较于原图来讲是不清晰的,那么本人也得以先对图纸做缩减,然后再高斯,最后再推广,获得的结果也与原图直接模糊结果同样,都以不分明的。当然假若对于清晰度来讲的话,能够通过模糊半径radius来做调度。压缩太大就相比较模糊,能够经过减小radius,相反,压缩太小则通过增添radius就可以。

Matrix matrix = new Matrix();
matrix.setScale(0.1f , 0.1f);
Bitmap scaleBitmap = Bitmap.createBitmap(srcBitmap , 0 , 0 , srcBitmap.getWidth() , srcBitmap.getHeight() , matrix , true);

是因为速度的主题素材,网络就有广大优化算法的达成。

2.3.2 算法上的优化

前方能够看到,当前算法的复杂度则是
O(width×height×(2×radius)2),radius为模糊半径。
前边讲到的管理格局都以起家在二维的动静下进行的。高斯模糊也足以在二维图像上对多少个单身的一维空间分别进行总括,那叫作线性可分。那也实属,使用二维矩阵转换获得的功用也足以通过在档期的顺序方向实行一维高斯矩阵转变加上竖直方向的一维高斯矩阵转变获得。

回去前面一维高斯的总括公式

图片 41

一维高斯

面前边同样,可是这里的权重矩阵变为一维的

     //根据radius创建权重矩阵.
        int size = 2 * radius + 1;
        double[] matrix = new double[size];
        double sigama = (double) radius / 3;
        double sigamaDouble = 2 * sigama * sigama;
        double sqlPi = Math.sqrt(2 * Math.PI);
        double sigamaPi = sigama * sqlPi;
        int row = 0;
        double sum = 0;
        for(int i = -radius ; i <= radius ; i++){
            double x = i * i;
            matrix[row] = Math.exp(-x/sigamaDouble)/sigamaPi;
            sum += matrix[row];
            row++;
        }
        //归一处理目的是让权重总值等于1。
        //否则的话,使用总值大于1的滤镜会让图像偏亮,小于1的滤镜会让图像偏暗。
        for(int i = 0 ; i < size ; i++){
            matrix[i] /= sum;
        }

个别对横纵方向进行拍卖

  double[] matrix = getOneMatrix(radius);
        int width  = scaleBitmap.getWidth();
        int height = scaleBitmap.getHeight();
        int[] currentPixels = new int[width * height];
        int red[] = new int[width * height];
        int green[] = new int[width * height];
        int blue[] = new int[width * height];
        scaleBitmap.getPixels(currentPixels, 0, width, 0, 0, width, height);
        for(int j = 0 ; j < height ; j++){
            for(int i = 0 ; i < width ; i++){
                int n = 0;
                int x = i - radius;
                int y = j - radius;
                //先过滤边界值
                if(x >=0 && y >= 0 && (i+radius < width && j+radius < height)) {
                    for (int temp = -radius; temp <= radius; temp++) {
                        int point = temp + i;
                        int colorPoint = j * width + point;
                        int color = currentPixels[colorPoint];
                        red[colorPoint] += Color.red(color) * matrix[n];
                        green[colorPoint] += Color.green(color) * matrix[n];
                        blue[colorPoint] += Color.blue(color) * matrix[n];
                        n++;
                    }
                }
            }
        }

        for(int i = 0 ; i < width ; i++){
            for(int j = 0 ; j < height ; j++){
                int n = 0;
                int r = 0 , b = 0 , g = 0;
                int x = i - radius;
                int y = j - radius;
                //先过滤边界值
                if(x >=0 && y >= 0 && (i+radius < width && j+radius < height)) {
                    for (int temp = -radius; temp <= radius; temp++) {
                        int currentPoint = (j + temp) * width + i;
                        Log.e(TAG, "temp = " + temp + "  i = " + i + " j : " + j + " currentPoint = " + currentPoint
                        );
                        r += red[currentPoint] * matrix[n];
                        g += green[currentPoint] * matrix[n];
                        b += blue[currentPoint] * matrix[n];
                        n++;
                    }
                    currentPixels[j*width + i] = Color.rgb(r, g, b);
                }
            }
        }

这时候的光阴复杂度则为
O(width×height×2×radius×2)相比前面的话则少了radius倍。
自然这里算法的优化只是中间之一,网络有广大优化后的高斯模糊,比方android-stackblurFastBlur等,法斯特Blur则是参照他事他说加以考察Javascript来做个落实的,然而法斯特Blur的兑现利用了过多附加的内部存款和储蓄器(它会复制整个位图到二个缓充区中),由此它适用于小位图,对于大图来说则相比轻巧导致OOM

前边笔者也发过一篇《异常的快高斯模糊算法》,在同等条件下,那个算法已经举个例子经济法快上十数倍。

2.3.3 转换到JNI层

对于Java与JNI来说,同样的代码在JNI层调用所耗的时辰要比Java的调用要少的多,极度是在一些图像算法,也许游戏逻辑的时候。

JNI层面来讲能够带来品质升高,它能够突破VM的内部存款和储蓄器限制,由友好来保管内部存款和储蓄器,而Java的内部存款和储蓄器管理整个由虚构机来管理的。

由于那份代码实在难以阅读学习,所以,小编对其进展了更上一层楼的调动和优化。

三.RenderScript

官方网站介绍:RenderScript是Android平台上用来周转总括密集任务的框架。RenderScript主借使面向数据并行总括,当然了,RenderScript中采纳串行计算成效也很好。RenderScript是足够利用手提式有线电话机GPU,CPU的持筹握算本事,让开荒者专一于算法而不在于调节。大家编辑的代码不须要关心具体的硬件的不等,都能写出高质量的代码。

RenderScript是基于C99语言的,大家需求通过写叁个RenderScript脚本来调节。
构成官方网站络来做个入门。

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);
}

3.1 编写rs内核脚本

在类型的代码目录下(即src根目录下/src/)创立rs文件,表示是本子文件。这里新建三个image.rs,输入

#pragma version(1)
#pragma rs java_package_name(com.rdc.zzh.stackblurtest)
uchar4 __attribute__((kernel)) invert(uchar4 in)
{
  uchar4 out = in;
  out.r =255- in.r;
  out.g = 255-in.g;
  out.b = 255-in.b;
  return out;

}
  • 对于第一行的话,#pragma
    version(1)是指版本号,表示近日剧本所使用的本子,但是这里不得不是1才是平价的,#pragma是符号给编写翻译器看的。
  • 一模一样第二行,能够看出这里是告诉编写翻译器当前接纳的包名。因为每种rs文件都会自动生成对应的Java代码,举例,大家新建的hello.rs文件,会自动生成ScriptC_hello类,因此,大家必要在rs注脚包的名称。
  • 那边运用到了 uchar4 __attribute__((kernel)) invert(uchar4 in)
    能够精通那是二个调用方法。
    率先对此RenderScript来讲它有三种总计内核格局,分别是
    映射(mapping)内核减少(reduction)内核
  • 参数类型,uchar4表示的是二个4字节的体系,uchar4
    in中得以平昔用in.r,in.g,in.b,in.a
    抽出对应像素点的色值消息,每种值各占贰个字节,取值范围则是在(0`255)也正如说得通。上边函数则代表对叁个像素点做拍卖。

  个中有一部分算法优化本事,想来也能起到一点引玉之砖的功用。

3.1.1 映射内核mapping kernel

辉映内核:它是叁个对一样维度的Allocations集结实行操作的并行函数。平日是将八个输入Allocations分配集转成另贰个输出Allocations分配集。譬喻

uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  out.r = 255 - in.r;
  out.g = 255 - in.g;
  out.b = 255 - in.b;
  return out;
}

此间将输入in(传递的是Allocations),输出则是另一个out。
在比较多景色下,这与C语言的正规函数语法一样,在此间 RS_KERNEL
它则是一个宏定义常量,表示的是贰个炫目内核并不是四个可调用函数。它的定义为
#define RS_KERNEL __attribute__((kernel))
由地点例子可见晓大家用的是炫目内核的花样,会有多少个Allocations做为输入和输出。

其余,八个映射内核可有多少个依然八个输入Allocations,有二个依然多少个Allocation输出

贴个成效图:

3.1.2 减弱内核reduction kernel

减掉水源则是在同样维数的对输入Allocations实行操作的函数族。它至关心重视要用来“降维”二个输入的Allocation集结成二个单独的值。

上面则是二个调减水源将输入元素累加起来的事例

#pragma rs reduce(addint) accumulator(addintAccum)

static void addintAccum(int *accum, int val) {
  *accum += val;
}

在此地例子中,#pragma rs reduce则是用于定义内核的名字(这里代表的是addint内核),而前边的addintAccum则代表它是贰个accumulator方法,全数那样的点子都应该是static的。而多个收缩水源平时供给三个accumulator方法,它的重回值必须是void。

别的,二个滑坡水源具有三个还是四个输入Allocation,然而未有输出Allocation。详细内容还得参考官方API文书档案。

图片 42

3.2 Java代码调用

        mInBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        mOutBitmap = Bitmap.createBitmap(mInBitmap.getWidth(),mInBitmap.getHeight(), mInBitmap.getConfig());
        mSrcImageView.setImageBitmap(mInBitmap);

        RenderScript rs = RenderScript.create(this);
        mScript = new ScriptC_image(rs);

       Allocation aIn = Allocation.createFromBitmap(rs, mInBitmap);
       Allocation aOut = Allocation.createFromBitmap(rs, mInBitmap);


        mScript.forEach_invert(aIn, aOut);
        aOut.copyTo(mOutBitmap);
        mDstImageView.setImageBitmap(mOutBitmap);
        rs.destroy();

第一这里先创建三个RenderScript对象,接着则是将编制的rs文件对应的自动生成的java类ScriptC_image起初化,接着则是创办五个Allocation,从名字能够看看是用来分配内存,作为炫人眼目内核的输入和输出,createFromBitmap则是依照Bitmap分配内部存款和储蓄器,把Bitmap的像素值传递到Allocation里面。

那四个Allocation的Element类型必需一律,在函数调用时RenderScript会检查,假若不想同会抛相当。这里涉及了Element,Elemtent是指Allocation里的一项。举个例子我们要管理的是Bitmap,则Element表示的门类是像素。

接着调用到forEach_invert,前面包车型大巴invert则是我们在rs里面编写的措施名字,这里则是炫目到了ScriptC_image里面了,RenderScript会自行将aIn里的各个成分(Element)并行的去试行invert函数.获得的结果归入aOut里。最终调用Allocation的copyTo函数把计算的结果转入到Bitmap中。

自然这里所列举的都以相比较简单的例子,很多有关图像间的操作都足以编写制定对应的rs文件来进行拍卖,RenderScript所关联到的知识点非常多,详细的还得多参谋官方网址API。

正文只是引玉之砖一下,若有另外相关主题材料只怕要求也可以邮件联系本人商量。

3.3 RenderScript高斯模糊

后边可以阅览,实现的关键在于编写对应的rs文件生成响应的Script类,以此来拓宽调用。官方也已经付诸了对应高斯模糊的落到实处类ScriptIntrinsicBlur,使用时的调用则为

//先对图片进行压缩然后再blur
Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, Math.round(bitmap.getWidth() * bitmap_scale),
                Math.round(bitmap.getHeight() * bitmap_scale), false);
//创建空的Bitmap用于输出
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
//①、初始化Renderscript
RenderScript rs = RenderScript.create(context);
//②、Create an Intrinsic Blur Script using the Renderscript
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
//③、native层分配内存空间
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
//④、设置blur的半径然后进行blur
theIntrinsic.setRadius(blur_radius);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
//⑤、拷贝blur后的数据到java缓冲区中
 tmpOut.copyTo(outputBitmap);
//⑥、销毁Renderscript
rs.destroy();
bitmap.recycle();

而是这里的界定标法规是API要超越17技术够调用,也能够友善导入一些v8包容包。

 邮箱地址是:

3.4 对比

对此大图来讲,法斯特Blur的贯彻效果与利益是不及RenderScript好的,以至会发出OOM难点。
而对此RenderScript来说,无论大图小图耗费时间则都以1050mm左右,而FastBlur在小图的情况下可以达到1050mm,甚至比RenderScript要好

gaozhihan@vip.qq.com

四.参照他事他说加以考察链接

图片资料来源于

相关文章