RGB色彩校正

时间:2018-02-15 09:42:19

标签: python image colors rgb

我试图通过将RGB值校准到具有相同图像会话的24个值的颜色检查器的图像来对我的采集图像进行颜色校正。

我的想法是使用真实已知的相应值将最佳多项式函数拟合到24个颜色检查点(平均值)的RGB值。我正在做GridSearch以找到最佳度数,然后使用该函数来校正我的图像的RGB值。

# This are the known values of the color checkers (ground truth)
vector = [[115,82,68],[194,150,130],[98,122,157],[87,108,67],[133,128,177],[103,189,170],[214,126,44],[80,91,166],[193,90,99],[94,60,108],[157,188,64],[224,163,46],[56,61,150],[70,148,73],[175,54,60],[231,199,31],[187,86,149],[8,133,161],[243,243,242],[200,200,200],[160,160,160],[122,122,121],[85,85,85],[52,52,52]]
# This are the average RGB values after image acquisition of the color checker plate
X = [[42,20,31],[147,67,119],[40,47,142],[36,41,36],[76,53,158],[49,107,165],[162,47,33],[30,34,168],[167,28,66],[32,16,62],[101,121,74],[172,80,38],[17,26,164],[35,92,74],[168,20,32],[165,158,61],[165,37,146],[25,72,174],[176,175,179],[173,172,176],[137,121,173],[71,63,126],[33,31,63],[15,14,28]]

如果我绘制数据,它看起来像这样: enter image description here

在GridSearch之后,2度应该是最佳的

({'linearregression__normalize':True,'polynomialfeatures__degree':2,'linearregression__fit_intercept':True})

from sklearn.grid_search import GridSearchCV
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline

def PolynomialRegression(degree=2, **kwargs):
    return make_pipeline(PolynomialFeatures(degree),
                         LinearRegression(**kwargs))

param_grid = {'polynomialfeatures__degree': np.arange(21),
              'linearregression__fit_intercept': [True, False],
              'linearregression__normalize': [True, False]}

grid = GridSearchCV(PolynomialRegression(), param_grid, cv=7)
grid.fit(X, vector)
print grid.best_params_

如果我尝试更正颜色检查器图像的一部分,我没有得到预期的RGB值:

import cv2
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

example = cv2.imread('cc_part12.png')
poly = PolynomialFeatures(degree=2)
X_ = poly.fit_transform(X)
clf = linear_model.LinearRegression()
t = clf.fit(X_, vector)

img = example.copy()
height, width, depth = img.shape
print height, width, img.shape
for i in range(0, height):
    for j in range(0, (width)):
        predict_ = poly.fit_transform(example[i, j])
        img[i,j] = clf.predict(predict_)[0]
cv2.imwrite('out.png', img)

测试图像: Color checker test

输出图片: Output after prediction

左边矩形的平均RGB值应该是[115,82,68],而右边的矩形[194,150,130]应该相差很多〜[78,69,88]和〜[151,135,174]。< / p>

任何建议都会非常感激,我也想知道如何改进预测。迭代每个像素效率不高。

修改

正如Kel Solaar建议的那样,我试图通过使用oetf_reverse_sRGB将地面实况RGB和颜色检查器图像的RGB值转换为线性空间:

# First I scale the RGB values from 0 to 1 
a = []
for i in X:
    a.append((i[0]/255, i[1]/255, i[2]/255))
b = []
for j in y:
    b.append((j[0]/255, j[1]/255, j[2]/255))

# Convert to linear scale
z = oetf_reverse_sRGB(a)
q = oetf_reverse_sRGB(b)

然后我应用了颜色拟合函数:

c = first_order_colour_fit(z, q)

这给了我3x3颜色拟合矩阵:

[[ 0.68864569 -0.21360123  0.0493316 ]
[-0.03219473  0.48606749 -0.02698569]
[-0.05330554  0.01785467  0.79400138]]

如果我理解正确,我必须将我的图像的RGB值乘以:

R2 = R*0.68864569
G2 = G*0.48606749
B2 = B*0.79400138

我像以前一样为我的颜色检查器图像的一小部分做了这个,不幸的是它给了我这个输出: enter image description here

第一个方框的平均值是(30,10,21),第二个方格的平均值是(103,33,87),远离预期值(115,82.68)和(194,150,130)。

也许我误会了什么?

2 个答案:

答案 0 :(得分:1)

我前段时间为Basler相机做过校准算法。 基本上相机提供了一些可以调整的参数,例如RGB,alpha,曝光,对比度,但如果您使用的相机无法校准,您也可以在计算机中更正图像。

我的经验是,要微调颜色检查器,您需要的不仅仅是三个RGB值。

我最终做的是梯度下降算法。

基本上从一个足够好的点开始。

然后通过修改单个参数或它们的组合来生成子项。

带上最好的孩子并重复直到收敛。

最后我还校准了白色,以确保纯白色没有色彩偏移。

此外,您的图像嘈杂且肮脏,因此要分析当前颜色,需要几个像素(数百个像素)并删除异常值。最后将中位数作为当前颜色。这样您就可以尽可能多地过滤图像的噪点。

答案 1 :(得分:1)

您的地面实况颜色再现图表的值是使用sRGB光电转换函数(OETF)进行非线性编码的,即它们位于伽玛空间中。在执行线性回归和线性代数时,这可能会妨碍你。

Colour执行如下的一阶颜色拟合,即多元线性回归:

def first_order_colour_fit(m_1, m_2):
    """
    Performs a first order colour fit from given :math:`m_1` colour array to
    :math:`m_2` colour array. The resulting colour fitting matrix is computed
    using multiple linear regression.

    The purpose of that object is for example the matching of two
    *ColorChecker* colour rendition charts together.

    Parameters
    ----------
    m_1 : array_like, (3, n)
        Test array :math:`m_1` to fit onto array :math:`m_2`.
    m_2 : array_like, (3, n)
        Reference array the array :math:`m_1` will be colour fitted against.

    Returns
    -------
    ndarray, (3, 3)
        Colour fitting matrix.

    Examples
    --------
    >>> m_1 = np.array(
    ...     [[0.17224810, 0.09170660, 0.06416938],
    ...      [0.49189645, 0.27802050, 0.21923399],
    ...      [0.10999751, 0.18658946, 0.29938611],
    ...      [0.11666120, 0.14327905, 0.05713804],
    ...      [0.18988879, 0.18227649, 0.36056247],
    ...      [0.12501329, 0.42223442, 0.37027445],
    ...      [0.64785606, 0.22396782, 0.03365194],
    ...      [0.06761093, 0.11076896, 0.39779139],
    ...      [0.49101797, 0.09448929, 0.11623839],
    ...      [0.11622386, 0.04425753, 0.14469986],
    ...      [0.36867946, 0.44545230, 0.06028681],
    ...      [0.61632937, 0.32323906, 0.02437089],
    ...      [0.03016472, 0.06153243, 0.29014596],
    ...      [0.11103655, 0.30553067, 0.08149137],
    ...      [0.41162190, 0.05816656, 0.04845934],
    ...      [0.73339206, 0.53075188, 0.02475212],
    ...      [0.47347718, 0.08834792, 0.30310315],
    ...      [0.00000000, 0.25187016, 0.35062450],
    ...      [0.76809639, 0.78486240, 0.77808297],
    ...      [0.53822392, 0.54307997, 0.54710883],
    ...      [0.35458526, 0.35318419, 0.35524431],
    ...      [0.17976704, 0.18000531, 0.17991488],
    ...      [0.09351417, 0.09510603, 0.09675027],
    ...      [0.03405071, 0.03295077, 0.03702047]]
    ... )
    >>> m_2 = np.array(
    ...     [[0.15579559, 0.09715755, 0.07514556],
    ...      [0.39113140, 0.25943419, 0.21266708],
    ...      [0.12824821, 0.18463570, 0.31508023],
    ...      [0.12028974, 0.13455659, 0.07408400],
    ...      [0.19368988, 0.21158946, 0.37955964],
    ...      [0.19957425, 0.36085439, 0.40678123],
    ...      [0.48896605, 0.20691688, 0.05816533],
    ...      [0.09775522, 0.16710693, 0.47147724],
    ...      [0.39358649, 0.12233400, 0.10526425],
    ...      [0.10780332, 0.07258529, 0.16151473],
    ...      [0.27502671, 0.34705454, 0.09728099],
    ...      [0.43980441, 0.26880559, 0.05430533],
    ...      [0.05887212, 0.11126272, 0.38552469],
    ...      [0.12705825, 0.25787860, 0.13566464],
    ...      [0.35612929, 0.07933258, 0.05118732],
    ...      [0.48131976, 0.42082843, 0.07120612],
    ...      [0.34665585, 0.15170714, 0.24969804],
    ...      [0.08261116, 0.24588716, 0.48707733],
    ...      [0.66054904, 0.65941137, 0.66376412],
    ...      [0.48051509, 0.47870296, 0.48230082],
    ...      [0.33045354, 0.32904184, 0.33228886],
    ...      [0.18001305, 0.17978567, 0.18004416],
    ...      [0.10283975, 0.10424680, 0.10384975],
    ...      [0.04742204, 0.04772203, 0.04914226]]
    ... )
    >>> first_order_colour_fit(m_1, m_2)  # doctest: +ELLIPSIS
    array([[ 0.6982266...,  0.0307162...,  0.1621042...],
           [ 0.0689349...,  0.6757961...,  0.1643038...],
           [-0.0631495...,  0.0921247...,  0.9713415...]])
    """

    return np.transpose(np.linalg.lstsq(m_1, m_2)[0])

sRGB OETF的反向如下:

def oetf_reverse_sRGB(V):
    """
    Defines the *sRGB* colourspace reverse opto-electronic transfer function
    (OETF / OECF).

    Parameters
    ----------
    V : numeric or array_like
        Electrical signal :math:`V`.

    Returns
    -------
    numeric or ndarray
        Corresponding *luminance* :math:`L` of the image.

    References
    ----------
    -   :cite:`InternationalElectrotechnicalCommission1999a`
    -   :cite:`InternationalTelecommunicationUnion2015i`

    Examples
    --------
    >>> oetf_reverse_sRGB(0.461356129500442)  # doctest: +ELLIPSIS
    0.1...
    """

    V = np.asarray(V)

    return as_numeric(
        np.where(V <= oetf_sRGB(0.0031308), V / 12.92, ((V + 0.055) / 1.055) **
                 2.4))