旧 Android 相机的简单图像校正

Simple Image Correction for Older Android Cameras

我有一个 android 应用程序,我试图让用户使用本机相机捕获文档图像,然后将其发送到服务器以进行黑白处理。

在 android 应用程序中,我正在关注 this example 从相机拍摄全尺寸照片。

我遇到的问题是旧的 Nexus 4 相机会产生较暗的图像,因此文档的某些部分会变成黑色而不是白色(主要围绕文档的边缘)。但是使用较新的 Nexus 6 相机就没有问题了。

使用 this code 更改 contrast/brightness 可以很好地提高 Nexus 4 图像的质量,但会冲掉较新的 Nexus 6 图像。

有没有一种方法可以自动检测来自较旧 android 设备的较暗、质量较差的图像,以便仅对这些图像应用图像校正?

使用 this code 简单地计算图像的平均亮度会因文档周围的黑暗背景的可能性而被忽略。

大概标准不是亮度,而是对比度。无论如何,挑战是艰巨的,你不能指望完美的流程,但要准备好通过反复试验来应对。

尽量拍一张对比度正常的照片;如果失败,请设置一个标志以在下次使用额外亮度。请注意,在野外的设备可能有非常不同的相机行为,而且照片条件即使相同 phone。

您可以将 Nexus 4 的标志初始化为高亮度。实际上,我不确定这是否适用于所有 Nexus 4 设备,还是仅适用于某些设备。

如果您的应用将被大量使用,您可以收集每个模型的统计信息,并在安装应用时将其用作初始猜测。

我最终在 C++ OpenCV 中找到了这个 algorithm,并且我在 Java 中为我们的后端服务器编写了一个实现。它似乎可以很好地处理来自旧相机的较暗图像。虽然它似乎只适用于 JPG 图片,不适用于 PNG。

private BufferedImage autoCorrectBrightnessAndContrast(final BufferedImage image) {
    final int[] histogram = makeHistogram(image);

    if (LOG.isDebugEnabled()) {
        LOG.debug("Histogram: {}", Arrays.toString(histogram));
    }

    final int[] cumulativeHistogram = getCumulativeHistogram(histogram);
    /*
     * Finding the minimum and maximum indices in the histogram where the value at the index is greater than a given
     * threshold, computed from CLIP_PERCENT.
     */
    final int min = getMinIndex(cumulativeHistogram);
    final int max = getMaxIndex(cumulativeHistogram);
    LOG.debug("Min: {} Max: {}", min, max);

    /*
     * alpha is the scaling factor, or the amount that we need to expand the histogram graph horizontally so that it
     * fills the entire width. Essentially increasing contrast.
     */
    double alpha = 1;
    if (max != min) {
        alpha = MAX_COLOR / (double) (max - min);
    }
    /*
     * beta is the translating factor, or the amount that we need to slide the histogram graph left or right so that
     * it lines up with the origin. Essentially adjusting brightness.
     */
    final double beta = -min * alpha;

    return adjustBrightnessAndContrast(image, alpha, beta);
}

/**
 * Creates an int array of length MAX_COLOR representing a histogram from the input image. Each index represents the
 * number of pixels of that greyscale shade in the image.
 * 
 * @param image
 *            the bufferedImage to create a histogram from.
 * @return an int array represenation of the histogram.
 * 
 * @see <a href="">Plot a histogram for a buffered image</a>
 */
private static int[] makeHistogram(final BufferedImage image) {
    final int[] histogram = new int[MAX_COLOR];

    final ColorSpace colorSpace = image.getColorModel().getColorSpace();
    LOG.debug("Color space type: {}, is RGB = {}", colorSpace.getType(), colorSpace.isCS_sRGB());

    for (int x = 0; x < image.getWidth(); x++) {
        for (int y = 0; y < image.getHeight(); y++) {
            final int color = image.getRGB(x, y);
            final int red =   (color & 0x00ff0000) >> 16;
            final int green = (color & 0x0000ff00) >> 8;
            final int blue =   color & 0x000000ff;
            // Constructing a weighted average of the three color bands
            // based on how much they contribute to the overall brightness
            // of a pixel. (Relative luminance - https://en.wikipedia.org/wiki/Relative_luminance)
            final double greyscaleBrightness = .2126 * red + .7152 * green + .0722 * blue;
            histogram[(int) greyscaleBrightness]++;
        }
    }
    return histogram;
}

/**
 * @param histogram
 * @return an int array representing the cumulative histogram of the given histogram.
 * @see <a href="https://en.wikipedia.org/wiki/Histogram#Cumulative_histogram">Cumulative Histogram</a>
 */
private static int[] getCumulativeHistogram(final int[] histogram) {
    final int[] cumulativeHistogram = new int[histogram.length];

    cumulativeHistogram[0] = histogram[0];
    for (int i = 1; i < histogram.length; i++) {
        cumulativeHistogram[i] = cumulativeHistogram[i - 1] + histogram[i];
    }
    return cumulativeHistogram;
}

/**
 * @param cumulativeHistogram
 * @return the minimum index where the cumulative histogram goes above the threshold set by CLIP_PERCENT.
 */
private static int getMinIndex(final int[] cumulativeHistogram) {
    final int maxValue = cumulativeHistogram[cumulativeHistogram.length - 1];
    final double clipThreshold = CLIP_PERCENT * (maxValue / 100.0) * .5;
    int minIndex = 0;

    while (cumulativeHistogram[minIndex] < clipThreshold) {
        minIndex++;
    }
    return minIndex;
}

/**
 * @param cumulativeHistogram
 * @return the maximum index where the cumulative histogram goes below the threshold set by CLIP_PERCENT.
 */
private static int getMaxIndex(final int[] cumulativeHistogram) {
    final int maxValue = cumulativeHistogram[cumulativeHistogram.length - 1];
    final double clipThreshold = CLIP_PERCENT * (maxValue / 100.0) * .5;
    int maxIndex = cumulativeHistogram.length - 1;

    while (cumulativeHistogram[maxIndex] >= maxValue - clipThreshold) {
        maxIndex--;
    }
    return maxIndex;
}

/**
 * @param image
 * @param alpha
 *            the scaling factor to adjust the contrast.
 * @param beta
 *            the offset factor to adjust the brightness.
 * @return the adjusted image.
 */
private static BufferedImage adjustBrightnessAndContrast(final BufferedImage image, final double alpha, final double beta) {
    BufferedImage processedImage = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
    processedImage = new RescaleOp((float) alpha, (float) beta, null).filter(image, processedImage);
    LOG.debug("alpha: {} beta: {}", (float) alpha, (float) beta);
    return processedImage;
}