Matplotlib Colors

  • Matplotlib은 Visualization용 Library로만 알고 계시는 분들이 많습니다.

  • 이미지 데이터의 색상 관련 operation을 위해서 opencv, pillow, scikit-image 등을 추가로 설치하고 사용하시는 분들이 많습니다만, 의외로 matplotlib에서도 색상 관련 기능을 제공하고 있습니다.

  • Matplotlib 공식 홈페이지에서 다양한 예제를 제공하지만 알려지지 않은 부분이 많아 여기에 정리해 봅니다.

    1
    2
    3
    4
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    %matplotlib inline
    print(mpl.__version__)
    • 실행결과: 버전은 3.1.3. 기준입니다.
    1
    '3.1.3'

References

Matplotlib 3.1.3 (Usage Guide)
matplotlib.colors

1. Colors

  • Matplotlib에 어떤 색상이 있고, 이들을 어떻게 사용하는지 살펴보겠습니다.

1.1. Matplotlib Color Set

List of Named Colors
Choosing Colormaps in Matplotlib

  • Matplotlib에는 크게 3가지 부류의 색상이 있습니다.


  • 이들이 어떻게 저장되어 있는지를 확인하기 위해 출력해보면 재밌는 사실을 알게 됩니다.

    1
    2
    3
    4
    5
    import matplotlib.colors as mcolors

    print("mcolors.BASE_COLORS['r']: {}, type={}".format(mcolors.BASE_COLORS['r'], type(mcolors.BASE_COLORS['r'])))
    print("mcolors.TABLEAU_COLORS['tab:red']: {}, type={}".format(mcolors.TABLEAU_COLORS['tab:red'], type(mcolors.TABLEAU_COLORS['tab:red'])))
    print("mcolors.CSS4_COLORS['red']: {}, type={}".format(mcolors.CSS4_COLORS['red'], type(mcolors.CSS4_COLORS['red'])))
    • 실행결과, 각 항목이 담고 있는 색상정보의 형식이 미묘하게 다릅니다.
    • BASE_COLORStuple 형태인 반면 나머지 둘은 string입니다.
    • 그리고 같은 red라고 해도 TABLEAU_COLORS와 CSS4_COLORS의 값, 대소문자가 다릅니다.
    • 실제 위 그림에서 봐도 색이 다릅니다.
      1
      2
      3
      mcolors.BASE_COLORS['r']: (1, 0, 0), type=<class 'tuple'>
      mcolors.TABLEAU_COLORS['tab:red']: #d62728, type=<class 'str'>
      mcolors.CSS4_COLORS['red']: #FF0000, type=<class 'str'>
  • 다시 말하면 Matplotlib 내에서 다루어지는 색상 표현이 한 가지로 고정되지 않았고, 이들은 당연히 matplotlib 내부 기능을 통해 변환이 가능합니다.

  • Matplotlib 내부의 color는 다음과 같이 dictionary 형태로 한번에 수집해서 활용할 수 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 색상 전체 명단 수집
    colorlist = {}
    colorlist.update(mcolors.BASE_COLORS)
    colorlist.update(mcolors.TABLEAU_COLORS)
    colorlist.update(mcolors.CSS4_COLORS)

    # 색상 이름과 코드로 분리해서 관리
    colornames = []
    colorcodes = []
    for name, color in colorlist.items():
    colornames.append(name)
    colorcodes.append(color)

1.2. Cycler

Styling with Cycler
Specifying Colors

  • 설명에 앞서 아래 그림을 잠깐 봅시다.

  • 12개의 직선이 일정한 간격을 두고 평행하게 늘어선 그림인데, 자세히 보면 색상에 중복이 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import numpy as np

    data_x = np.array([0,20])
    data_y = [0,10]

    fig, ax = plt.subplots(figsize=(6,3))
    for i in range(12):
    ax.plot(data_x + 3*i, data_y)

    plt.show()

  • 첫번째와 두번째 푸른 직선과 오렌지색 직선이 마지막 두 직선에서 반복되는 것을 확인할 수 있습니다.

  • matplotlib에 기본적으로 10개의 색이 주어지고 직선이 반복될 때마다 이를 순차적으로 반복하는 설정이 되어 있기 때문입니다.

  • rcParamsaxes.prop_cycle항목을 통해 확인할 수 있습니다.

    1
    print(plt.rcParams['axes.prop_cycle'])
    • 실행결과 : 색상 명단이 출력됩니다.
      1
      cycler('color', ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'])
  • mpl.rc('axes', prop_cycle=)명령을 통해서 수정할 수 있고,

  • matplotlibrc파일의 axes.prop_cycle 부분을 통해 수정할 수도 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # cycler 수정
    from cycler import cycler
    plt.rc('axes', prop_cycle=(cycler(color=['r', 'g', 'b', 'y']) +
    cycler(linestyle=['-', '--', ':', '-.'])))

    data_x = np.array([0,20])
    data_y = [0,10]

    fig, ax = plt.subplots(figsize=(6,3))
    for i in range(12):
    ax.plot(data_x + 3*i, data_y)
    plt.show()
    • 실행결과 : 위의 그림에서 선의 모양과 색깔이 변경되었습니다.

  • cycler로 지정된 color는 “CN” color selection이라는 개념을 통해 선택이 가능합니다.

  • 아래는 동일한 그래프에 seaborn-whitegrid style의 첫번째(C0)부터 네번째(C4)까지 color를 다르게 적용하는 예시입니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl

    th = np.linspace(0, 2*np.pi, 128)

    mpl.style.use('seaborn-whitegrid')
    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))

    def demo(num):

    ax[num].plot(th, np.cos(th), f'C{num}', label=f'C{num}')
    ax[num].plot(th, np.sin(th), f'C{num+2}', label=f'C{num+2}')
    ax[num].legend()

    demo(0)
    demo(1)


2. Color Functions

  • matplotlib에 내장된 color function을 훑어봅시다.
  • 예제로 사용할 color 다섯 개를 먼저 정의합니다.
  • Lena image 위에 덮인 색상에서 알 수 있듯 alpha는 불투명도를 의미합니다.
    1
    2
    3
    4
    5
    6
    7
    import matplotlib.colors as mcolor

    c1 = 'darkred' # matplotlib color name
    c2 = '#AABBFF' # hex code without alpha
    c3 = '#AABBFF55' # hex code with alpha
    c4 = [0.2, 0.4, 0.5] # RGB
    c5 = [0.2, 0.4, 0.5, 0.6] # RGBA: RGB + alpha

2.1. to_hex(), to_rgb(), to_rgba(): 색상 표현 변경

  • 색상 이름을 입력받아 hex 또는 rgb 형식으로 변환하는 함수입니다.
  • 백문이 불여일견이므로 우선 pd.DataFrame으로 요약해서 실행해보겠습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import pandas as pd

    colors = pd.DataFrame(columns=['input', 'to_hex(F)', 'to_hex(T)',
    'to_rgb()', 'to_rgba(alpha=None)', 'to_rgba(alpha=0.2)'])

    for i in range(5):
    # to_hex()
    hex_code0 = mcolors.to_hex(eval(f'c{i+1}')), keep_alpha=False)
    hex_code1 = mcolors.to_hex(eval(f'c{i+1}')), keep_alpha=True)

    # to_rgb()
    rgb_code0 = np.round(mcolors.to_rgb(eval(f'c{i+1}')), 3)

    # to_rgba()
    rgb_code1 = np.round(mcolors.to_rgba(eval(f'c{i+1}')), 3)
    rgb_code2 = np.round(mcolors.to_rgba(eval(f'c{i+1}'), 0.2), 3)

    colors.loc[i] = [eval(f'c{i+1}'), hex_code0, hex_code1, rgb_code0, rgb_code1, rgb_code2]
  • colors DataFrame 출력 결과,
    • to_hex()keep_alpha 옵션에 따라 alpha 채널이 삭제/추가되는 반면
    • to_rgb()alpha 채널이 자동적으로 삭제되고,
    • to_rgba()alpha 옵션에 따라 alpha 값이 원래 값 vs 지정 값으로 설정됩니다.

2.2. to_rgba_array(): 일괄 변경

Numpy Masked Array
Zhang et al., Sensors 14(9) 16128 (2014)

  • 0~1 사이 값들이 저장된 (n, 4) array를 RGBA array로 일괄 변경합니다.

  • (n, 3) array가 입력되는 경우 alpha=1로 자동 설정되며, column 수가 3 미만인 경우 에러가 납니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from random import random

    # random (5, 4) array
    colors = []
    for i in range(5):
    carray = [random(), random(), random(), random()]
    colors.append(carray)
    print('# colors=\n', colors)
    print('# type(colors)=', type(colors))

    # to_rgba_array
    rgb_code = mcolors.to_rgba_array(colors)
    print('\n# rgb_code=\n', rgb_code)
    print('# type(rgb_code)=', type(rgb_code3))
    • 실행결과: type이 numpy.ndarray로 바뀌었습니다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # colors=
      [[0.40375029735610757, 0.6210923647004767, 0.6568133189767286, 0.8470279451052253], [0.5433032508130815, 0.14398085571268193, 0.7303598026172411, 0.7963294484621202], [0.8613064656492081, 0.8976140923488195, 0.05826502488661911, 0.42778774608985837], [0.39198017055539114, 0.8541915083363212, 0.04344057833193116, 0.5649137794285194], [0.8159908658774397, 0.21485709604877146, 0.7452954405793056, 0.4980798212359918]]
      # type(colors)= <class 'list'>

      # rgb_code=
      [[0.4037503 0.62109236 0.65681332 0.84702795]
      [0.54330325 0.14398086 0.7303598 0.79632945]
      [0.86130647 0.89761409 0.05826502 0.42778775]
      [0.39198017 0.85419151 0.04344058 0.56491378]
      [0.81599087 0.2148571 0.74529544 0.49807982]]
      # type(rgb_code)= <class 'numpy.ndarray'>
  • Numpy에는 Masked Array라는 기능이 있습니다.

  • invalid 등 특정 데이터에 mask를 씌워 별도의 처리를 하는 것인데, to_rgba_array()에서는 RGBA로 변환하지 말아야 할 부분이라는 의미입니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import numpy.ma as ma

    # masked array
    mask=[[0,0,0,0], [0,0,0,0], [1,1,1,0], [0,0,0,1], [0,0,0,0]]
    mx = ma.masked_array(colors, mask)
    print('# mx=\n', mx)
    print('# type(mx)=\n', type(mx))

    # to_rgba_array
    rgb_code = mcolors.to_rgba_array(mx)
    print('\n# rgb_code, masked=\n', rgb_code)
    print('# type(rgb_code, masked)=', type(rgb_code3))
    • 실행결과: 일부라도 mask 처리된 데이터가 모두 [0, 0, 0, 0]으로 변환되었습니다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      # mx=
      [[0.40375029735610757 0.6210923647004767 0.6568133189767286
      0.8470279451052253]
      [0.5433032508130815 0.14398085571268193 0.7303598026172411
      0.7963294484621202]
      [-- -- -- 0.42778774608985837]
      [0.39198017055539114 0.8541915083363212 0.04344057833193116 --]
      [0.8159908658774397 0.21485709604877146 0.7452954405793056
      0.4980798212359918]]
      # type(mx)=
      <class 'numpy.ma.core.MaskedArray'>

      # rgb_code, masked=
      [[0.4037503 0.62109236 0.65681332 0.84702795]
      [0.54330325 0.14398086 0.7303598 0.79632945]
      [0. 0. 0. 0. ]
      [0. 0. 0. 0. ]
      [0.81599087 0.2148571 0.74529544 0.49807982]]
      # type(rgb_code, masked)= <class 'numpy.ndarray'>

2.3. rgb_to_hsv() and hsv_to_rgb()

  • color 표현 방법은 크게 RGB(Red, Green, Blue)와 HSV(Hue, Saturation, Value)로 나뉩니다.
  • BGR(Blue, Green Red), HSL(Hue, Saturation, Lightness), YCrCb(Luma, Blue-difference, Red-difference), CIE(Commission internationale de l’éclairage) 등도 있으나 여기서는 다루지 않겠습니다.
  • RGB color space 위에 HSV를 얹으면 대략 이런 관계를 가집니다.
  • 변환식이 matplotlib에 내장되어 한 줄로 편하게 실행할 수 있습니다.
    1
    2
    hsv_c4 = mcolors.rgb_to_hsv(c4)
    rgb_c4 = mcolors.hsv_to_rgb(hsv_c4)


도움이 되셨나요? 카페인을 투입하시면 다음 포스팅으로 변환됩니다

Share