<b> 圖像基本處理算法的簡單實作(三)</b>
<b>書内叙述的細化算法:</b>
/**
* 對二值化Bitmap進行細化運算後傳回
*
* 采用“精通Visual.Cpp數字圖像處理典型算法及實作(第2版)”内叙述的細化算法
* JNIEnv* jni環境(jni必要參數)
* jobject java對象(jni必要參數)
* jintArray Bitmap所有像素值
* int Bitmap寬度
* int Bitmap高度
*/
JNIEXPORT jintArray JNICALL Java_org_join_image_util_JoinImage_thinning2(
JNIEnv* env, jobject obj, jintArray buf, int w, int h) {
LOGE("==thinning2==");
jint * cbuf;
cbuf = (*env)->GetIntArrayElements(env, buf, 0); // 擷取int數組元素
int black = 0xFF000000; // 不透明黑色
unsigned char foreground = 0xFF; // 前景灰階值:255(白)
unsigned char background = 0; // 背景灰階值:0(黑)
jboolean modified = 1; // 設定髒标記:true
unsigned char count; // 計數器
unsigned char mark[w][h]; // 可删除标記
int i, j, m, n; // 循環标記
unsigned char gray; // 灰階值
unsigned char grays[5][5]; // 5×5相鄰區域像素值
jint *p; // 指向源圖像像素的指針
/*
* 一次疊代操作(直到沒有點再滿足标記條件)
*
* 8-領域示意圖:
* P3 P2 P9
* P4 P1 P8
* P5 P6 P7
* 删除條件:
* (1.1) 2<=NZ(p1)<=6
* (1.2) Zo(p1)=1
* (1.3) p2*p4*p8=0或者Zo(p2)!=1
* (1.4) p2*p4*p6=0或者Zo(p4)!=1
* NZ(p1):p1的非零鄰點的個數
* Zo(p1):以p2 ,p3 ,…… ,p9為序時這些點的值從0到1變化的次數
*/
while (modified) {
modified = 0; // 設定髒标記:false
memset(mark, 0, sizeof(mark)); // 重置删除标記為false
// 由于使用5×5的結構元素,防止越界,不處理上下左右四邊兩層像素
for (i = 2; i < h - 2; i++) {
for (j = 2; j < w - 2; j++) {
p = cbuf + w * i + j; // 指向源圖像i行j列
gray = (*p) & 0xFF; // 獲得灰階值
// 如果目前點為背景灰階值則跳過
if (gray == background) {
continue;
}
// 獲得目前點相鄰的5×5區域内像素值(前景用1代表,背景用0代表)
for (m = -2; m <= 2; m++) {
for (n = -2; n <= 2; n++) {
// 前景色灰階值為255,是以直接除255即可
grays[m + 2][n + 2] = ((*(p + w * m + n)) & 0xFF) / 255;
}
// 判斷條件(1.1) 2<=NZ(p1)<=6
count = grays[1][1] + grays[1][2] + grays[1][3] + grays[2][1]
+ grays[2][3] + grays[3][1] + grays[3][2] + grays[3][3];
if (2 <= count && count <= 6) {
} else {
continue; // 條件(1.1)不成立,跳出循環
// 計算Zo(p1):四周像素由0變1的次數
count = 0; // 重置計數器
if (grays[1][2] < grays[1][1])
count++; // p2->p3
if (grays[1][1] < grays[2][1])
count++; // p3->p4
if (grays[2][1] < grays[3][1])
count++; // p4->p5
if (grays[3][1] < grays[3][2])
count++; // p5->p6
if (grays[3][2] < grays[3][3])
count++; // p6->p7
if (grays[3][3] < grays[2][3])
count++; // p7->p8
if (grays[2][3] < grays[1][3])
count++; // p8->p9
if (grays[1][3] < grays[1][2])
count++; // p9->p2
// 判斷條件(1.2) Zo(p1)=1
if (1 == count) {
continue; // 條件(1.2)不成立,跳出循環
// 判斷條件(1.3) p2*p4*p8=0或者Zo(p2)!=1
if (grays[1][2] * grays[2][1] * grays[2][3] == 0) {
// 計算Zo(p2):四周像素由0變1的次數
count = 0;
if (grays[0][2] < grays[0][1])
count++;
if (grays[0][1] < grays[1][1])
if (grays[1][1] < grays[2][1])
if (grays[2][1] < grays[2][2])
if (grays[2][2] < grays[2][3])
if (grays[2][3] < grays[1][3])
if (grays[1][3] < grays[0][3])
if (grays[0][3] < grays[0][2])
if (count != 1) {
} else {
continue; // 條件(1.3)不成立,跳出循環
// 判斷條件(1.4) p2*p4*p6=0或者Zo(p4)!=1
if (grays[1][2] * grays[2][1] * grays[3][2] == 0) {
// 計算Zo(p4):四周像素由0變1的次數
if (grays[1][1] < grays[1][0])
if (grays[1][0] < grays[2][0])
if (grays[2][0] < grays[3][0])
if (grays[3][0] < grays[3][1])
if (grays[3][1] < grays[3][2])
if (grays[3][2] < grays[2][2])
if (grays[2][2] < grays[1][2])
if (grays[1][2] < grays[1][1])
continue; // 條件(1.4)不成立,跳出循環
/*
* 四條件都成立時
*/
mark[j][i] = 1; // 删除标記為true
modified = 1; // 髒标記為true
}
}
// 由删除标記去除
if (modified) {
for (i = 2; i < h - 2; i++) {
for (j = 2; j < w - 2; j++) {
// 如果删除标記為true
if (1 == mark[j][i]) {
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;
}
<b>6</b><b>)歐拉數</b>
歐拉數被定義為連接配接體數與其中的孔洞數之差,用公式表述:E=C-H。由此,我們可以求得孔洞數H=C-E(其實一樣的,隻是判斷時友善了解==)。
實作為八鄰域的的(公式亦看實作代碼注釋裡^^)。主要注意方式1,二值化圖像周圍一圈需要是背景色。詳細如下:
* 以該方式求歐拉數,二值化圖像周圍一圈需要是背景色
* 如果二值化圖像切割到邊緣(周圍一圈有前景色)
* 這時兩側和下邊被包在前景色内的背景色都會算作孔洞,如下:
* 0 1 1 0 \亦表示0,用于區分
* 1 \ \ 1
* \ \ 1 \
* \ 1 \ \
* 1 1 1 1
* 1 1 \ 1
* 右斜杠部分會被視作是個孔洞(也就是歐拉數非預期值)
* 歐拉數=-2;孔洞數=1-(-2)=3
<b>方式1</b><b>(廣泛應用,包括MATLAB</b><b>):</b>
* 求二值圖像歐拉數(二值化圖像周圍一圈需要是背景色)
int euler(jint *color, int w, int h) {
/**
* E(8) = (S1-S3-2X) / 4(8-連通)
* S1、S3 和X代表二值圖像 中具有下列模式的2×2方塊個數
* S1:1個1,3個0的模式
* S3:3個1,1個0的模式
* X:2個1在一條對角線,2個0在另一條對角線上的模式
int count_s1 = 0, count_s3 = 0, count_x = 0; // 模式計數
int count_1; // 1的計數
int gray[2][2]; // 2×2方塊顔色值
int white = 0xFFFFFFFF; // 不透明白色(前景色)
int i, j, m, n;
// 防止越界,最右和最下兩行像素不處理
for (i = 0; i < h - 1; i++) {
for (j = 0; j < w - 1; j++) {
// 計數置0
count_1 = 0;
// 擷取2×2方塊灰階值
for (m = 0; m < 2; m++) {
for (n = 0; n < 2; n++) {
gray[m][n] = *(color + w * i + j + w * m + n);
// 判斷是否為前景色
if (white == gray[m][n]) {
count_1++;
// 判斷是否為S1模式
if (1 == count_1) {
count_s1++;
// 判斷是否為S3模式
else if (3 == count_1) {
count_s3++;
// 判斷是否為X模式
else if (2 == count_1) {
// 判斷對角線是否同背景色
if (gray[0][0] == gray[1][1]) {
count_x++;
// LOGE("s1=%d;s2=%d;x=%d", count_s1, count_s3, count_x);
// 傳回歐拉數
return (count_s1 - count_s3 - 2 * count_x) / 4;
<b>方式2</b><b>(圖段方式):</b>
* 定義圖段結構
struct scope {
int start; // 開始索引
int end; // 結束索引
// int length; // end - start + 1
};
* 求某行[start, end]間圖段個數
int countScope(jint *line, int start, int end) {
unsigned char tag = 0; // 變換标記
int i, count = 0;
for (i = start; i <= end; i++) {
if (*(line + i) == white) { // 如果是前景色
if (tag == 0) { // 如果之前是背景色
tag = 1; // 設定為前景色标記
count++; // 交點+1
} else { // 否則背景色
if (tag == 1) { // 如果之前是前景色
tag = 0; // 設定為背景色标記
return count;
* 求二值圖像歐拉數(由圖段和相領數計算)
int euler2(jint *color, int w, int h) {
unsigned char tag; // 變換标記
* E = ∑[i=1->I] ∑[n=0->N(i)] (1- Vm)
* I:圖像的行數
* N(i):圖像第i行内的圖段個數
* Vm:圖像第i行,第n個圖段所對應的相鄰數
jint *line, *frontLine; // 定義行指針
line = color; // 指向首位址
int scopeNum, scopeNeighbor; // 圖段計數,相鄰圖段數
int eulerNum = 0; // 歐拉數
int count; // 計數
int i, j, k, m, n;
for (i = 0; i <= h - 1; i++) {
scopeNum = countScope(line, 0, w - 1); // 計算該行圖段數
struct scope sco[scopeNum]; // 定義相應圖段結構體數組
count = 0; // 計數置0
tag = 0; // 标記置0
// 該行圖段指派
for (j = 0; j <= w - 1; j++) {
if (*(line + j) == white) { // 如果是前景色
if (tag == 0) { // 如果之前是背景色
tag = 1; // 設定為前景色标記
sco[count].start = j; // 該行對應圖段起始索引
// 如果該前景色已是最後一位
if (j == w - 1) {
sco[count].end = j; // 該行對應圖段結束索引
} else { // 否則背景色
if (tag == 1) { // 如果之前是前景色
tag = 0; // 設定為背景色标記
sco[count].end = j - 1; // 該行對應圖段結束索引
count++; // 計數+1
// 該行各圖段貢獻值
for (k = 0; k <= scopeNum - 1; k++) {
/* 求Vm:圖段f(i,j:j+K-1)的相鄰數
*
* 4-連通:f(i-1,j:j+K-1) 該圖段範圍上一行内圖段數
* 8-連通:f(i-1,j-1:j+K) 該圖段範圍上一行左右各+1像素内圖段數
*/
if (i == 0) { // 如果是第一行
scopeNeighbor = 0; // 圖段相鄰數
} else {
// 圖段相鄰數(8-連通)
frontLine = line - w; // 前一行指針
m = (sco[k].start - 1 <= 0) ? 0 : sco[k].start - 1; // 開始位置
n = (sco[k].end + 1 >= w - 1) ? w - 1 : sco[k].end + 1; // 結束位置
scopeNeighbor = countScope(frontLine, m, n);
// LOGE( "第%d行第%d個圖段相鄰數:%d", i + 1, k + 1, scopeNeighbor);
eulerNum = eulerNum + 1 - scopeNeighbor;
line = line + w; // 指向下一行
return eulerNum;
方式2從下載下傳的論文文獻裡看到的^^。
<b>三、後記</b>
好吧,小弟承認基本也就隻知道這麼多了T^T。
中間再加一下分割,用過線數啥的方法,簡單識别識别數字還是可以的==。
推薦閱讀
1、精通Visual.Cpp數字圖像處理典型算法及實作 張宏林
2、【課件】數字圖像處理和分析技術(清華大學) 章毓晉
ps:其實這些算法的話,在matlab調用一個函數就可以了==。可以Google搜尋“MATLAB 數值計算 方法”類似的關鍵字。
本文轉自winorlose2000 51CTO部落格,原文連結:http://blog.51cto.com/vaero/823004,如需轉載請自行聯系原作者