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