【问题标题】:Automatically adjusting brightness of image with OpenCV使用 OpenCV 自动调整图像的亮度
【发布时间】:2019-11-23 14:07:25
【问题描述】:

我想在 OpenCV 中将图像的亮度调整为某个值。例如,考虑这张图片:

我计算亮度:

import cv2
img = cv2.imread(filepath)
cols, rows = img.shape
brightness = numpy.sum(img) / (255 * cols * rows)

平均亮度为 35%。例如,要将其提高到 66%,我会这样做:

minimum_brightness = 0.66
alpha = brightness / minimum_brightness
bright_img = cv2.convertScaleAbs(img, alpha = alpha, beta = 255 * (1 - alpha))

我得到一张似乎有 50% 透明面纱的图像:

我可以通过只使用偏差来避免这种影响:

bright_img = cv2.convertScaleAbs(img, alpha = 1, beta = 128)

而且图像似乎也蒙上了一层面纱:

如果我手动操作,例如在 Photoshop 中将亮度调整为 150,结果似乎还不错:

但是,这不是自动的,也不会给出目标亮度。

我可以通过伽马校正和/或直方图均衡来获得更自然的结果,但除了反复试验之外,我没有看到获得目标亮度的简单方法。

有没有人成功地根据目标自动调整亮度?

更新

卡纳特建议:

bright_img = cv2.convertScaleAbs(img, alpha = 1, beta = 255 * (minimum_brightness - brightness))

结果更好但仍然有面纱:

Yves Daoust 建议保留beta = 0,所以我调整了alpha = minimum_brightness / brightness 以获得目标亮度:

ratio = brightness / minimum_brightness
if ratio >= 1:
    print("Image already bright enough")
    return img

# Otherwise, adjust brightness to get the target brightness
return cv2.convertScaleAbs(img, alpha = 1 / ratio, beta = 0)

结果还不错:

【问题讨论】:

  • 重新阅读 convertScaleAbs 的文档。你应该保持 beta=0。
  • beta = 0,图像变得更暗,所以我尝试使用alpha = minimum_brightness / brightness,它给出了一个很好的结果和目标亮度,除了饱和度算法。你想写一个答案,我会编辑它以添加结果吗?
  • 我在问题中添加了详细信息;随意将这些移到您的答案中。
  • @mLstudent33:是的。
  • @mLstudent33 我确认代码alpha = minimum_brightness / brightness 工作正常。转换为y = alpha * x + betax 平均为brightness,因此alpha 如上所述,beta = 0 将给出y 平均为minimum_brightness

标签: python image opencv image-processing computer-vision


【解决方案1】:

您可以尝试使用带有直方图裁剪的对比度优化来自动调整亮度。您可以通过增加直方图剪辑百分比来增加目标亮度 (clip_hist_percent)。这是 25% 裁剪的结果

Alpha 和 beta 是自动计算的

阿尔法 3.072289156626506

测试版-144.3975903614458

这是剪辑的可视化。蓝色(原始)、橙色(自动调整后)。

削波为 35% 的结果

阿尔法 3.8059701492537314

测试版-201.71641791044777

其他方法可以使用Histogram Equalization or CLAHE

import cv2
import numpy as np
# from matplotlib import pyplot as plt

# Automatic brightness and contrast optimization with optional histogram clipping
def automatic_brightness_and_contrast(image, clip_hist_percent=25):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Calculate grayscale histogram
    hist = cv2.calcHist([gray],[0],None,[256],[0,256])
    hist_size = len(hist)

    # Calculate cumulative distribution from the histogram
    accumulator = []
    accumulator.append(float(hist[0]))
    for index in range(1, hist_size):
        accumulator.append(accumulator[index -1] + float(hist[index]))

    # Locate points to clip
    maximum = accumulator[-1]
    clip_hist_percent *= (maximum/100.0)
    clip_hist_percent /= 2.0

    # Locate left cut
    minimum_gray = 0
    while accumulator[minimum_gray] < clip_hist_percent:
        minimum_gray += 1

    # Locate right cut
    maximum_gray = hist_size -1
    while accumulator[maximum_gray] >= (maximum - clip_hist_percent):
        maximum_gray -= 1

    # Calculate alpha and beta values
    alpha = 255 / (maximum_gray - minimum_gray)
    beta = -minimum_gray * alpha

    '''
    # Calculate new histogram with desired range and show histogram 
    new_hist = cv2.calcHist([gray],[0],None,[256],[minimum_gray,maximum_gray])
    plt.plot(hist)
    plt.plot(new_hist)
    plt.xlim([0,256])
    plt.show()
    '''

    auto_result = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)
    return (auto_result, alpha, beta)

image = cv2.imread('1.png')
auto_result, alpha, beta = automatic_brightness_and_contrast(image)
print('alpha', alpha)
print('beta', beta)
cv2.imshow('auto_result', auto_result)
cv2.imwrite('auto_result.png', auto_result)
cv2.imshow('image', image)
cv2.waitKey()

另一种版本是使用饱和算法而不是使用 OpenCV 的 cv2.convertScaleAbs 为图像添加偏差和增益。内置方法不采用绝对值,这会导致无意义的结果(例如,44 处的像素,alpha = 3 和 beta = -210 在 OpenCV 中变为 78,而实际上它应该变为 0)。

import cv2
import numpy as np
# from matplotlib import pyplot as plt

def convertScale(img, alpha, beta):
    """Add bias and gain to an image with saturation arithmetics. Unlike
    cv2.convertScaleAbs, it does not take an absolute value, which would lead to
    nonsensical results (e.g., a pixel at 44 with alpha = 3 and beta = -210
    becomes 78 with OpenCV, when in fact it should become 0).
    """

    new_img = img * alpha + beta
    new_img[new_img < 0] = 0
    new_img[new_img > 255] = 255
    return new_img.astype(np.uint8)

# Automatic brightness and contrast optimization with optional histogram clipping
def automatic_brightness_and_contrast(image, clip_hist_percent=25):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Calculate grayscale histogram
    hist = cv2.calcHist([gray],[0],None,[256],[0,256])
    hist_size = len(hist)

    # Calculate cumulative distribution from the histogram
    accumulator = []
    accumulator.append(float(hist[0]))
    for index in range(1, hist_size):
        accumulator.append(accumulator[index -1] + float(hist[index]))

    # Locate points to clip
    maximum = accumulator[-1]
    clip_hist_percent *= (maximum/100.0)
    clip_hist_percent /= 2.0

    # Locate left cut
    minimum_gray = 0
    while accumulator[minimum_gray] < clip_hist_percent:
        minimum_gray += 1

    # Locate right cut
    maximum_gray = hist_size -1
    while accumulator[maximum_gray] >= (maximum - clip_hist_percent):
        maximum_gray -= 1

    # Calculate alpha and beta values
    alpha = 255 / (maximum_gray - minimum_gray)
    beta = -minimum_gray * alpha

    '''
    # Calculate new histogram with desired range and show histogram 
    new_hist = cv2.calcHist([gray],[0],None,[256],[minimum_gray,maximum_gray])
    plt.plot(hist)
    plt.plot(new_hist)
    plt.xlim([0,256])
    plt.show()
    '''

    auto_result = convertScale(image, alpha=alpha, beta=beta)
    return (auto_result, alpha, beta)

image = cv2.imread('1.jpg')
auto_result, alpha, beta = automatic_brightness_and_contrast(image)
print('alpha', alpha)
print('beta', beta)
cv2.imshow('auto_result', auto_result)
cv2.imwrite('auto_result.png', auto_result)
cv2.imshow('image', image)
cv2.waitKey()

【讨论】:

  • 这是一个内容丰富的答案。是否有一个简单的公式来计算从目标亮度进行裁剪的百分位数,而不是反复试验?
  • 这是一个粗略的估计。 Clipping percentile = target brightness / 2。因此,如果您想要 50% 的亮度,您可以将百分位数设置为 25。您可能需要进行额外的测试才能获得准确的公式
  • 我刚刚通过反复试验对此进行了测试,将百分位数增加 0.1 以获得正确的亮度,并且它可以工作。它也比我预期的要快得多。
  • @mLstudent33,是的,看看here 3 通道彩色图像的示例结果
  • @miguelmorin,我已经手动添加了更新。它似乎被社区自动拒绝了。
【解决方案2】:

您需要修改对比度和亮度。

我不使用 OpenCV,但这是我为 Imagemagick 构建的 (Unix) bash 脚本的解决方案。 注意,mean 控制亮度,std 控制对比度。

该脚本最初旨在调整一张图像以匹配另一张图像的颜色/亮度/对比度。匹配使用每个图像的均值和标准差,公式如下:(I2-Mean2)/Std2 = (I1-Mean1)/Std1。该等式表示归一化强度,由于除以标准偏差,它具有零均值和大致相同的值范围。我们根据 I2 = A x I1 + B 求解这个方程以形成 I1 和 I2 之间的线性变换,其中 A=(Std2/Std1) 是斜率或增益,B=(Mean2 - A x Mean1) 是截距偏见。如果没有提供第二张图像并且提供了(一组)平均值和标准偏差,则第一个文件将与提供的平均值和标准偏差相匹配。 斜率或增益与对比度相关,截距或偏差与亮度相关。

输入:

matchimage -c rgb -m 0.6 -s 0.25 bunny.png result1.png


或者稍微对比一下:

matchimage -c rgb -m 0.6 -s 0.35 bunny.png result2.png


参数标准化为 0 到 1 的范围。所以 mean=0.6 相当于 60%。我认为 66% 可能太亮了,但您可以根据需要更改值。

在这种情况下,由于您的图像大部分是灰度图像,因此我使用色彩空间 RGB 进行处理。可以在其他几种颜色空间中进行处理。

有一个类似的 Python 脚本 here,它只是将一个图像与另一个图像匹配,但在 LAB 颜色空间中这样做。但是,将其更改为将一个图像与一组均值和标准参数相匹配应该很容易。

(我的脚本可用here

【讨论】:

  • @nathancy。感谢您的指正。但是,我不知道有关内联链接的任何政策。你能指出我在哪里讨论了这一点以及任何其他类似的风格政策。我在帮助中心没有找到任何关于此的内容。谢谢
【解决方案3】:

一种解决方案是调整图像的伽玛。在下面的代码中,我首先将图像饱和到范围顶部和底部的某个百分位,然后调整伽马校正直到达到所需的亮度。

import cv2
import numpy as np

def saturate(img, percentile):
    """Changes the scale of the image so that half of percentile at the low range
    becomes 0, half of percentile at the top range becomes 255.
    """

    if 2 != len(img.shape):
        raise ValueError("Expected an image with only one channel")

    # copy values
    channel = img[:, :].copy()
    flat = channel.ravel()

    # copy values and sort them
    sorted_values = np.sort(flat)

    # find points to clip
    max_index = len(sorted_values) - 1
    half_percent = percentile / 200
    low_value = sorted_values[math.floor(max_index * half_percent)]
    high_value = sorted_values[math.ceil(max_index * (1 - half_percent))]

    # saturate
    channel[channel < low_value] = low_value
    channel[channel > high_value] = high_value

    # scale the channel
    channel_norm = channel.copy()
    cv2.normalize(channel, channel_norm, 0, 255, cv2.NORM_MINMAX)

    return channel_norm

def adjust_gamma(img, gamma):
    """Build a lookup table mapping the pixel values [0, 255] to
    their adjusted gamma values.
    """

    # code from
    # https://www.pyimagesearch.com/2015/10/05/opencv-gamma-correction/

    invGamma = 1.0 / gamma
    table = np.array([((i / 255.0) ** invGamma) * 255 for i in np.arange(0, 256)]).astype("uint8")

    # apply gamma correction using the lookup table
    return cv2.LUT(img, table)


def adjust_brightness_with_gamma(gray_img, minimum_brightness, gamma_step = GAMMA_STEP):

    """Adjusts the brightness of an image by saturating the bottom and top
    percentiles, and changing the gamma until reaching the required brightness.
    """
    if 3 <= len(gray_img.shape):
        raise ValueError("Expected a grayscale image, color channels found")

    cols, rows = gray_img.shape
    changed = False
    old_brightness = np.sum(gray_img) / (255 * cols * rows)
    new_img = gray_img
    gamma = 1

    while True:
        brightness = np.sum(new_img) / (255 * cols * rows)
        if brightness >= minimum_brightness:
            break

        gamma += gamma_step
        new_img = adjust_gamma(gray_img, gamma = gamma)
        changed = True

    if changed:
        print("Old brightness: %3.3f, new brightness: %3.3f " %(old_brightness, brightness))
    else:
        print("Maintaining brightness at %3.3f" % old_brightness)

    return new_img

def main(filepath):

    img = cv2.imread(filepath)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    saturated = saturate(gray, 1)
    bright = adjust_brightness_with_gamma(saturated, minimum_brightness = 0.66)

结果在这里,不如接受的答案:

根据图像,我在接受的答案中使用 alpha-beta 调整,或者我包括 gamma 以避免剪裁过多的高光。每个步骤的大小、裁剪的百分位数和校正的 gamma 决定了每次调整的权重。

PERCENTILE_STEP = 1
GAMMA_STEP = 0.01

def adjust_brightness_alpha_beta_gamma(gray_img, minimum_brightness, percentile_step = PERCENTILE_STEP, gamma_step = GAMMA_STEP):
    """Adjusts brightness with histogram clipping by trial and error.
    """

    if 3 <= len(gray_img.shape):
        raise ValueError("Expected a grayscale image, color channels found")

    new_img = gray_img
    percentile = percentile_step
    gamma = 1
    brightness_changed = False

    while True:
        cols, rows = new_img.shape
        brightness = np.sum(new_img) / (255 * cols * rows)

        if not brightness_changed:
            old_brightness = brightness

        if brightness >= minimum_brightness:
            break

        # adjust alpha and beta
        percentile += percentile_step
        alpha, beta = percentile_to_bias_and_gain(new_img, percentile)
        new_img = convertScale(gray_img, alpha = alpha, beta = beta)
        brightness_changed = True

        # adjust gamma
        gamma += gamma_step
        new_img = adjust_gamma(new_img, gamma = gamma)

    if brightness_changed:
        print("Old brightness: %3.3f, new brightness: %3.3f " %(old_brightness, brightness))
    else:
        print("Maintaining brightness at %3.3f" % old_brightness)

    return new_img

【讨论】:

    【解决方案4】:

    如果你这样尝试会怎样:

    bright_img = cv2.convertScaleAbs(img, alpha = 1, beta = 255 * (minimum_brightness - brightness))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-20
      • 2019-03-26
      • 1970-01-01
      • 1970-01-01
      • 2019-11-16
      • 1970-01-01
      • 1970-01-01
      • 2012-12-23
      相关资源
      最近更新 更多