仅在 python 中调亮图像的暗区

Brighten only dark areas of image in python

我正在尝试处理图像,我想使图像的暗区变亮。我尝试了直方图均衡化,但是由于图像中也有一些明亮的区域,结果并不令人满意。这就是为什么我正在寻找一种方法来增亮仅图像的黑暗区域

举个例子,左边是输入图,右边是预期结果,女孩的头发和脸都变亮了

ImageMagick 似乎提供了一些实现此目的的可能性,但是,我想使用 python

来实现

如果您想避免颜色失真,您可以:

  • 转换为 HSV 色彩空间,
  • 拆分频道,
  • 提高 V(值)通道
  • 重新组合渠道
  • 保存

可能是这样的:

from PIL import Image

# Open the image
im = Image.open('hEHxh.jpg')

# Convert to HSV colourspace and split channels for ease of separate processing
H, S, V = im.convert('HSV').split()

# Increase the brightness, or Value channel
# Change 30 to 50 for bigger effect, or 10 for smaller effect
newV = V.point(lambda i: i + int(30*(255-i)/255))

# Recombine channels and convert back to RGB
res = Image.merge(mode="HSV", bands=(H,S,newV)).convert('RGB')

res.save('result.jpg')

本质上,我将亮度从黑色映射更改为绿色映射:


提醒健忘的自己...“马克,你把情节写成了这样”:

import matplotlib.pyplot as plt
import numpy as np

# Generate some straight-line data
xdata = np.arange(0,256)
# And the new mapping
ydata = xdata + 30*(255-xdata)/255

# Plot
plt.plot(xdata,xdata,'.k')
plt.plot(xdata,ydata,'g^')
plt.title('Adjustment of V')
plt.xlabel('Input V')
plt.ylabel('Output V')
plt.grid(True)
plt.show()

以下是仅使用 cv2 模块即可完成此操作的方法:

import cv2

def lighten(img, value=30):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    v = hsv[..., 2]
    v[:] = cv2.add(v, value)
    return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

img = cv2.imread("image.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mask = cv2.threshold(gray, 175, 255, cv2.THRESH_BINARY)[1] == 0
img[mask] = lighten(img)[mask]
cv2.imshow("result", img)
cv2.waitKey(0)

输入图像和输出图像:

这是在 Imagemagick 和 Python/OpenCV 中执行此操作的一种方法。使用三角法对 LAB 颜色空间的 L 通道进行阈值处理。然后使整个图像变亮。然后使用阈值作为蒙版合并原始图像和增亮图像。

Imagemagick:

magick girl_on_chair.jpg \
\( -clone 0 -colorspace LAB -channel 0 -separate +channel \
-auto-threshold triangle -negate +write thresh.png \) \
\( -clone 0 -evaluate multiply 4 \) \
+swap -compose over -composite \
girl_on_chair_processed.jpg

阈值:

结果:

Python/OpenCV:

import cv2
import numpy as np

# read image
img = cv2.imread("girl_on_chair.jpg")

# convert to LAB and extract L  channel
LAB = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
L = LAB[:,:,0]

# threshold L channel with triangle method
value, thresh = cv2.threshold(L, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_TRIANGLE)
print(value)

# threshold with adjusted value
value = value + 10
thresh = cv2.threshold(L, value, 255, cv2.THRESH_BINARY)[1]

# invert threshold and make 3 channels
thresh = 255 - thresh
thresh = cv2.merge([thresh, thresh, thresh])

gain = 3
blue = cv2.multiply(img[:,:,0], gain)
green = cv2.multiply(img[:,:,1], gain)
red = cv2.multiply(img[:,:,2], gain)
img_bright = cv2.merge([blue, green, red])

# blend original and brightened using thresh as mask
result = np.where(thresh==255, img_bright, img)

# save result
cv2.imwrite('girl_on_chair_thresh.jpg', thresh)
cv2.imwrite('girl_on_chair_brighten.jpg', result)

cv2.imshow('img', img)
cv2.imshow('L', L)
cv2.imshow('thresh', thresh)
cv2.imshow('img_bright', img_bright)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

阈值:

结果:

在 Imagemagick 中执行此操作的另一种方法是使用 -fx 来设置阈值并使用 LAB 的 L 通道或 YCbCr 的 Y 通道或 HSV 的 V 通道通过在该通道的位置添加一个常数值来增加亮度小于某个阈值。

(注意:在-fx中,u等图像的范围从0到1不等。)

magick girl_on_chair.jpg \
-colorspace LAB -separate \
\( -clone 0 -fx "u<0.15?u+0.15:u" \) \
-swap 0,3 +delete \
-set colorspace LAB -combine -colorspace sRGB \
girl_on_chair_proc2.jpg

magick girl_on_chair.jpg \
-colorspace YCBCR -separate \
\( -clone 0 -fx "u<0.15?u+0.15:u" \) \
-swap 0,3 +delete \
-set colorspace YCBCR -combine -colorspace sRGB \
girl_on_chair_proc3.jpg

magick girl_on_chair.jpg \
-colorspace HSV -separate \
\( -clone 2 -fx "u<0.15?u+0.15:u" \) \
+swap +delete \
-set colorspace HSV -combine -colorspace sRGB \
girl_on_chair_proc4.jpg

实验结果:

YCbCr 结果:

HSV 结果:

可以使用Python/OpenCV进行类似的处理,如下:

import cv2
import numpy as np

# read image
img = cv2.imread("girl_on_chair.jpg")

# convert to LAB and extract L  channel
LAB = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
L,A,B = cv2.split(LAB)

# process L channel
L_proc = np.where(L<40, cv2.add(L,40), L)

# recombine L_proc with A and B
LAB_new = cv2.merge([L_proc, A, B])

# convert back to BGR
result = cv2.cvtColor(LAB_new, cv2.COLOR_LAB2BGR)

# save result
cv2.imwrite('girl_on_chair_brighten2.jpg', result)

cv2.imshow('img', img)
cv2.imshow('L', L)
cv2.imshow('L_proc', L_proc)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果:

这是使用 tonelut operation in pyvips 的解决方案。它应该是高质量的,而且很快。

它使 CIELAB 中的 L* 变亮。对于此类操作,这是一个不错的选择,因为 L* 将亮度分为 100 个刚好可见的差异级。 L* 范围的 20%、50% 和 80% 是阴影、中间调和高光位置的有用经验法则。

它是这样工作的:

  • 图像已转换为 LABS 色彩空间。这是 CIELAB,但表示为带符号的短,因此 L* 为 0-32767,a* 和 b*
  • 为 +/- 32767
  • 它找到 L*(零)波段的直方图,并使用它来确定此图像的 1% 和 99% 点
  • 然后将这些传递给tonelut,为生成的色调调整函数设置黑白。用户可以通过 command-line 参数
  • 调整阴影、中间调和高光增强
  • tonelut使用一组样条生成平滑单调的色调重映射函数
  • L*通过色调调整曲线映射
  • 最后,它转换回原始色彩空间并写入

代码:

#!/usr/bin/python3

import sys
import pyvips

input_filename = sys.argv[1]
output_filename = sys.argv[2]
shadow_boost = float(sys.argv[3])
midtone_boost = float(sys.argv[4])
highlight_boost = float(sys.argv[5])

image = pyvips.Image.new_from_file(sys.argv[1])

# we work in LABS format, a 16-bit version of LAB, with 0 - 32767 for L*, and
# +/- 32767 for a* and b* +/- 128
original_interpretation = image.interpretation
image = image.colourspace("labs")

# find the histogram of L* and use it to pick the 1% and 99% points as black
# and white
hist = image[0].hist_find();
norm = hist.hist_cum().hist_norm()  # normalised cumulative histogram
black = (norm > norm.width * 0.01).profile()[1].avg()
white = (norm > norm.width * 0.99).profile()[1].avg()

# tonelut wants percentages of 0-32767
black = 100.0 * black / 32767.0
white = 100.0 * white / 32767.0

# make a tone-mapping function using those black and white points
tone = pyvips.Image \
    .tonelut(Lb=black, Lw=white, \
        S=shadow_boost, M=midtone_boost, H=highlight_boost)

# remap L* of the image
image = (image[0].maplut(tone)).bandjoin(image[1:])

# back to the original interpretation, and save
image = image.colourspace(original_interpretation)

image.write_to_file(output_filename)

我可以运行这样:

$ ./tone.py ~/pics/girl.jpg x.jpg +10 +10 0

这也增强了中间调,因为那是皮肤掉落的地方。这是之前和之后:

我们可以从 better-quality 测试图像中提取更多信息。