Java实现直方图均衡化增强灰度和彩色图像
直方图均衡化增强彩色图像
上篇已经跟大家说过是那种增强灰度图像的方法,但是对于彩色图像我们能用那三种方法增强吗?
答案当然是可以,只不过我们需要对图像的色彩空间进行转换,并对转换后的色彩空间进行操作。这里我将再介绍一种图像增强的方法,并利用该方法对彩色图像了进行增强!
首先我来介绍简单介绍一下直方图均衡化增强图像的方法。直方图是指一张图片出现的该像素值的像素点个数统计,以数学表达中的直方图形式表现出来。直方图反映了图像的清晰程度,例如:整体颜色偏暗的,该图像的直方图柱必定主要集中在像素值较低的部位;而整体偏亮则主要集中在像素值较高的部位。如下图所示可清晰的表示一张图片不同清晰程度在直方图上的体现:
从这张图片中,我们就可以直观的感受到一张图片的清晰程度与直方图的关系了。那么我们该如何将上图中前三个图片的直方图转换成第四张的直方图,以增强图片对比度呢?
这就是我们接下来讲到的直方图直方图均衡化增强图像。这里我们有一个明确的目标就是将原始图像的直方图变换成在整个灰度范围内均匀分布的形式。为此我们通常采用的步骤如下:
1) 统计每个像素值个数,并计算占总体比例,记录下本图像中的最大的像素值;
2) 一次从最小的像素值比例开始累加,例如:像素值1占比2%,像素值2占比3%,像素值3占比1%,依次累加后,分别为2%,5%,6%。这个过程也称为累计直方图统计;
3) 将统计好的累加比乘以最大像素值,而后将该值赋给原本的像素值。即上一条中的像素值2变换为“最大像素值乘以5%”,以此类推,各个像素值均通过该变化成为新像素值。
至此,直方图均衡化的步骤基本实现完毕了。
下面是代码实现过程:
//对直方图进行均衡化
public static BufferedImage image_Histogram(BufferedImage leftImage) {
int width = leftImage.getWidth();
int height = leftImage.getHeight();
int srcRGBs[] = leftImage.getRGB(0, 0, width, height, null, 0, width);
int rgb[]=new int[3];
int rgb1[]=new int[3];
BufferedImage destImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
double count0[] = new double[256];
double count1[] = new double[256];
double count2[] = new double[256];
for (int j = 0; j < height; j++) {
for(int i=0; i<width; i++) {
ImageUtil.decodeColor(srcRGBs[j*width+i],rgb);
if (count0[rgb[0]]==0){
count0[rgb[0]]=1;
}else {
count0[rgb[0]]+=1; //统计各灰度值在整张图片中出现的个数
}
if (count1[rgb[1]]==0){
count1[rgb[1]]=1;
}else {
count1[rgb[1]]+=1;
}
if (count2[rgb[2]]==0){
count2[rgb[2]]=1;
}else {
count2[rgb[2]]+=1;
}
}
}
double gl0[] = new double[256];
for (int i=0;i<256;i++){
gl0[i]=count0[i]/(width*height); //统计该灰度值所占整体的比例
}
double gl1[] = new double[256];
for (int i=0;i<256;i++){
gl1[i]=count1[i]/(width*height);
}
double gl2[] = new double[256];
for (int i=0;i<256;i++){
gl2[i]=count2[i]/(width*height);
}
double sk0[]=new double[256];
for (int i=0;i<256;i++){
for (int j=0;j<=i;j++){
sk0[i]+=gl0[j]; //统计累计直方图,即累计百分比
}
}
double sk1[]=new double[256];
for (int i=0;i<256;i++){
for (int j=0;j<=i;j++){
sk1[i]+=gl1[j];
}
}
double sk2[]=new double[256];
for (int i=0;i<256;i++){
for (int j=0;j<=i;j++){
sk2[i]+=gl2[j];
}
}
double Sk0[]=new double[256];
for (int i=0;i<256;i++){
Sk0[i]=((255)*sk0[i]+0.5); //记录该像素值变换后的新像素值
}
double Sk1[]=new double[256];
for (int i=0;i<256;i++){
Sk1[i]=((255)*sk1[i]+0.5);
}
double Sk2[]=new double[256];
for (int i=0;i<256;i++){
Sk2[i]=((255)*sk2[i]+0.5);
}
for (int j=0;j<height;j++){
for(int i=0;i<width;i++){
ImageUtil.decodeColor(srcRGBs[j*width+i],rgb);
rgb1[0]=(int)Sk0[rgb[0]]; //将新像素值赋给原本的像素值
rgb1[1]=(int)Sk1[rgb[1]];
rgb1[2]=(int)Sk2[rgb[2]];
destImage.setRGB(i,j, ImageUtil.encodeColor(rgb1));
}
}
return destImage;
}
细心的同学可能会发现,我上面的直方图均衡化时,分别对rgb色彩空间中的红绿蓝进行了处理,其实这也算是一种对彩色图像进行直方图均衡化增强的策略,但是这种策略从某种程度上来说是无用的策略,增强后的图像严重失真,所以上面的直方图均衡化还是针对灰度图像才有比较好的效果。
下面是效果图:
好,至此我相信直方图均衡化增强图像的方法已经介绍差不多了,现在我们回归主题,对于彩色图像的直方图均衡化增强简单介绍一下。
前面其实我们就已经卖过关子了,我说想要对彩色图像进行直方图均衡化需要先进行色彩空间的转换,那么这色彩空间到底是个什么东西呢?
我们都知道红绿蓝是三基色,通过这三种颜色可以组成许多中颜色,其实从空间的角度来看,为我们可以把RGB分别当成XYZ轴,这样组成了一个三维空间,所有颜色也均是该空间上的某一点;然而色彩除了由RGB这样的色彩空间,从另一个层面来看,一种颜色也可以分为色调、饱和度和亮度,也即HSI色彩空间。实验证明当我们对一张图像的HSI色彩空间进行操作时,更不容易失真,因此我们这里进行直方图彩色图像均衡化就是要利用HSI色彩空间。
但是,我们无法直接获取HSI色彩空间的颜色值,而我们通过编程可以获得RGB色彩空间的值。因此,我们需要学会利用RGB色彩空间,将RGB色彩空间转换成HSI色彩空间,对HSI色彩空间操作完成后,我们再将HSI色彩空间转换称RGB色彩空间,重新赋值给输入的图像。至此,我们对彩色图像进行的增强理论部分已经结束。但是我们还需要掌握两个色彩空间的互相转换关系。
RGB到HSI空间的转换
HIS到RGB空间的转换:
好的,现在我们万事俱备,只差代码这个东风了,上代码!
public static BufferedImage image_HistogramHSI(BufferedImage leftImage) {
int width = leftImage.getWidth();
int height = leftImage.getHeight();
int srcRGBs[] = leftImage.getRGB(0, 0, width, height, null, 0, width);
int rgb[]=new int[3];
int rgb1[]=new int[3];
BufferedImage destImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
double H,S,I;
double count[] = new double[256];
for (int j = 0; j < height; j++) {
for(int i=0; i<width; i++) {
ImageUtil.decodeColor(srcRGBs[j*width+i],rgb);
I= ((double)(rgb[0]+rgb[1]+rgb[2])/(double)3);
if (count[(int)I]==0){
count[(int)I]=1;
}else {
count[(int)I]+=1;
}
}
}
double gl[]=new double[256];
for (int i=0;i<256;i++){
gl[i]=(double)count[i]/(double)(width*height);
}
double sk[] = new double[256];
for (int i=0;i<256;i++){
for (int j=0;j<=i;j++){
sk[i]+=gl[j];
}
}
double Sk[]=new double[256];
for (int i=0;i<256;i++){
Sk[i]=(255*sk[i]+0.5);
}
for (int j=0;j<height;j++){
for(int i=0;i<width;i++){
ImageUtil.decodeColor(srcRGBs[j*width+i],rgb);
if (rgb[1]>rgb[2]){
H=Math.acos((rgb[0]-rgb[1]+rgb[0]-rgb[2])
/(2.0*Math.sqrt((rgb[0]-rgb[1])*(rgb[0]-rgb[1])+(rgb[0]-rgb[2])*(rgb[1]-rgb[2]))));
}else{
H=2.0*Math.PI-Math.acos((rgb[0]-rgb[1]+rgb[0]-rgb[2])
/(2.0*Math.sqrt((rgb[0]-rgb[1])*(rgb[0]-rgb[1])+(rgb[0]-rgb[2])*(rgb[1]-rgb[2]))));
}
sort(rgb);
I= ((double)(rgb[0]+rgb[1]+rgb[2])/(double)3);
I=Sk[(int)I];
if (rgb[0]==rgb[1]&&rgb[1]==rgb[2]){
rgb1[0]=(int)I;
rgb1[1]=(int)I;
rgb1[2]=(int)I;
}else {
S = 1 - ((double) 3 / (double) (rgb[0] + rgb[1] + rgb[2])) * (double) rgb[0];
if(H<=2.09){
rgb1[0]=(int)(I*(1.0+(S*Math.cos(H))/Math.cos((Math.PI/3.0)-H))); //jxh
rgb1[0]=rgb1[0]>255?255:rgb1[0];
rgb1[0]=rgb1[0]<0?0:rgb1[0];
rgb1[2]=(int)(I*(1.0-S));
rgb1[2]=rgb1[2]<0?0:rgb1[2];
rgb1[2]=rgb1[2]>255?255:rgb1[2];
rgb1[1]=(int)(3.0*I-rgb1[0]-rgb1[2]);
rgb1[1]=rgb1[1]>255?255:rgb1[1];
rgb1[1]=rgb1[1]<0?0:rgb1[1];
}else if(H>2.09&&H<=4.18){
rgb1[1]=(int)(I*(1.0+(S*Math.cos(H-(Math.PI*2.0/3.0)))/Math.cos((Math.PI)-H)));//jxh
rgb1[1]=rgb1[1]>255?255:rgb1[1];
rgb1[1]=rgb1[1]<0?0:rgb1[1];
rgb1[0]=(int)(I*(1.0-S));
rgb1[0]=rgb1[0]>255?255:rgb1[0];
rgb1[0]=rgb1[0]<0?0:rgb1[0];
rgb1[2]=(int)(3.0*I-rgb1[0]-rgb1[1]);
rgb1[2]=rgb1[2]>255?255:rgb1[2];
rgb1[2]=rgb1[2]<0?0:rgb1[2];
}else if(H>4.18){
rgb1[2]=(int)(I*(1.0+(S*Math.cos(H-(Math.PI*4.0/3.0)))/Math.cos((Math.PI*5.0/3.0)-H))); //jxh
rgb1[2]=rgb1[2]>255?255:rgb1[2];
rgb1[2]=rgb1[2]<0?0:rgb1[2];
rgb1[1]=(int)(I*(1.0-S));
rgb1[1]=rgb1[1]>255?255:rgb1[1];
rgb1[1]=rgb1[1]<0?0:rgb1[1];
rgb1[0]=(int)(3.0*I-rgb1[1]-rgb1[2]);
rgb1[0]=rgb1[0]>255?255:rgb1[0];
rgb1[0]=rgb1[0]<0?0:rgb1[0];
}
}
destImage.setRGB(i,j, ImageUtil.encodeColor(rgb1));
}
}
return destImage;
}
虽然这部分代码较为冗长,但是总体来说并没有什么难的地方,需要注意的是几个细节主要是注意HSI到RGB的公式以及对于RGB超出255和0的处理。下面是效果图: