天天看點

android高斯模糊平均值,高斯模糊

前言

通常,圖像處理軟體會提供"模糊"(blur)濾鏡,使圖檔産生模糊的效果。

android高斯模糊平均值,高斯模糊

“模糊”的算法不隻一種,高斯模糊隻是其中一種,甚至它隻是其中效率很差的一種。

在Android中使用高斯模糊,需要使用到 JNI 技術,Android Studio開發之 JNI 篇已具體讨論JNI的用法等。本文主要講述高斯模糊原理及編碼等。

高斯模糊原理

所謂"模糊",可以了解成每一個像素都取周邊像素的平均值。

android高斯模糊平均值,高斯模糊

如圖所示,2是中間點,周圍點都是1。中間點取周圍點平均值,就會變成1。在數值上,這是一種"平滑化"。在圖形上,就相當于産生"模糊"效果,"中間點"失去細節。

顯然,計算平均值時,取值範圍越大,"模糊效果"越強烈。

如果使用簡單平均,顯然不是很合理,因為圖像都是連續的,越靠近的點關系越密切,越遠離的點關系越疏遠。是以,權重平均更合理,距離越近的點權重越大,距離越遠的點權重越小。

高斯模糊根據正态分布,決定周圍點的權重值。

android高斯模糊平均值,高斯模糊

正态分布是一種鐘形曲線,越接近中心,取值越大,越遠離中心,取值越小。

計算平均值的時候,我們隻需要将"中心點"作為原點,其他點按照其在正态曲線上的位置,配置設定權重,就可以得到一個權重平均值。

正态分布的一維公式為:

android高斯模糊平均值,高斯模糊

由于每次計算都是以中間點為原點,是以u為标準差,即為0。是以公式進一步進化為:

android高斯模糊平均值,高斯模糊

由于圖像是二維的,需要根據二維正态分布函數來計算權重值,它的公式以及曲線如下:

android高斯模糊平均值,高斯模糊

不過為了代碼效率問題,不會采用二維正态分布的計算方式,而是分别對 X 軸和 Y 軸進行兩次高斯模糊,也能達到效果(即通過一維正态分布計算權重)。

高斯模糊代碼

先分别計算正态分布各參數,sigma與高斯模糊半徑有關系,2.57既是1除以根号2 PI得來。

float sigma = 1.0 * radius / 2.57;

float deno = 1.0 / sigma * sqrt(2.0 * PI);

float nume = -1.0 / (2.0 * sigma * sigma);

因為對于每一個像素點來說,周圍點在正态分布中所占的權重值都是一樣的,是以正态分布計算一次即可。

float *gaussMatrix = (float *) malloc(sizeof(float) * (radius + radius + 1));

float gaussSum = 0.0;

for (int i = 0, x = -radius; x <= radius; ++x, ++i) {

float g = deno * exp(1.0 * nume * x * x);

gaussMatrix[i] = g;

gaussSum += g;

}

因為是以中間點自身為原點,是以 x 的取值範圍是從 -radius 到 radius,計算結果存儲的數組中。請注意周圍點權重值與數組的對應關系,x 等于 -radius 時,而 i 等于0,後文會用到。

由于并沒有計算所有的周圍點,是以權重總合必然不為1,是以需要歸一化,設法使權重值為一。

int len = radius + radius + 1;

for (int i = 0; i < len; ++i) {

gaussMatrix[i] /= gaussSum;

}

先進行 x 軸的模糊。

for (int y = 0; y < h; ++y) {

//取一行像素資料,注意像素總數組的通路方式是 x + y * w

memcpy(rowData, pix + y * w, sizeof(int) * w);

for (int x = 0; x < w; ++x) {

float r = 0, g = 0, b = 0;

gaussSum = 0;

//以目前坐标點 x、y 為中心,檢視前後一個模糊半徑的周圍點,根據正态分布

//重新計算像素點的顔色值

for (int i = -radius; i <= radius; ++i) {

// k 表示周圍點的真實坐标

int k = x + i;

// 邊界上的像素點,它的周圍點隻有正常的一半,是以要保證 k 的取值範圍

if (k >= 0 && k <= w) {

// 取到周圍點的像素,并根據 argb 的排列方式,計算 r、g、b分量

int color = rowData[k];

int cr = (color & 0x00ff0000) >> 16;

int cg = (color & 0x0000ff00) >> 8;

int cb = (color & 0x000000ff);

//真實點坐标為 k,與它對應的權重數組下标是 i + radius

//前文中計算正态分布權重時已經說明相關的對應關系。

//根據正态分布的權重關系,計算中心點的 r g b各分量

int index = i + radius;

r += cr * gaussMatrix[index];

g += cg * gaussMatrix[index];

b += cb * gaussMatrix[index];

gaussSum += gaussMatrix[index];

}

}

//因為邊界點的存在,gaussSum值不一定為1,是以需要除以gaussSum,歸一化。

int cr = (int) (r / gaussSum);

int cg = (int) (g / gaussSum);

int cb = (int) (b / gaussSum);

//根據權重值與各周圍點像素相乘之和,得到新的中間點像素。

pix[y * w + x] = cr << 16 | cg << 8 | cb | 0xff000000;

}

}

y軸的模糊原理和x軸基本一樣,這裡就不再重複說明了。

JNI圖檔接口

JNI中處理圖檔,需要引用 bitmap.h,頭檔案中主要定義三個方法。

int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,

AndroidBitmapInfo* info);

int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);

AndroidBitmap_getInfo:擷取圖檔資訊,比如寬、高、圖檔格式等

AndroidBitmap_lockPixels:顧名思義,鎖定像素

AndroidBitmap_unlockPixels:解鎖。

AndroidBitmap_lockPixels 和 AndroidBitmap_unlockPixels 成對調用,在兩個方法之間可對圖檔像素進行相應處理,解鎖像素以後,對圖檔的調整效果可以立即看到,并不需要再重新生成圖檔了。

ps:有時并不知道 JNI 有哪些接口可以調用,最好的方式就是看源碼,有哪些接口,一目了然。

其它模糊方法

除了高斯模糊之外,還有其它模糊方法,比如說 fastblur,不過這個算法還沒看明白,此處不再詳述,具體代碼本人的github上都有,歡迎通路。