Source code for colour.models.rgb.ycbcr

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Y'CbCr Colour Encoding
======================

Defines the *Y'CbCr* colour encoding related transformations:

-   :func:`RGB_to_YCbCr`
-   :func:`YCbCr_to_RGB`
-   :func:`RGB_to_YcCbcCrc`
-   :func:`YcCbcCrc_to_RGB`

Notes
-----
-   *Y'CbCr* is not an absolute colourspace.

See Also
--------
`YCbCr Colours Encoding Jupyter Notebook
<http://nbviewer.jupyter.org/github/colour-science/colour-notebooks/\
blob/master/notebooks/models/ycbcr.ipynb>`_

References
----------
.. [1]  Wikipedia. (n.d.). YCbCr. Retrieved February 29, 2016, from
        https://en.wikipedia.org/wiki/YCbCr
.. [2]  International Telecommunication Union. (2015). Recommendation
        ITU-R BT.709-6 - Parameter values for the HDTV standards for
        production and international programme exchange BT Series Broadcasting
        service (Vol. 5). Retrieved from https://www.itu.int/dms_pubrec/\
itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf
.. [3]  International Telecommunication Union. (2015). Recommendation
        ITU-R BT.2020 - Parameter values for ultra-high definition television
        systems for production and international programme exchange (Vol. 1).
        Retrieved from https://www.itu.int/dms_pubrec/\
itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf
.. [4]  Society of Motion Picture and Television Engineers. (1999).
        ANSI/SMPTE 240M-1995 - Signal Parameters - 1125-Line High-Definition
        Production Systems, 1–7. Retrieved from
        http://car.france3.mars.free.fr/\
HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf
.. [5]  International Telecommunication Union. (2011). Recommendation ITU-T
        T.871 - Information technology – Digital compression and coding of
        continuous-tone still images: JPEG File Interchange Format (JFIF).
        Retrieved from https://www.itu.int/rec/dologin_pub.asp?lang=e&\
id=T-REC-T.871-201105-I!!PDF-E&type=items
"""

from __future__ import division, unicode_literals

import numpy as np

from colour.utilities import CaseInsensitiveMapping, tsplit, tstack
from colour.models.rgb.transfer_functions import oetf_BT2020, eotf_BT2020

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2017 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Development'

__all__ = ['YCBCR_WEIGHTS',
           'RGB_range',
           'YCbCr_ranges',
           'RGB_to_YCbCr',
           'YCbCr_to_RGB',
           'RGB_to_YcCbcCrc',
           'YcCbcCrc_to_RGB']

YCBCR_WEIGHTS = CaseInsensitiveMapping(
    {'Rec. 601': np.array([0.2990, 0.1140]),
     'Rec. 709': np.array([0.2126, 0.0722]),
     'Rec. 2020': np.array([0.2627, 0.0593]),
     'SMPTE-240M': np.array([0.2122, 0.0865])})
"""
Luma weightings presets.

YCBCR_WEIGHTS : dict
    **{'Rec. 601', 'Rec. 709', 'Rec. 2020', 'SMPTE-240M}**
"""


[docs]def RGB_range(bits, is_legal, is_int): """" Returns the *RGB* range array for given bit depth, range legality and representation. Parameters ---------- bits : int Bit depth of the *RGB* output ranges array. is_legal : bool Whether the *RGB* output ranges array is legal. is_int : bool Whether the *RGB* output ranges array represents integer code values. Returns ------- ndarray *RGB* ranges array. Examples -------- >>> RGB_range(8, True, True) array([ 16, 235]) >>> RGB_range(8, True, False) # doctest: +ELLIPSIS array([ 0.0627451..., 0.9215686...]) >>> RGB_range(10, False, False) array([ 0., 1.]) """ if is_legal: ranges = np.array([16, 235]) ranges *= 2 ** (bits - 8) else: ranges = np.array([0, 2 ** bits - 1]) if not is_int: ranges = ranges.astype(np.float_) / (2 ** bits - 1) return ranges
[docs]def YCbCr_ranges(bits, is_legal, is_int): """" Returns the *Y'CbCr* colour encoding ranges array for given bit depth, range legality and representation. Parameters ---------- bits : int Bit depth of the *Y'CbCr* colour encoding ranges array. is_legal : bool Whether the *Y'CbCr* colour encoding ranges array is legal. is_int : bool Whether the *Y'CbCr* colour encoding ranges array represents integer code values. Returns ------- ndarray *Y'CbCr* colour encoding ranges array. Examples -------- >>> YCbCr_ranges(8, True, True) array([ 16, 235, 16, 240]) >>> YCbCr_ranges(8, True, False) # doctest: +ELLIPSIS array([ 0.0627451..., 0.9215686..., 0.0627451..., 0.9411764...]) >>> YCbCr_ranges(10, False, False) array([ 0. , 1. , -0.5, 0.5]) """ if is_legal: ranges = np.array([16, 235, 16, 240]) ranges *= 2 ** (bits - 8) else: ranges = np.array([0, 2 ** bits - 1, 0, 2 ** bits - 1]) if not is_int: ranges = ranges.astype(np.float_) / (2 ** bits - 1) if is_int and not is_legal: ranges[3] = 2 ** bits if not is_int and not is_legal: ranges[2] = -0.5 ranges[3] = 0.5 return ranges
[docs]def RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['Rec. 709'], in_bits=10, in_legal=False, in_int=False, out_bits=8, out_legal=True, out_int=False, **kwargs): """ Converts an array of *R'G'B'* values to the corresponding *Y'CbCr* colour encoding values array. Parameters ---------- RGB : array_like Input *R'G'B'* array of floats or integer values. K : array_like, optional Luma weighting coefficients of red and blue. See :attr: `YCBCR_WEIGHTS` for presets. Default is `(0.2126, 0.0722)`, the weightings for Rec. 709. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is `235 / 255`. Default is `10`. in_legal : bool, optional Whether to treat the input values as legal range. Default is `False`. in_int : bool, optional Whether to treat the input values as `in_bits` integer code values. Default is `False`. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is `235 / 255`. Ignored if `out_legal` and `out_int` are both False. Default is `8`. out_legal : bool, optional Whether to return legal range values. Default is `True`. out_int : bool, optional Whether to return values as `out_bits` integer code values. Default is `False`. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as `in_range = (RGB_min, RGB_max)`. If `in_range` is undefined, `RGB_min` and `RGB_max` will be computed using :func:`RGB_range` definition. out_range : array_like, optional Array overriding the computed range such as `out_range = (Y_min, Y_max, C_min, C_max)`. If `out_range` is undefined, `Y_min`, `Y_max`, `C_min` and `C_max` will be computed using :func:`YCbCr_ranges` definition. Returns ------- ndarray *Y'CbCr* colour encoding array of integer or float values. Warning ------- For *Recommendation ITU-R BT.2020*, :func:`RGB_to_YCbCr` definition is only applicable to the non-constant luminance implementation. :func:`RGB_to_YcCbcCrc` definition should be used for the constant luminance case as per [3]_. Notes ----- - The default arguments, ``**{'in_bits': 10, 'in_legal': False, 'in_int': False, 'out_bits': 8, 'out_legal': True, 'out_int': False}`` transform a float *R'G'B'* input array in range [0, 1] (`in_bits` is ignored) to a float *Y'CbCr* output array where *Y'* is in range [16 / 255, 235 / 255] and *Cb* and *Cr* are in range [16 / 255, 240./255]. The float values are calculated based on an [0, 255] integer range, but no 8-bit quantisation or clamping are performed. Examples -------- >>> RGB = np.array([1.0, 1.0, 1.0]) >>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS array([ 0.9215686..., 0.5019607..., 0.5019607...]) Matching float output of The Foundry Nuke's Colorspace node set to YCbCr: >>> RGB_to_YCbCr( # doctest: +ELLIPSIS ... RGB, ... out_range=(16 / 255, 235 / 255, 15.5 / 255, 239.5 / 255)) array([ 0.9215686..., 0.5 , 0.5 ]) Matching float output of The Foundry Nuke's Colorspace node set to YPbPr: >>> RGB_to_YCbCr( # doctest: +ELLIPSIS ... RGB, ... out_legal=False, ... out_int=False) array([ 1., 0., 0.]) Creating integer code values as per standard 10-bit SDI: >>> RGB_to_YCbCr(RGB, out_legal=True, out_bits=10, out_int=True) array([940, 512, 512]) For JFIF JPEG conversion as per ITU-T T.871 [5]_: >>> RGB = np.array([102, 0, 51]) >>> RGB_to_YCbCr( ... RGB, ... K=YCBCR_WEIGHTS['Rec. 601'], ... in_range=(0, 255), ... out_range=(0, 255, 0, 256), ... out_int=True) array([ 36, 136, 175]) Note the use of 256 for the max *Cb / Cr* value, which is required so that the *Cb* and *Cr* output is centered about 128. Using 255 centres it about 127.5, meaning that there is no integer code value to represent achromatic colours. This does however create the possibility of output integer codes with value of 256, which cannot be stored in 8-bit integer representation. Recommendation ITU-T T.871 specifies these should be clamped to 255. These JFIF JPEG ranges are also obtained as follows: >>> RGB_to_YCbCr( ... RGB, ... K=YCBCR_WEIGHTS['Rec. 601'], ... in_bits=8, ... in_int=True, ... out_legal=False, ... out_int=True) array([ 36, 136, 175]) """ RGB = np.asarray(RGB) Kr, Kb = K RGB_min, RGB_max = kwargs.get( 'in_range', RGB_range(in_bits, in_legal, in_int)) Y_min, Y_max, C_min, C_max = kwargs.get( 'out_range', YCbCr_ranges(out_bits, out_legal, out_int)) RGB_float = RGB.astype(np.float_) - RGB_min RGB_float *= 1 / (RGB_max - RGB_min) R, G, B = tsplit(RGB_float) Y = Kr * R + (1 - Kr - Kb) * G + Kb * B Cb = 0.5 * (B - Y) / (1 - Kb) Cr = 0.5 * (R - Y) / (1 - Kr) Y *= Y_max - Y_min Y += Y_min Cb *= C_max - C_min Cr *= C_max - C_min Cb += (C_max + C_min) / 2 Cr += (C_max + C_min) / 2 YCbCr = tstack((Y, Cb, Cr)) YCbCr = np.round(YCbCr).astype(np.int_) if out_int else YCbCr return YCbCr
[docs]def YCbCr_to_RGB(YCbCr, K=YCBCR_WEIGHTS['Rec. 709'], in_bits=8, in_legal=True, in_int=False, out_bits=10, out_legal=False, out_int=False, **kwargs): """ Converts an array of *Y'CbCr* colour encoding values to the corresponding *R'G'B'* values array. Parameters ---------- YCbCr : array_like Input *Y'CbCr* colour encoding array of integer or float values. K : array_like, optional Luma weighting coefficients of red and blue. See :attr: `YCBCR_WEIGHTS` for presets. Default is `(0.2126, 0.0722)`, the weightings for Rec. 709. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is `235 / 255`. Default is `10`. in_legal : bool, optional Whether to treat the input values as legal range. Default is `False`. in_int : bool, optional Whether to treat the input values as `in_bits` integer code values. Default is `False`. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is `235 / 255`. Ignored if `out_legal` and `out_int` are both False. Default is `8`. out_legal : bool, optional Whether to return legal range values. Default is `True`. out_int : bool, optional Whether to return values as `out_bits` integer code values. Default is `False`. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as `in_range = (Y_min, Y_max, C_min, C_max)`. If `in_range` is undefined, `Y_min`, `Y_max`, `C_min` and `C_max` will be computed using :func:`YCbCr_ranges` definition. out_range : array_like, optional Array overriding the computed range such as `out_range = (RGB_min, RGB_max)`. If `out_range` is undefined, `RGB_min` and `RGB_max` will be computed using :func:`RGB_range` definition. Returns ------- ndarray *R'G'B'* array of integer or float values. Warning ------- For *Recommendation ITU-R BT.2020*, :func:`YCbCr_to_RGB` definition is only applicable to the non-constant luminance implementation. :func:`YcCbcCrc_to_RGB` definition should be used for the constant luminance case as per [3]_. Examples -------- >>> YCbCr = np.array([502, 512, 512]) >>> YCbCr_to_RGB( ... YCbCr, ... in_bits=10, ... in_legal=True, ... in_int=True) array([ 0.5, 0.5, 0.5]) """ YCbCr = np.asarray(YCbCr) Y, Cb, Cr = tsplit(YCbCr.astype(np.float_)) Kr, Kb = K Y_min, Y_max, C_min, C_max = kwargs.get( 'in_range', YCbCr_ranges(in_bits, in_legal, in_int)) RGB_min, RGB_max = kwargs.get( 'out_range', RGB_range(out_bits, out_legal, out_int)) Y -= Y_min Cb -= (C_max + C_min) / 2 Cr -= (C_max + C_min) / 2 Y *= 1 / (Y_max - Y_min) Cb *= 1 / (C_max - C_min) Cr *= 1 / (C_max - C_min) R = Y + (2 - 2 * Kr) * Cr B = Y + (2 - 2 * Kb) * Cb G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb) RGB = tstack((R, G, B)) RGB *= RGB_max - RGB_min RGB += RGB_min RGB = np.round(RGB).astype(np.int_) if out_int else RGB return RGB
[docs]def RGB_to_YcCbcCrc(RGB, out_bits=10, out_legal=True, out_int=False, is_12_bits_system=False, **kwargs): """ Converts an array of *RGB* linear values to the corresponding *Yc'Cbc'Crc'* colour encoding values array. Parameters ---------- RGB : array_like Input *RGB* array of linear float values. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is `235 / 255`. Ignored if `out_legal` and `out_int` are both False. Default is `10`. out_legal : bool, optional Whether to return legal range values. Default is `True`. out_int : bool, optional Whether to return values as `out_bits` integer code values. Default is `False`. is_12_bits_system : bool, optional *Recommendation ITU-R BT.2020* OETF (OECF) adopts different parameters for 10 and 12 bit systems. Default is `False`. Other Parameters ---------------- out_range : array_like, optional Array overriding the computed range such as `out_range = (Y_min, Y_max, C_min, C_max)`. If `out_range` is undefined, `Y_min`, `Y_max`, `C_min` and `C_max` will be computed using :func:`YCbCr_ranges` definition. Returns ------- ndarray *Yc'Cbc'Crc'* colour encoding array of integer or float values. Warning ------- This definition is specifically for usage with *Recommendation ITU-R BT.2020* [3]_ when adopting the constant luminance implementation. Examples -------- >>> RGB = np.array([0.18, 0.18, 0.18]) >>> RGB_to_YcCbcCrc( ... RGB, ... out_legal=True, ... out_bits=10, ... out_int=True, ... is_12_bits_system=False) array([422, 512, 512]) """ RGB = np.asarray(RGB) R, G, B = tsplit(RGB) Y_min, Y_max, C_min, C_max = kwargs.get( 'out_range', YCbCr_ranges(out_bits, out_legal, out_int)) Yc = 0.2627 * R + 0.6780 * G + 0.0593 * B Yc = oetf_BT2020(Yc, is_12_bits_system=is_12_bits_system) R = oetf_BT2020(R, is_12_bits_system=is_12_bits_system) B = oetf_BT2020(B, is_12_bits_system=is_12_bits_system) Cbc = np.where((B - Yc) <= 0, (B - Yc) / 1.9404, (B - Yc) / 1.5816) Crc = np.where((R - Yc) <= 0, (R - Yc) / 1.7184, (R - Yc) / 0.9936) Yc *= Y_max - Y_min Yc += Y_min Cbc *= C_max - C_min Crc *= C_max - C_min Cbc += (C_max + C_min) / 2 Crc += (C_max + C_min) / 2 YcCbcCrc = tstack((Yc, Cbc, Crc)) YcCbcCrc = np.round(YcCbcCrc).astype(np.int_) if out_int else YcCbcCrc return YcCbcCrc
[docs]def YcCbcCrc_to_RGB(YcCbcCrc, in_bits=10, in_legal=True, in_int=False, is_12_bits_system=False, **kwargs): """ Converts an array of *Yc'Cbc'Crc'* colour encoding values to the corresponding *RGB* array of linear values. Parameters ---------- YcCbcCrc : array_like Input *Yc'Cbc'Crc'* colour encoding array of linear float values. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is `235 / 255`. Default is `10`. in_legal : bool, optional Whether to treat the input values as legal range. Default is `False`. in_int : bool, optional Whether to treat the input values as `in_bits` integer code values. Default is `False`. is_12_bits_system : bool, optional *Recommendation ITU-R BT.2020* EOTF (EOCF) adopts different parameters for 10 and 12 bit systems. Default is `False`. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as `in_range = (Y_min, Y_max, C_min, C_max)`. If `in_range` is undefined, `Y_min`, `Y_max`, `C_min` and `C_max` will be computed using :func:`YCbCr_ranges` definition. Returns ------- ndarray *RGB* array of linear float values. Warning ------- This definition is specifically for usage with *Recommendation ITU-R BT.2020* [3]_ when adopting the constant luminance implementation. Examples -------- >>> YcCbcCrc = np.array([1689, 2048, 2048]) >>> YcCbcCrc_to_RGB( # doctest: +ELLIPSIS ... YcCbcCrc, ... in_legal=True, ... in_bits=12, ... in_int=True, ... is_12_bits_system=True) array([ 0.1800903..., 0.1800903..., 0.1800903...]) """ YcCbcCrc = np.asarray(YcCbcCrc) Yc, Cbc, Crc = tsplit(YcCbcCrc.astype(np.float_)) Y_min, Y_max, C_min, C_max = kwargs.get( 'in_range', YCbCr_ranges(in_bits, in_legal, in_int)) Yc -= Y_min Cbc -= (C_max + C_min) / 2 Crc -= (C_max + C_min) / 2 Yc *= 1 / (Y_max - Y_min) Cbc *= 1 / (C_max - C_min) Crc *= 1 / (C_max - C_min) B = np.where(Cbc <= 0, Cbc * 1.9404 + Yc, Cbc * 1.5816 + Yc) R = np.where(Crc <= 0, Crc * 1.7184 + Yc, Crc * 0.9936 + Yc) Yc = eotf_BT2020(Yc, is_12_bits_system=is_12_bits_system) B = eotf_BT2020(B, is_12_bits_system=is_12_bits_system) R = eotf_BT2020(R, is_12_bits_system=is_12_bits_system) G = (Yc - 0.0593 * B - 0.2627 * R) / 0.6780 RGB = tstack((R, G, B)) return RGB