轉自 http://vaero.blog.51cto.com/4350852/822997
一、引言
圖像處理基本算法整理。
拿來舉例的實現代碼是在JNI方法內直接實現的,且傳入參數爲int[]顏色值,返回爲新的int[]顏色值,可能頭上還包括了長寬。(很醜,見諒T^T)
2.2的NDK提供了Bitmap.h,這種方式可參考《Android NDK基礎樣例》的樣例3,灰度化圖像(Bitmap作爲參數)。
二、目錄
1)縮放算法
據說有最鄰近插值、雙線性內插值、高階插值、三次卷積法等等。(我已經暈了~)
縮放是從原圖像->目標圖像的過程。目標圖像的新顏色值,由圖像長寬比反向計算在原圖像的位置,從而獲得。反向計算得到的座標一般爲浮點座標,表示爲(i+u,j+v)(i,j整數整數、u,v小數部分)。
1)最鄰近插值:取(i,j)的顏色值即可,效果不咋的==
2)雙線性內插值:由(i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)四點距(i+u,j+v)遠近計算比例求得(四領域乘以相應的權重)。效果不錯了哈==
公式:f(i+u,j+v)=(1-u)(1-v)*f(i,j)+(1-u)v*f(i,j+1)+u(1-v)*f(i+1,j)+uv*f(i+1,j+1)
複製:雙線性內插值具有低通濾波器性質,使高頻風量受損,可能會使圖像輪廓在一定程度上變得模糊。尤其放大處理,影響將更爲明顯。
3)高階插值、三次卷積法等:說是雙線性使細節柔化、會有鋸齒什麼的。這些算法就是能夠更好的修正這些不足,但計算量更大==。(高階插值沒搜索到具體算法啊,是指一類概念麼?雙三次插值屬於高階插值這類的意思?)
雙線性內插值的實現:int min(int x, int y) {
return (x <= y) ? x : y;
}
int alpha(int color) {
return (color >> 24) & 0xFF;
}
int red(int color) {
return (color >> 16) & 0xFF;
}
int green(int color) {
return (color >> 8) & 0xFF;
}
int blue(int color) {
return color & 0xFF;
}
int ARGB(int alpha, int red, int green, int blue) {
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
/**
* 按雙線性內插值算法將對應源圖像四點顏色某一顏色值混合
*
* int(*fun)(int)指向從color中獲取某一顏色值的方法
*/
int mixARGB(int *color, int i, int j, float u, float v, int(*fun)(int)) {
// f(i+u,j+v)=(1-u)(1-v)*f(i,j)+(1-u)v*f(i,j+1)+u(1-v)*f(i+1,j)+uv*f(i+1,j+1)
return (1 - u) * (1 - v) * (*fun)(color[0]) + (1 - u) * v * (*fun)(color[1])
+ u * (1 - v) * (*fun)(color[2]) + u * v * (*fun)(color[3]);
}
/**
* 按雙線性內插值算法將對應源圖像四點顏色值混合
*
* color[]需要有四個顏色值,避免越界
*/
int mixColor(int *color, int i, int j, float u, float v) {
int a = mixARGB(color, i, j, u, v, alpha); // 獲取alpha混合值
int r = mixARGB(color, i, j, u, v, red); // 獲取red混合值
int g = mixARGB(color, i, j, u, v, green); // 獲取green混合值
int b = mixARGB(color, i, j, u, v, blue); // 獲取blue混合值
return ARGB(a, r, g, b);
}
/**
* 將Bitmap縮放後返回(雙線性內插值算法)
*
* JNIEnv* jni環境(jni必要參數)
* jobject java對象(jni必要參數)
* jintArray Bitmap所有像素值
* int Bitmap寬度
* int Bitmap高度
* int Bitmap新寬度
* int Bitmap新高度
*/
JNIEXPORT jintArray JNICALL Java_org_join_image_util_JoinImage_stretch(
JNIEnv* env, jobject obj, jintArray buf, int srcW, int srcH, int dstW,
int dstH) {
LOGE("==stretch==");
jint * cbuf;
cbuf = (*env)->GetIntArrayElements(env, buf, 0); // 獲取int數組元素
int newSize = dstW * dstH;
jint rbuf[newSize]; // 新圖像像素值
float rateH = (float) srcH / dstH; // 高度縮放比例
float rateW = (float) srcW / dstW; // 寬度縮放比例
int dstX, dstY; // 目標圖像XY座標
float srcX, srcY; // 目標圖像對應源圖像XY座標
int i, j; // 對應源圖像XY座標整數部分
int i1, j1; // 對應源圖像XY座標整數部分+1
float u, v; // 對應源圖像XY座標小數部分
int color[4]; // f(i+u,j+v)對應源圖像(i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)的像素值
for (dstY = 0; dstY <= dstH - 1; dstY++) {
srcY = dstY * rateH; // 對應源圖像Y座標
j = (int) srcY; // 對應源圖像Y座標整數部分
j1 = min(j + 1, srcH - 1); // 對應源圖像Y座標整數部分+1
v = srcY - j; // 對應源圖像Y座標小數部分
for (dstX = 0; dstX <= dstW - 1; dstX++) {
srcX = dstX * rateW; // 對應源圖像X座標
i = (int) srcX; // 對應源圖像X座標整數部分
i1 = min(i + 1, srcW - 1); // 對應源圖像X座標整數部分+1
u = srcX - i; // 對應源圖像X座標小數部分
// 雙線性內插值算法(注意ARGB時,需要分別由插值算法求得後重組):
// f(i+u,j+v)=(1-u)(1-v)*f(i,j)+(1-u)v*f(i,j+1)+u(1-v)*f(i+1,j)+uv*f(i+1,j+1)
color[0] = cbuf[j * srcW + i]; // f(i,j)顏色值
color[1] = cbuf[j1 * srcW + i]; // f(i,j+1)顏色值
color[2] = cbuf[j * srcW + i1]; // f(i+1,j)顏色值
color[3] = cbuf[j1 * srcW + i1]; // f(i+1,j+1)顏色值
// 給目標圖像賦值爲雙線性內插值求得的混合色
rbuf[dstY * dstW + dstX] = mixColor(color, i, j, u, v);
}
}
jintArray result = (*env)->NewIntArray(env, newSize); // 新建一個jintArray
(*env)->SetIntArrayRegion(env, result, 0, newSize, rbuf); // 將rbuf轉存入result
(*env)->ReleaseIntArrayElements(env, buf, cbuf, 0); // 釋放int數組元素
return result;
}
2)灰度化
把圖像變灰,有好些方法,求RGB平均值啊,RGB最大值啊什麼的。不過還是建議按規範的標準來。
彩色轉灰度的著名心理學公式:Gray = R*0.299 + G*0.587 + B*0.114(話說我心理學與生活一本書都看完了也沒提到這公式啊==)
實際應用中爲了避免浮點運算,然後就有了移位運算代替了。
2至20位精度的係數:
Gray = (R*1 + G*2 + B*1) >> 2
Gray = (R*2 + G*5 + B*1) >> 3
Gray = (R*4 + G*10 + B*2) >> 4
Gray = (R*9 + G*19 + B*4) >> 5
Gray = (R*19 + G*37 + B*8) >> 6
Gray = (R*38 + G*75 + B*15) >> 7
Gray = (R*76 + G*150 + B*30) >> 8
Gray = (R*153 + G*300 + B*59) >> 9
Gray = (R*306 + G*601 + B*117) >> 10
Gray = (R*612 + G*1202 + B*234) >> 11
Gray = (R*1224 + G*2405 + B*467) >> 12
Gray = (R*2449 + G*4809 + B*934) >> 13
Gray = (R*4898 + G*9618 + B*1868) >> 14
Gray = (R*9797 + G*19235 + B*3736) >> 15
Gray = (R*19595 + G*38469 + B*7472) >> 16
Gray = (R*39190 + G*76939 + B*14943) >> 17
Gray = (R*78381 + G*153878 + B*29885) >> 18
Gray = (R*156762 + G*307757 + B*59769) >> 19
Gray = (R*313524 + G*615514 + B*119538) >> 20
3與4、7與8、10與11、13與14、19與20的精度說是一樣的==。16位運算下最好的計算公式是使用7位精度。而遊戲由於場景經常變化,用戶感覺不到,最常用2位精度。
灰度化實現:
JNIEXPORT jintArray JNICALL Java_org_join_image_util_JoinImage_imgToGray(
JNIEnv* env, jobject obj, jintArray buf, int w, int h) {
LOGE("==imgToGray==");
jint * cbuf;
cbuf = (*env)->GetIntArrayElements(env, buf, 0); // 獲取int數組元素
int alpha = 0xFF; // 不透明值
int i, j, color, red, green, blue;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
color = cbuf[w * i + j]; // 獲得color值
red = (color >> 16) & 0xFF; // 獲得red值
green = (color >> 8) & 0xFF; // 獲得green值
blue = color & 0xFF; // 獲得blue值
color = (red * 38 + green * 75 + blue * 15) >> 7; // 灰度算法(16位運算下7位精度)
color = (alpha << 24) | (color << 16) | (color << 8) | color; // 由ARGB組成新的color值
cbuf[w * i + j] = color; // 設置新color值
}
}
int size = w * h;
jintArray result = (*env)->NewIntArray(env, size); // 新建一個jintArray
(*env)->SetIntArrayRegion(env, result, 0, size, cbuf); // 將cbuf轉存入result
(*env)->ReleaseIntArrayElements(env, buf, cbuf, 0); // 釋放int數組元素
return result;
}
3)二值化
灰度值[0,255]和一閾值比較,變成0或255,要麼純黑要麼純白==。但是閾值的獲取就牽扯算法了。只知道有Otsu、Bernsen…,具體算法查下就好^^
Otsu:最大類間方差法,整體算出一個閾值。計算次數少但抗干擾性差,適合光照均勻的圖像。
Bernsen:局部閾值法,在一點周圍一定範圍內(相當於一窗口)計算出一閾值。計算次數多但抗干擾性強,用於非均勻光照的圖像。
二值化(Otsu)的實現:
/**
* 將灰度化Bitmap各像素值二值化後返回
*
* JNIEnv* jni環境(jni必要參數)
* jobject java對象(jni必要參數)
* jintArray Bitmap所有像素值
* int Bitmap寬度
* int Bitmap高度
*/
JNIEXPORT jintArray JNICALL Java_org_join_image_util_JoinImage_binarization(
JNIEnv* env, jobject obj, jintArray buf, int w, int h) {
LOGE("==binarization==");
jint * cbuf;
cbuf = (*env)->GetIntArrayElements(env, buf, 0); // 獲取int數組元素
int white = 0xFFFFFFFF; // 不透明白色
int black = 0xFF000000; // 不透明黑色
int thresh = otsu(cbuf, w, h); // OTSU獲取分割閥值
LOGE("==[閥值=%d]==", thresh);
int i, j, gray;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
gray = (cbuf[w * i + j]) & 0xFF; // 獲得灰度值(red=green=blue)
if (gray < thresh) {
cbuf[w * i + j] = white; // 小於閥值設置爲白色(前景)
} else {
cbuf[w * i + j] = black; // 否則設置爲黑色(背景)
}
}
}
int size = w * h;
jintArray result = (*env)->NewIntArray(env, size); // 新建一個jintArray
(*env)->SetIntArrayRegion(env, result, 0, size, cbuf); // 將cbuf轉存入result
(*env)->ReleaseIntArrayElements(env, buf, cbuf, 0); // 釋放int數組元素
return result;
}
/**
* OTSU算法求最適分割閾值
*/
int otsu(jint* colors, int w, int h) {
unsigned int pixelNum[256]; // 圖象灰度直方圖[0, 255]
int color; // 灰度值
int n, n0, n1; // 圖像總點數,前景點數, 後景點數(n0 + n1 = n)
int w0, w1; // 前景所佔比例, 後景所佔比例(w0 = n0 / n, w0 + w1 = 1)
double u, u0, u1; // 總平均灰度,前景平均灰度,後景平均灰度(u = w0 * u0 + w1 * u1)
double g, gMax; // 圖像類間方差,最大類間方差(g = w0*(u0-u)^2+w1*(u1-u)^2 = w0*w1*(u0-u1)^2)
double sum_u, sum_u0, sum_u1; // 圖像灰度總和,前景灰度總和, 後景平均總和(sum_u = n * u)
int thresh; // 閾值
memset(pixelNum, 0, 256 * sizeof(unsigned int)); // 數組置0
// 統計各灰度數目
int i, j;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
color = (colors[w * i + j]) & 0xFF; // 獲得灰度值
pixelNum[color]++; // 相應灰度數目加1
}
}
// 圖像總點數
n = w * h;
// 計算總灰度
int k;
for (k = 0; k <= 255; k++) {
sum_u += k * pixelNum[k];
}
// 遍歷判斷最大類間方差,得到最佳閾值
for (k = 0; k <= 255; k++) {
n0 += pixelNum[k]; // 圖像前景點數
if (0 == n0) { // 未獲取前景,直接繼續增加前景點數
continue;
}
if (n == n0) { // 前景點數包括了全部時,不可能再增加,退出循環
break;
}
n1 = n - n0; // 圖像後景點數
sum_u0 += k * pixelNum[k]; // 前景灰度總和
u0 = sum_u0 / n0; // 前景平均灰度
u1 = (sum_u - sum_u0) / n1; // 後景平均灰度
g = n0 * n1 * (u0 - u1) * (u0 - u1); // 類間方差(少除了n^2)
if (g > gMax) { // 大於最大類間方差時
gMax = g; // 設置最大類間方差
thresh = k; // 取最大類間方差時對應的灰度的k就是最佳閾值
}
}
return thresh;
}