Java實現(xiàn)高斯模糊和圖像的空間卷積

高斯模糊

高斯模糊(英語:Gaussian Blur)价淌,也叫高斯平滑淤齐,是在Adobe Photoshop榨馁、GIMP以及Paint.NET等圖像處理軟件中廣泛使用的處理效果憨栽,通常用它來減少圖像雜訊以及降低細節(jié)層次。這種模糊技術(shù)生成的圖像翼虫,其視覺效果就像是經(jīng)過一個半透明屏幕在觀察圖像屑柔,這與鏡頭焦外成像效果散景以及普通照明陰影中的效果都明顯不同。高斯平滑也用于計算機視覺算法中的預(yù)先處理階段珍剑,以增強圖像在不同比例大小下的圖像效果掸宛。 從數(shù)學(xué)的角度來看,圖像的高斯模糊過程就是圖像與正態(tài)分布做卷積招拙。由于正態(tài)分布又叫作高斯分布唧瘾,所以這項技術(shù)就叫作高斯模糊。圖像與圓形方框模糊做卷積將會生成更加精確的焦外成像效果别凤。由于高斯函數(shù)的傅立葉變換是另外一個高斯函數(shù)饰序,所以高斯模糊對于圖像來說就是一個低通濾波器。

高斯模糊運用了高斯的正態(tài)分布的密度函數(shù)规哪,計算圖像中每個像素的變換求豫。

gaussian-function.png

根據(jù)一維高斯函數(shù),可以推導(dǎo)得到二維高斯函數(shù):

二維高斯函數(shù).png
二維的正太分布.png

其中r是模糊半徑,r^2 = x^2 + y^2蝠嘉,σ是正態(tài)分布的標(biāo)準(zhǔn)偏差最疆。在二維空間中,這個公式生成的曲面的等高線是從中心開始呈正態(tài)分布的同心圓蚤告。分布不為零的像素組成的卷積矩陣與原始圖像做變換努酸。每個像素的值都是周圍相鄰像素值的加權(quán)平均。原始像素的值有最大的高斯分布值杜恰,所以有最大的權(quán)重获诈,相鄰像素隨著距離原始像素越來越遠,其權(quán)重也越來越小心褐。這樣進行模糊處理比其它的均衡模糊濾波器更高地保留了邊緣效果烙荷。

其實,在iOS上實現(xiàn)高斯模糊是件很容易的事兒檬寂。早在iOS 5.0就有了Core Image的API,而且在CoreImage.framework庫中戳表,提供了大量的濾鏡實現(xiàn)桶至。

+(UIImage *)coreBlurImage:(UIImage *)image withBlurNumber:(CGFloat)blur 
{ 
    CIContext *context = [CIContext contextWithOptions:nil]; 
    CIImage *inputImage= [CIImage imageWithCGImage:image.CGImage]; 
    //設(shè)置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上實現(xiàn)高斯模糊也可以使用原生的API-----RenderScript,不過需要Android的API是17以上匾旭,也就是Android 4.2版本镣屹。

    /**
     * 使用RenderScript實現(xiàn)高斯模糊的算法
     * @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;

    }

我們開發(fā)的圖像框架cv4j也提供了一個濾鏡來實現(xiàn)高斯模糊。

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

RxImageData.bitmap(bitmap).addFilter(filter).into(image2);
使用RenderScript實現(xiàn)高斯模糊.png
使用cv4j實現(xiàn)高斯模糊.png

可以看出价涝,cv4j實現(xiàn)的高斯模糊跟RenderScript實現(xiàn)的效果一致女蜈。

其中,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;
    }
}

空間卷積

二維卷積在圖像處理中會經(jīng)常遇到色瘩,圖像處理中用到的大多是二維卷積的離散形式伪窖。

二維卷積的離散形式.png

以下是cv4j實現(xiàn)的各種卷積效果。

各種卷積效果1.png

各種卷積效果2.png

cv4j 目前支持如下的空間卷積濾鏡

filter 名稱 作用
ConvolutionHVFilter 卷積 模糊或者降噪
MinMaxFilter 最大最小值濾波 去噪聲
SAPNoiseFilter 椒鹽噪聲 增加噪聲
SharpFilter 銳化 增強
MedimaFilter 中值濾波 去噪聲
LaplasFilter 拉普拉斯 提取邊緣
FindEdgeFilter 尋找邊緣 梯度提取
SobelFilter 梯度 獲取x居兆、y方向的梯度提取
VarianceFilter 方差濾波 高通濾波
MaerOperatorFilter 馬爾操作 高通濾波
USMFilter USM 增強

總結(jié)

cv4jgloomyfish和我一起開發(fā)的圖像處理庫覆山,目前還處于早期的版本。

目前已經(jīng)實現(xiàn)的功能:


cv4j.png

這周泥栖,我們對 cv4j 做了較大的調(diào)整簇宽,對整體架構(gòu)進行了優(yōu)化。還加上了空間卷積功能(圖片增強吧享、銳化魏割、模糊等等)。接下來钢颂,我們會做二值圖像的分析(腐蝕钞它、膨脹、開閉操作、輪廓提取等等)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末须揣,一起剝皮案震驚了整個濱河市盐股,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耻卡,老刑警劉巖疯汁,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卵酪,居然都是意外死亡幌蚊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門溃卡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溢豆,“玉大人,你說我怎么就攤上這事瘸羡′鱿桑” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵犹赖,是天一觀的道長队他。 經(jīng)常有香客問我,道長峻村,這世上最難降的妖魔是什么麸折? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮粘昨,結(jié)果婚禮上垢啼,老公的妹妹穿的比我還像新娘。我一直安慰自己张肾,他們只是感情好芭析,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捌浩,像睡著了一般放刨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尸饺,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天进统,我揣著相機與錄音,去河邊找鬼浪听。 笑死螟碎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的迹栓。 我是一名探鬼主播掉分,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了酥郭?” 一聲冷哼從身側(cè)響起华坦,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎不从,沒想到半個月后惜姐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡椿息,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年歹袁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寝优。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡条舔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乏矾,到底是詐尸還是另有隱情孟抗,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布钻心,位于F島的核電站夸浅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扔役。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一警医、第九天 我趴在偏房一處隱蔽的房頂上張望亿胸。 院中可真熱鬧,春花似錦预皇、人聲如沸侈玄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽序仙。三九已至,卻和暖如春鲁豪,著一層夾襖步出監(jiān)牢的瞬間潘悼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工爬橡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留治唤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓糙申,卻偏偏與公主長得像宾添,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容