使用python生成颜色表(color chart)

在做色彩相关的算法分析时候,经常需要使用规则的颜色表来进行辅助。常用的有以下一些内容:

  • 512*512的渐变色表,以稀疏采样的方式对rgb做了全覆盖采样,也常作为 3d LUT的基础。
  • color checker classic 24 色卡表,24色卡代表自然界最常出现的颜色,也是色彩校准常用的比对标准。
  • 其他一些纯色快,如rgb,cmy等
  • 色彩渐变条带
  • rgb加色表
  • cmy减色表
  • 色相(hue)

下面用python(numpy和opencv)来生成包含上述信息的一张图片。图片和代码如下:
请添加图片描述

# -*- coding: utf-8 -*-
import cv2
import numpy as np

COLOR_CHECKER = np.uint8(np.array(
    [
        [[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]],
    ]
))


def expand_pixel_to_grid(matrix, grid_width, grid_height):
    """
    Expand a pixel to a grid. Inside the grid, every pixel have the same value
    as the source pixel.

    Parameters
    ----------
    matrix: 2D numpy array
    grid_width: width of grid
    grid_height: height of grid
    """
    height, width = matrix.shape[:2]
    new_heigt = height * grid_height
    new_width = width * grid_width
    repeat_num = grid_width * grid_height

    matrix = np.expand_dims(matrix, axis=2).repeat(repeat_num, axis=2)
    matrix = np.reshape(matrix, (height, width, grid_height, grid_width))
    # put `height` and `grid_height` axes together;
    # put `width` and `grid_width` axes together.
    matrix = np.transpose(matrix, (0, 2, 1, 3))
    matrix = np.reshape(matrix, (new_heigt, new_width, 1))
    return matrix


def generate_single_color_band(left_color, right_color, grade=256,
                               height=None):
    color_band = np.linspace(left_color, right_color, grade)
    if height is not None:
        color_band = np.expand_dims(color_band, axis=0)
        color_band = np.repeat(color_band, repeats=height, axis=0)
    color_band = np.clip(np.round(color_band), 0, 255).astype(np.uint8)
    return color_band


def generate_color_bands(color_pairs, grade=256, height=32):
    """
    Generate color bands by uniformly changing from left colors to right
    colors. Note that there might be multiple bands.

    Parameters
    ----------
    color_pairs: two dimension list/tuple. The outer dimension denotes the
        number of color bands. The inner dimension denotes one color pair,
        which contains two elements, the first denotes the starting color of
        a color band, while the second denotes the ending color.
    grade: how many colors are contained in one color band.
    height: height of one color band.
    """
    # check and process color parameters, which should be 2D list
    # after processing
    if not isinstance(color_pairs, (tuple, list)):
        raise ValueError("color_pairs must be a two dimension list/tuple")
    if not isinstance(color_pairs[0], (tuple, list)):
        raise ValueError("color_pairs must be a two dimension list/tuple")

    # initialize channel, and all other colors should have the same channel
    if not isinstance(color_pairs[0][0], (tuple, list)):
        channel = 1
    else:
        channel = len(color_pairs[0][0])

    # band_num = len(color_pairs)
    result = []
    for color_pair in color_pairs:
        left_color = color_pair[0]
        right_color = color_pair[1]
        if channel != 1 and \
                (len(left_color) != channel or len(right_color) != channel):
            raise ValueError("All colors should have same channel number")

        color_band = generate_single_color_band(left_color, right_color, grade,
                                                height)
        result.append(color_band)
    result = np.concatenate(result, axis=0)
    result = np.squeeze(result)
    return result


def generate_circle_centers(image_height, image_width, radius):
    rx = radius / 2.
    ry = np.sqrt(3.) / 2. * radius

    x1 = image_width / 2.
    y1 = (image_height - ry) / 2.
    x2 = x1 - rx
    y2 = y1 + ry
    x3 = x1 + rx
    y3 = y2

    xx = [x1, x2, x3]
    yy = [y1, y2, y3]
    return xx, yy


def generate_additive_circles(image_height,
                              image_width,
                              radius,
                              bg_color=(0, 0, 0),
                              colors=('red', 'green', 'blue')):
    xx, yy = generate_circle_centers(image_height, image_width, radius)

    # generate color index
    color_index = {
        'red': 0,
        'green': 1,
        'blue': 2
    }

    # build meshgrid and initialize output
    xmesh, ymesh = np.meshgrid(np.arange(0, image_width),
                               np.arange(0, image_height))
    res = np.zeros((image_height, image_width, 3), dtype=np.uint8)
    bg_idx = np.ones((image_height, image_width), dtype=np.bool_)

    for i in range(3):
        r = np.sqrt((xmesh - xx[i]) ** 2 + (ymesh - yy[i]) ** 2)
        space_idx = r < radius
        color_idx = color_index[colors[i]]
        res[space_idx, color_idx] = 255
        bg_idx = bg_idx & (~space_idx)
    res = res[..., ::-1]
    res[bg_idx] = bg_color
    return res


def generate_subtractive_circles(image_height,
                                 image_width,
                                 radius,
                                 bg_color=(255, 255, 255),
                                 colors=('cyan', 'magenta', 'yellow')):
    xx, yy = generate_circle_centers(image_height, image_width, radius)

    # generate color index
    color_index = {
        'cyan': 0,
        'magenta': 1,
        'yellow': 2
    }

    # build meshgrid and initialize output
    xmesh, ymesh = np.meshgrid(np.arange(0, image_width),
                               np.arange(0, image_height))
    res = np.ones((image_height, image_width, 3), dtype=np.uint8) * 255
    bg_idx = np.ones((image_height, image_width), dtype=np.bool_)

    for i in range(3):
        r = np.sqrt((xmesh - xx[i]) ** 2 + (ymesh - yy[i]) ** 2)
        space_idx = r < radius
        color_idx = color_index[colors[i]]
        res[space_idx, color_idx] = 0
        bg_idx = bg_idx & (~space_idx)
    res = res[..., ::-1]
    res[bg_idx] = bg_color
    return res


def generate_color_chart(grid_sample_x,
                         grid_sample_y,
                         grid_num_x,
                         grid_num_y):
    """
    Generate color chart by uniformly distributed color indexes, only support
    8 bit (uint8).
    For a commonly seen 512*512 color chart:
        grid_sample_x = grid_sample_y = 64
        grid_num_x = grid_num_y = 8

    Parameters
    ----------
    grid_sample_x: Width of color grid.
    grid_sample_y: Height of color grid.
    grid_num_x: Number of grids in x direction.
    grid_num_y: Number of grids in y direction.
    """
    grid_sample_x = round(grid_sample_x)
    grid_sample_y = round(grid_sample_y)
    grid_num_x = round(grid_num_x)
    grid_num_y = round(grid_num_y)
    grid_num = grid_num_x * grid_num_y

    red_index = np.uint8(np.round(np.linspace(0, 255, grid_sample_x)))
    green_index = np.uint8(np.round(np.linspace(0, 255, grid_sample_y)))
    blue_index = np.uint8(np.round(np.linspace(0, 255, grid_num)))

    # compute sizes
    width = grid_sample_x * grid_num_x
    height = grid_sample_y * grid_num_y
    result = np.zeros((height, width, 3), dtype=np.uint8)

    # compute red-green block, (blue will be combined afterward)
    red_block, green_block = np.meshgrid(red_index, green_index)
    red_block = red_block[..., np.newaxis]
    green_block = green_block[..., np.newaxis]
    rg_block = np.concatenate([red_block, green_block], axis=2)

    # combine blue channel
    for col in range(grid_num_x):
        for row in range(grid_num_y):
            blue = blue_index[row * grid_num_x + col]
            blue = np.ones_like(rg_block[..., 0], dtype=np.uint8) * blue
            color_block = np.concatenate([rg_block, blue[..., np.newaxis]],
                                         axis=2)
            xmin = col * grid_sample_x
            xmax = xmin + grid_sample_x
            ymin = row * grid_sample_y
            ymax = ymin + grid_sample_y
            result[ymin:ymax, xmin:xmax, :] = color_block

    result = result[..., ::-1]  # convert from rgb to bgr
    return result


def comprehensive_color_chart():
    height = 1024
    width = 1024
    black = [0, 0, 0]
    white = [255, 255, 255]
    red = [0, 0, 255]
    green = [0, 255, 0]
    blue = [255, 0, 0]
    yellow = [0, 255, 255]
    magenta = [255, 0, 255]
    cyan = [255, 255, 0]
    res = np.zeros((height, width, 3), dtype=np.uint8)

    # top left: color chart64
    res[:512, :512] = generate_color_chart(64, 64, 8, 8)

    # top right: color blocks
    res[:64, 512:576] = np.uint8(255 * 0.8)
    res[:64, 576:640] = yellow
    res[:64, 640:704] = cyan
    res[:64, 704:768] = green
    res[:64, 768:832] = magenta
    res[:64, 832:896] = red
    res[:64, 896:960] = blue
    res[:64, 960:] = np.uint8(255 * 0.4)

    # top right: color bands
    res[64:448, 512:768] = generate_color_bands(
        [[black, red], [black, green], [black, blue], [black, cyan],
         [black, magenta], [black, yellow]], height=64)
    res[64:448, 768:] = generate_color_bands(
        [[white, red], [white, green], [white, blue], [white, cyan],
         [white, magenta], [white, yellow]], height=64)

    res[448:512, 512:768] = generate_color_bands([[black, white]], height=64)
    temp = generate_color_bands([[0, 255]], grade=16, height=64)
    temp = expand_pixel_to_grid(temp, 16, 1).repeat(3, axis=2)
    res[448:512, 768:] = temp

    # middle: HSL color band
    hls = np.uint8(np.linspace(0, 180, 64)).reshape([1, -1])
    hls = expand_pixel_to_grid(hls, 16, 64).repeat(3, axis=2)
    hls[..., 1] = 128
    hls[..., 2] = 255
    hls2rgb = cv2.cvtColor(hls, cv2.COLOR_HLS2BGR)
    res[512:576, :] = hls2rgb

    # bottom left: color checker 24
    cc = COLOR_CHECKER[..., ::-1]
    size = 96
    margin = 16
    rows = np.int32(np.arange(584, 1024, size + margin))
    cols = np.int32(np.arange(16, 600, size + margin))
    for i in range(4):
        for j in range(6):
            res[rows[i]:rows[i] + size, cols[j]:cols[j] + size] = cc[i, j]

    # bottom right: additive and subtractive colors
    res[584:792, 688:1008] = generate_additive_circles(208, 320, 64)
    res[808:1016, 688:1008] = generate_subtractive_circles(208, 320, 64)
    return res


if __name__ == '__main__':
    color_chart_64 = generate_color_chart(64, 64, 8, 8)
    color_chart = comprehensive_color_chart()
    cv2.imwrite(r'color_chart_64.png', color_chart_64)
    cv2.imwrite(r'color_chart.png', color_chart)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值