【问题标题】:Replicate Photoshop sRGB to LAB Conversion将 Photoshop sRGB 复制到 LAB 转换
【发布时间】:2017-07-22 08:34:27
【问题描述】:

我想要完成的任务是复制 Photoshop RGB 到 LAB 的转换。
为简单起见,我将描述我为仅提取 L 通道所做的工作。

提取 Photoshop 的 L 通道

这是包含所有 RGB 颜色的 RGB 图片(请点击下载):

为了提取 Photoshop 的 LAB,我做了以下操作:

  1. 已将图像加载到 Photoshop 中。
  2. 将模式设置为 LAB。
  3. 在通道面板中选择了 L 通道。
  4. 将模式设置为灰度。
  5. 将模式设置为 RGB。
  6. 另存为 PNG。

这是 Photoshop 的 L 通道(这正是在 LAB 模式下选择 L 通道时在屏幕上看到的内容):

sRGB 到 LAB 转换

我的主要参考是Bruce Lindbloom great site
还知道Photoshop is using D50 White Point in its LAB Mode(另见Wikipedia's LAB Color Space Page)。

假设 RGB 图像为 sRGB 格式,则转换为:

sRGB -> XYZ (White Point D65) -> XYZ (White Point D50) -> LAB

假设数据在 [0, 1] 范围内的浮点数中,阶段由下式给出:

  1. Transform sRGB into XYZ.
    转换矩阵由RGB -> XYZ Matrix 给出(参见 sRGB D65)。
  2. 从 XYZ D65 转换为 XYZ D50
    使用Chromatic Adaptation Matrix 完成转换。由于上一步是矩阵乘法,它们可以组合成一个矩阵,从 sRGB -> XYZ D50 开始(参见RGB to XYZ Matrix 的底部)。请注意,Photoshop 使用 Bradford 适应方法。
  3. 从 XYZ D50 转换为 LAB
    使用XYZ to LAB Steps 完成转换。

MATLAB 代码

因为,首先,我只是在 L 通道之后事情变得更简单一些。图像被加载到 MATLAB 并转换为 Float [0, 1] 范围。

这是代码:

%% Setting Enviorment Parameters

INPUT_IMAGE_RGB             = 'RgbColors.png';
INPUT_IMAGE_L_PHOTOSHOP     = 'RgbColorsL.png';


%% Loading Data

mImageRgb   = im2double(imread(INPUT_IMAGE_RGB));
mImageLPhotoshop     = im2double(imread(INPUT_IMAGE_L_PHOTOSHOP));
mImageLPhotoshop     = mImageLPhotoshop(:, :, 1); %<! All channels are identical


%% Convert to L Channel

mImageLMatlab = ConvertRgbToL(mImageRgb, 1);


%% Display Results
figure();
imshow(mImageLPhotoshop);
title('L Channel - Photoshop');

figure();
imshow(mImageLMatlab);
title('L Channel - MATLAB');

函数ConvertRgbToL()由以下给出:

function [ mLChannel ] = ConvertRgbToL( mRgbImage, sRgbMode )

OFF = 0;
ON  = 1;

RED_CHANNEL_IDX     = 1;
GREEN_CHANNEL_IDX   = 2;
BLUE_CHANNEL_IDX    = 3;

RGB_TO_Y_MAT = [0.2225045, 0.7168786, 0.0606169]; %<! D50

Y_CHANNEL_THR = 0.008856;

% sRGB Compensation
if(sRgbMode == ON)
    vLinIdx = mRgbImage < 0.04045;

    mRgbImage(vLinIdx)  = mRgbImage(vLinIdx) ./ 12.92;
    mRgbImage(~vLinIdx) = ((mRgbImage(~vLinIdx) + 0.055) ./ 1.055) .^ 2.4;
end

% RGB to XYZ (D50)
mY = (RGB_TO_Y_MAT(1) .* mRgbImage(:, :, RED_CHANNEL_IDX)) + (RGB_TO_Y_MAT(2) .* mRgbImage(:, :, GREEN_CHANNEL_IDX)) + (RGB_TO_Y_MAT(3) .* mRgbImage(:, :, BLUE_CHANNEL_IDX));

vYThrIdx = mY > Y_CHANNEL_THR;

mY3 = mY .^ (1 / 3);

mLChannel = ((vYThrIdx .* (116 * mY3 - 16.0)) + ((~vYThrIdx) .* (903.3 * mY))) ./ 100;


end

正如人们所看到的那样,结果是不同的。
Photoshop 对于大多数颜色来说要暗得多。

有人知道如何复制 Photoshop 的 LAB 转换吗?
任何人都可以发现此代码中的问题?

谢谢。

【问题讨论】:

  • 也许您关于 Photoshop 如何完成这项工作的参考资料不正确。由于它是专有软件,除了他们之外没有人可以回答这个问题。
  • 不清楚具体要遵循哪些步骤。我会尝试分阶段验证/比较,以找出结果不同的地方。例如,您确定灰度转换是相同的吗?当您说它是“浮点数”时,您确定它是双精度而不是单精度吗?我也只会查看文件/数据中的值,而不是您在屏幕上看到的值。并且你尝试过 Matlab 的rgb2lab 和类似的功能吗?
  • 另外,您的一些资料来源似乎已有几年历史了。我们不知道 Photoshop 是否可能在此期间在后台进行了更改。您使用的是什么版本的 Photoshop 和 Matlab?
  • MATLAB 内部 RGB ro LAB 和 MATLAB File Exchange 上的任何文件都在进行上述数学运算,而无需使用 D65 白点进行 sRGB Gamma 补偿。即使是带有 D50 White Point 的 MATLAB 也无法与 Photoshop 匹配。
  • 尝试反转我自己的代码来重写你的,你会得到你的结果

标签: matlab image-processing colors photoshop color-space


【解决方案1】:

Photoshop 中色彩空间之间的所有转换都是通过 CMM 进行的,这在大约 2000 年的硬件上足够快,但不太准确。如果您检查“循环” - RGB -> Lab -> RGB,Adobe CMM 可能会出现很多 4 位错误和一些 7 位错误。这可能会导致后代化。我的转换总是基于公式,而不是 CMM。然而,Adobe CMM 和 Argyll CMM 的平均误差 deltaE 是完全可以接受的。

Lab 转换与 RGB 非常相似,只是在第一步应用了非线性 (gamma);像这样:

  1. 将 XYZ 归一化为白点

  2. 将结果带到 gamma 3(保持阴影部分线性,取决于实现)

  3. 将结果乘以 [0 116 0 -16; 500 -500 0 0; 0 200 -200 0]'

【讨论】:

    【解决方案2】:

    最新答案(我们现在知道错了,等待正确答案)

    Photoshop 是一个非常古老且杂乱无章的软件。当您执行从一种模式到另一种模式的转换时,没有明确的文档说明为什么像素值会发生这种或那种情况。

    出现问题是因为当您在 Adob​​e Photoshop 中将选定的 L* 通道转换为灰度时,伽玛值发生了变化。原生地,转换使用 1.74 的伽马进行单通道到灰度转换。不要问我为什么,我猜这与旧的激光打印机有关(?)。

    无论如何,这是我发现的最好的方法:

    打开你的文件,把它调到 LAB 模式,只选择 L 通道

    然后转到:

    编辑 > 转换为个人资料

    您将选择“自定义 gamma”并输入值 2.0(不要问我为什么 2.0 效果更好,我不知道 Adob​​e 的软件制造商在想什么......) 此操作会将您的图片变成只有一个通道的灰度图片

    然后就可以转成RGB模式了。

    如果您将结果与您的结果进行比较,您会发现差异高达 4 点 % - 全部位于最暗的区域。

    我怀疑这是因为伽马曲线应用程序不适用于暗值中的 LAB 模式(参见您知道的,所有低于 0.008856 的 XYZ 值在 LAB 中都是线性的)

    结论:

    据我所知,Adobe Photoshop 中没有正确的实现方式来将 L 通道从 LAB 模式提取到灰度模式!

    上一个答案

    这是我用自己的方法得到的结果:

    它似乎与 Adob​​e Photoshop 的结果完全相同。

    我不确定您那边出了什么问题,因为您描述的步骤与我遵循的步骤完全相同,并且我会建议您遵循。我没有Matlab所以我用python:

    import cv2, Syn
    
    # your file
    fn = "EASA2.png"
    
    #reading the file
    im = cv2.imread(fn,-1)
    
    #openCV works in BGR, i'm switching to RGB
    im = im[:,:,::-1]
    
    #conversion to XYZ
    XYZ = Syn.sRGB2XYZ(im)
    
    #white points D65 and D50
    WP_D65 = Syn.Yxy2XYZ((100,0.31271, 0.32902))
    WP_D50 = Syn.Yxy2XYZ((100,0.34567, 0.35850))
    
    #bradford
    XYZ2 = Syn.bradford_adaptation(XYZ, WP_D65, WP_D50) 
    
    #conversion to L*a*b*
    LAB = Syn.XYZ2Lab(XYZ2, WP_D50)
    
    #picking the L channel only
    L = LAB[:,:,0] /100. * 255.
    
    #image output
    cv2.imwrite("result.png", L)
    

    Syn 库是我自己的东西,这里是函数(抱歉弄乱了):

    def sRGB2XYZ(sRGB):
    
        sRGB = np.array(sRGB)
        aShape = np.array([1,1,1]).shape
        anotherShape = np.array([[1,1,1],[1,1,1]]).shape
        origShape = sRGB.shape
    
        if sRGB.shape == aShape:
            sRGB = np.reshape(sRGB, (1,1,3))
    
        elif len(sRGB.shape) == len(anotherShape):
            h,d = sRGB.shape
            sRGB = np.reshape(sRGB, (1,h,d))
    
        w,h,d = sRGB.shape
    
        sRGB = np.reshape(sRGB, (w*h,d)).astype("float") / 255.
    
        m1 = sRGB[:,0] > 0.04045
        m1b = sRGB[:,0] <= 0.04045
        m2 = sRGB[:,1] > 0.04045
        m2b = sRGB[:,1] <= 0.04045
        m3 = sRGB[:,2] > 0.04045
        m3b = sRGB[:,2] <= 0.04045
    
        sRGB[:,0][m1] = ((sRGB[:,0][m1] + 0.055 ) / 1.055 ) ** 2.4
        sRGB[:,0][m1b] = sRGB[:,0][m1b] / 12.92
    
        sRGB[:,1][m2] = ((sRGB[:,1][m2] + 0.055 ) / 1.055 ) ** 2.4
        sRGB[:,1][m2b] = sRGB[:,1][m2b] / 12.92
    
        sRGB[:,2][m3] = ((sRGB[:,2][m3] + 0.055 ) / 1.055 ) ** 2.4
        sRGB[:,2][m3b] = sRGB[:,2][m3b] / 12.92
    
        sRGB *= 100. 
    
        X = sRGB[:,0] * 0.4124 + sRGB[:,1] * 0.3576 + sRGB[:,2] * 0.1805
        Y = sRGB[:,0] * 0.2126 + sRGB[:,1] * 0.7152 + sRGB[:,2] * 0.0722
        Z = sRGB[:,0] * 0.0193 + sRGB[:,1] * 0.1192 + sRGB[:,2] * 0.9505
    
        XYZ = np.zeros_like(sRGB)
    
        XYZ[:,0] = X
        XYZ[:,1] = Y
        XYZ[:,2] = Z
    
        XYZ = np.reshape(XYZ, origShape)
    
        return XYZ
    
    def Yxy2XYZ(Yxy):
    
        Yxy = np.array(Yxy)
        aShape = np.array([1,1,1]).shape
        anotherShape = np.array([[1,1,1],[1,1,1]]).shape
        origShape = Yxy.shape
    
        if Yxy.shape == aShape:
            Yxy = np.reshape(Yxy, (1,1,3))
    
        elif len(Yxy.shape) == len(anotherShape):
            h,d = Yxy.shape
            Yxy = np.reshape(Yxy, (1,h,d))
    
        w,h,d = Yxy.shape
    
        Yxy = np.reshape(Yxy, (w*h,d)).astype("float")
    
        XYZ = np.zeros_like(Yxy)
    
        XYZ[:,0] = Yxy[:,1] * ( Yxy[:,0] / Yxy[:,2] )
        XYZ[:,1] = Yxy[:,0]
        XYZ[:,2] = ( 1 - Yxy[:,1] - Yxy[:,2] ) * ( Yxy[:,0] / Yxy[:,2] )
    
        return np.reshape(XYZ, origShape)
    
    def bradford_adaptation(XYZ, Neutral_source, Neutral_destination):
        """should be checked if it works properly, but it seems OK"""
    
        XYZ = np.array(XYZ)
        ashape = np.array([1,1,1]).shape
        siVal = False
    
        if XYZ.shape == ashape:
    
    
            XYZ = np.reshape(XYZ, (1,1,3))
            siVal = True
    
    
        bradford = np.array(((0.8951000, 0.2664000, -0.1614000),
                              (-0.750200, 1.7135000,  0.0367000),
                              (0.0389000, -0.068500,  1.0296000)))
    
        inv_bradford = np.array(((0.9869929, -0.1470543, 0.1599627),
                                  (0.4323053,  0.5183603, 0.0492912),
                                  (-.0085287,  0.0400428, 0.9684867)))
    
        Xs,Ys,Zs = Neutral_source
        s = np.array(((Xs),
                       (Ys),
                       (Zs)))
    
        Xd,Yd,Zd = Neutral_destination
        d = np.array(((Xd),
                       (Yd),
                       (Zd)))
    
    
        source = np.dot(bradford, s)
        Us,Vs,Ws = source[0], source[1], source[2]
    
        destination = np.dot(bradford, d)
        Ud,Vd,Wd = destination[0], destination[1], destination[2]
    
        transformation = np.array(((Ud/Us, 0, 0),
                                    (0, Vd/Vs, 0),
                                    (0, 0, Wd/Ws)))
    
        M = np.mat(inv_bradford)*np.mat(transformation)*np.mat(bradford)
    
        w,h,d = XYZ.shape
        result = np.dot(M,np.rot90(np.reshape(XYZ, (w*h,d)),-1))
        result = np.rot90(result, 1)
        result = np.reshape(np.array(result), (w,h,d))
    
        if siVal == False:
            return result
        else:
            return result[0,0]
    
    def XYZ2Lab(XYZ, neutral):
        """transforms XYZ to CIE Lab
        Neutral should be normalized to Y = 100"""
    
        XYZ = np.array(XYZ)
        aShape = np.array([1,1,1]).shape
        anotherShape = np.array([[1,1,1],[1,1,1]]).shape
        origShape = XYZ.shape
    
        if XYZ.shape == aShape:
            XYZ = np.reshape(XYZ, (1,1,3))
    
        elif len(XYZ.shape) == len(anotherShape):
            h,d = XYZ.shape
            XYZ = np.reshape(XYZ, (1,h,d))
    
        N_x, N_y, N_z = neutral
        w,h,d = XYZ.shape
    
        XYZ = np.reshape(XYZ, (w*h,d)).astype("float")
    
        XYZ[:,0] = XYZ[:,0]/N_x
        XYZ[:,1] = XYZ[:,1]/N_y
        XYZ[:,2] = XYZ[:,2]/N_z
    
        m1 = XYZ[:,0] > 0.008856
        m1b = XYZ[:,0] <= 0.008856
        m2 = XYZ[:,1] > 0.008856 
        m2b = XYZ[:,1] <= 0.008856
        m3 = XYZ[:,2] > 0.008856
        m3b = XYZ[:,2] <= 0.008856
    
        XYZ[:,0][m1] = XYZ[:,0][XYZ[:,0] > 0.008856] ** (1/3.0)
        XYZ[:,0][m1b] = ( 7.787 * XYZ[:,0][m1b] ) + ( 16 / 116.0 )
    
        XYZ[:,1][m2] = XYZ[:,1][XYZ[:,1] > 0.008856] ** (1/3.0)
        XYZ[:,1][m2b] = ( 7.787 * XYZ[:,1][m2b] ) + ( 16 / 116.0 )
    
        XYZ[:,2][m3] = XYZ[:,2][XYZ[:,2] > 0.008856] ** (1/3.0)
        XYZ[:,2][m3b] = ( 7.787 * XYZ[:,2][m3b] ) + ( 16 / 116.0 )
    
        Lab = np.zeros_like(XYZ)
    
        Lab[:,0] = (116. * XYZ[:,1] ) - 16.
        Lab[:,1] = 500. * ( XYZ[:,0] - XYZ[:,1] )
        Lab[:,2] = 200. * ( XYZ[:,1] - XYZ[:,2] )
    
        return np.reshape(Lab, origShape)
    

    【讨论】:

    • 看来你的结果和我的一样。比 Photoshop 的暗一点。
    • 我使用 Photoshop CS6。我完全按照您的描述进行:your_pic > LAB > L_channel > grayscale > RGB。然后我把这张图片作为我的一个图层,并把混合模式“差异”:我得到一张黑色的图片,零差异。
    • 我想我明白这里发生了什么。我比较了 MATLAB 中的值。既然我在Windows中有Display ICC,难道Photoshop写的PNG有不同的值?我的屏幕配置文件是否会更改 Photoshop 写入 PNG 的值?
    • 让我改写一下,假设我加载了 sRGB(全彩色图像)的 PNG,将其转换为 LAB 并执行所有过程,然后将其保存为 PNG。在不同的显示配置文件的情况下,该 PNG 的值会有所不同吗?
    • 嗯,不完全是。当两者都加载到屏幕上的 Photoshop 时,它们是相同的。但如果我执行以下操作:mA = im2double(imread('40qEJq4.jpg')); mB = im2double(imread('K7ftH.png')); mE = mA - mB; figure(); plot(mE(:)); 这就是结果 - i.imgur.com/oaN8FwA.png。您可以看到最多有 10% 的差异。你再次得到我所做的结果。在 Photoshop 上它们是相同的(你让我注意到了),但是编码为 PNG 的值是不同的。因此,我想知道是否拥有屏幕配置文件可能会更改写入 PNG 文件的值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-27
    • 2016-04-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多