在图像处理软件Photoshop中有一个自动对比度功能,可以一键调节图像亮度、对比度。比如,像下面这张由于曝光不足形成的非常暗的图像,只有图像中间比较亮一些的路面才能看到,其他地面上的物体几乎无法看见。

java图片相同对比 java图像对比_i++

        那么再来看一下使用Photoshop自动对比度之后效果。

java图片相同对比 java图像对比_java图片相同对比_02

        一键操作后,图像上的大部分物体都体现出来了,调整后的亮度、对比度、饱和度等几乎不会给人失真的感觉,效果比手动调节亮度、对比度、或者曲线调整要好很多。实际上,这个功能的牛逼之处不止如此,再看另一张图。

java图片相同对比 java图像对比_Image_03

        这是一张受到雾霾干扰的图像,整体感觉就是非常朦胧,看不清地面物体。用自动对比度看看效果:

java图片相同对比 java图像对比_java图片相同对比_04

        去雾效果杠杠的,完全就像是没有雾霾的天气拍出来的。 那么如此牛逼的功能是怎么实现的呢?其实原理还是比较简单,一般图像由RGB三个通道合成,每个通道由0~255数值来体现,如果是比较暗的图像,其RGB通道数值可能在比较低的范围内,比如第一张非常暗的图像,其RGB三个通道数值范围为(0~60),即最亮的地方才60的亮度值,离最大亮度255还差的远。    

        有了图像的各个通道数据,就可以在此基础上做些文章了,比如,可以直接将(0~60)映射到(0~255)。使用java编写代码测试一下,用ImageIO读取和写入图像,用bufferedImage对象获取图像RGB信息,然后对各个通道值进行处理。

代码如下:

// 自动对比度
public void autoContrast(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight();
    for(int i = 0; i < width; i++) {
    for(int j = 0; j < height; j++) {
        int rgb = image.getRGB(i, j);
        double r = (rgb >> 16) & 0xff;
        double g = (rgb >> 8) & 0xff;
        double b = rgb & 0xff;
        //进行转换
        r = getContrastByMaxMin(r);
        g = getContrastByMaxMin(g);
        b = getContrastByMaxMin(b);
        rgb = (255 & 0xff) << 24 | (clamp((int)r) & 0xff) << 16 | (clamp((int)g) & 0xff) << 8 | (clamp((int)b) & 0xff);
        image.setRGB(i, j, rgb);
        }
    }
}
//根据亮度最大最小值拉伸对比度
private double getContrastByMaxMin(double a) {
    if (a >= arr[0] && a <= arr[1]) {
        result = (a - arr[0]) * 255 / (arr[1] - arr[0]);
    }
    return result;
}

  运行效果如下:

java图片相同对比 java图像对比_i++_05

        图像确实变亮了!但是仔细看看,效果似乎比Photoshop差一些,这是怎么回事?

        其实,虽然获取到了各个通道的动态范围(0~60),但这只是最大最小值,而大部分数值范围可能在(10~50)之间,那么在进行拉伸的时候,很多值并没有拉伸到合适的数值上。那么Photoshop是怎么处理的呢?很简单!就是对上下范围进行裁切,Photoshop自动对比度是裁切掉上下范围各0.001%的像素,再得到需要进行映射的范围,对范围以外的数值直接映射到0或255。当然,要获得准确的裁切值,需要对各个通道值进行统计,即统计图像各个通道在(0~255)上分布概率。

代码如下:

// 自动对比度
public void autoContrast(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight();
    //计算图像强度分布频率
    Map<Integer,Double> map = getPDF(image);
    //获取最大最小强度
    int min = getMin(map);
    int max = getMax(map);
    int[] arr = new int[] {min,max};
    //进行对比度拉伸
    for(int i = 0; i < width; i++) {
        for(int j = 0; j < height; j++) {
            int rgb = image.getRGB(i, j);
            double r = (rgb >> 16) & 0xff;
            double g = (rgb >> 8) & 0xff;
            double b = rgb & 0xff;
            //进行转换
            r = getContrastByMaxMin(r, arr);
            g = getContrastByMaxMin(g, arr);
            b = getContrastByMaxMin(b, arr);
            rgb = (255 & 0xff) << 24 | (clamp((int)r) & 0xff) << 16 | (clamp((int)g) & 0xff) << 8 | (clamp((int)b) & 0xff);
            image.setRGB(i, j, rgb);
            }
        }
}

//取出最小强度
private int getMin(Map<Integer, Double> map){
    double temp = 0.0;
    int result = 0;
    for(int i = 0; i < 256; i++) {
        double num = map.get(i);
        temp += num;
        if (temp >= 0.001) {
            result = i;
            break;
            }
        }
    return result;
}

//取出最大强度
private int getMax(Map<Integer, Double> map){
    double temp = 0.0;
    int result = 0;
    for(int i = 255; i >= 0; i--) {
        double num = map.get(i);
        temp += num;
        if (temp >= 0.001) {
            result = i;
            break;
        }
    }
    return result;
}

//根据亮度最大最小值拉伸对比度
private double getContrastByMaxMin(double a, int[] arr) {
    double result = 0.0;
    if (arr[0] == arr[1]) {
        result = arr[0];
    }
    if (a > arr[1]) {
        result = 255;
    }
    if (a < arr[0]) {
        result = 0;
    }
    if (a >= arr[0] && a <= arr[1]) {
        result = (a - arr[0]) * 255 / (arr[1] - arr[0]);
    }
    return result;
}

/**
* 统计图像从0~255之间分布
*
* @return
*/
private Map<Integer, Double> getPDF(BufferedImage image) {
    Map<Integer, Double> map = new HashMap<>();
    int width = image.getWidth();
    int height = image.getHeight();
    double totalPixel = width * height;
    for (int i = 0; i < 256; i++) {
    map.put(i, 0.0);// 通过循环,往集合里面填充0~255个位置,初始值都为0
}
    //分别统计图像上0~255上分布总数
    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {
            int rgb = image.getRGB(i, j);
            int r = (rgb >> 16) & 0xff;
            int g = (rgb >> 8) & 0xff;
            int b = rgb & 0xff;
            map.put(r, map.get(r) + 1);
            map.put(g, map.get(g) + 1);
            map.put(b, map.get(b) + 1);
        }
    }
    //计算分布概率
    for(int i = 0; i < 256; i++) {
        double value = map.get(i);
        value = value / (3 * totalPixel);
        map.put(i, value);
    }

    return map;
}
// 判断a,r,g,b值,大于256返回256,小于0则返回0,0到256之间则直接返回原始值
private int clamp(int rgb) {
    if (rgb > 255)
    return 255;
    if (rgb < 0)
        return 0;
    return rgb;
}

        再看效果:

java图片相同对比 java图像对比_Photoshop_06

        是不是跟Photoshop一毛一样!