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

效果

二值化图片能做深度学习吗 图像二值化处理软件_二值化图片能做深度学习吗

            

二值化图片能做深度学习吗 图像二值化处理软件_图像处理_02

非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>

效果

二值化图片能做深度学习吗 图像二值化处理软件_二值化_03

 

二值化图片能做深度学习吗 图像二值化处理软件_非0即1_04

 

二值化图片能做深度学习吗 图像二值化处理软件_二值化图片能做深度学习吗_05

三、双固定阈值法

 

思路:

  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>

效果 

二值化图片能做深度学习吗 图像二值化处理软件_非0即1_06

 

二值化图片能做深度学习吗 图像二值化处理软件_图像处理_07

 

二值化图片能做深度学习吗 图像二值化处理软件_Android_08

代码已上传到github,点击这里可以下载体验