天天看點

Android數字圖像處理之二值化

Android數字圖像處理之二值化

下面blabla一段廢話心急的同志們可以跳過。

        一幅圖像包括目标物體,背景還有噪聲,怎樣從多值的數字圖像中取出目标物體,最常用的方法就是設定某一門檻值T,用T将圖像的資料分成兩大部分:大于T的像素群和小于T的像素群。這是研究灰階變換最特殊的方法,成為圖像的二值化。二值化處理就是把圖像f(x,y)分成目标物體和背景兩個區域,然後求其門檻值。二值化是數字圖像進行中一項最基本的變換方法,通過非0取1、固定門檻值、雙固定門檻值等不同的門檻值化變換方法,使一幅灰階圖變成了黑白二值圖像,将所需的目标部分從複雜的圖像背景中脫離出來,以利于以後的研究。

一、非0即1

思路:

  1. 傳入需要處理的圖像
  2. 周遊整個圖像取出圖像中每個點的像素值存到一個數組中(oldPix)
  3. 在循環中擷取每個像素點的顔色值,并抽取每個像素中的r,g,b,a分量準備處理
  4. 利用灰階公式計算出每個點的灰階值(範圍0-255),并做溢出處理
  5. 如果某點的灰階值為0則不做處理
  6. 如果某點的灰階值大于0,則将其灰階值置為255(非0即1)
  7. 将處理後的灰階值合成像素點的顔色值,存到一個新數組中(newPix)
  8. 建立一個高度、寬度和原圖完全一樣的新圖
  9. 将存新數組中的顔色值賦給新圖
  10. 将新圖像傳回

用于處理圖像的函數如下

private Bitmap zeroAndOne(Bitmap bm) {
        int width = bm.getWidth();//原圖像寬度
        int height = bm.getHeight();//原圖像高度
        int color;//用來存儲某個像素點的顔色值
        int r, g, b, a;//紅,綠,藍,透明度
        //建立空白圖像,寬度等于原圖寬度,高度等于原圖高度,用ARGB_8888渲染,這個不用了解,這樣寫就行了
        Bitmap bmp = Bitmap.createBitmap(width, height
                , Bitmap.Config.ARGB_8888);

        int[] oldPx = new int[width * height];//用來存儲原圖每個像素點的顔色資訊
        int[] newPx = new int[width * height];//用來處理處理之後的每個像素點的顔色資訊
        /**
         * 第一個參數oldPix[]:用來接收(存儲)bm這個圖像中像素點顔色資訊的數組
         * 第二個參數offset:oldPix[]數組中第一個接收顔色資訊的下标值
         * 第三個參數width:在行之間跳過像素的條目數,必須大于等于圖像每行的像素數
         * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标
         * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标
         * 第六個參數width:每行需要讀取的像素個數
         * 第七個參數height:需要讀取的行總數
         */
        bm.getPixels(oldPx, 0, width, 0, 0, width, height);//擷取原圖中的像素資訊

        for (int i = 0; i < width * height; i++) {//循環處理圖像中每個像素點的顔色值
            color = oldPx[i];//取得某個點的像素值
            r = Color.red(color);//取得此像素點的r(紅色)分量
            g = Color.green(color);//取得此像素點的g(綠色)分量
            b = Color.blue(color);//取得此像素點的b(藍色分量)
            a = Color.alpha(color);//取得此像素點的a通道值

            //此公式将r,g,b運算獲得灰階值,經驗公式不需要了解
            int gray = (int)((float)r*0.3+(float)g*0.59+(float)b*0.11);
            //下面前兩個if用來做溢出處理,防止灰階公式得到到灰階超出範圍(0-255)
            if(gray > 255) {
                gray = 255;
            }

            if(gray < 0) {
                gray = 0;
            }

            if (gray != 0) {//如果某像素的灰階值不是0(黑色)就将其置為255(白色)
                gray = 255;
            }

            newPx[i] = Color.argb(a,gray,gray,gray);//将處理後的透明度(沒變),r,g,b分量重新合成顔色值并将其存儲在數組中
        }
        /**
         * 第一個參數newPix[]:需要賦給新圖像的顔色數組//The colors to write the bitmap
         * 第二個參數offset:newPix[]數組中第一個需要設定給圖像顔色的下标值//The index of the first color to read from pixels[]
         * 第三個參數width:在行之間跳過像素的條目數//The number of colors in pixels[] to skip between rows.
         * Normally this value will be the same as the width of the bitmap,but it can be larger(or negative).
         * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标//The x coordinate of the first pixels to write to in the bitmap.
         * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标//The y coordinate of the first pixels to write to in the bitmap.
         * 第六個參數width:每行需要讀取的像素個數The number of colors to copy from pixels[] per row.
         * 第七個參數height:需要讀取的行總數//The number of rows to write to the bitmap.
         */
        bmp.setPixels(newPx, 0, width, 0, 0, width, height);//将處理後的像素資訊賦給新圖
        return bmp;//傳回處理後的圖像
    }
           

tips:表面上看每個像素點的alpha分量并沒有用到,但是處理png圖像時就能看到效果了。因為png圖像的背景是透明的,我們在合成新像素值時利用了原像素點的alph值,也就是新圖像的背景也是透明的。如果不用alpha分量,那麼處理後的圖像背景就不透明了(可能為純黑純白或其他顔色)。如果你不處理png圖形則在合成顔色資訊時可以用下面這個方法。

newPx[i] = Color.rgb(gray,gray,gray);
           

效果

Android數字圖像處理之二值化
Android數字圖像處理之二值化

非0即1法隻有被處理圖像中的純黑部分(也就是r,g,b分量都為0)才會保留下來,其他部分都将變成白色。是以在使用範圍上有一定的局限性。

二、固定門檻值法

思路:

  1. 傳入需要處理的圖像
  2. 擷取使用者輸入的門檻值T
  3. 周遊整個圖像取出圖像中每個點的像素值存到一個數組中(oldPix)
  4. 在循環中擷取每個像素點的顔色值,并抽取每個像素中的r,g,b,a分量準備處理
  5. 利用灰階公式計算出每個點的灰階值(範圍0-255)
  6. 如果某點的灰階值小于給定門檻值則将此點灰階值置為0(黑色)
  7. 如果某點的灰階值大于或等于給定門檻值,則将該點灰階值置為255(白色)
  8. 将處理後的灰階值合成像素點的顔色值,存到一個新數組中(newPix)
  9. 建立一個高度、寬度和原圖完全一樣的新圖
  10. 将存新數組中的顔色值賦給新圖
  11. 将得到的新圖傳給ImageView

用于處理圖像的函數如下:

private void singleThreshold(final Bitmap bm) {

        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());//建立AlertDialog對話框
        builder.setTitle("固定門檻值法");//對話框标題
        builder.setMessage("請輸入門檻值");//對話框内容
        View view1 = LayoutInflater.from(getContext()).inflate(R.layout.threshold,null);//載入自定義布局
        final EditText mSingleThresholdEt = view1.findViewById(R.id.digit_dialog);//自定義布局中的EditText,用于接收使用者輸入
        builder.setView(view1);//将布局設定到對話框中


        builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {//對話框的确定按鈕點選事件
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {

                int width = bm.getWidth();//原圖像寬度
                int height = bm.getHeight();//原圖高度
                int color;//用來存儲某個像素點的顔色值
                int r, g, b, a;//紅,綠,藍,透明度
                int digit = 0;//用于存儲使用者在對話框中屬于的門檻值
                //建立空白圖像,寬度等于原圖寬度,高度等于原圖高度,用ARGB_8888渲染,這個不用了解,這樣寫就行了
                Bitmap bmp = Bitmap.createBitmap(width, height
                        , Bitmap.Config.ARGB_8888);

                int[] oldPx = new int[width * height];//用來存儲原圖每個像素點的顔色資訊
                int[] newPx = new int[width * height];//用來處理處理之後的每個像素點的顔色資訊
                /**
                 * 第一個參數oldPix[]:用來接收(存儲)bm這個圖像中像素點顔色資訊的數組
                 * 第二個參數offset:oldPix[]數組中第一個接收顔色資訊的下标值
                 * 第三個參數width:在行之間跳過像素的條目數,必須大于等于圖像每行的像素數
                 * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标
                 * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标
                 * 第六個參數width:每行需要讀取的像素個數
                 * 第七個參數height:需要讀取的行總數
                 */
                bm.getPixels(oldPx, 0, width, 0, 0, width, height);//擷取原圖中的像素資訊

                String str = mSingleThresholdEt.getText().toString();//擷取使用者輸入的内容

                if("".equals(str)) {//如果使用者輸入的内容為空
                    digit = 0;//将門檻值置為0
                } else {//否則
                    digit = Integer.valueOf(str);//将使用者輸入的門檻值轉換為整數
                }

                for (int j = 0; j < width * height; j++) {//循環處理圖像中每個像素點的顔色值
                    color = oldPx[j];//取得某個點的像素值
                    r = Color.red(color);//取得此像素點的r(紅色)分量
                    g = Color.green(color);//取得此像素點的g(綠色)分量
                    b = Color.blue(color);//取得此像素點的b(藍色分量)
                    a = Color.alpha(color);//取得此像素點的a通道值

                    //此公式将r,g,b運算獲得灰階值,經驗公式不需要了解
                    int gray = (int)((float)r*0.3+(float)g*0.59+(float)b*0.11);
                    
                    if(gray < digit) {//如果某點像素灰階小于給定門檻值
                        gray = 0;//将該點像素的灰階值置為0(黑色)
                    } else {//如果某點像素灰階大于或等于給定門檻值
                        gray = 255;//将該點像素的灰階值置為1(白色)
                    }
                    newPx[j] = Color.argb(a,gray,gray,gray);//将處理後的透明度(沒變),r,g,b分量重新合成顔色值并将其存儲在數組中
                }
                /**
                 * 第一個參數newPix[]:需要賦給新圖像的顔色數組//The colors to write the bitmap
                 * 第二個參數offset:newPix[]數組中第一個需要設定給圖像顔色的下标值//The index of the first color to read from pixels[]
                 * 第三個參數width:在行之間跳過像素的條目數//The number of colors in pixels[] to skip between rows.
                 * Normally this value will be the same as the width of the bitmap,but it can be larger(or negative).
                 * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标//The x coordinate of the first pixels to write to in the bitmap.
                 * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标//The y coordinate of the first pixels to write to in the bitmap.
                 * 第六個參數width:每行需要讀取的像素個數The number of colors to copy from pixels[] per row.
                 * 第七個參數height:需要讀取的行總數//The number of rows to write to the bitmap.
                 */
                bmp.setPixels(newPx, 0, width, 0, 0, width, height);//将處理後的像素資訊賦給新圖
                mImageView.setImageBitmap(bmp);//将處理後的新圖賦給ImageView
            }
        });


        AlertDialog dialog = builder.create();
        dialog.show();
    }
           

下面是我們在接收使用者輸入時使用的對話框所引入的自定義布局threshold.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="門檻值:"/>
    <EditText
        android:id="@+id/digit_dialog"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
           

效果

Android數字圖像處理之二值化
Android數字圖像處理之二值化
Android數字圖像處理之二值化

三、雙固定門檻值法

思路:

  1. 傳入需要處理的圖像
  2. 擷取使用者輸入的門檻值下限T1和門檻值上限T2
  3. 周遊整個圖像取出圖像中每個點的像素值存到一個數組中(oldPix)
  4. 在循環中擷取每個像素點的顔色值,并抽取每個像素中的r,g,b,a分量準備處理
  5. 利用灰階公式計算出每個點的灰階值(範圍0-255)
  6. 如果某點的灰階值小于門檻值下限T1或大于門檻值上限T2,将其灰階值置為0
  7. 如果某點的灰階值大于等于門檻值下限T1或小于等于門檻值上限T2,将其灰階值置為255
  8. 将處理後的灰階值合成像素點的顔色值,存到一個新數組中(newPix)
  9. 建立一個高度、寬度和原圖完全一樣的新圖
  10. 将存新數組中的顔色值賦給新圖
  11. 将得到的新圖傳給ImageView

用于處理圖像的函數如下

private void doubleThreshold(final Bitmap bm) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle("雙固定門檻值法(0-255-0)");
        builder.setMessage("請輸入門檻值");
        View view1 = LayoutInflater.from(getContext()).inflate(R.layout.double_threshold_layout,null);
        mLowThresholdEt = view1.findViewById(R.id.low_digit_dialog);
        mHighThresholdEt = view1.findViewById(R.id.heigh_digit_dialog);
        builder.setView(view1);


        builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {

                int width = bm.getWidth();//原圖像寬度
                int height = bm.getHeight();//原圖高度
                int color;//用來存儲某個像素點的顔色值
                int r, g, b, a;//紅,綠,藍,透明度
                //建立空白圖像,寬度等于原圖寬度,高度等于原圖高度,用ARGB_8888渲染,這個不用了解,這樣寫就行了
                Bitmap bmp = Bitmap.createBitmap(width, height
                        , Bitmap.Config.ARGB_8888);

                int[] oldPx = new int[width * height];//用來存儲原圖每個像素點的顔色資訊
                int[] newPx = new int[width * height];//用來處理處理之後的每個像素點的顔色資訊
                /**
                 * 第一個參數oldPix[]:用來接收(存儲)bm這個圖像中像素點顔色資訊的數組
                 * 第二個參數offset:oldPix[]數組中第一個接收顔色資訊的下标值
                 * 第三個參數width:在行之間跳過像素的條目數,必須大于等于圖像每行的像素數
                 * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标
                 * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标
                 * 第六個參數width:每行需要讀取的像素個數
                 * 第七個參數height:需要讀取的行總數
                 */
                bm.getPixels(oldPx, 0, width, 0, 0, width, height);

                String str1 = mLowThresholdEt.getText().toString();
                String str2 = mHighThresholdEt.getText().toString();
                if("".equals(str1)) {
                    lowDigit = 0;
                } else {
                    lowDigit = Integer.valueOf(str1);//使用者輸入的門檻值下限
                }

                if("".equals(str2)) {
                    heighDigit = 255;
                } else {
                    heighDigit = Integer.valueOf(str2);//使用者輸入的門檻值上限
                }

                for (int j = 0; j < width * height; j++) {//循環處理圖像中每個像素點的顔色值
                    color = oldPx[j];//取得某個點的像素值
                    r = Color.red(color);//取得此像素點的r(紅色)分量
                    g = Color.green(color);//取得此像素點的g(綠色)分量
                    b = Color.blue(color);//取得此像素點的b(藍色分量)
                    a = Color.alpha(color);//取得此像素點的a通道值
                    //此公式将r,g,b運算獲得灰階值,經驗公式不需要了解
                    int gray = (int)((float)r*0.3+(float)g*0.59+(float)b*0.11);

                    if(gray < lowDigit || gray > heighDigit) {//如果某點像素的灰階值小于門檻值下限或大于門檻值上限
                        gray = 0;//将該點灰階值置為0(黑色)
                    } else {//如果某點像素的灰階值大于等于門檻值下限或小于等于門檻值上限
                        gray = 255;//将該點灰階值置為255(白色)
                    }
                    newPx[j] = Color.argb(a,gray,gray,gray);//将處理後的透明度(沒變),r,g,b分量重新合成顔色值并将其存儲在數組中
                }
                /**
                 * 第一個參數newPix[]:需要賦給新圖像的顔色數組//The colors to write the bitmap
                 * 第二個參數offset:newPix[]數組中第一個需要設定給圖像顔色的下标值//The index of the first color to read from pixels[]
                 * 第三個參數width:在行之間跳過像素的條目數//The number of colors in pixels[] to skip between rows.
                 * Normally this value will be the same as the width of the bitmap,but it can be larger(or negative).
                 * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标//The x coordinate of the first pixels to write to in the bitmap.
                 * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标//The y coordinate of the first pixels to write to in the bitmap.
                 * 第六個參數width:每行需要讀取的像素個數The number of colors to copy from pixels[] per row.
                 * 第七個參數height:需要讀取的行總數//The number of rows to write to the bitmap.
                 */
                bmp.setPixels(newPx, 0, width, 0, 0, width, height);//将處理後的像素資訊賦給新圖
                mImageView.setImageBitmap(bmp);//将處理後的新圖賦給ImageView
            }
        });


        AlertDialog dialog = builder.create();
        dialog.show();
    }
           

tips:此處我們用的模型為(0-255-0)即将小于下限或大于下限的灰階值置為0,将在下限和上限之間的灰階值置為255。讀者可以仿照此例,做一個(255-0-255)的雙門檻值固定法

下面是我們在接收使用者輸入時使用的對話框所引入的自定義布局double_threshold_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下限值:"/>
        <EditText
            android:id="@+id/low_digit_dialog"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="上限值:"/>
        <EditText
            android:id="@+id/heigh_digit_dialog"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>
           

效果 

Android數字圖像處理之二值化
Android數字圖像處理之二值化
Android數字圖像處理之二值化

代碼已上傳到github,點選這裡可以下載下傳體驗