使用Python图像库(PIL)规范化一组图像的直方图(亮度和对比度)
我有一个脚本,它使用谷歌地图的API来下载一系列大小相同的方形卫星图像,并生成一个PDF文件。这些图像在生成之前需要旋转,我已经用PIL(Python图像库)完成了这一步。
我发现,由于光线和地形条件不同,有些图像太亮,有些则太暗,导致生成的PDF看起来有点糟糕,阅读起来不太理想。我的使用场景是山地自行车骑行,我希望能有一份特定交叉路口的打印缩略图。
(补充说明)我的目标是让所有图像的亮度和对比度看起来相似。所以,太亮的图像需要变暗,而太暗的图像需要变亮。(顺便提一下,我之前用过imagemagick的autocontrast
、auto-gamma
、equalize
、autolevel
等功能,效果在医学图像上还不错,但我不知道如何在PIL中实现这些功能。)
我在转换为灰度图像后使用了一些图像修正(之前有一台灰度打印机),但结果也不太好。这是我的灰度代码:
#!/usr/bin/python
def myEqualize(im)
im=im.convert('L')
contr = ImageEnhance.Contrast(im)
im = contr.enhance(0.3)
bright = ImageEnhance.Brightness(im)
im = bright.enhance(2)
#im.show()
return im
这段代码是针对每张图像独立工作的。我在想,是否先分析所有图像,然后再“标准化”它们的视觉属性(对比度、亮度、伽马等)会更好。
另外,我觉得有必要对图像进行一些分析(比如直方图?),这样就可以根据每张图像的情况应用自定义的修正,而不是对所有图像都用相同的修正(尽管任何“增强”功能在某种程度上都会考虑初始条件)。
有没有人遇到过类似的问题,或者知道有什么好的方法可以处理彩色图像(而不是灰度图像)?
任何帮助都将不胜感激,谢谢你的阅读!
2 个回答
下面的代码是用来处理显微镜拍的图片(这些图片很相似),目的是在拼接之前做好准备。我在一组20张测试图片上使用了这个代码,效果还不错。
这里的亮度平均值函数来自另一个Stackoverflow的问题。
from PIL import Image
from PIL import ImageStat
import math
# function to return average brightness of an image
# Source: https://stackoverflow.com/questions/3490727/what-are-some-methods-to-analyze-image-brightness-using-python
def brightness(im_file):
im = Image.open(im_file)
stat = ImageStat.Stat(im)
r,g,b = stat.mean
return math.sqrt(0.241*(r**2) + 0.691*(g**2) + 0.068*(b**2)) #this is a way of averaging the r g b values to derive "human-visible" brightness
myList = [0.0]
deltaList = [0.0]
b = 0.0
num_images = 20 # number of images
# loop to auto-generate image names and run prior function
for i in range(1, num_images + 1): # for loop runs from image number 1 thru 20
a = str(i)
if len(a) == 1: a = '0' + str(i) # to follow the naming convention of files - 01.jpg, 02.jpg... 11.jpg etc.
image_name = 'twenty/' + a + '.jpg'
myList.append(brightness(image_name))
avg_brightness = sum(myList[1:])/num_images
print myList
print avg_brightness
for i in range(1, num_images + 1):
deltaList.append(i)
deltaList[i] = avg_brightness - myList[i]
print deltaList
到目前为止,“修正”值(也就是每个值和平均值之间的差)已经存储在deltaList里。接下来的部分会把这个修正值逐一应用到所有图片上。
for k in range(1, num_images + 1): # for loop runs from image number 1 thru 20
a = str(k)
if len(a) == 1: a = '0' + str(k) # to follow the naming convention of files - 01.jpg, 02.jpg... 11.jpg etc.
image_name = 'twenty/' + a + '.jpg'
img_file = Image.open(image_name)
img_file = img_file.convert('RGB') # converts image to RGB format
pixels = img_file.load() # creates the pixel map
for i in range (img_file.size[0]):
for j in range (img_file.size[1]):
r, g, b = img_file.getpixel((i,j)) # extracts r g b values for the i x j th pixel
pixels[i,j] = (r+int(deltaList[k]), g+int(deltaList[k]), b+int(deltaList[k])) # re-creates the image
j = str(k)
new_image_name = 'twenty/' +'image' + j + '.jpg' # creates a new filename
img_file.save(new_image_name) # saves output to new file name
你可能在寻找一种工具,它可以进行“直方图拉伸”。这里有一个实现的例子。我相信还有其他的实现方式。你想要的是保留原来的色调,并且在所有颜色通道上均匀地应用这个功能。
当然,有可能在某些区域拼接的时候,会出现明显的色差。这种情况的避免需要对“拉伸”参数进行空间插值,这样的解决方案会复杂得多。(……不过如果有这个需求,做一下也是个不错的练习。)
编辑:
这里有一个调整,可以保留图像的色调:
import operator
def equalize(im):
h = im.convert("L").histogram()
lut = []
for b in range(0, len(h), 256):
# step size
step = reduce(operator.add, h[b:b+256]) / 255
# create equalization lookup table
n = 0
for i in range(256):
lut.append(n / step)
n = n + h[i+b]
# map image through lookup table
return im.point(lut*im.layers)