6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「色 いろいろ」Advent Calendar 2021

Day 4

【きれい】RGB色空間の断面図アニメーションをPython+matplotlib+ffmpegで作成した

Last updated at Posted at 2018-07-22

できたもの

RGB_CS_cross-section.gif

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色空間 と言います:

RGBCS_exp02.png

この図では豆腐の表面の色だけが見えていますが、もちろん中身もカラフルです。断面図を見たい! と思ったのですが、"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() を追加すると軸やら何やら全部消えます。めっちゃきれい。食べたい。
RGB_color_space_base_nogrid.png

  1. matplotlibを使ったのはあまりいい選択ではなかった。matplotlibは3次元の描画があまり得意でないようで、確かに、出力される画像の左側、「いまここの断面を表示している」ことを示す平面が豆腐の手前か奥にあるようにしか描画されない。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?