« 最大公約数 - ユークリッドの互除法 | メイン | 関数オブジェクトの名前 »

2008年02月07日

日常の備忘録:: 色温度の導出

    

色温度という言葉は良く聞くけど、それが何かは良くわからなかった。
調べてみると、理想的な黒体がある温度において放射する光の色なんだとか。
よくある理想物体か。
まあ、それはどうでも良くて、ある温度での色がわかると炎や爆発などの色を作るときに都合が良い。
と思ったんだけど、どうも計算式で求められるものではなく、「黒体の絶対温度と色度座標との関係」というデータテーブルが存在し、そこから導出するっぽい。
でも、そのデータテーブルが見つけられなかったので、とりあえずこの色温度-色色雑学 | コニカミノルタのページのグラフから、交点っぽいところをリストアップして、このデータを基に近似式を作ることにした。
近似式は、xy色度図上の x と y の関係、ケルビンと x との関係を出し、ケルビンから x と y を求められるようにする。
ということで、このデータを Excel に入れて近似曲線を描かせる。
x と y の関係は、単純な2次の多項式近似でかなり近いものが出た。
※ ちなみに Excel のグラフの近似曲線は、近似曲線の書式設定のオプションの数式の表示にチェックを入れてやると数式を見ることが出来る。また、多項式近似は、Excel で回帰分析した結果と一致するようだ。

が、ケルビンとの関係は誤差が大きい。
いい近似曲線が出せない。
誤差が大きくてもいいかと思ったが、よくよく色温度の図を見てみるとケルビンは対数的な変化をしているように見える。
試しにケルビンの自然対数値を求め、それと x とのグラフを作ってやって3次の多項式近似で近似曲線を描くとかなり一致している。
ケルビンは対数値を取ってから求めた方が良さそうだ。

以上の方法で求めた近似式から出した色を描画すると以下のようになった。

2008_02_07_color_temperture.png

範囲は、自然対数値で 7.0 ~ 10.0 。
ケルビンに直すと 1096K ~ 22026K 。
Wikipedia の 色温度 のカラーチャートと上の図はだいぶ似ていると思う。

ってことで、ケルビンから色温度の色を求めるソースは以下の通り。

#include <math.h>

class ColorTemperture
{
  //! ケルビンの自然対数値からx座標に変換する
  static inline float KelvinLnToX( float x ) {
    return( -0.0108*x*x*x + 0.3407*x*x - 3.5987*x + 12.929 );
  }
  //! ケルビンからx座標に変換する
  static inline float KelvinToX( float K ) {
    return KelvinLnToX( log(K) );
  }
  //! x座標からy座標を求める
  static inline float XToY( float X ) {
    return -2.700931324 * X * X + 2.675633166 * X - 0.250545382;
  }
public:
  //! @param K : ケルビン
  //! @param Y : 輝度 - 0.0 ~ 1.0
  //! @return RGB24値 - 0x00RRGGBB
  static unsigned long GetColor( float K, float Y );
  //! @param K : ケルビンの自然対数値
  //! @param Y : 輝度 - 0.0 ~ 1.0
  //! @return RGB24値 - 0x00RRGGBB
  static unsigned long GetColorLn( float K, float Y );
};
unsigned long ColorTemperture::GetColor( float K, float Y )
{
  RColor  Yxy;
  Yxy.Y = Y;
  Yxy.x = KelvinToX( K );
  Yxy.y = XToY( Yxy.x );
  return ColorConversion::YxyToRGB32( Yxy );
}

unsigned long ColorTemperture::GetColorLn( float K, float Y )
{
  RColor  Yxy;
  Yxy.Y = Y;
  Yxy.x = KelvinLnToX( K );
  Yxy.y = XToY( Yxy.x );
  return ColorConversion::YxyToRGB32( Yxy );
}

ケルビンとの関係で出したのは、Yxy 色空間の色なので、これは RGB 色空間に変換してやらないと使い辛い。
上のソースで呼んでいる色変換メソッドのソースは以下の通り。

//! 実数形式での色
union RColor {
  struct { float  R, G, B; };
  struct { float  Y, x, y; };
};
//! 色変換
struct ColorConversion {
  // RGB -> Yxy
  static void RGBToYxy( const RColor& rgb, RColor& Yxy ) {
    float X = 0.412453 * rgb.R + 0.35758 * rgb.G + 0.180423 * rgb.B;
    Yxy.Y   = 0.212671 * rgb.R + 0.71516 * rgb.G + 0.072169 * rgb.B;
    float Z = 0.019334 * rgb.R + 0.119193 * rgb.G + 0.950227 * rgb.B;
    Yxy.x = X / ( X + Yxy.Y + Z);
    Yxy.y = Yxy.Y / ( X + Yxy.Y + Z);
  }
  // Yxy -> RGB
  static void YxyToRGB( const RColor& Yxy, RColor& rgb ) {
    float X = Yxy.x / Yxy.y * Yxy.Y;
    float Z = (1.0 - Yxy.x - Yxy.y) / Yxy.y * Yxy.Y;
    rgb.R = 3.240479 * X - 1.53715 * Yxy.Y - 0.498535 * Z;
    rgb.G = -0.969256 * X + 1.875991 * Yxy.Y + 0.041556 * Z;
    rgb.B = 0.055648 * X - 0.204043 * Yxy.Y + 1.057311 * Z;
  }
  // Yxy -> RGB24 (0x00RRGGBB)
  static unsigned long YxyToRGB32( const RColor& Yxy ) {
    RColor  rgb;
    YxyToRGB( Yxy, rgb );
    // 各値を0 - 255にする
    int r = rgb.R * 255;
    int g = rgb.G * 255;
    int b = rgb.B * 255;
    r = r >= 255 ? 255 : r <= 0 ? 0 : r;
    g = g >= 255 ? 255 : g <= 0 ? 0 : g;
    b = b >= 255 ? 255 : b <= 0 ? 0 : b;
    return ( (r << 16) | (g << 8) | b );
  }
};

ってことで、このソースがあればケルビンから色が出せる。

「黒体の絶対温度と色度座標との関係」というデータテーブルがどこにあるか知っている人がいたら教えてくれるとありがたいです。
上のソースでもかなり近いものが出ているけど、そのデータがあればもっと近づけられるはず。
まあ、ゲームなどの用途であれば上のソースでもなんら問題ないんだけど。



投稿者 Takenori : 2008年02月07日 23:01



コメント

黒体放射の式はこちら
http://ja.wikipedia.org/wiki/%E9%BB%92%E4%BD%93
スペクトルが得られるので、適当な等色関数を使えばX,Y,Zに直せますが...
標準光源の式を使うのが手っ取り早いでしょう
http://en.wikipedia.org/wiki/Standard_illuminant

あと、RGBにはガンマがかかっているので、Yxyと変換する際はリニアに戻してやらないと不正確です。
手前味噌ですがこちらもご参考になれば。
http://www.geocities.jp/aji_0/colormetrix.html

投稿者 あじ : 2008年02月28日 00:39

あじさん、こんにちは。

ありがとうございます。
やはり変換式が既にあったのですね。
そちらに直したいと思います。

ただ、RGBにはガンマがかかっていると言うのがよく理解できませんでした。
ガンマがかかっているかどうかは入力ソースによると思うのですが何か認識違いをしているのでしょうか?
もう少し調べてみようと思います。

それでは。

投稿者 Takenori : 2008年03月01日 14:15

PCで扱うRGBも、デジタル映像信号のYUVもみんな2.2~2.4のガンマがかかっています。
数字が変わるに従って、滑らかにグラデーションする場合はまずガンマがかかっています。
色彩工学的な数字の決め方をしていますので。

ですが、光学的な計算の場合はガンマ1.0でないといけません。
RGB⇔XYZの変換は光学的な変換なので、ガンマの補正が必要なのです。
・RGB(γ2.2)⇒XYZなら RGBを2.2乗してからRGB⇒XYZ変換、
・XYZ⇒RGB(γ2.2)なら XTZ⇒RGB変換してRGBを1/2.2乗
となります。

投稿者 あじ : 2008年03月03日 21:27

あじさん、こんばんは。

例えば、デジカメのRAWデータなどは、ガンマ補正がかかっていないと思います。
そこで引っかかっていました。
ほとんどのRGBデータにはガンマ補正がかかっているので、工学的な輝度値への変換が必要ということだったのですね。
つまり、Yxy から RGB へ変換してディスプレイで表示するためには、ガンマ補正もしくはトーンマッピングのような処理が必要であると。

理解できました。
ありがとうございます。

それでは。

投稿者 Takenori : 2008年03月03日 22:19


comments powered by Disqus
Total : Today : Yesterday : なかのひと