二值化的目的是将图片分成两种颜色,白色或者黑色;
二值化前一步是将彩色的图片转化为灰色的图片,这样的图片二值化比较精确一些;
二值化有一个阈值(读音,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进行实现相同的效果,最中效果如下:
图片分别为,原始图,灰度化图,二值化图;