使用Python图像库(PIL)对一组图像进行直方图归一化(亮度和对比度)。

14
我有一个脚本,使用Google Maps API下载一系列相同大小的正方形卫星图像并生成PDF。这些图像需要事先进行旋转,我已经使用PIL完成了这个步骤。

我注意到,由于不同的光照和地形条件,一些图像过亮,另一些则过暗,导致生成的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

这段代码针对每个图像独立运行。我想知道先分析所有图像然后“规范化”它们的视觉特性(对比度、亮度、伽马等)是否更好。

此外,我认为有必要对图像进行一些分析(直方图?),以便根据每个图像应用自定义校正,而不是对所有图像进行相同的校正(尽管任何“增强”函数都隐含考虑了初始条件)。

是否有人遇到过这样的问题和/或知道一个在彩色图像(非灰度)上执行此操作的好方法?

非常感谢您的阅读,期待您的帮助!


好问题!不过需要进一步澄清。此外,如果能发布一些示例图片供人们用作测试用例将非常有帮助。首先,问题是在下载时瓦片边缘不匹配吗?还是你正在寻找一种方法来提亮暗色瓦片并调暗亮色瓦片?或者你需要同时保持边缘连续性而做到后者? - Paul
边缘不是问题,因为图像集不连续。目标是使最亮的变暗,最暗的变亮,就像你说的那样。 - heltonbiker
2个回答

9
您可能正在寻找的是一种执行“直方图拉伸”操作的工具。以下是一个实现示例:这里有一个,当然还有其他替代方案。我认为您希望保留原始色调,并在所有颜色通道上均匀应用此函数。
当然,有很大的机会在瓷砖连接处出现明显的不连续水平。避免这种情况涉及到对“拉伸”参数进行空间插值,需要更加复杂的解决方案(但如果需要的话,这是一个很好的练习)。 编辑: 以下是一种保留图像色调的微调方法:
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)

哇,看起来正是我想要的。我会快速尝试并很快发布一些反馈! - heltonbiker
实际上,这个实现似乎一次只能处理一张图片,我正在考虑先分析所有的图片,然后再应用均衡化。此外,这些图片不会被平铺,它们来自不同的位置,通常不会重叠。我会测试你的建议并看看结果。谢谢! - heltonbiker
我在我的相机上尝试了常规数字图像,效果非常好。但是当我尝试对谷歌卫星图像的截屏进行处理时,效果很差。我认为这些卫星图像可能已经过度分色或其他处理。 - Paul
3
我尝试运行你的代码,但是在Image类的最后一行出现了不存在的属性layers的错误。 我正在使用Python 2.7,但在PIL文档中找不到这个属性。 有什么想法? :o( - heltonbiker
3
针对 RGB 图像,im.layers 值为 3;针对 RGBA 图像,im.layers 值为 4。只需用相应的值进行替换即可。 - Paul

3
以下代码适用于显微镜图像(相似),在拼接之前对其进行处理。我在一个包含20张图片的测试集上使用它,结果较为合理。
亮度平均函数来自另一个Stackoverflow问题
from PIL import Image
from PIL import ImageStat
import math

# function to return average brightness of an image
# Source: https://dev59.com/6nA75IYBdhLWcg3wFEoI

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接