使用Python图像库(PIL)规范化一组图像的直方图(亮度和对比度)

14 投票
2 回答
28697 浏览
提问于 2025-04-16 23:55

我有一个脚本,它使用谷歌地图的API来下载一系列大小相同的方形卫星图像,并生成一个PDF文件。这些图像在生成之前需要旋转,我已经用PIL(Python图像库)完成了这一步。

我发现,由于光线和地形条件不同,有些图像太亮,有些则太暗,导致生成的PDF看起来有点糟糕,阅读起来不太理想。我的使用场景是山地自行车骑行,我希望能有一份特定交叉路口的打印缩略图。

(补充说明)我的目标是让所有图像的亮度和对比度看起来相似。所以,太亮的图像需要变暗,而太暗的图像需要变亮。(顺便提一下,我之前用过imagemagick的autocontrastauto-gammaequalizeautolevel等功能,效果在医学图像上还不错,但我不知道如何在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 个回答

3

下面的代码是用来处理显微镜拍的图片(这些图片很相似),目的是在拼接之前做好准备。我在一组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
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)

撰写回答