How to use color science and OpenCV in Python

Question:

Here is my code using Colour to do color calibration. It uses numpy float64 type but how can I convert back to the format that is compatible in openCV, ideally uint8 because Canny only works with uint8?

import colour
import numpy as np
import cv2 

IMAGE = cv2.imread('/Users/kelsolaar/Downloads/EKcv1.jpeg')
IMAGE = cv2.cvtColor(IMAGE, cv2.COLOR_BGR2RGB)/255

# 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))

# 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],
    ]
)
corrected = colour.colour_correction(IMAGE, REFERENCE_RGB, TEST_RGB)
colour.plotting.plot_image(
    corrected
)

This is some ways I found on stackoverflow but the image in uint8 doesn’t look like the corrected image

#Method 1 which works but not uint8.....
img = cv2.cvtColor(corrected.astype(np.float32), cv2.COLOR_RGB2BGR)

# When I convert to unint8, it doesn't look like the original corrected image
# Method 2
corrected *= 255
corrected = corrected.astype(np.uint8)
img = cv2.cvtColor(corrected, cv2.COLOR_RGB2BGR)
# Method 3
img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
img = img.astype(np.uint8)*255
Asked By: user16971617

||

Answers:

We use OpenCV in colour-checker-detection which uses colour-science under the hood. Here is our dedicated 8-bit conversion function pasted in its entirety:

def as_8_bit_BGR_image(image: ArrayLike) -> NDArray:
    """
    Convert and encodes given linear float *RGB* image to 8-bit *BGR* with
    *sRGB* reverse OETF.
    Parameters
    ----------
    image
        Image to convert.
    Returns
    -------
    :class:`numpy.ndarray`
        Converted image.
    Notes
    -----
    -   In the eventuality where the image is already an integer array, the
        conversion is by-passed.
    Examples
    --------
    >>> from colour.algebra import random_triplet_generator
    >>> prng = np.random.RandomState(4)
    >>> image = list(random_triplet_generator(8, random_state=prng))
    >>> image = np.reshape(image, [4, 2, 3])
    >>> print(image)
    [[[ 0.96702984  0.25298236  0.0089861 ]
      [ 0.54723225  0.43479153  0.38657128]]
    <BLANKLINE>
     [[ 0.97268436  0.77938292  0.04416006]
      [ 0.71481599  0.19768507  0.95665297]]
    <BLANKLINE>
     [[ 0.69772882  0.86299324  0.43614665]
      [ 0.2160895   0.98340068  0.94897731]]
    <BLANKLINE>
     [[ 0.97627445  0.16384224  0.78630599]
      [ 0.00623026  0.59733394  0.8662893 ]]]
    >>> image = as_8_bit_BGR_image(image)
    >>> image = as_8_bit_BGR_image(image)
    >>> print(image)
    [[[ 23 137 251]
      [167 176 195]]
    <BLANKLINE>
     [[ 59 228 251]
      [250 122 219]]
    <BLANKLINE>
     [[176 238 217]
      [249 253 128]]
    <BLANKLINE>
     [[229 112 252]
      [239 203  18]]]
    >>> as_8_bit_BGR_image(image)
    >>> as_8_bit_BGR_image(image)
    array([[[ 23, 137, 251],
            [167, 176, 195]],
    <BLANKLINE>
           [[ 59, 228, 251],
            [250, 122, 219]],
    <BLANKLINE>
           [[176, 238, 217],
            [249, 253, 128]],
    <BLANKLINE>
           [[229, 112, 252],
            [239, 203,  18]]], dtype=uint8)
    """

    image = np.asarray(image)[..., :3]

    if image.dtype == np.uint8:
        return image

    return cv2.cvtColor(
        cast(NDArray, cctf_encoding(image) * 255).astype(np.uint8),
        cv2.COLOR_RGB2BGR,
    )

Something worth noting is that in your code above, you apply the correction matrix on IMAGE which is still non-linearly encoded. You should linearise it with the colour.cctf_decoding definition before applying the correction then re-encode it for display with the colour.cctf_encoding definition.

Answered By: Kel Solaar

One more step I need to do after applying as_8_bit_BGR_image(img) is to remove those pixels that are out of range.

def toOpenCVU8(img):
    out = img * 255
    out[out < 0] = 0
    out[out > 255] = 255
    out = cv2.cvtColor(out.astype(np.uint8), cv2.COLOR_RGB2BGR)
Answered By: user16971617
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.