【问题标题】:What Is the Algorithm Behind Photoshop's “Black and White” Adjustment Layer?Photoshop的“黑白”调整层背后的算法是什么?
【发布时间】:2019-10-18 06:43:14
【问题描述】:

我做了很多研究,但没有找到任何东西(但我也不知道要准确搜索什么样的关键字)。我希望能够将输入的 RGB 图像转换为 灰度,但我希望能够添加更多或更少的 Reds/Yellows/Greens/Cyans/Blues /Magentas 就像在 Photoshop 中一样。你知道方程是什么吗?或者我在哪里可以找到这些方程,以便我可以实现自己优化的 RGB 到灰度的转换?

编辑: 在 Photoshop 中称为黑白调整图层。我发现了一些东西,但实际上它似乎不起作用。这是我的实现(在 cmets 中是理解算法所需的资源):

import numpy as np
import scipy.misc
import matplotlib.pyplot as plt


%matplotlib inline

# Adapted from the answers of Ivan Kuckir and Royi here:
# https://dsp.stackexchange.com/questions/688/what-is-the-algorithm-behind-photoshops-black-and-white-adjustment-layer?newreg=77420cc185fd44099d8be961e736eb0c

def rgb2hls(img):
    """Adapted to use numpy from
       https://github.com/python/cpython/blob/2.7/Lib/colorsys.py"""
    r, g, b = img[:, :, 0], img[:, :, 1], img[:, :, 2]

    maxc = np.max(img, axis=-1)
    minc = np.min(img, axis=-1)
    l = (minc + maxc) / 2

    mask = np.ones_like(r)
    mask[np.where(minc == maxc)] = 0
    mask = mask.astype(np.bool)

    smask = np.greater(l, 0.5).astype(np.float32)

    s = (1.0 - smask) * ((maxc - minc) / (maxc + minc)) + smask * ((maxc - minc) / (2.0 - maxc - minc))
    s[~mask] = 0
    rc = np.where(mask, (maxc - r) / (maxc - minc), 0)
    gc = np.where(mask, (maxc - g) / (maxc - minc), 0)
    bc = np.where(mask, (maxc - b) / (maxc - minc), 0)

    rmask = np.equal(r, maxc).astype(np.float32)
    gmask = np.equal(g, maxc).astype(np.float32)
    rgmask = np.logical_or(rmask, gmask).astype(np.float32)

    h = rmask * (bc - gc) + gmask * (2.0 + rc - bc) + (1.0 - rgmask) * (4.0 + gc - rc)
    h = np.remainder(h / 6.0, 1.0)
    h[~mask] = 0
    return np.stack([h, l, s], axis=-1)


def black_and_white_adjustment(image, weights):  
    # normalize input image to (0, 1) if uint8
    if 'uint8' in (image).dtype.name:
        image = image / 255

    # linearly remap input coeff [-200, 300] to [-2.5, 2.5]
    weights = (weights - 50) / 100
    n_weights = len(weights)
    h, w = image.shape[:2]

    # convert rgb to hls
    hls_img = rgb2hls(image)

    output = np.zeros((h, w), dtype=np.float32)

    # see figure 9 of https://en.wikipedia.org/wiki/HSL_and_HSV
    # to understand the algorithm
    for y in range(h):
        for x in range(w):
            hue_val = 6 * hls_img[y, x, 0]

            # Use distance on a hexagone (maybe circular distance is better?)
            diff_val = min(abs(0 - hue_val), abs(1 - (0 - hue_val)))
            luminance_coeff = weights[0] * max(0, 1 - diff_val)

            for k in range(1, n_weights):
                luminance_coeff += weights[k] * max(0, 1 - abs(k - hue_val))

            # output[y, x] = min(max(hls_img[y, x, 1] * (1 + luminance_coeff), 0), 1)
            output[y, x] = hls_img[y, x, 1] * (1 + luminance_coeff)


    return output


image = scipy.misc.imread("your_image_here.png")
w = np.array([40, 85, 204, 60, 20, 80])
out = black_and_white_adjustment(image, w)
plt.figure(figsize=(15, 20))
plt.imshow(out, cmap='gray')

谢谢

【问题讨论】:

  • 作为选择性颜色或混合成一系列像素?
  • 作为一个例子来更准确地理解问题。您可以使用 photopea。一个免费的在线Photoshop工具。您加载一张图片,然后转到 Image -> Adjustment -> Black/White。那里有 6 个光标,您可以调整青色/蓝色/品红色/黄色/... 我想知道如何编写这样的代码?我不知道从什么开始
  • 抱歉,回复晚了,这应该可以通过 PILLOW 分叉的 Python Imaging Library 实现。我正在研究一个示例,并在完成后将其作为答案发布。同时here 是文档,如果你想看看自己
  • 我发现有人问了同样的问题。显然,Photopea 的开发者回答了这个问题(dsp.stackexchange.com/questions/688/…)。我已经重新实现了他在python中所说的(我也使用了Royi和matlab的答案)但是输出与photopea的输出不匹配
  • 你能把你的适应添加到你的答案中吗?

标签: python image-processing colors reverse-engineering photoshop


【解决方案1】:

这里尝试使用PIL 而不是numpy。它应该很容易转换。如果没有 Photoshop 的副本进行比较,我不能保证它与输出完全匹配,但它确实会为您的链接中显示的示例生成准确的值。值r_w, y_w, g_w, c_w, b_w, m_w 是要应用于每种颜色的权重,在相应的 Photoshop 滑块中,1.0 等于 100%。当然,它们也可以是负数。

from PIL import Image
im = Image.open(r'c:\temp\temp.png')
def ps_black_and_white(im, weights):
    r_w, y_w, g_w, c_w, b_w, m_w = [w/100 for w in weights]
    im = im.convert('RGB')
    pix = im.load()
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            r, g, b = pix[x, y]
            gray = min([r, g, b])
            r -= gray
            g -= gray
            b -= gray
            if r == 0:
                cyan = min(g, b)
                g -= cyan
                b -= cyan
                gray += cyan * c_w + g * g_w + b * b_w
            elif g == 0:
                magenta = min(r, b)
                r -= magenta
                b -= magenta
                gray += magenta * m_w + r * r_w + b * b_w
            else:
                yellow = min(r, g)
                r -= yellow
                g -= yellow
                gray += yellow * y_w + r * r_w + g * g_w
            gray = max(0, min(255, int(round(gray))))
            pix[x, y] = (gray, gray, gray)
    return im

使用此提供的测试图像,以下是一些示例结果。

ps_black_and_white(im, [-17, 300, -100, 300, -200, 300])

ps_black_and_white(im, [40, 60, 40, 60, 20, 80])

ps_black_and_white(im, [106, 65, 17, 17, 104, 19])

【讨论】:

  • 谢谢!!它实际上工作得很好(即使它与 Photoshop 中的结果不完全匹配)。我尝试了不同的亮度定义(您在代码中命名为 gray 的变量),但我无法使其与 Photoshop 完美匹配。我还使用numpyscipy 翻译了您的代码。我会等一会儿。如果没有人能弄清楚如何完美匹配 Photoshop,我会接受你的回答。我还将发布使用numpy的代码
  • @priseJack 如果你可以发布一张带有一堆不匹配的颜色方块的图片,我可以调整这个公式。
  • 我这里没有 Photoshop。我明天会做,并使用指向某些结果的链接更新此评论。非常感谢
  • @priseJack 我做了一个可能修复它的更改,我用grayw添加了两行。当我们都没有能力比较结果时,这很粗糙!
  • 你好。我尝试了您在我的调色板上所做的更改。情况更糟。所以我上传了 Photoshop 和你的算法(第一个版本)之间的比较。一切都在这里:imgur.com/a/nKLVcMR
【解决方案2】:

我通过添加代码的 numpy/scipy 版本来回答我自己的问题,如果将来有人对它感兴趣的话。 如果您想为答案投票,您应该为 Mark Ransom 的答案投票!

import numpy as np
import scipy.misc
import matplotlib.pyplot as plt

%matplotlib inline

def black_and_white_adjustment(img, weights):
    rw, yw, gw, cw, bw, mw = weights / 100

    h, w = img.shape[:2]
    min_c = np.min(img, axis=-1).astype(np.float)
    # max_c = np.max(img, axis=-1).astype(np.float)

    # Can try different definitions as explained in the Ligtness section from
    # https://en.wikipedia.org/wiki/HSL_and_HSV
    # like: luminance = (min_c + max_c) / 2 ...
    luminance = min_c 
    diff = img - min_c[:, :, None]

    red_mask = (diff[:, :, 0] == 0)
    green_mask = np.logical_and((diff[:, :, 1] == 0), ~red_mask)
    blue_mask = ~np.logical_or(red_mask, green_mask)

    c = np.min(diff[:, :, 1:], axis=-1)
    m = np.min(diff[:, :, [0, 2]], axis=-1)
    yel = np.min(diff[:, :, :2], axis=-1)

    luminance = luminance + red_mask * (c * cw + (diff[:, :, 1] - c) * gw + (diff[:, :, 2] - c) * bw) \
                + green_mask * (m * mw + (diff[:, :, 0] - m) * rw + (diff[:, :, 2] - m) * bw)  \
                + blue_mask * (yel * yw + (diff[:, :, 0] - yel) * rw + (diff[:, :, 1] - yel) * gw)

    return np.clip(luminance, 0, 255).astype(np.uint8)

input_img = scipy.misc.imread("palette.jpg")

weights = np.array([106, 65, 17, 17, 104, 19])
bw_image = black_and_white_adjustment(input_img, weights)

plt.figure(figsize=(15, 20))
plt.imshow(bw_image, cmap="gray")

此代码使用 vect 操作,速度更快。

【讨论】:

    猜你喜欢
    • 2019-11-14
    • 2012-08-23
    • 1970-01-01
    • 2012-03-31
    • 2011-05-23
    • 1970-01-01
    • 2018-04-11
    • 2010-09-15
    • 1970-01-01
    相关资源
    最近更新 更多