先来看几张图片,直观的感受一下亮度和对比度的变化
左上:低亮度
右上:高亮度
左下:高对比度
右下:低对比度
那么什么是图像亮度,什么是图像的对比度以及我们在图像处理时是如何表示的。亮度反映的是物品反射光的强弱,也就是我们通俗说是明亮程度,而对比度则是表示物体明暗度,即感觉物品亮处与暗处的反差,在这里,我们以ARGB色彩模式为例,这种模式中我们用为用32位来表示一个像素的色彩情况,每个原色8位,共256级(0-255),刚和好一个INT型变量长度相同,所以我们在java中获得的矩阵是int型的。而每个像素值共同变大或变小,反映出的就是亮度的变化,而将大的值加大,小的值减小,则反映的是对比度的调整。这就是我们处理亮度和对比度的思路。
但我们在处理对比度时,原则上要保证总体的亮度值不变。
下面先给出亮度和对比度的计算公式
(RGB表示原图的色彩分量的值,nRGB表不处理后的值,mBrightness表示调整的亮度值,mContrast表示调整的对比度值,avg表示整个图像像素的平均值)
亮度:nRGB=RGB+mBrightness
对比度:nRGB=(RGB-avg)*(1-percent%)+avg percent%取值范围为(-1~1) 0为原始值
对比度公式也很好证明,将其展开
nRGB=RGB-RGB*percent%-avg+avg*percent%+avg
nRGB=RGB-RGB*percent%+avg*percent%
对于整个图像矩阵来说,要保证亮度不变,即整个矩阵的代数和不变。
而avg=(RGB1+RGB2+....RGBn)/n (1)
(nRGB1+nRGB2+.....nRGBn)=(RGB1+RGB2+...RGBn)+n*avg*percent%-(RGB1+RGB2+...RGBn)*percent% (2)
将(1)式代入(2)式即可
以上是原理,但在实际应用我们通常不这么做,因为考虑到我们要处理的图像可能很大,比如1366*768这样一样壁纸就有1049088这么多像素点,太耗性能,所以我就取128,简化计算;
所以我们在写程序时,只需先利用移位计算出每个分量的值,然后利用公式计算(RGB-128)*(1-p%)+avg即可
而且我们也可以和高度公式合在一起。 即
mContrast只要设定为对称的范围即可,比如(-100~100)
那么可定义 d=(100+mContrast)/100
nRGB=(RGB-128)*d+avg+128+0.5+mBrightness 0.5是为了补充在强制类型换化时丢掉的精度。
最后还要提醒一点,因为我们上面计算的像素值范围为0~255,所以要注意在计算输入值时,还要加个判断,如果算出来的值大于255,则设为255
如果小于255,则设为0;
这里我还想补充一个思想:LookUp Table 检索表
因为图像处理中,我们要对每一个像素都要有一个计算,仅仅一张1366*768的壁纸,计算量也是挺惊人的。
有没有什么办法可以减少这计算的呢,就要利用LUT了,因为我们发现,一种颜色的等级只有256级,也就是说,随便取一个像素值的色彩分量,结果不超过256种
如果我们预先计算好这256种值,存入数组中,要用的时候不就直接按照数组的索引,直接获得了吗?
这其实也是一种牺牲空间换取时间的方法,效果随着图片大小的增大而增大。
比如上例中的对比度,我们先定义一个preMutipiled[256]的数组
然后
for(int i=0;i<256;i++)
{
preMutipiled[i]=(i-128)*d+avg+128+0.5+mBrightness
}
要计算某一像素的红色分量值
r=preMutipiled[argb>>16 & 0xFF],即可直接获得。
这种思想不仅仅用在这,在wiki百科中有一个例子也挺好,就是计算一个int型转化为byte型后,1的位数,比如37(100101)=3,10(1010)=2
通常我们是这样计算的
public int countOne(int x){
int res=0;
while(x!=0){
x=x&(x-1) //每次消去一位‘1’
res++;
}
return res;
}
但如果按照上面的方法,我先计算出前8位的所有情况并存储在preset[256]数组中 ,0x00=0,0x01=1,0x02=1......0xFF=8;
然后我们就可以这样写
public int countOne(int x){
return preset[x & 0xFF]+preset[(x >>8) & 0xFF]+preset[(x >>16) & 0xFF]+preset[(x >> 24) & 0xFF];
}
如果将x改为long型,long long 型,这速度优势瞬间体现出来了。。。。