为了提高Tesseract识别的准确性,需要对图片进行一些处理。
灰度化
RGB颜色模型
一种加色模型,将红(Red)、绿(Green)、蓝(Blue)三原色的色光以不同的比例相加,以产生多种多样的色光,且三原色的红绿蓝不可能用其他单色光合成。
RGB色彩模式使用RGB模型为图像中每个像素的RGB分量分配一个0~255范围内的强度值。RGB图像仅仅使用三种颜色,R(red)、G(green)、B(blue),就能够使它们依照不同的比例混合,在屏幕上呈现16777216(256 * 256 * 256)种颜色。
什么是灰度化
在RGB模型中,如果R=G=B时,则彩色表示一种灰度颜色,其中R=G=B的值叫灰度值,因此,灰度图像每个像素只需一个字节存放灰度值(又称强度值、亮度值),灰度范围为0-255。0%的灰度RGB数值是255,255,255;1%灰度的RGB数值是253,253,253;2%灰度RGB值为250,250,250。
灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑白两种颜色,灰度图像在黑色与白色之间还有许多级的颜色深度。
灰度化的方法
约定:
f(i,j) 为二维图片中坐标为 (i,j) 的点的灰度值,R(i,j)、G(i,j)、B(i,j)分别为坐标为 (i,j) 的点在red、green、bule分量上的值。
1. 分量法
将彩色图像中的三分量的亮度作为三个灰度图像的灰度值,可根据应用需要选取一种灰度图像。
f1(i,j)=R(i,j)
f2(i,j)=G(i,j)
f3(i,j)=B(i,j)
2. 最大值法
将彩色图像中的三分量亮度的最大值作为灰度图的灰度值。
f(i,j) = max(R(i,j),G(i,j),B(i,j))
3. 平均值法
将彩色图像中的三分量亮度求平均得到一个灰度图。
f(i,j) = (R(i,j)+G(i,j)+B(i,j))/3
4. 加权平均法
根据重要性及其它指标,将三个分量以不同的权值进行加权平均。由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像。
f(i,j)=0.30*R(i,j)+0.59*G(i,j)+0.11*B(i,j)
二值化
二值化(Thresholding)是图像分割的一种最简单的方法。二值化可以把灰度图像转换成二值图像。把大于某个临界灰度值的像素灰度设为灰度极大值,把小于这个值的像素灰度设为灰度极小值,从而实现二值化。
根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值。 比较常用的二值化方法则有:双峰法、P参数法、迭代法和OTSU法等。
OTSU算法
用了OTSU算法来实现二值化,OTSU(大津法)是属于最大类间方差法,它是自适应计算单阈值的简单高效方法。
实现思路
- 统计灰度值的直方分布
- 分别计算背景图像、前景图像的平均灰度、背景图像像素数所占比例
- 遍历0~255各灰阶,计算并寻找类间方差极大值所对应灰度值
计算公式的推导:
- 记i为前景与背景的分割阈值;
- 前景点数占图像比例为w0,平均灰度为u0;
- 背景点数占图像比例为w1,平均灰度为u1。
- 图像的总平均灰度为:u=w0*u0+w1*u1。
- 从最小灰度值到最大灰度值遍历i,当i使得值g=w0*(u0-u)^2+w1*(u1-u)^2 最大时i即为分割的最佳阈值。
- 利用w0+w1=1,u=w0*u0+w1*u1可推导出等价公式:g=w0*w1*(u0-u1)^2。
编程实现
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class SolvePicture {
public static void grayAndThresholding(String filename) throws IOException {
File file = new File(filename);
BufferedImage image = ImageIO.read(file);
int w = image.getWidth();
int h = image.getHeight();
//灰度化
int[][] gray = new int[w][h];
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
gray[x][y] = getGray(image.getRGB(x, y));
}
}
// 二值化
int threshold = getThreshold(gray, w, h);
BufferedImage binaryBufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
if (gray[x][y] > threshold) {
gray[x][y] |= 0x00FFFF;
} else {
gray[x][y] &= 0xFF0000;
}
binaryBufferedImage.setRGB(x, y, gray[x][y]);
}
}
File destfile = new File(filename+"-result.jpg");
if (destfile.exists()){
destfile.delete();
}
ImageIO.write(binaryBufferedImage, "jpg", destfile);
System.out.println("ok");
}
//计算该点的灰度值
public static int getGray(int rgb){
Color color = new Color(rgb);
//加权平均法
return (int)(0.30*color.getRed()+0.59*color.getGreen()+0.11*color.getBlue());
}
//利用ostu算法获取分割的阀值
public static int getThreshold(int gray[][],int w,int h){
int threshold = 0;
int[] hisData = new int[256];
int graySum = 0; //所有点的灰度值之和
int x0 = 0, x1 = 0; //前、后景点数
int sum0 = 0, sum1 = 0; //前、后景点的平均灰度
double w0 = 0, w1 = 0; //前、后景点出现的概率
double u0 = 0, u1 = 0; //前、后景点的平均灰度
double varMax = 0; //函数的最大值
int totalPoints = w * h;//所有像素点的个数
//依次遍历每个点去统计灰度的分布,同时统计所有点的灰度值之和
for(int i = 0; i < w; i++){
for(int j = 0; j < h; j++){
int graynum = gray[i][j];
graySum += graynum;
hisData[graynum]++;
}
}
for(int i = 0; i < 256; i++){
sum0 += i * hisData[i];
x0 += hisData[i];
if(x0 == 0) //无法分割
continue;
x1 = totalPoints - x0;
sum1 = graySum - sum0;
if(x1 == 0) //所有情况已经计算完
break;
w0 = (1.0 * x0) / totalPoints;
w1 = 1 - w0;
u0 = (1.0 * sum0) / x0;
u1 = (1.0 * sum1) / x1;
//带入公式
double temp = w0*w1*(u0-u1)*(u0-u1);
if(temp > varMax){
varMax = temp;
threshold = i;
}
}
return threshold;
}
}
测试
处理前后图片对比:
写在最后
关于对图像的处理,还有很多其他操作,请听下回分解。