【问题标题】:PIL - apply the same operation to every pixelPIL - 对每个像素应用相同的操作
【发布时间】:2016-06-16 15:54:15
【问题描述】:

我创建一个图像并填充像素:

img = Image.new( 'RGB', (2000,2000), "black") # create a new black image
pixels = img.load() # create the pixel map

for i in range(img.size[0]):    # for every pixel:
    for j in range(img.size[1]):
      #do some stuff that requires i and j as parameter

这可以做得更优雅(并且可能更快,因为理论上循环是可并行化的)?

【问题讨论】:

    标签: python python-imaging-library


    【解决方案1】:

    注意:我会先回答这个问题,然后提出一个我认为更好的替代方案

    回答问题

    如果不知道您打算应用哪些更改以及将图像加载为 PIL 图像是问题的一部分还是给定的,很难给出建议。

    • 更优雅的 Python 语言通常意味着使用列表推导
    • 对于并行化,您可以查看multiprocessing 模块或joblib 之类的东西

    根据您在图像中创建/加载的方法,您可能会对list_of_pixels = list(img.getdata())img.putdata(new_list_of_pixels) 函数感兴趣。

    这可能是什么样子的一个例子:

    from PIL import Image
    from multiprocessing import Pool
    
    img = Image.new( 'RGB', (2000,2000), "black")
    
    # a function that fixes the green component of a pixel to the value 50
    def update_pixel(p):
        return (p[0], 50, p[2])
    
    list_of_pixels = list(img.getdata())
    pool = Pool(4)
    new_list_of_pixels = pool.map(update_pixel, list_of_pixels)
    pool.close()
    pool.join()
    img.putdata(new_list_of_pixels)
    

    但是,我认为这不是一个好主意...当您在 Python 中看到数千个元素的循环(和列表推导)并且您想到了性能时,您可以确定有一个库可以会让这更快。

    更好的选择

    首先,快速指向Channel Operations module, 由于您没有指定您打算执行的像素操作类型,并且您显然已经了解 PIL 库,因此我假设您已经知道它并且它不会执行您想要的操作。

    然后,Python 中任何中等复杂的矩阵操作都将受益于拉入 PandasNumpyScipy...

    纯 numpy 示例:

    import numpy as np
    import matplotlib.pyplot as plt
    #black image
    img = np.zeros([100,100,3],dtype=np.uint8)
    #show
    plt.imshow(img)
    #make it green
    img[:,:, 1] = 50
    #show
    plt.imshow(img)
    

    由于您只是使用标准的 numpy.ndarray,因此您可以使用任何可用的功能,例如 np.vectorize、apply、map 等。使用 update_pixel 函数显示与上述类似的解决方案:

    import numpy as np
    import matplotlib.pyplot as plt
    #black image
    img = np.zeros([100,100,3],dtype=np.uint8)
    #show
    plt.imshow(img)
    #make it green
    def update_pixel(p):
        return (p[0], 50, p[2])
    green_img = np.apply_along_axis(update_pixel, 2, img)
    #show
    plt.imshow(green_img)
    

    再举个例子,这次直接从索引计算图像内容,而不是从现有图像像素内容(无需先创建空图像):

    import numpy as np
    import matplotlib.pyplot as plt
    
    def calc_pixel(x,y):
        return np.array([100-x, x+y, 100-y])
    
    img = np.frompyfunc(calc_pixel, 2, 1).outer(np.arange(100), np.arange(100))    
    plt.imshow(np.array(img.tolist()))
    #note: I don't know any other way to convert a 2D array of arrays to a 3D array...
    

    而且,很明显,scipy 具有读取和写入图像的方法,在两者之间,您可以使用 numpy 将它们作为“经典”多维数组进行操作。 (顺便说一句,scipy.misc.imread 取决于 PIL)

    More example code.

    【讨论】:

    • 您好,感谢您的回答。一些进一步的问题:@1st 变体:并行化每个像素似乎效率低下 - 只有外循环似乎更合适。 numpy 替代方案看起来也不错,但我怎样才能使分配 img[:,:, 1] = 50 依赖于实际索引?我对python很陌生,找不到示例(我猜主要是因为缺少正确的搜索词。这种img[:,:, 1]这种访问模式是怎么调用的?)
    • 这称为切片 (docs.scipy.org/doc/numpy/reference/arrays.indexing.html)。在这种情况下,它基本上是说,对于每一行和每一列,用 50 替换三个颜色值中的第二个-read: green-)索引 1 表示第二个值,因为 Python 索引是基于 0 的
    • 如果您是 PIL 的新手,我肯定会建议您查看我提到的 Channel Operations 模块,它可能会满足您的需求
    • 频道操作不符合我的要求。 atm 我只是在玩计算干涉图案和分形,想知道如何高效/优雅/pythonish 写下类似foreach x,y in img: img[x,y] = calc(x,y)
    • @vlad_tepesch 也许你可以使用numpy.ndenumerate
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多