使用 Numpy 更有效地改变色调

Shift hue more efficiently, using Numpy

我正在制作一个在线图像编辑器,我正在实现一个 Hue/Sat/Lum 编辑器。这是我使用给定值更改图像的函数。

def make_edit(pixels, hue, sat, lum):
    shape = pixels.shape
    new = np.empty(shape)
    print(time.time())
    for row_count, row in enumerate(pixels):
        for pixel_count, p in enumerate(row):
            new_hue = p[0] + hue
            if new_hue < 0:
                new_hue += 255
            elif new_hue > 255:
                new_hue -= 255

            new_sat = p[1] + sat
            if new_sat < 0:
                new_sat += 255
            elif new_sat > 255:
                new_sat -= 255

            new_lum = p[2] + lum
            if new_lum < 0:
                new_lum = 0
            elif new_lum > 255:
                new_lum = 255

            new[row_count, pixel_count] = np.array([new_hue, new_sat, new_lum])
    print(time.time())
    return new

该函数接受一个形状为 (height, width, 3) 的 numpy 数组。我逐个像素地进行处理,然后将色相、饱和度和亮度值添加到每个像素。它需要 13 秒(在 (648, 1152, 3) 形状的数组上)但是,显然太长了。是否有一个 numpy 函数可以将所有值抵消我给它的数量。 p.s。该功能还没有工作,hue 似乎可以,但是 sat 和 lum 没有给出正确的图像。

由于您还没有让 sat 和 lum 正常工作,所以这可能需要根据您的最终代码进行调整,但是它确实与您当前过程的结果相匹配并且速度快了几个数量级:

def getPic():   
    return  np.random.randint(0, 255, 648*1152*3).reshape(648, 1152, 3)

def make_edit(pixels, hue, sat, lum):
    shape = pixels.shape
    new = np.empty(shape)
    #print(time.time())
    for row_count, row in enumerate(pixels):
        for pixel_count, p in enumerate(row):
            new_hue = p[0] + hue
            if new_hue < 0:
                new_hue += 255
            elif new_hue > 255:
                new_hue -= 255

            new_sat = p[1] + sat
            if new_sat < 0:
                new_sat += 255
            elif new_sat > 255:
                new_sat -= 255

            new_lum = p[2] + lum
            if new_lum < 0:
                new_lum = 0
            elif new_lum > 255:
                new_lum = 255

            new[row_count, pixel_count] = np.array([new_hue, new_sat, new_lum])
    #print(time.time())
    return new


def new_make_edit(pixels, hue, sat, lum):
    new = np.empty_like(pixels)
    new[:,:,0] = pixels[:,:,0] + hue
    new[:,:,0][new[:,:,0]<0] += 255
    new[:,:,0][new[:,:,0]>255] -= 255

    new[:,:,1] = pixels[:,:,1] + sat
    new[:,:,1][new[:,:,1]<0] += 255
    new[:,:,1][new[:,:,1]>255] -= 255

    new[:,:,2] = pixels[:,:,2] + lum
    new[:,:,2][new[:,:,2]<0] = 0
    new[:,:,2][new[:,:,2]>255] = 255
    return new

def tEd():
    pic = getPic()
    old = make_edit(pic, 10, -25, 211)
    new = new_make_edit(pic, 10, -25, 211)
    return old, new

def timeOld():
    pic = getPic()
    old = make_edit(pic, 10, -25, 211)
    return old

def timeNew():
    pic = getPic()
    new = new_make_edit(pic, 10, -25, 211)
    return new

在同一图像上执行旧的和新的并验证输出匹配:

>>> o,n=tEd()
>>> np.all(o==n)
True

性能比较:

>>> timeit.timeit(timeNew, number=10)
0.5608299169980455
>>> timeit.timeit(timeOld, number=10)
58.86368254100671

我不认为你的算法或接受的答案是正确的 - 如果你将 pixels 数组创建为 uint8(而不是 int64,你会看到这个当前是)如果您将输出限制在 0..255.

范围内,这大概就是您所拥有的

您需要将色相与饱和度和亮度区别对待。色调是圆形的,这意味着它 "wraps around" 一个 0..255 "degrees" 的圆。这意味着当它达到 255 并且你加 1 时,你应该回到零并再次绕过。在数学上,这意味着模数 %。饱和度和亮度不是圆形的,这意味着如果图像几乎完全明亮,比如 250,如果你将亮度增加 100,它应该最大 "burn out"。在数学上,这是 "clipping"。饱和度也一样。

所以,我相信你想要更像这样的东西:

#!/usr/bin/env python3

import numpy as np

def make_edit(im, hue, sat, lum):
    # Make signed and larger to accommodate wrap-around
    im = im.astype(np.int32)

    # Add constant amount of hue to each pixel, wrapping around at 255
    im[:,:,0] = (im[:,:,0] + hue) % 256 

    # Add constant amount of saturation, and lightness to each pixel
    im[:,:,1] += sat
    im[:,:,2] += lum

    # Clip results to range 0..255 and return as uint8
    return np.clip(im,0,255).astype(np.uint8)

# Make our randomness deterministic!
np.random.seed(42)

# Create 4x2 array of HSL pixels - note UINT8
im = np.random.randint(0,255,(2,4,3),dtype=np.uint8) 

# array([[[102, 220, 225],
#        [ 95, 179,  61],
#        [234, 203,  92],
#        [  3,  98, 243]],
#
#       [[ 14, 149, 245],
#        [ 46, 106, 244],
#        [ 99, 187,  71],
#        [212, 153, 199]]], dtype=uint8)

res = make_edit(im, 100, 50, 20)
print(res)

#[[[202 255 245]
#  [195 229  81]
#  [ 78 253 112]
#  [103 148 255]]
#
# [[114 199 255]
#  [146 156 255]
#  [199 237  91]
#  [ 56 203 219]]]

res = make_edit(im, -100, -50, -20)
print(res)

#[[[  2 170 205]
#  [251 129  41]
#  [134 153  72]
#  [159  48 223]]
#
# [[170  99 225]
#  [202  56 224]
#  [255 137  51]
#  [112 103 179]]]

请注意,如果您只想在终端中通过 运行 命令测试您的代码,则可以使用 ImageMagick。您可以像这样使用 -modulate 运算符:

magick INPUTIMAGE -modulate BRIGHTNESS,SATURATION,HUE OUTPUTIMAGE

例如亮度减半:

magick input.png -modulate 50 result.jpg

要将亮度保持在其先前值的 100% 不变,将饱和度增加 20% 并将色相逆时针旋转 90 度(因为 180 的 50% 是 90):

magic input.png -modulate 100,120,50 result.jpg

关键字:Python,图像处理,色调旋转,HSL,HSV,调制。