Je veux régler la luminosité d'une image à une certaine valeur dans OpenCV. Par exemple, considérez cette image:
Je calcule la luminosité avec:
import cv2
img = cv2.imread(filepath)
cols, rows = img.shape
brightness = numpy.sum(img) / (255 * cols * rows)
et j'obtiens une luminosité moyenne de 35%. Pour le porter à 66%, par exemple, je fais:
minimum_brightness = 0.66
alpha = brightness / minimum_brightness
bright_img = cv2.convertScaleAbs(img, alpha = alpha, beta = 255 * (1 - alpha))
et j'obtiens une image qui semble avoir un voile de transparence à 50%:
Je peux éviter cet effet en utilisant uniquement le biais:
bright_img = cv2.convertScaleAbs(img, alpha = 1, beta = 128)
et l'image semble aussi avoir un voile:
Si je le fais à la main, par exemple dans Photoshop avec un réglage de la luminosité à 150, le résultat semble correct:
Mais ce n'est pas automatique et ne donne pas la luminosité cible.
Je pourrais le faire avec une correction gamma et/ou une égalisation d'histogramme pour un résultat peut-être plus naturel, mais je ne vois pas de moyen facile d'obtenir la luminosité cible autre que des essais et erreurs.
Quelqu'un a-t-il réussi à régler automatiquement la luminosité sur une cible?
Kanat a suggéré:
bright_img = cv2.convertScaleAbs(img, alpha = 1, beta = 255 * (minimum_brightness - brightness))
et le résultat est meilleur mais a toujours un voile:
Yves Daoust a suggéré de garder beta = 0
, j'ai donc ajusté alpha = minimum_brightness / brightness
pour obtenir la luminosité cible:
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)
et le résultat est bon:
Une solution consiste à ajuster le gamma de l'image. Dans le code ci-dessous, je sature d'abord l'image à un certain centile en haut et en bas de la plage, puis ajuste la correction gamma jusqu'à atteindre la luminosité requise.
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)
Le résultat est ici et inférieur à la réponse acceptée:
Selon l'image, j'utilise soit l'ajustement alpha-bêta dans la réponse acceptée, soit j'inclus le gamma pour éviter d'écraser trop de hautes lumières. La taille de chaque pas, le centile pour l'écrêtage et le gamma pour la correction, détermine le poids de chaque ajustement.
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