基於正態分佈的圖片高斯模糊演算法
前言:
先來看看下面這張圖,我把一張圖進行了二等份了,左邊是經過高斯模糊過的,右邊是原圖。
圖-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.卷積影象處理