无循环的3x3矩阵转换(RGB颜色转换)

3 投票
2 回答
3118 浏览
提问于 2025-04-17 05:38

我有一张RGB图像,通过PIL加载到了一个numpy数组里。这个数组的形状是行数 x 列数 x 3。经过一番折腾,我写出了以下代码。我想学习如何在不使用循环的情况下进行这样的数组或矩阵操作。

# Note using matrix not array.
rgb_to_ycc = np.matrix(
     (0.2990,  0.5870,  0.1140,
    -0.1687, -0.3313,  0.5000,
     0.5000, -0.4187, -0.0813,)
).reshape( 3,3 )

ycc_to_rgb = np.matrix(
    ( 1.0, 0.0, 1.4022,
      1.0, -0.3456, -0.7145,
      1.0, 1.7710, 0, )
).reshape( 3, 3 )

def convert_ycc_to_rgb( ycc ) :
    # convert back to RGB
    rgb = np.zeros_like( ycc )
    for row in range(ycc.shape[0]) :
        rgb[row] = ycc[row] * ycc_to_rgb.T
    return rgb

def convert_rgb_to_ycc( rgb ) :
    ycc = np.zeros_like( rgb )
    for row in range(rgb.shape[0]):
        ycc[row] = rgb[row] * rgb_to_ycc.T
    return ycc

我可以使用colormath库(通过使用Python转换颜色格式?),但我现在是想通过这个练习来学习numpy。

前面提到的Colormath库使用了点积运算。

# Perform the adaptation via matrix multiplication.
result_matrix = numpy.dot(var_matrix, rgb_matrix)

我的数学水平不太够。np.dot()是我最好的选择吗?

补充说明:在深入阅读colormath的apply_RGB_matrix()-color_conversions.py后,我发现如果我的转换3x3矩阵不是矩阵,np.dot()就可以工作。这有点奇怪。

def convert_rgb_to_ycc( rgb ) :
    return np.dot( rgb, np.asarray( rgb_to_ycc ).T )

2 个回答

3
def convert_ycc_to_rgb(ycc):
    return ycc * ycc_to_rgb.T

def convert_rgb_to_ycc(rgb):
    return rgb * rgb_to_ycc.T

就这么简单,记住矩阵乘法是如何通过行和列的内积来定义的。

补充说明:

我之前假设rgb和ycc矩阵只是一个行数等于像素数,列数等于颜色分量的矩阵。所以我们首先需要把它们重新调整成一个 (行数*列数, 3) 的形状,然后再调整回 (行数, 列数, 3) 的形状。

所以最终的代码是:

def convert_ycc_to_rgb(ycc):
    shape = ycc.shape
    return np.array(ycc.reshape(-1,3) * ycc_to_rgb.T).reshape(shape)

def convert_rgb_to_ycc(rgb):
    shape = rgb.shape
    return np.array(rgb.reshape(-1,3) * rgb_to_ycc.T).reshape(shape)
4

我不太确定你用来将RGB转换为YCC的公式,所以我不想说这是完整的计算。不过,为了简化你发布的函数,没错,应该用np.dot和numpy数组,而不是numpy矩阵。

np.dot比用*来处理numpy矩阵更通用。当你用*处理numpy矩阵时,这两个矩阵必须是二维的。但是np.dot可以处理不同形状的数组,这对你的应用很重要,因为rgb是三维的(比如形状是(1470, 2105, 3))。

关于np.dot的文档说:

    For N dimensions it is a sum product over the last axis of `a` and
    the second-to-last of `b`::

        dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])

这是一种常规矩阵乘法的推广。


我建议把你的最终函数命名为rgb_to_ycc,而不是把这个名字给常量矩阵。(这样更简短,也能准确表达你想让这个函数做什么。)

所以下面的rgb_to_ycc是我建议的函数,我做了一些小修改,让convert_rgb_to_ycc不会抛出异常,并执行我认为你想要的计算。

最后一行np.allclose(...)显示这两个函数返回的结果是一样的。

import numpy as np

def rgb_to_ycc(rgb):
    M = np.array(
         (0.2990,  0.5870,  0.1140,
        -0.1687, -0.3313,  0.5000,
         0.5000, -0.4187, -0.0813,)
        ).reshape( 3,3 )
    return np.dot(rgb, M.T)

def convert_rgb_to_ycc( rgb ) :
    M = np.matrix(
         (0.2990,  0.5870,  0.1140,
        -0.1687, -0.3313,  0.5000,
         0.5000, -0.4187, -0.0813,)
        ).reshape( 3,3 )
    shape=rgb.shape
    rgb=rgb.reshape((-1,3))
    ycc = np.zeros_like( rgb )
    for i in range(len(rgb)):
        ycc[i] = rgb[i] * M.T
    return ycc.reshape(shape)

rgb=np.random.random((100,100,3))
assert np.allclose(rgb_to_ycc(rgb),convert_rgb_to_ycc(rgb))

撰写回答