图像二值化,首先要将原图转换成灰度图,这里展示Android代码:
/**
* 将彩色图转换为灰度图
*
* @param img 位图
* @return 返回转换好的位图
*/
public static Bitmap convertGreyImg(Bitmap img) {
//获取位图的宽
int width = img.getWidth();
//获取位图的高
int height = img.getHeight();
//通过位图的大小创建像素点数组
int[] pixels = new int[width * height];
img.getPixels(pixels, 0, width, 0, 0, width, height);
int alpha = 0xFF << 24;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int grey = pixels[width * i + j];
int red = ((grey & 0x00FF0000) >> 16);
int green = ((grey & 0x0000FF00) >> 8);
int blue = (grey & 0x000000FF);
grey = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11);
grey = alpha | (grey << 16) | (grey << 8) | grey;
pixels[width * i + j] = grey;
}
}
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
result.setPixels(pixels, 0, width, 0, 0, width, height);
return result;
}
1.OSTU大律法二值化:
Ostu方法又名最大类间差方法,通过统计整个图像的直方图特性来实现全局阈值T的自动选取,其算法步骤为:
1) 先计算图像的直方图,即将图像所有的像素点按照0~255共256个bin,统计落在每个bin的像素点数量
2) 归一化直方图,也即将每个bin中像素点数量除以总的像素点
3) i表示分类的阈值,也即一个灰度级,从0开始迭代
4) 通过归一化的直方图,统计0~i 灰度级的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例w0,并统计前景像素的平均灰度u0;统计i~255灰度级的像素(假设像素值在此范围的像素叫做背景像素) 所占整幅图像的比例w1,并统计背景像素的平均灰度u1;
5) 计算前景像素和背景像素的方差 g = w0*w1*(u0-u1) (u0-u1)
6) i++;转到4),直到i为256时结束迭代
7)将最大g相应的i值作为图像的全局阈值
该识别算法对有光照不均匀效果的图片处理不是很好。
//生成二值图
public static Bitmap convertbBnaryBitmap(Bitmap img) {
int threshold = computeThreshold(img);
filerMap = new ArrayList<>(filterNum);
//获取位图的宽
int width = img.getWidth();
//获取位图的高
int height = img.getHeight();
//通过位图的大小创建像素点数组
int[] pixels = new int[width * height];
img.getPixels(pixels, 0, width, 0, 0, width, height);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int grey = pixels[width * i + j] & 0x000000FF;
if (grey >= threshold) {
pixels[width * i + j] = 0xFFFFFFFF;
filerMap.add(width * i + j);
} else {
pixels[width * i + j] = 0xFF000000;
}
}
}
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
result.setPixels(pixels, 0, width, 0, 0, width, height);
return result;
}
//计算生成二值图的阈值
public static int computeThreshold(Bitmap img) {
int threshold = 0;
//获取位图的宽
int width = img.getWidth();
//获取位图的高
int height = img.getHeight();
int totalPixels = width * height;
//通过位图的大小创建像素点数组
int[] pixels = new int[width * height];
img.getPixels(pixels, 0, width, 0, 0, width, height);
double[] numTagArr = new double[256];
//256个桶装入对应像素值得像素个数
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int grey = pixels[width * i + j] & 0x000000FF;
// Log.e("MainActivity", String.format("grey:%d", grey));
numTagArr[grey]++;
}
}
//数值归一化
for (int i = 0; i < numTagArr.length; i++) {
numTagArr[i] = numTagArr[i] / (double) totalPixels;
}
double g_tmp = 0;
double g_max = 0;
for (int i = 1; i < numTagArr.length - 1; i++) {
double w_pre = 0;
double w_behind = 0;
double u_pre = 0;
double u_behind = 0;
for (int pre = 0; pre < i; pre++) {
w_pre += numTagArr[pre];
u_pre += (pre * numTagArr[pre]);
}
if (w_pre - 0 < 0.00001) {
continue;
}
u_pre = u_pre / w_pre;
for (int behind = i; behind < numTagArr.length; behind++) {
w_behind += numTagArr[behind];
u_behind += (behind * numTagArr[behind]);
}
u_behind = u_behind / w_behind;
g_tmp = w_pre * w_behind * (u_pre - u_behind) * (u_pre - u_behind);
if (g_tmp > g_max) {
threshold = i;
g_max = g_tmp;
filterNum = (int) (w_behind * totalPixels);
}
}
return threshold;
}
2.Wellner自适应阈值算法的二维扩展:(对光照不均匀处理很好)
一维算法则是横向或纵向长为s的线段所包含的像素点的像素平均值与当前像素点像素值比较,一维算法有弊端,从左到右、从右到左、从上到下、从下到上,还是双端交替,都会使最终的图像在这个方向上受到影响,因此推荐使用二维拓展算法:
1.求积分图像,即每个像素点的值 = 当前像素点到左上角顶点像素的一个矩形区域所有的像素点的像素值之和;
2.给定s,求出以当前像素为中心、边长为s的正方形区域像素值的平均值mean;
3.当前像素值与该平均值mean的(1 - t/100)比较,如果大于则该点像素值为1(白色),如果小于则该点像素值为0(黑色),即生成了二值化图像。
下面给出一个过滤背景的代码,即基于原图,先将原图转换成灰度图,然后进行二值化分析,将白色区域的像素变成透明,黑色区域的像素保持原图在该点的像素值,则可达到去背景效果:
/**
* Wellner自适应阈值算法的二维扩展
*
* @param grayBitmap:灰度图像
* @param srcBitmap:原始图像
* @param s:用以求出正方形的边长
* @param t:比率(1-t/100)
*/
public static Bitmap analyzeFilterPixelMap(Bitmap grayBitmap, Bitmap srcBitmap, int s, int t) {
//获取位图的宽
int width = grayBitmap.getWidth();
//获取位图的高
int height = grayBitmap.getHeight();
//通过位图的大小创建像素点数组
int[] grayPixels = new int[width * height];
int[] srcPixels = new int[width * height];
//当前像素点到正方形边界的像素点个数
int s_len = (int) (width / s * 0.5);
double t_precent = 1 - t / 100.0;
//积分图像
long[][] integralImageSumArr = new long[height][width];
grayBitmap.getPixels(grayPixels, 0, width, 0, 0, width, height);
srcBitmap.getPixels(srcPixels, 0, width, 0, 0, width, height);
/****** 第一步:计算积分图像 ******/
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
/**
* A--B
* | |
* C--D
*积分图算法: D = B + C - A + Xd; 其中Xd表示D位置的灰度值
* */
int currentPixelGray = grayPixels[w + h * width] & 0x000000FF;
integralImageSumArr[h][w] =
(h < 1 ? 0 : integralImageSumArr[h - 1][w])
+ (w < 1 ? 0 : integralImageSumArr[h][w - 1])
- ((h < 1 || w < 1) ? 0 : integralImageSumArr[h - 1][w - 1])
+ currentPixelGray;
}
}
/**
* 第二步:
* (1).求以该像素为中心,s为边长的正方形里面的像素的和的平均值;
* (2).比较当前像素与平均值的(1-t/100)倍的大小,大于则记录该像素点是二值化的背景,反之则记录为前景。
**/
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int currentPixelGray = grayPixels[w + h * width] & 0x000000FF;
if (currentPixelGray > getSquarePixelsGrayMean(integralImageSumArr, w, h, s_len) * t_precent) {
srcPixels[w + h * width] = 0x00FFFFFF;
}
}
}
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
private static double getSquarePixelsGrayMean(long[][] integralImageSumArr, int w, int h, int s_len) {
/**
* 算法:
*
* 积分图: 像素灰度值:
* A--B--C a--b--c
* | | | | | |
* D--E--F d--e--f
* | | | | | |
* G--H--I g--h--i
*那么,e + f + h + i = I - C - G + A
* */
int w_real = integralImageSumArr[0].length;
int h_real = integralImageSumArr.length;
//求出正方形四个顶点的索引,正方形操作像素边界的部分直接舍弃
int w_index_left = (w - s_len - 1 < 0 ? 0 : w - s_len - 1);
int w_index_right = (w + s_len + 1 > w_real - 1 ? w_real - 1 : w + s_len + 1);
int h_index_top = (h - s_len - 1 < 0 ? 0 : h - s_len - 1);
int h_index_bottom = (h + s_len + 1 > h_real - 1 ? h_real - 1 : h + s_len + 1);
int grayNum = 0;
long graySum = 0;
long I = integralImageSumArr[h_index_bottom][w_index_right];
long C = (h_index_top == 0 ? 0 : integralImageSumArr[h_index_top - 1][w_index_right]);
long G = (w_index_left == 0 ? 0 : integralImageSumArr[h_index_bottom][w_index_left - 1]);
long A;
if (h_index_top == 0 && w_index_left != 0) {
A = integralImageSumArr[0][w_index_left - 1];
} else if (h_index_top != 0 && w_index_left == 0) {
A = integralImageSumArr[h_index_top - 1][0];
} else if (h_index_top == 0 && w_index_left == 0) {
A = integralImageSumArr[0][0];
} else {
A = integralImageSumArr[h_index_top - 1][w_index_left - 1];
}
graySum = I - C - G + A;
grayNum = (h_index_bottom - h_index_top + 1) * (w_index_right - w_index_left + 1);
return (double) graySum / grayNum;
}