二值化的目的是将图片分成两种颜色,白色或者黑色;

二值化前一步是将彩色的图片转化为灰色的图片,这样的图片二值化比较精确一些;

二值化有一个阈值(读音,yu,不是kuo,更不是ga,哈哈);

获取一张图的阈值有一个算法,这个算法是别人写好的,代码如下:

int otsu2(jint* colors, int w, int h) {
    unsigned int pixelNum[256]; // 图象灰度直方图[0, 255]
    int color; // 灰度值
    int n, n0, n1; //  图像总点数,前景点数, 后景点数(n0 + n1 = n)
    int w0, w1; // 前景所占比例, 后景所占比例(w0 = n0 / n, w0 + w1 = 1)
    double u, u0, u1; // 总平均灰度,前景平均灰度,后景平均灰度(u = w0 * u0 + w1 * u1)
    double g, gMax; // 图像类间方差,最大类间方差(g = w0*(u0-u)^2+w1*(u1-u)^2 = w0*w1*(u0-u1)^2)
    double sum_u, sum_u0, sum_u1; // 图像灰度总和,前景灰度总和, 后景平均总和(sum_u = n * u)
    int thresh; // 阈值

    memset(pixelNum, 0, 256 * sizeof(unsigned int)); // 数组置0

    // 统计各灰度数目
    int i, j;
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            color = (colors[w * i + j]) & 0xFF; // 获得灰度值
            pixelNum[color]++; // 相应灰度数目加1
        }
    }

    // 图像总点数
    n = w * h;

    // 计算总灰度
    int k;
    for (k = 0; k <= 255; k++) {
        sum_u += k * pixelNum[k];
    }

    // 遍历判断最大类间方差,得到最佳阈值
    for (k = 0; k <= 255; k++) {
        n0 += pixelNum[k]; // 图像前景点数
        if (0 == n0) { // 未获取前景,直接继续增加前景点数
            continue;
        }
        if (n == n0) { // 前景点数包括了全部时,不可能再增加,退出循环
            break;
        }
        n1 = n - n0; // 图像后景点数

        sum_u0 += k * pixelNum[k]; // 前景灰度总和
        u0 = sum_u0 / n0; // 前景平均灰度
        u1 = (sum_u - sum_u0) / n1; // 后景平均灰度

        g = n0 * n1 * (u0 - u1) * (u0 - u1); // 类间方差(少除了n^2)

        if (g > gMax) { // 大于最大类间方差时
            gMax = g; // 设置最大类间方差
            thresh = k; // 取最大类间方差时对应的灰度的k就是最佳阈值
        }
    }

    return thresh;
}

返回的结果是通过算法获取的,要精确阈值,可以更换算法或者优化算法;

将图片灰度化的方法代码如下:

extern "C"
JNIEXPORT jintArray JNICALL
Java_org_fdcch_opencv_MainActivity_gray(JNIEnv *env, jobject instance, jintArray buf, jint w,
                                        jint h) {
    jint *cbuf = env->GetIntArrayElements(buf, JNI_FALSE );
    if (cbuf == NULL) {
        return 0;
    }

    Mat imgData(h, w, CV_8UC4, (unsigned char *) cbuf);

    uchar* ptr = imgData.ptr(0);
    for(int i = 0; i < w*h; i ++){
        //计算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B
        //对于一个int四字节,其彩色值存储方式为:BGRA
        int grayScale = (int)(ptr[4*i+2]*0.299 + ptr[4*i+1]*0.587 + ptr[4*i+0]*0.114);
        ptr[4*i+1] = grayScale;
        ptr[4*i+2] = grayScale;
        ptr[4*i+0] = grayScale;
    }

    int size = w * h;
    jintArray result = env->NewIntArray(size);
    env->SetIntArrayRegion(result, 0, size, cbuf);
    env->ReleaseIntArrayElements(buf, cbuf, 0);
    return result;
}

将图片二值化的方法如下:

extern "C"
JNIEXPORT jintArray JNICALL
Java_org_fdcch_opencv_MainActivity_binaryzation(JNIEnv *env, jobject instance, jintArray buf,
                                                jint w, jint h) {
    jint *cbuf = env->GetIntArrayElements(buf, JNI_FALSE);

    if (cbuf==NULL){

        return 0;

    }

    int white = 0xFFFFFFFF; // 不透明白色

    int black = 0xFF000000; // 不透明黑色

    int thresh = otsu2(cbuf, w, h); // OTSU获取分割阀值

    int i, j, gray;

    for (i = 0; i < h; i++) {

        for (j = 0; j < w; j++) {

            gray = (cbuf[w * i + j]) & 0xFF; // 获得灰度值(red=green=blue)

            if (gray < thresh) {

                cbuf[w * i + j] = white; // 小于阀值设置为白色(前景)

            } else {

                cbuf[w * i + j] = black; // 否则设置为黑色(背景)

            }

        }
    }

    int size = w * h;

    jintArray result = env->NewIntArray(size);//为其分配空间

    env->SetIntArrayRegion(result, 0, size, cbuf);//为result赋值

    env->ReleaseIntArrayElements(buf, cbuf, 0);

    return result;


}

java调用步骤是,先进行灰度化,再进行二值化,代码如下:

package org.fdcch.opencv;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;


public class MainActivity extends AppCompatActivity{

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.aa);

        ImageView imageView=findViewById(R.id.iv_a);

        ImageView imageView1=findViewById(R.id.iv_b);

        ImageView imageView2=findViewById(R.id.iv_c);

        imageView.setImageBitmap(bitmap);

        Bitmap bitmap1 = toGray(bitmap);

        imageView1.setImageBitmap(bitmap1);

        Bitmap bitmap2 = toBinaryzation(bitmap1);

        imageView2.setImageBitmap(bitmap2);


    }


    private Bitmap toGray(Bitmap bitmap){

        int w = bitmap.getWidth();

        int h = bitmap.getHeight();

        int[] piexl = new int[w * h];

        bitmap.getPixels(piexl, 0, w, 0, 0, w, h);

        int[] gray = gray(piexl, w, h);

        Bitmap bitmap1 = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

        bitmap1.setPixels(gray, 0, w, 0, 0, w, h);

        return bitmap1;

    }

    private Bitmap toBinaryzation(Bitmap bitmap){

        int w = bitmap.getWidth();

        int h = bitmap.getHeight();

        int[] piexl = new int[w * h];

        bitmap.getPixels(piexl, 0, w, 0, 0, w, h);// 检索指定坐标点的GRB像素值

        int result[] = binaryzation(piexl, w, h);

        Bitmap bitmap1= Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

        bitmap1.setPixels(result, 0, w, 0, 0, w, h);

        return bitmap1;

    }



    public native String stringFromJNI();

    public native int []gray(int[] buf, int w, int h);

    public native int [] binaryzation(int [] buf,int w,int h);


}

注意,上面的这些是通过jni进行调用编写的,可以使用开源已经写好的java进行实现相同的效果,最中效果如下:

openCv for android 图像二值化

图片分别为,原始图,灰度化图,二值化图;

 

相关文章: