天天看點

Android數字圖像處理之灰階變換

一、視窗灰階變換

        當圖像中大部分像素的灰階級在[L,U]範圍内,少部分像素分布在小于L和大于U的區間内時,可用兩端“截取式”的變換使小于灰階級L和大于等于灰階級U的像素強行壓縮為0和255,如下圖所示。盡管将會造成一小部分資訊丢失,不過有時為了某種應用,做這種“犧牲”是值得的,如利用遙感在氣象資料中分析降水時,在預進行中去掉非氣象資訊,既可減少運算量,又可提高分析精度,這種變換叫灰階的視窗變換。灰階的視窗變換也是一種常見的點運算,它的操作和門檻值變換相類似。從實作方法上看作是灰階折現變換的特例。它限定一個視窗範圍,在視窗中的灰階值保持不變;小于視窗下限的灰階值直接設定為0;大于該視窗上限的灰階值直接設定為255。視窗灰階變換處理結合了雙固定門檻值法,與其不同之處在于視窗内的灰階值保持不變。

Android數字圖像處理之灰階變換

灰階視窗變換的變換函數表達式如下

Android數字圖像處理之灰階變換

思路:

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

用于處理圖像的函數如下

private void window(final Bitmap bm) {

        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());//建立AlertDialog.Builder
        builder.setTitle("視窗灰階變換");//對話框标題
        builder.setMessage("請輸入上下限值");//對話框内容
        View view1 = LayoutInflater.from(getContext()).inflate(R.layout.double_threshold_layout,null);//載入自定義布局
        final EditText mLowThresholdEt = view1.findViewById(R.id.low_digit_dialog);//自定義布局中的EditText,用于接收使用者輸入的下限值
        final EditText mHighThresholdEt = view1.findViewById(R.id.heigh_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 gray;//用來存儲計算得到的灰階值
                int lowDigit = 0;//用于存儲使用者在對話框中輸入的下限值
                int highDigit = 255;//用于存儲使用者在對話框中輸入的上限值
                //建立空白圖像,寬度等于原圖寬度,高度等于原圖高度,用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這個圖像中像素點顔色資訊的數組//The array to receive the bitmap’s colors
                 * 第二個參數offset:oldPix[]數組中第一個接收顔色資訊的下标值// The first index to write into pixels[]
                 * 第三個參數width:在行之間跳過像素的條目數,必須大于等于圖像每行的像素數//The number of entries in pixels[] to skip between rows (must be >= bitmap’s width). Can be negative.
                 * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标 The x coordinate of the first pixel to read from the bitmap
                 * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标The y coordinate of the first pixel to read from the bitmap
                 * 第六個參數width:每行需要讀取的像素個數The number of pixels to read from each row
                 * 第七個參數height:需要讀取的行總數The number of rows to read
                 */
                bm.getPixels(oldPx, 0, width, 0, 0, width, height);


                String str1 = mLowThresholdEt.getText().toString();//擷取使用者下限值輸入的内容
                String str2 = mHighThresholdEt.getText().toString();//擷取使用者上限值輸入的内容
                if("".equals(str1)) {//如果使用者輸入的内容為空
                    lowDigit = 0;//将使用者輸入的下限值置為0
                } else {//否則
                    lowDigit = Integer.valueOf(str1);//将使用者輸入的下限值轉換為整數
                }

                if("".equals(str2)) {//如果使用者輸入的内容為空
                    highDigit = 255;//将使用者輸入的上限值置為255
                } else {//否則
                    highDigit = 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運算獲得灰階值,經驗公式不需要了解
                    gray = (int)((float)r*0.3+(float)g*0.59+(float)b*0.11);

                    if(gray < lowDigit) {//如果某點灰階值小于下限值
                        gray = 0;//将此點灰階值置為0
                    } else if(gray > highDigit){//如果某點灰階值大于上限值
                        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();//建立AlertDialog對話框
        dialog.show();//顯示AlertDialog對話框
    }
           

下面是我們在接收使用者輸入時使用的對話框所引入的自定義布局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數字圖像處理之灰階變換

二、分段線性變換

        分段線性變換和灰階的線性變換有點類似,都用了灰階的線性變換。但不同之處在于分段線性變換不是完全的線性變換,而是分段進行的線性變換。将圖像灰階區間分成兩段乃至更多段分别做線性變換,稱之為分段線性變換。下圖所示是分三段的現行變換示意圖。分段線性變換的優點是可以根據使用者的需要,拉伸特征物體的灰階細節,相對抑制不感興趣的灰階級。下圖中的(0,x1),(x1,x2),(x2,255)等變換區間邊界能通過鍵盤随時做變換式輸入,是以,分段線性是非常靈活的。

Android數字圖像處理之灰階變換

 它的灰階變換函數的函數表達式如下

Android數字圖像處理之灰階變換

        該變換的運算結果是将原圖x1和x2之間的灰階拉伸到y1和y2之間。通過有選擇地拉伸某段灰階區間,能夠更加靈活地控制圖像灰階直方圖的分布,以改善輸出圖像的品質。如果一幅圖像灰階集中在較暗的區域而導緻圖像偏暗,可以用灰階拉伸功能來拉伸(斜率>1)物體灰階區間以改善圖像品質;同樣如果圖像灰階集中在較亮的區域而導緻圖像偏亮,也可以用灰階拉伸功能來壓縮(斜率<1)物體灰階區間以改善圖像品質。

tips:以上舉例為分三段線性變換,使用者可以根據需要分任意段來處理。

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

private void partLinearity(final Bitmap bm) {

        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());//建立AlertDialog.Builder
        builder.setTitle("分段線性變換");//對話框标題
        builder.setMessage("灰階拉伸參數");//對話框内容
        View view1 = LayoutInflater.from(getContext()).inflate(R.layout.dialog_part_linearity_layout,null);//載入自定義布局
        final EditText mFirstXEt = view1.findViewById(R.id.first_x_dialog);//自定義布局中的EditText,用于接收使用者輸入的第一個點橫坐标
        final EditText mFirstYEt = view1.findViewById(R.id.first_y_dialog);//自定義布局中的EditText,用于接收使用者輸入的第一個點縱坐标
        final EditText mSecondtXEt = view1.findViewById(R.id.second_x_dialog);//自定義布局中的EditText,用于接收使用者輸入的第二個點橫坐标
        final EditText mSecondYEt = view1.findViewById(R.id.second_y_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;//紅,綠,藍,透明度
                //建立空白圖像,寬度等于原圖寬度,高度等于原圖高度,用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這個圖像中像素點顔色資訊的數組//The array to receive the bitmap’s colors
                 * 第二個參數offset:oldPix[]數組中第一個接收顔色資訊的下标值// The first index to write into pixels[]
                 * 第三個參數width:在行之間跳過像素的條目數,必須大于等于圖像每行的像素數//The number of entries in pixels[] to skip between rows (must be >= bitmap’s width). Can be negative.
                 * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标 The x coordinate of the first pixel to read from the bitmap
                 * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标The y coordinate of the first pixel to read from the bitmap
                 * 第六個參數width:每行需要讀取的像素個數The number of pixels to read from each row
                 * 第七個參數height:需要讀取的行總數The number of rows to read
                 */
                bm.getPixels(oldPx, 0, width, 0, 0, width, height);

                String str1 = mFirstXEt.getText().toString();//擷取使用者第一個點橫坐标輸入的内容
                String str2 = mFirstYEt.getText().toString();//擷取使用者第一個點縱坐标輸入的内容
                String str3 = mSecondtXEt.getText().toString();//擷取使用者第二個點橫坐标輸入的内容
                String str4 = mSecondYEt.getText().toString();//擷取使用者第二個點縱坐标輸入的内容
                if("".equals(str1)) {//如果使用者輸入的第一個點橫坐标為空
                    firstX = 1;//将使用者輸入的第一個橫坐标置為1
                } else {//否則
                    firstX = Integer.valueOf(str1);//将使用者輸入的第一個點橫坐标轉換為整數
                }

                if("".equals(str2)) {//如果使用者輸入的第一個點縱坐标為空
                    firstY = 1;//将使用者輸入的第一個橫坐标置為1
                } else {//否則
                    firstY = Integer.valueOf(str2);//将使用者輸入的第一個點縱坐标轉換為整數
                }

                if("".equals(str3)) {//如果使用者輸入的第二個點橫坐标為空
                    secondX = 255;//将使用者輸入的第二個橫坐标置為255
                } else {//否則
                    secondX = Integer.valueOf(str1);//将使用者輸入的第二個點橫坐标轉換為整數
                }

                if("".equals(str4)) {//如果使用者輸入的第二個點縱坐标為空
                    firstY = 255;//将使用者輸入的第二個縱坐标置為255
                } else {//否則
                    firstY = Integer.valueOf(str4);//将使用者輸入的第二個點縱坐标轉換為整數
                }

                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);

                    //防止出現分母為0
                    if(firstX == 0) {
                        firstX = 1;
                    }
                    if(firstX == secondX) {
                        secondX = secondX + 1;
                    }
                    if(secondX == 255) {
                        secondX = 254;
                    }

                    if(gray > 0 && gray < firstX) {
                        gray = (int)(gray * firstY / firstX);
                    } else if(gray > firstX && gray < secondX) {
                        gray = (int)((secondY - firstY)*(gray - firstX) / (secondX -firstX) + firstY);
                    } else {
                        gray = (int)((255 - secondY) * (gray - secondX) / (255 - secondX) + secondY);
                    }
                    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();//建立AlertDialog對話框
        dialog.show();//顯示AlertDialog對話框
    }
           

下面是我們在接收使用者輸入時使用的對話框所引入的自定義布局dialog_part_linearity_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"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <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="第一點X:"/>
            <EditText
                android:id="@+id/first_x_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="第一點Y:"/>
            <EditText
                android:id="@+id/first_y_dialog"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <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="第二點X:"/>
            <EditText
                android:id="@+id/second_x_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="第二點Y:"/>
            <EditText
                android:id="@+id/second_y_dialog"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </LinearLayout>


</LinearLayout>
           

效果:

Android數字圖像處理之灰階變換
Android數字圖像處理之灰階變換
Android數字圖像處理之灰階變換

三、反色變換

        将圖像的灰階值反轉,使亮的部分變暗,暗的部分變亮即為反色變換。

思路:

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

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

private Bitmap contraty(Bitmap bm) {
        int width = bm.getWidth();//原圖像寬度
        int height = bm.getHeight();//原圖像高度
        int color;//用來存儲某個像素點的顔色值
        int gray;//用來存儲計算得到的灰階值
        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這個圖像中像素點顔色資訊的數組//The array to receive the bitmap’s colors
         * 第二個參數offset:oldPix[]數組中第一個接收顔色資訊的下标值// The first index to write into pixels[]
         * 第三個參數width:在行之間跳過像素的條目數,必須大于等于圖像每行的像素數//The number of entries in pixels[] to skip between rows (must be >= bitmap’s width). Can be negative.
         * 第四個參數x:從圖像bm中讀取的第一個像素的橫坐标 The x coordinate of the first pixel to read from the bitmap
         * 第五個參數y:從圖像bm中讀取的第一個像素的縱坐标The y coordinate of the first pixel to read from the bitmap
         * 第六個參數width:每行需要讀取的像素個數The number of pixels to read from each row
         * 第七個參數height:需要讀取的行總數The number of rows to read
         */
        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運算獲得灰階值,經驗公式不需要了解
            gray = (int)((float)r*0.3+(float)g*0.59+(float)b*0.11);

            gray = 255 -gray;//将該點灰階值取反,

            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;
    }
           

 效果

Android數字圖像處理之灰階變換
Android數字圖像處理之灰階變換

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