这段 Python/numpy 代码如何实现 XYZ->rgb 颜色 space 变换的白色缩放?

How does this Python/numpy code achieve white scaling for XYZ->rgb colour space transformation?

我正在阅读 this guide 关于将光谱转换为 rgb 颜色坐标的内容。

我基本上明白代数是做什么的,但是作者并没有真正解释处理白点的代数,我看不懂Python/numpy完成这项工作的代码

import numpy as np

class ColourSystem:

    def __init__(self, red, green, blue, white):

        self.red, self.green, self.blue = red, green, blue
        self.white = white

        # The chromaticity matrix (rgb -> xyz) and its inverse
        self.M = np.vstack((self.red, self.green, self.blue)).T 
        self.MI = np.linalg.inv(self.M)

        # White scaling array
        self.wscale = self.MI.dot(self.white)

        # xyz -> rgb transformation matrix
        self.T = self.MI / self.wscale[:, np.newaxis]

最后两行让我很困惑。我的解释是 self.white 是一个列向量,所以 self.MI.dot(self.white) 是一个矩阵向量乘法产生另一个列向量。

但在这种解释中,最后一行读起来像用向量除矩阵,这对我来说毫无意义。

通过修改 rgb->xyz 矩阵的逆来生成 xyz->rgb 矩阵的最后一行是做什么的?

抱歉回答晚了,这更像是对评论的回答,而不是对原始问题的回答,但是很长并且有一些代码,所以它不适合放在那里。让我重写一个独立的代码片段,它可以完成您所要求的相同事情(初始化编号取自您提供的 link):

import numpy as np
def xyz_from_xy(x, y):
    """Return the vector (x, y, 1-x-y)."""
    return np.array((x, y, 1-x-y))

red=xyz_from_xy(0.67, 0.33)
green=xyz_from_xy(0.21, 0.71)
blue=xyz_from_xy(0.15, 0.06)
white=xyz_from_xy(0.3127, 0.3291)

# The chromaticity matrix (rgb -> xyz) and its inverse
M = np.vstack((red, green, blue)).T 
MI = np.linalg.inv(M)
# White scaling array
wscale = MI.dot(white)
# xyz -> rgb transformation matrix
T = MI / wscale[:, np.newaxis]

写入 T 的值是以下矩阵:

array([[ 6.20584986, -1.71746142, -1.04788582],

[-2.71554014, 5.51336937, 0.09687197],

[ 0.19384968, -0.39357359, 2.9841102 ]])

现在,这是如何计算的以及如何涉及广播?

正如名字 "vstack" 所暗示的那样,M 只是将红色、绿色和蓝色向量一个堆叠在另一个上:

array([[ 6.70000000e-01, 2.10000000e-01, 1.50000000e-01],

[ 3.30000000e-01, 7.10000000e-01, 6.00000000e-02],

[-5.55111512e-17, 8.00000000e-02, 7.90000000e-01]])

(其中,根据函数 xyz_from_xy,最后一个分量始终只是 1 减去前两个分量之和)。 MI 是它的倒数:

array([[ 1.72809198, -0.47824736, -0.29179615],

[-0.81013052, 1.64481044, 0.02889994],

[ 0.08203853, -0.16656308, 1.26289621]])

而且,正如您正确提到的,wscale 只是一个矢量(一维),作为 MIwhite=array([0.3127, 0.3291, 0.3582]):

的标量积获得

array([0.27846178, 0.29833126, 0.42320696])

现在开始广播。首先,wscale[:, np.newaxis] 与 wscale 是一回事,但用形状 (3,1):

重塑了

array([[0.27846178],

[0.29833126],

[0.42320696]])

(写成wscale.reshape(3,1)也可以达到同样的效果)。当您在 MI(形状为 (3,3))和另一个对象(形状为 (3,1))之间进行算术运算时,无论是求和、乘法、除法,甚至是一些看起来更奇怪的东西**,它只是在元素方面这样做。但是由于维度不同,它尝试 "broadcast",即通过多次重复相同的向量,使每个等于 1 的维度等于另一个对象中的相应维度(在本例中为 3) .明确地说,您将 MI 按元素除以通过水平重复 wscale 三次获得的矩阵。您将通过以下方式以更详细的方式获得相同的结果:

T = MI / np.hstack((wscale.reshape(3,1), wscale.reshape(3,1), wscale.reshape(3,1)))

如果你想像点积一样进行矩阵运算,你需要显式地写 MI.dot(wscale[:, np.newaxis])(在这种情况下会 return 一个 (3,1) 形张量)。但这不是完成的:T = MI / wscale[:, np.newaxis] 的结果是:

array([[ 6.20584986, -1.71746142, -1.04788582],

[-2.71554014, 5.51336937, 0.09687197],

[ 0.19384968, -0.39357359, 2.9841102 ]])

这只是将 MI 的任意列按元素除以 wscale 的结果。例如,看第一个,如果我们做 MI[:,0] / wscale 我们确实得到:

array([ 6.20584986, -2.71554014, 0.19384968])

即第一列。