2017年12月24日日曜日

Iron palettes

 FLIRの画像でよく使われる、青紫っぽい熱画像のスケール。

 熱画像だと、温かいところが赤、冷たいところが青、中間が緑、というのがよくあるが、これは輝度と値が比例しないので、慣れないと理解しづらいという問題がある。また、印刷する際にカラーで印刷しないと情報が失われてしまうので、紙ベースで処理してコストを気にするようなところでは、モノクロで印刷しても情報が失われないIron paletteのほうが良い。輝度と値が比例するので、慣れていなくてもぱっと見て理解できる、という利点もある。


 javascript - Thermal imaging palette - Stack Overflow
 適当な大きさのパレットで持って、中間は補完しろ、みたいなことが書いてある。

***

 色をグラフ化すると、なんとなく波形っぽいので、とりあえず正弦波で近似してみた。


 1色あたり、8個のパラメータが必要になる。C#で乱数を作って、FLIRの画像から取り出したスケールとの差を最小になるパラメータを書き出した。500万セットくらい乱数を作っているが、5分位で処理できる。アルゴリズム上の問題で500万セットくらいしか処理できないが、アルゴリズムを修正すれば寝てる間に500倍くらいの乱数セットを処理できそう。
 ただ、今は1色ごとをFLIRの値と比較しているが、本来であれば輝度へ変換した上で、輝度のリニアリティを優先するべき。このあたりは追々。


 値から色に変換するには、値を0-1で正規化して、(sin(値*周期1+位相1)*ゲイン1+オフセット1)*(sin(値*周期2+位相2)*ゲイン2*オフセット2)で計算できる。

 上記のパラメータではこんな感じ。


 かなり綺麗に表現できてる気がする。
 三角関数が6個必要なので、組み込みだと計算負荷が高め。ま、組み込みでこういうスケールが必要な事態は少ないだろうし、問題ないはず。PCとかで処理するなら、三角関数で好きなだけ分解能を増やせる。

 緑の片方はゲインが0に近いので、正弦波5個で計算できるかも。


 最初はパラメータを手動で設定していたけど、乱数で設定したら簡単にそれっぽい値にできた。それなりに時間はかかるけど、放置しておけば終わるので楽。
 もうちょっと色々試してみる予定。


追記:2017/12/25
 アルゴリズムを変えて生成してみた。



 微妙に直線性が悪い気がするのと、グレーがなんとなくガタガタしてる気がする。
 とりあえず0で輝度が10%、1で輝度が85%になるような値になっている。0が完全な黒ではなく、1も完全な白ではない。背景が真っ黒のページにグラフを表示しても境界がわかりやすいし、真っ白な紙に印刷しても同様。
 なんとなく1側が青白くて、輝度が低い気がする。

 とりあえず以下のソースコードは別言語への移植も含めて自由に使っていいです。完全無保証無サポートですけど。あとFLIR Systemsとかに怒られても知らないけど(あ、怒られたら怒られたって教えてくれると助かります)。

 C# src
public class Iron_palette
{
    protected const double R_f1 = 0.5107;
    protected const double R_f2 = 0.6085;
    protected const double G_f1 = 2.9332;
    protected const double G_f2 = 0.0599;
    protected const double B_f1 = 4.6611;
    protected const double B_f2 = 0.646;

    protected const double R_p1 = 1.0094;
    protected const double R_p2 = 2.8977;
    protected const double G_p1 = 1.193;
    protected const double G_p2 = 0.8202;
    protected const double B_p1 = 1.5839;
    protected const double B_p2 = 1.0357;

    protected const double R_g1 = 3.8616;
    protected const double R_g2 = 2.3461;
    protected const double G_g1 = 4.2843;
    protected const double G_g2 = 0.2103;
    protected const double B_g1 = 2.7105;
    protected const double B_g2 = 1.6943;

    protected const double R_o1 = -3.2597;
    protected const double R_o2 = 2.1916;
    protected const double G_o1 = -4.9513;
    protected const double G_o2 = -0.2657;
    protected const double B_o1 = 2.8918;
    protected const double B_o2 = -1.4018;

    public static Color to_color(double value)
    {
        if (value < 0 || value > 1)
        {
            throw (new ArgumentOutOfRangeException());
        }

        return (Color.FromArgb(
            (int)(
                (Math.Sin(value * R_f1 + R_p1) * R_g1 + R_o1) *
                (Math.Sin(value * R_f2 + R_p2) * R_g2 + R_o2) *
                255),
            (int)(
                (Math.Sin(value * G_f1 + G_p1) * G_g1 + G_o1) *
                (Math.Sin(value * G_f2 + G_p2) * G_g2 + G_o2) *
                255),
            (int)(
                (Math.Sin(value * B_f1 + B_p1) * B_g1 + B_o1) *
                (Math.Sin(value * B_f2 + B_p2) * B_g2 + B_o2) *
                255)));
    }
}

 それぞれの三角関数の値。左上の、ガタガタしてるのはFLIRの画像から拾ってきた輝度。


 ざっと眺める感じ、1個の直線+1個の三角関数、で近似できそうな気がしてきた。Rs2はほとんど直線っぽいし、Gs2もほとんど直線。Bs2は微妙に曲線だけど、これくらいなら直線で近似できるんじゃないかなぁ。
 あとで片方直線、片方三角関数、でパラメータを探ってみよう。
 何かのシミュレーション結果をIron palettesで動画化する場合、4k動画なら1枚で約50M回のsin計算が必要になる。60fpsで1分の動画なら180G回のsin計算が必要になる。これが半分になれば結構大きい気がする。

0 件のコメント:

コメントを投稿