1. 程式人生 > >基於正態分佈的圖片高斯模糊演算法

基於正態分佈的圖片高斯模糊演算法

前言:

  先來看看下面這張圖,我把一張圖進行了二等份了,左邊是經過高斯模糊過的,右邊是原圖。

  

圖-1 高斯模糊效果對比圖

概述:

  高斯模糊也叫做高斯平滑,是一種影象平滑處理的技術。高斯模糊演算法的原理是選取一箇中心點,以及一個半徑周圍的所有點,再計算這些畫素點的平均值,這個值就是目前這個中心點的值了。這樣實現的效果是可以降低影象中的噪音干擾,以達到忽略影象中的細節的目的。

原理說明:

  上面說到高斯模糊是計算某些畫素點的平均值,那麼究竟是什麼樣的呢?看圖-2。


圖-2 畫素點均值計算

  現在我們假設我們就是按照上面的圖形進行選取一些畫素點的,可是我們的實際影象的畫素可不止這些。所以,這裡就涉及到另一個知識點了:卷積。不要被卷積這個字面上的詞語所驚嚇,如果你覺得書面上卷積晦澀難懂,那麼你可以看看下面這幅圖,它大致描繪了在本文中使用到的卷積基礎。

  從下面的圖解中,我們可以看到,就像是有一塊固定大小的區域在沿著橫向或是縱向滑動一樣。是的,這裡我們是以亮綠色為中心點,包含一個半徑為1的周圍點進行向右和向下的平移。這個很像在計算機網路中學到的滑動視窗一樣。


圖-3 卷積概念圖解(部分)

邏輯實現:

1.樸素的高斯模糊演算法

  如上面的圖-2和圖-3,我們知道模糊一張圖片的做是可以卷積計算每個點的平均值。那麼現在我們就選取以9個點為一個單位模組進行卷積計算,再求其平均值。效果如下圖-4.


圖-4 樸素高斯模糊效果對比圖

  從效果圖中我們的確是可以看出有一些模糊的效果,不過感覺上像是圖片進行了一定畫素上的滑移。而且,影象很暗。結論:演算法很糟糕。

2.基於正態分佈的高斯模糊演算法

  上面樸素的做法是讓中心點和它周圍的點具有相同數值的權重。這樣是不合理的,為什麼?

  我們知道圖片是連續的,距離越近的畫素點,數值應該是更相近的。那麼,樸素高斯模糊中簡單平均的做法肯定就不合理了。

  所以這裡我們是使用正態分佈來分配權重。正態分佈的分佈圖如下圖-5所示:


圖-5 一維正態分佈圖

  這幅圖相信大家都並不陌生,高中時的課本上就有的。不過因為我們的圖片是一個二維的影象,那麼單純的一維還是解決不了問題,下面看看一下二維的高斯分佈圖吧。


圖-6 二維高斯分佈圖

  圖-6中的說明之所以修改成了高斯分佈圖,是因為高斯模糊中使用的正態分佈,跟我們在高中時期學習的正態分佈公式有一些不一樣。

  一維高斯函式:


  二維高斯函式:


  二維高斯函式的實現如下:

/**
     * 二維高斯函式
     * 
     * @param n
     *      二維高斯的範圍[-n, n]
     * @param σ
     *      標準方差
     * @return
     *      範圍[-n, n]之內的高斯函式值
     */
    public static float[][] getTwoDimenGaussianFunction(int n, float σ) {        
        int size = 2 * n + 1;
        float σ22 = 2 * σ * σ;
        float σ22PI = (float)Math.PI * σ22;
        float[][] kernalData = new float[size][size];
        int row = 0;
        for(int i = -n; i <= n; i++) {
            int column = 0;
            for(int j = -n; j <= n; j++) {
                float xDistance = i * i;
                float yDistance = j * j;
                kernalData[row][column] = (float)Math.exp(-(xDistance + yDistance) / σ22) / σ22PI;
                column++;
            }
            row++;
        }
        return kernalData;
    }
  根據以上內容我們可以編寫以下高斯模糊核心演算法的Java程式碼:
public class GaussianBlur implements ImageInterface {
    
    private int radius;
    private int round;
    
    // 高斯函式的權重矩陣
    private float[][] normal_distribution = null;
    
    public GaussianBlur(int _round, int _radius) {
        ... ...
        initEvent(radius, 1.5f);
    }
    
    public static void main(String[] args) {
        GaussianBlur blur = new GaussianBlur(5, 1);
        try {
            blur.gaussianBlur("F:\\Wall Paper\\9.jpg", "F:\\Wall Paper\\9-gb.jpg");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 基於正態分佈的圖片高斯模糊
     */
    public void gaussianBlur(String sourcePath, String targetPath) throws IOException {
        gaussianBlur(sourcePath, targetPath, round, radius);
    }
    
    /**
     * 基於正態分佈的圖片高斯模糊
     */
    public void gaussianBlur(String sourcePath, String targetPath, int round, int radius) throws IOException {
        BufferedImage bufferedImage = ImageIO.read(new File(sourcePath));
        int height = bufferedImage.getHeight();
        int width = bufferedImage.getWidth();
        
        int matrixLength = 2 * radius + 1;
        int[][] matrix = new int[matrixLength][matrixLength];
        int[] values = new int[matrixLength * matrixLength];
        
        for (int r = 0; r < round; r++) {
            for (int i = 0; i < width / 2; i++) {
                for (int j = 0; j < height; j++) {
                    readPixel(bufferedImage, i, j, values);
                    fillMatrix(matrix, values);
                    bufferedImage.setRGB(i, j, avgMatrix(matrix));
                }
            }
        }
        
        ImageIO.write(bufferedImage, "jpg", new File(targetPath));
    }
    
    /*
     * 初始化任務
     */
    private void initEvent(int n, float σ) {
        normal_distribution = MathUtils.getTwoDimenGaussianSumOne(n, σ);
    }
    
    /*
     * 讀取圖片上的畫素點
     */
    private void readPixel(BufferedImage img, int x, int y, int[] pixels) {
        int radius = (int) ((Math.sqrt(pixels.length) - 1) / 2);
        int raw = 2 * radius + 1;
        int clo = 2 * radius + 1;
        
        int xStart = x - radius;
        int yStart = y - radius;
        int current = 0;
        for (int i = xStart; i < clo + xStart; i++) {
            for (int j = yStart; j < raw + yStart; j++) {
                int tx = i;
                // 邊界處理
                if (tx < 0) {
                    tx = -tx;
                } else if (tx >= img.getWidth()) {
                    tx = x;
                }

                int ty = j;
                // 邊界處理
                if (ty < 0) {
                    ty = -ty;
                } else if (ty >= img.getHeight()) {
                    ty = y;
                }
                pixels[current++] = img.getRGB(tx, ty);
            }
        }
    }

    /*
     * 將讀取的畫素RGB值儲存到二維陣列中
     */
    private void fillMatrix(int[][] matrix, int... values) {
        int filled = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                x[j] = values[filled++];
            }
        }
    }

    /*
     * 計算平均值重新寫入圖片
     */
    private int avgMatrix(int[][] matrix) {
        int red = 0;
        int green = 0;
        int blue = 0;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                Color color = new Color(matrix[i][j]);
                red += (normal_distribution[i][j] * color.getRed());
                green += (normal_distribution[i][j] * color.getGreen());
                blue += (normal_distribution[i][j] * color.getBlue());
            }
        }
        return new Color(red, green, blue).getRGB();
    }
}

效果圖:


圖-7 一輪高斯模糊


圖-8 三輪高斯模糊


圖-9 五輪高斯模糊


圖-10 十輪高斯模糊

Ref:

注:本文部分圖片也是來源於上面的兩個部落格,在此特作說明。

關聯知識點:

1.正態分佈

2.卷積影象處理