What Is the Algorithm Behind Photoshop's “Black and White” Adjustment Layer?

若如初见. 提交于 2019-12-08 05:04:13

问题


I did lot's of research but I didn't find anything (but I also don't know what kind of keywords to search for exactly). I want to be able to convert an input RGB image to grayscale but I want to be able to add more or less Reds/Yellows/Greens/Cyans/Blues/Magentas like in Photoshop. Do you know what are the equation or where I can found these equations so that I can implemented my own optimized RGB to Grayscale conversion?

Edit: In Photoshop it is called Black/White adjustment layer. I have found something but actually it doesn't seem to work. Here is my implementation (in comments are the resources needed to understand the algorithm):

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')

Thank you


回答1:


Here's an attempt using PIL rather than numpy. It should be easy to convert. Without a copy of Photoshop to compare with, I can't guarantee it matches the output exactly but it does produce the exact values for the sample shown in your link. The values r_w, y_w, g_w, c_w, b_w, m_w are the weights to be applied to each color, with 1.0 equating to 100% in the corresponding Photoshop slider. Naturally they can also be negative.

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

Using this provided test image, here are some example results.

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])




回答2:


I answer my own question by adding the numpy/scipy version of the code, if it can be of any interest for anybody in the future. If you want to upvote an answer, you should upvote the answer of 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")

This code is faster as it uses vect operations.



来源:https://stackoverflow.com/questions/55185251/what-is-the-algorithm-behind-photoshops-black-and-white-adjustment-layer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!