使用最小二乘法进行色彩校正

3

我曾尝试使用最小二乘法来对一张图像进行颜色校正。但我不明白为什么它不能生效,因为这应该是标准的颜色校准方式。

首先,我将上述图像以CR3格式导入,转换为RGB空间,然后使用OpenCV boundingRect和inRange函数裁剪出四个颜色块,并将这四个块保存在名为coloursRect的数组中。vstack函数用于使存储每个像素颜色的数组从3D变为2D。例如,colour0存储“红色块”每个像素的RGB值。

colour0 = np.vstack(coloursRect[0])
colour1 = np.vstack(coloursRect[1])
colour2 = np.vstack(coloursRect[2])
colour3 = np.vstack(coloursRect[3])

lstsq_a = np.array(np.vstack((colour0,colour1,colour2,colour3)))

然后,我声明RGB中的原始参考颜色。

r_ref = [240,0,22]
y_ref = [252,222,10]
g_ref = [30,187,22]
b_ref = [26,0,165]
ref_patches = [r_ref,y_ref,g_ref, b_ref]

每个参考颜色的数量根据实际图片颜色块中的像素数量进行乘法运算,例如,r_ref将乘以colour0数组的长度。(我知道这是一种不好的数据操作方式,但从理论上讲应该可以工作)
lstsq_b_0to255 = np.array(np.vstack(([ref_patches[0]]*colour0.shape[0],[ref_patches[1]]*colour1.shape[0],[ref_patches[2]]*colour2.shape[0],[ref_patches[3]]*colour3.shape[0]))) 

计算最小二乘法,然后与图像相乘。

lstsq_x_0to255 = np.linalg.lstsq(lstsq_a, lstsq_b_0to255)[0]


img_shape = img.shape
img_s = img.reshape((-1, 3))
img_corr_s = img_s @ lstsq_x_0to255
img_corr = img_corr_s.reshape(img_shape).astype('uint8')

然而,这种颜色校正方法无效,图像中的颜色是不正确的。请问问题出在哪里?

编辑:使用RGB而非HSV作为参考颜色。


仅仅一瞥之下:很明显卡片上的蓝色和绿色被正确转换了,但是红色被更正为黄色,黄色被更正为青色。你确定你的测试区域与参考颜色的顺序相同吗? - nick
是的,我也怀疑过,但它们与相应的BGR值的顺序是正确的。 - Chloe
首先:您确定您正在正确转换参考值吗?为什么它们以HSV形式给出?HSV不是一种色度学颜色空间,就参考而言没有任何意义,即没有已知的色域,也没有已知的白点。 - Kel Solaar
此外,您的图像使用非常特殊的ICC配置文件,即Color LCD,因此您需要将采样值转换为与参考值相同的RGB空间。 - Kel Solaar
@KelSolaar 参考值是以CMYK给出的,我现在正在将它们转换为RGB。在取样值之前,我还将图像转换为RGB。但结果仍然相同。 - Chloe
1个回答

4

忽略图像ICC配置文件未正确解码的事实,根据您参考的RGB值并使用Colour,这是期望的结果:

import colour
import numpy as np


# Reference values a likely non-linear 8-bit sRGB values.
# "colour.cctf_decoding" uses the sRGB EOTF by default.
REFERENCE_RGB = colour.cctf_decoding(
    np.array(
        [
            [240, 0, 22],
            [252, 222, 10],
            [30, 187, 22],
            [26, 0, 165],
        ]
    )
    / 255
)

colour.plotting.plot_multi_colour_swatches(colour.cctf_encoding(REFERENCE_RGB))

IMAGE = colour.cctf_decoding(colour.read_image("/Users/kelsolaar/Downloads/EKcv1.jpeg"))

# Measured test values, the image is not properly decoded as it has a very specific ICC profile.
TEST_RGB = np.array(
    [
        [0.578, 0.0, 0.144],
        [0.895, 0.460, 0.0],
        [0.0, 0.183, 0.074],
        [0.067, 0.010, 0.070],
    ]
)

colour.plotting.plot_image(
    colour.cctf_encoding(colour.colour_correction(IMAGE, REFERENCE_RGB, TEST_RGB))
)

参考样品 色彩校正

该模块中提供的主要功能 如下:

def least_square_mapping_MoorePenrose(y: ArrayLike, x: ArrayLike) -> NDArray:
    """
    Compute the *least-squares* mapping from dependent variable :math:`y` to
    independent variable :math:`x` using *Moore-Penrose* inverse.

    Parameters
    ----------
    y
        Dependent and already known :math:`y` variable.
    x
        Independent :math:`x` variable(s) values corresponding with :math:`y`
        variable.

    Returns
    -------
    :class:`numpy.ndarray`
        *Least-squares* mapping.

    References
    ----------
    :cite:`Finlayson2015`

    Examples
    --------
    >>> prng = np.random.RandomState(2)
    >>> y = prng.random_sample((24, 3))
    >>> x = y + (prng.random_sample((24, 3)) - 0.5) * 0.5
    >>> least_square_mapping_MoorePenrose(y, x)  # doctest: +ELLIPSIS
    array([[ 1.0526376...,  0.1378078..., -0.2276339...],
           [ 0.0739584...,  1.0293994..., -0.1060115...],
           [ 0.0572550..., -0.2052633...,  1.1015194...]])
    """

    y = np.atleast_2d(y)
    x = np.atleast_2d(x)

    return np.dot(np.transpose(x), np.linalg.pinv(np.transpose(y)))


def matrix_augmented_Cheung2004(
    RGB: ArrayLike,
    terms: Literal[3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22] = 3,
) -> NDArray:
    """
    Perform polynomial expansion of given *RGB* colourspace array using
    *Cheung et al. (2004)* method.

    Parameters
    ----------
    RGB
        *RGB* colourspace array to expand.
    terms
        Number of terms of the expanded polynomial.

    Returns
    -------
    :class:`numpy.ndarray`
        Expanded *RGB* colourspace array.

    Notes
    -----
    -   This definition combines the augmented matrices given in
        :cite:`Cheung2004` and :cite:`Westland2004`.

    References
    ----------
    :cite:`Cheung2004`, :cite:`Westland2004`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> matrix_augmented_Cheung2004(RGB, terms=5)  # doctest: +ELLIPSIS
    array([ 0.1722481...,  0.0917066...,  0.0641693...,  0.0010136...,  1...])
    """

    RGB = as_float_array(RGB)

    R, G, B = tsplit(RGB)
    tail = ones(R.shape)

    existing_terms = np.array([3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22])
    closest_terms = as_int(closest(existing_terms, terms))
    if closest_terms != terms:
        raise ValueError(
            f'"Cheung et al. (2004)" method does not define an augmented '
            f"matrix with {terms} terms, closest augmented matrix has "
            f"{closest_terms} terms!"
        )

    if terms == 3:
        return RGB
    elif terms == 5:
        return tstack(
            [
                R,
                G,
                B,
                R * G * B,
                tail,
            ]
        )
    elif terms == 7:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                tail,
            ]
        )
    elif terms == 8:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R * G * B,
                tail,
            ]
        )
    elif terms == 10:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                tail,
            ]
        )
    elif terms == 11:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                tail,
            ]
        )
    elif terms == 14:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                R**3,
                G**3,
                B**3,
                tail,
            ]
        )
    elif terms == 16:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                R**2 * G,
                G**2 * B,
                B**2 * R,
                R**3,
                G**3,
                B**3,
            ]
        )
    elif terms == 17:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                R**2 * G,
                G**2 * B,
                B**2 * R,
                R**3,
                G**3,
                B**3,
                tail,
            ]
        )
    elif terms == 19:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                R**2 * G,
                G**2 * B,
                B**2 * R,
                R**2 * B,
                G**2 * R,
                B**2 * G,
                R**3,
                G**3,
                B**3,
            ]
        )
    elif terms == 20:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                R**2 * G,
                G**2 * B,
                B**2 * R,
                R**2 * B,
                G**2 * R,
                B**2 * G,
                R**3,
                G**3,
                B**3,
                tail,
            ]
        )
    elif terms == 22:
        return tstack(
            [
                R,
                G,
                B,
                R * G,
                R * B,
                G * B,
                R**2,
                G**2,
                B**2,
                R * G * B,
                R**2 * G,
                G**2 * B,
                B**2 * R,
                R**2 * B,
                G**2 * R,
                B**2 * G,
                R**3,
                G**3,
                B**3,
                R**2 * G * B,
                R * G**2 * B,
                R * G * B**2,
            ]
        )


def matrix_colour_correction_Cheung2004(
    M_T: ArrayLike,
    M_R: ArrayLike,
    terms: Literal[3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22] = 3,
) -> NDArray:
    """
    Compute a colour correction matrix from given :math:`M_T` colour array to
    :math:`M_R` colour array using *Cheung et al. (2004)* method.

    Parameters
    ----------
    M_T
        Test array :math:`M_T` to fit onto array :math:`M_R`.
    M_R
        Reference array the array :math:`M_T` will be colour fitted against.
    terms
        Number of terms of the expanded polynomial.

    Returns
    -------
    :class:`numpy.ndarray`
        Colour correction matrix.

    References
    ----------
    :cite:`Cheung2004`, :cite:`Westland2004`

    Examples
    --------
    >>> prng = np.random.RandomState(2)
    >>> M_T = prng.random_sample((24, 3))
    >>> M_R = M_T + (prng.random_sample((24, 3)) - 0.5) * 0.5
    >>> matrix_colour_correction_Cheung2004(M_T, M_R)  # doctest: +ELLIPSIS
    array([[ 1.0526376...,  0.1378078..., -0.2276339...],
           [ 0.0739584...,  1.0293994..., -0.1060115...],
           [ 0.0572550..., -0.2052633...,  1.1015194...]])
    """

    return least_square_mapping_MoorePenrose(
        matrix_augmented_Cheung2004(M_T, terms), M_R
    )


def colour_correction_Cheung2004(
    RGB: ArrayLike,
    M_T: ArrayLike,
    M_R: ArrayLike,
    terms: Literal[3, 5, 7, 8, 10, 11, 14, 16, 17, 19, 20, 22] = 3,
) -> NDArray:
    """
    Perform colour correction of given *RGB* colourspace array using the
    colour correction matrix from given :math:`M_T` colour array to
    :math:`M_R` colour array using *Cheung et al. (2004)* method.

    Parameters
    ----------
    RGB
        *RGB* colourspace array to colour correct.
    M_T
        Test array :math:`M_T` to fit onto array :math:`M_R`.
    M_R
        Reference array the array :math:`M_T` will be colour fitted against.
    terms
        Number of terms of the expanded polynomial.

    Returns
    -------
    :class:`numpy.ndarray`
        Colour corrected *RGB* colourspace array.

    References
    ----------
    :cite:`Cheung2004`, :cite:`Westland2004`

    Examples
    --------
    >>> RGB = np.array([0.17224810, 0.09170660, 0.06416938])
    >>> prng = np.random.RandomState(2)
    >>> M_T = prng.random_sample((24, 3))
    >>> M_R = M_T + (prng.random_sample((24, 3)) - 0.5) * 0.5
    >>> colour_correction_Cheung2004(RGB, M_T, M_R)  # doctest: +ELLIPSIS
    array([ 0.1793456...,  0.1003392...,  0.0617218...])
    """

    RGB = as_float_array(RGB)
    shape = RGB.shape

    RGB = np.reshape(RGB, (-1, 3))

    RGB_e = matrix_augmented_Cheung2004(RGB, terms)

    CCM = matrix_colour_correction_Cheung2004(M_T, M_R, terms)

    return np.reshape(np.transpose(np.dot(CCM, np.transpose(RGB_e))), shape)

我建议直接使用Color,因为有多种方法可以根据训练集给出不同的结果。话虽如此,考虑到你只有4个色彩和没有无色色彩,我不会期望有很好的结果。对于这种校准需要的最小推荐图表是具有24个补丁的ColorChecker经典版。


1
谢谢您的回答,我发现在使用Colour提供的Vandermonde颜色校正方法并考虑白色补丁(假设参考值为[255,255,255])后,它的性能有了很大的提升。 - Chloe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接