できたもの
gifだとどうしても色数が減ってしまうので荒いです。動画版をyoutubeにアップしたのでぜひどうぞ。R軸方向・G軸方向・B軸方向それぞれの断面図アニメーションになってます: youtubeへ
解説
画像の色情報は(赤成分, 緑成分, 青成分)の組で表現することができます。RGBというやつです。
それぞれの成分は0から255の256階調のいずれかで表すのが普通です。
例:
-
(0,0,0)
... ぜんぶ0。黒 -
(255,255,255)
... ぜんぶ255。白 -
(255,0,0)
... R成分だけ255。真っ赤 -
(0,128,0)
... G成分だけ128。鈍い緑 -
(255,255,0)
... R成分とG成分が255。真っ黄色
R軸・G軸・B軸の3次元空間に各色を置くと、カラフルな豆腐として描画することができます。
こういう色の表し方を RGB色空間 と言います:
この図では豆腐の表面の色だけが見えていますが、もちろん中身もカラフルです。断面図を見たい! と思ったのですが、"RGB色空間 断面図"とかでググっても見つからないので作りました。
コード
軸方向にスライドしつつ断面図を1枚ずつ作る → それらの静止画を動画としてまとめる という2段構えです。
断面図を得る
Pythonとmatplotlibでやりました1。Pythonのバージョンは3.6.6、matplotlibのバージョンは2.2.2。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# R=255の平面, G=255の平面, B=255の平面を色付きで描画することで3DのRGB空間のイメージとする。
# 各平面の準備。0~255を1x1ずつ区切り、表示の際に各区画に色を振る
r_X = np.full((256, 256), 255)
r_y = np.linspace(0, 255, 256)
r_z = np.linspace(0, 255, 256)
r_Y, r_Z = np.meshgrid(r_y, r_z)
g_Y = np.full((256, 256), 255)
g_x = np.linspace(0, 255, 256)
g_z = np.linspace(0, 255, 256)
g_Z, g_X = np.meshgrid(g_z, g_x)
b_Z = np.full((256, 256), 255)
b_x = np.linspace(0, 255, 256)
b_y = np.linspace(0, 255, 256)
b_X, b_Y = np.meshgrid(b_x, b_y)
# 平面に振る色データを作成する。
# plot_surfaceに渡す平面のグリッド数と同じshape(=256x256)に、
# RGBをそれぞれはめ込んで256x256x3にする。
# facecolorsの制約上、RGBは0から1の範囲で指定する必要がある。
r_colors = np.zeros((256, 256, 3))
for i in range(256):
for j in range(256):
r_colors.itemset((j,i,0),1.0)
r_colors.itemset((j,i,1),i/255)
r_colors.itemset((j,i,2),j/255)
g_colors = np.zeros((256, 256, 3))
for i in range(256):
for j in range(256):
g_colors.itemset((i,j,0),i/255)
g_colors.itemset((i,j,1),1.0)
g_colors.itemset((i,j,2),j/255)
b_colors = np.zeros((256, 256, 3))
for i in range(256):
for j in range(256):
b_colors.itemset((j,i,0),i/255)
b_colors.itemset((j,i,1),j/255)
b_colors.itemset((j,i,2),1)
# 図の作成
space_margin=60
gp_margin=80
gp_fp = np.linspace(0-gp_margin, 255+gp_margin, 2)
gp_fp1, gp_fp2 = np.meshgrid(gp_fp, gp_fp)
for pos in range(256):
# 左側の図: RGB色空間とどこを表示しているか示す平面(guide_plain)
gp_vp = np.full((2, 2), pos-10) #10とか15は見た目ズレの補正
gp_vpZ = np.full((2, 2), pos-15)
fig = plt.figure(figsize=(16,9))
ax1 = fig.add_subplot(121, projection='3d')
ax1.set_xlabel("R", fontsize=20)
ax1.set_ylabel("G", fontsize=20)
ax1.set_zlabel("B", fontsize=20)
ax1.set_xlim(0-space_margin, 255+space_margin)
ax1.set_ylim(0-space_margin, 255+space_margin)
ax1.set_zlim(0-space_margin, 255+space_margin)
ax1.plot_surface(gp_vp, gp_fp1, gp_fp2, alpha=0.1, linewidth=0.5, edgecolors='black') #R軸
#ax1.plot_surface(gp_fp1, gp_vp, gp_fp2, alpha=0.1, linewidth=0.5, edgecolors='black') #G軸
#ax1.plot_surface(gp_fp1, gp_fp2, gp_vpZ, alpha=0.1, linewidth=0.5, edgecolors='black') #B軸
ax1.plot_surface(r_X, r_Y, r_Z, facecolors=r_colors, shade=False)
ax1.plot_surface(g_X, g_Y, g_Z, facecolors=g_colors, shade=False)
ax1.plot_surface(b_X, b_Y, b_Z, facecolors=b_colors, shade=False)
ax1.view_init(45.0, 45.0)
# 右側の図: 断面図
cross_section = np.zeros((256,256,3), np.uint8)
cross_section[:,:] = [pos, 0, 0] #R軸
#cross_section[:,:] = [0, pos, 0] #G軸
#cross_section[:,:] = [0, 0, pos] #B軸
for i in range(256):
for j in range(256):
cross_section.itemset((i,j,1),i) #R軸
cross_section.itemset((i,j,2),j) #R軸
#cross_section.itemset((i,j,0),i) #G軸
#cross_section.itemset((i,j,2),j) #G軸
#cross_section.itemset((i,j,0),i) #B軸
#cross_section.itemset((i,j,1),j) #B軸
# 左側の図と比較しやすくするために回転など
cross_section = np.rot90(cross_section, k=1) #R軸
#cross_section = cross_section.transpose(1, 0, 2) #G軸
#cross_section = np.rot90(cross_section, k=2) #G軸
#cross_section = np.rot90(cross_section, k=-1) #B軸
ax2 = fig.add_subplot(122)
ax2.tick_params(labelbottom=False, bottom=False, labelleft=False, left=False)
ax2.imshow(cross_section, vmin = 0, vmax = 255)
ax2.set_title("cross-section view: R={0}".format(pos), fontsize=20) #R軸
#ax2.set_title("cross-section view: G={0}".format(pos), fontsize=20) #G軸
#ax2.set_title("cross-section view: B={0}".format(pos), fontsize=20) #B軸
plt.savefig("RGB_color_space" + '{:0>3}'.format(pos) + ".png", dpi=300)
plt.close()
実行するとR軸方向の断面図がR=0からR=255まで256枚 出てきます。G軸方向・B軸方向の断面図を作る場合は、コード中で"#G軸"とか"#B軸"と書いてある行のコメントアウトを外します。
動画化
ffmpegを使って、上で得た連番静止画を動画に変換します。ffmpegのバージョンは2.8.14。
$ ffmpeg -framerate 30 -i RGB_color_space%03d.png -vcodec libx264 -b 2500k -an -pix_fmt yuv420p Rdir.mp4
おまけ: 軸とかすべて消すと超きれい
ax.set_axis_off()
を追加すると軸やら何やら全部消えます。めっちゃきれい。食べたい。
-
matplotlibを使ったのはあまりいい選択ではなかった。matplotlibは3次元の描画があまり得意でないようで、確かに、出力される画像の左側、「いまここの断面を表示している」ことを示す平面が豆腐の手前か奥にあるようにしか描画されない。 ↩