天天看點

圖像基本處理算法的簡單實作(三)

<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)-&gt;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&lt;=NZ(p1)&lt;=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 &lt; h - 2; i++) { 

            for (j = 2; j &lt; w - 2; j++) { 

                p = cbuf + w * i + j; // 指向源圖像i行j列 

                gray = (*p) &amp; 0xFF; // 獲得灰階值 

                // 如果目前點為背景灰階值則跳過 

                if (gray == background) { 

                    continue; 

                } 

                // 獲得目前點相鄰的5×5區域内像素值(前景用1代表,背景用0代表) 

                for (m = -2; m &lt;= 2; m++) { 

                    for (n = -2; n &lt;= 2; n++) { 

                        // 前景色灰階值為255,是以直接除255即可 

                        grays[m + 2][n + 2] = ((*(p + w * m + n)) &amp; 0xFF) / 255; 

                    } 

                // 判斷條件(1.1) 2&lt;=NZ(p1)&lt;=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 &lt;= count &amp;&amp; count &lt;= 6) { 

                } else { 

                    continue; // 條件(1.1)不成立,跳出循環 

                // 計算Zo(p1):四周像素由0變1的次數 

                count = 0; // 重置計數器 

                if (grays[1][2] &lt; grays[1][1]) 

                    count++; // p2-&gt;p3 

                if (grays[1][1] &lt; grays[2][1]) 

                    count++; // p3-&gt;p4 

                if (grays[2][1] &lt; grays[3][1]) 

                    count++; // p4-&gt;p5 

                if (grays[3][1] &lt; grays[3][2]) 

                    count++; // p5-&gt;p6 

                if (grays[3][2] &lt; grays[3][3]) 

                    count++; // p6-&gt;p7 

                if (grays[3][3] &lt; grays[2][3]) 

                    count++; // p7-&gt;p8 

                if (grays[2][3] &lt; grays[1][3]) 

                    count++; // p8-&gt;p9 

                if (grays[1][3] &lt; grays[1][2]) 

                    count++; // p9-&gt;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] &lt; grays[0][1]) 

                        count++; 

                    if (grays[0][1] &lt; grays[1][1]) 

                    if (grays[1][1] &lt; grays[2][1]) 

                    if (grays[2][1] &lt; grays[2][2]) 

                    if (grays[2][2] &lt; grays[2][3]) 

                    if (grays[2][3] &lt; grays[1][3]) 

                    if (grays[1][3] &lt; grays[0][3]) 

                    if (grays[0][3] &lt; 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] &lt; grays[1][0]) 

                    if (grays[1][0] &lt; grays[2][0]) 

                    if (grays[2][0] &lt; grays[3][0]) 

                    if (grays[3][0] &lt; grays[3][1]) 

                    if (grays[3][1] &lt; grays[3][2]) 

                    if (grays[3][2] &lt; grays[2][2]) 

                    if (grays[2][2] &lt; grays[1][2]) 

                    if (grays[1][2] &lt; grays[1][1]) 

                        continue; // 條件(1.4)不成立,跳出循環 

                /* 

                 * 四條件都成立時 

                 */ 

                mark[j][i] = 1; // 删除标記為true 

                modified = 1; // 髒标記為true 

            } 

        } 

        // 由删除标記去除 

        if (modified) { 

            for (i = 2; i &lt; h - 2; i++) { 

                for (j = 2; j &lt; w - 2; j++) { 

                    // 如果删除标記為true 

                    if (1 == mark[j][i]) { 

                        cbuf[w * i + j] = black; // 修改成背景色(黑) 

    } 

    int size = w * h; 

    jintArray result = (*env)-&gt;NewIntArray(env, size); // 建立一個jintArray 

    (*env)-&gt;SetIntArrayRegion(env, result, 0, size, cbuf); // 将cbuf轉存入result 

    (*env)-&gt;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 &lt; h - 1; i++) { 

        for (j = 0; j &lt; w - 1; j++) { 

            // 計數置0 

            count_1 = 0; 

            // 擷取2×2方塊灰階值 

            for (m = 0; m &lt; 2; m++) { 

                for (n = 0; n &lt; 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 &lt;= 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-&gt;I] ∑[n=0-&gt;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 &lt;= h - 1; i++) { 

        scopeNum = countScope(line, 0, w - 1); // 計算該行圖段數 

        struct scope sco[scopeNum]; // 定義相應圖段結構體數組 

        count = 0; // 計數置0 

        tag = 0; // 标記置0 

        // 該行圖段指派 

        for (j = 0; j &lt;= 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 &lt;= 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 &lt;= 0) ? 0 : sco[k].start - 1; // 開始位置 

                n = (sco[k].end + 1 &gt;= 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,如需轉載請自行聯系原作者