Last active
September 16, 2019 22:12
-
-
Save wakita/04537a15215608f2b341f87f96b92457 to your computer and use it in GitHub Desktop.
知覚的均等な色見に関するグラデーション
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
from colormath.color_objects import LabColor, XYZColor, sRGBColor | |
from colormath.color_conversions import convert_color | |
from colormath.color_diff import delta_e_cie2000 | |
# 各種関数を Numpy array に対応 | |
labcolor = np.vectorize(LabColor) | |
lab_l = np.vectorize(lambda lab: lab.lab_l) | |
lab_a = np.vectorize(lambda lab: lab.lab_a) | |
lab_b = np.vectorize(lambda lab: lab.lab_b) | |
srgb = np.vectorize(lambda lab: convert_color(lab, sRGBColor, rgb_gamma=1.8)) | |
Δ = np.vectorize(lambda lab1, lab2: delta_e_cie2000(lab1, lab2)) | |
def Radial(n_colors, L, R): | |
GRAY = LabColor(L, 0, 0) | |
# 問題の設定 | |
n_colors = 6 | |
L1, a1, b1 = 50, 30, 0 | |
L2, a2, b2 = 50, -30, 0 | |
lab1, lab2 = LabColor(L1, a1, b1), LabColor(L2, a2, b2) | |
# 初期解は GRAY を中心とした CIE a*-b* 平面上の半径 R の円に沿って等距離に取得 | |
initial_T = np.ones((n_colors, 1)) / n_colors | |
def objective(T): | |
# CIE a*列とb*列が与えられたときの目的関数 | |
t = T.cumsum() / T.sum() | |
labs = labcolor((1 - t) * L1 + t * L2, | |
(1 - t) * a1 + t * a2, | |
(1 - t) * b1 + t * b2) | |
return (np.abs(t.sum() - 1) * # 制約1: 媒介変数の和が 1 | |
np.var(Δ(labs[1:], labs[:-1]))) # 制約: 隣接色との距離の分散が小さい | |
from crfmnes import CRFMNES | |
# 最適化の設定: よくわからないまま設定した。 | |
optimizer = CRFMNES(np.size(initial_T), objective, initial_T, 1, 8) | |
T, f_best = optimizer.optimize(100) | |
# 得られた CIEL*a*b* 値について目的関数を評価 | |
print(T) | |
t = T.cumsum() / T.sum() | |
labs = labcolor((1 - t) * L1 + t * L2, | |
(1 - t) * a1 + t * a2, | |
(1 - t) * b1 + t * b2) | |
Ldist = Δ(labs[1:], labs[:-1]) | |
print('色差', Ldist.min(), Ldist.max(), Ldist.var()) | |
print(Ldist) | |
# 描画 | |
import matplotlib.pyplot as plt | |
fig, ax = plt.subplots(subplot_kw=dict(xlim=[0, n_colors])) | |
for i, srgb in zip(range(len(labs)), srgb(labs)): | |
print(srgb) | |
ax.add_patch(plt.Rectangle((i, 0), 1, 1, fc=srgb.get_rgb_hex())) | |
ax.set_axis_off() | |
plt.show() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
from colormath.color_objects import LabColor, XYZColor, sRGBColor | |
from colormath.color_conversions import convert_color | |
from colormath.color_diff import delta_e_cie2000 | |
# 各種関数を Numpy array に対応 | |
labcolor = np.vectorize(LabColor) | |
lab_l = np.vectorize(lambda lab: lab.lab_l) | |
lab_a = np.vectorize(lambda lab: lab.lab_a) | |
lab_b = np.vectorize(lambda lab: lab.lab_b) | |
srgb = np.vectorize(lambda lab: convert_color(lab, sRGBColor, rgb_gamma=1.8)) | |
Δ = np.vectorize(lambda lab1, lab2: delta_e_cie2000(lab1, lab2)) | |
# 問題の設定 | |
n_colors = 6 | |
lab1, lab2 = LabColor(70, 25, 0), LabColor(70, -25, 0) | |
initial_params = np.array([[50.0] * n_colors + [0.0, 0.0] * n_colors]).T | |
def boundary_condition(labs): | |
srgbs = srgb(labs) | |
for rgb in srgbs: | |
if rgb.rgb_r > 1 or rgb.rgb_g > 1 or rgb.rgb_b > 1: return False | |
if rgb.rgb_r < 0 or rgb.rgb_g < 0 or rgb.rgb_b < 0: return False | |
return True | |
def objective(params): | |
params = params[:,0] | |
labs = labcolor(params[:n_colors], params[n_colors:n_colors*2], params[n_colors*2:]) | |
if not boundary_condition(labs): return np.inf | |
diffs = Δ(labs[1:], labs[:-1]) | |
return (Δ(labs[0], lab1) * n_colors + # 制約1: 最初の色は lab1 と一致 | |
Δ(labs[-1], lab2) * n_colors + # 制約2: 最後の色は lab2 と一致 | |
diffs.sum() + # 制約3: 隣接色との距離の合計が小さい | |
np.var(diffs)) # 制約4: 隣接色との距離の分散が小さい | |
from crfmnes import CRFMNES | |
# 最適化の設定: よくわからないまま設定した。 | |
optimizer = CRFMNES(np.size(initial_params), objective, initial_params, 70, 16) | |
params, f_best = optimizer.optimize(300) | |
# 得られた CIEL*a*b* 値について目的関数を評価 | |
labs = labcolor(params[:n_colors], params[n_colors:n_colors*2], params[n_colors*2:]) | |
Ldist = Δ(labs[1:], labs[:-1]) | |
print('色差', Ldist.min(), Ldist.max(), Ldist.var()) | |
print(Ldist) | |
# 描画 | |
import matplotlib.pyplot as plt | |
fig, ax = plt.subplots(subplot_kw=dict(xlim=[0, n_colors])) | |
for i, srgb in zip(range(len(labs)), srgb(labs)): | |
print(srgb) | |
ax.add_patch(plt.Rectangle((i, 0), 1, 1, fc=srgb.get_rgb_hex())) | |
ax.set_axis_off() | |
plt.show() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
from colormath.color_objects import LabColor, XYZColor, sRGBColor | |
from colormath.color_conversions import convert_color | |
from colormath.color_diff import delta_e_cie2000 | |
# 各種関数を Numpy array に対応 | |
labcolor = np.vectorize(LabColor) | |
srgb = np.vectorize(lambda lab: convert_color(lab, sRGBColor, rgb_gamma=1.8)) | |
Δ = np.vectorize(lambda lab1, lab2: delta_e_cie2000(lab1, lab2)) | |
# 問題の設定 | |
n_colors = 6 | |
L, R = 70, 23 # CIE L* 値と無彩色 (GRAY) からのΔ値 | |
GRAY = LabColor(L, 0, 0) | |
# 初期解は GRAY を中心とした CIE a*-b* 平面上の半径 R の円に沿って等距離に取得 | |
initial_abs = R * np.array([(np.cos(t), np.sin(t)) | |
for t in np.arange(n_colors) * np.pi * 2 / n_colors]) | |
initial_abs = initial_abs.T.reshape(np.size(initial_abs), 1) | |
def boundary_condition(labs): | |
srgbs = srgb(labs) | |
for rgb in srgbs[:,0]: | |
if rgb.rgb_r > 1 or rgb.rgb_g > 1 or rgb.rgb_b > 1: return False | |
return True | |
assert(boundary_condition(labcolor(L, initial_abs[:n_colors], initial_abs[n_colors:]))) | |
def objective(abs): | |
# CIE a*列とb*列が与えられたときの目的関数 | |
labs = labcolor(L, abs[:n_colors], abs[n_colors:]) | |
if not boundary_condition(labs): return np.inf | |
return (np.abs(Δ(labs, GRAY) - R).mean() + # 制約1: 無彩色との距離が R | |
np.var(Δ(labs, np.roll(labs, 1)))) # 制約2: 隣接する色との距離の分散が小さい | |
from crfmnes import CRFMNES | |
# 最適化の設定: よくわからないまま設定した。 | |
optimizer = CRFMNES(np.size(initial_abs), objective, initial_abs, 1, 8) | |
abs, f_best = optimizer.optimize(200) | |
# 得られた CIEL*a*b* 値について目的関数を評価 | |
labs = labcolor(L, abs[:n_colors], abs[n_colors:]) | |
Rdist = np.abs(Δ(labs, GRAY) - R) | |
Ddist = Δ(labs, GRAY) | |
print('半径', Rdist.min(), Rdist.max(), Rdist.var()) | |
print('色差', Ddist.min(), Ddist.max(), Ddist.var()) | |
# 描画 | |
import matplotlib.pyplot as plt | |
fig, ax = plt.subplots(subplot_kw=dict(xlim=[0, n_colors])) | |
for i, srgb in zip(range(len(labs)), srgb(labs)): | |
print(srgb) | |
ax.add_patch(plt.Rectangle((i, 0), 1, 1, fc=srgb.get_rgb_hex())) | |
ax.set_axis_off() | |
plt.show() |
今回のように色相環に沿ったグラデーションを扱うのなら LabColor
ではなく、それを極座標で表現した LCHabColor
を利用する方が素直だったらしい。変数を半減できると思う。要修正。
隣接する色の色差を Δ_i として、Σ Δ_i + Var[Δ_i]
を最小化するのが直接的な解のような気がする。
gradation-l.py
は、lab1
, lab2
のグラデーションが CIE Lab* 空間上の線分上に乗ることを仮定し (lab1, lab2)
を一次補完した媒介変数 t
を最適化している。ただ、最適解が本当に線分上に乗るのかという点について自信がない。
gradation-l2.py
を追加。直前のコメントのように Δ_i に関する最適化を実施している。初期値は同一色とした。目的関数の重みの与え方に工夫が必要だった。決め手は * n_colors
。これをいれるまで、両端の色が指定した二色に一致せず、すべての色が一致する局所解に陥ってしまった。もしかすると * (n_colors - 1)
の方が収束が早いかもしれない。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
境界条件を設定して、色を sRGB で表現可能な範囲に納めました。(boundary_condition)
指定した無彩色との色差、指定した隣接色との色差を出力するようにしました。