2015年8月26日水曜日

C#で熱画像っぽい色合いを作る その2

前回、IFとかで熱画像っぽい色を無理やり作って満足していたのだが、どうにも物足りなくなってきた。というのは、本家FLIRの熱画像が素晴らしかったからだ。

この画像は本家FLIRの熱画像のスケールをスペクトル解析したもの。赤、緑、青と、輝度が表示されている。白い輝度の線を見ればわかるが、綺麗に直線になっているのだ。つまり「輝度でモノクロ化」した場合に、熱画像が綺麗に白黒になる。これはカラーで作成した資料を白黒でコピーした場合に大きく力を発揮するだろう。


そして僕が作ったのがこちら。
前回よりは若干直線性が増していると思う。しかしまだ完全ではない。

次にこの色をつくるための計算式だ。

Rf = 0.05 + Math.Sin(Value * 1.8 - 0.05) * 0.9;
Gf = 0.5 + Math.Sin((Value + 1.4) * 3.2) * 0.4;
Bf = 0.5 + Math.Sin(Value * 1.6 - 1) * 0.7 + Math.Sin(Value * 6 + 0.4) * 0.5;

たったこれだけである。前回のIF処理のネストは何だったのか。でも三角関数を使えば三角波なども作れるようなので、三角関数の組み合わせで遊ぶのは結構楽しい。

上記の計算だが、赤と緑は1)オフセット、2)位相、3)周期、4)振幅の4個のパラメータで構成されている。青は上記に位相、周期、振幅がもう1セット加わった7個のパラメーターとなる。人間の手で調整するのは大変だが、目的の形をプログラムで判定することができれば総当りで最適な計算式を作れるかもしれない。特に直線性は総当りで調べて行ったほうが簡単に作れる気がする。ただ、直線性だけを見たのでは熱画像風に使えるとは限らないので、候補を幾つか作らせて人間が確認する、ということになるだろうが。

とにかく、今回わかったことは、IFなんて使わなくていい ということだ。パラメータを調整するだけで任意のスペクトルを作れるんだから、次はもうちょっと最適化した計算を報告できるようにしたい。

C#で熱画像っぽい色合いを作る



昔の熱画像風ではなく、FLIRの熱画像風の色合いになってる。また上半分は結構のっぺりした感じになってる。これは赤と緑が早々と飽和してしまうため。
ウインドウ内、上は0-1の熱画像風、下は各色の割合を表示している。また下のウインドウは上のそれをPaint.netで白黒化したものとなる。それとデコボコしながら右肩上がりになっているグラフはRGBを輝度に近似したグラフとなっている。デコボコして入るが、一応右肩上がりになっているので白黒化してもBk-Whで変化しているのがわかると思う。
この熱画像風化では内部でサインテーブルを使用している(Math.Sinを使用)。そのために計算コストはかなり高いものとなっている。また画像を見てもわかるとおりに青は綺麗に増加させているわけではないため、多数の浮動小数点演算が行われる。今回はPCでちょっと使いたかったのでこのようになっているが、マイコンで使うにはかなり大変だろう。

2015年8月22日土曜日

C#でWaveファイルを作る

C#でWAVEファイルを作りたくなったのでメモ。

ヘッダ部分はCの構造体みたいな感じにスマートに作れるっぽいけど、とりあえず今回は1個1個ちまちま書き込んでる。
Visual Studio 2015のC#6からプロパティの初期化を行えるようになってちょっと便利になった(コンストラクタ作れって話だが)。

このプログラムはFormアプリケーションとして動作するが、起動時にデスクトップに"test.wav"というファイルを作ったらすぐに終了するようになっている。コンストラクタ内でCloseすると例外が発生するためやってはいけない。こういう「フォームなんて要らねぇよ」という場合はLoadイベントに匿名メソッドを追加し、その中でCloseするとヨサゲ。

作成するWAVEファイルは、44.1kHz16bit2chの標準的な感じの物。長さは10秒で、ch1/ch2共に1336Hzと941Hzを加算した正弦波を作る。今のところ、自動生成に対応しているのはヘッダのみ。実際の音声データは自前でバイナリレベルを扱う必要がある。でもまぁクラスに配列を渡すと最大4GiB近くを確保しようとしてOutOfMemoryExceptionが発生しちゃうから仕方ないね!!
BinaryWriterにちまちま書き込んでいけば4GB(4*10^9byte)の波形データでも扱える(書きだすのに非常に時間が掛かるが)。大容量のデータをR/Wする場合はすべてをバッファに読み出すことは不可能なので、その辺りをうまくゴニョゴニョする必要がある。

このプログラムで作ったWAVEをSoundEngine Freeで開いてFFTするとこんな感じ。


2015年8月16日日曜日

SQLiteに追加する

すでにSQLiteConnection SQLiteConnにデータベースが開かれているとする。

{
    string[][] Adds =
    {
        new string[] { "422100000", "422175000", "業務用ch5", "NFM", "Unidentified", "", "Unidentified", "0", "0", "Blackman-Harris 4", "8000", "440", "1", "82", "600", "", "2015-08-15T00:00:00", "0", "0" },
    };
            
    using (SQLiteTransaction sqlt = SQLiteConn.BeginTransaction())
    {
        using (SQLiteCommand command = SQLiteConn.CreateCommand())
        {
            foreach (string []Add in Adds)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("insert into Data('Frequency','Center','Description','Mode','Protocol','Call Sign','Service','Shift Enabled','Shift','Filter Type','Filter Bandwidth','Filter Order','Squelch Enabled','Squelch','CW Shift','Notes','Date','Locked','Flagged') values(");

                foreach (string s in Add)
                {
                    sb.Append("'");
                    sb.Append(s);
                    sb.Append("',");
                }

                sb.Remove(sb.Length - 1, 1);
                sb.Append(")");

                //MessageBox.Show(sb.ToString());
                //Clipboard.SetText(sb.ToString());

                command.CommandText = sb.ToString();
                command.ExecuteNonQuery();
            }
        }
        sqlt.Commit();
    }

    MessageBox.Show("ok");
}

レコードを追加するにはSQLiteCommandのCommandTextプロパテのsetを使うらしい。"insert into Data"でDataテーブルに追加できる。要素名はテーブル名の後ろのカッコ内にカンマ区切りで書く。要素名は必ずしもシングルクォーテーションで囲む必要はないが、半角スペースとかが入る場合はシングルクォーテーションで囲む必要がある。とりあえずは囲んでおいたほうが無難。
データについては"values"のあとに書き込む。

今までデータを扱うようなプログラムを書いたことがなかったので、かなり苦労してる。まぁこれをちゃんと動くようにできれば別のものを作るときにはもっと楽にできる と考えて作ってるわけだが。最初っから趣味でプログラムを書いてると、大規模なプログラムになった時に大変だねぇ。。。

2015年8月15日土曜日

SQLiteを読む

SDR#のFrequency Managerは周波数をXMLで保存しているので、これを書き換えるにはExcelで十分なのだが、スキャンをしようとするとFrequency Manager + Scannerが必要で、こいつはXMLではなくData Base Fileで周波数などを保存している。そしてこれはSQLiteというモノらしい。
Frequency Manager + ScannerのUIは非常に使いづらく、周波数を5個入力するだけでも大変。ということでどうにかできないかと模索中。とりあえずSQLiteは読めるようになった。大抵の場合読めれば書くのは楽だが、Data Baseはどうなんだろう?

C#でSQLiteを読むにはここからsqlite-netFx45-setup-bundle-x64-2012-1.0.97.0.exeをダウンロードしてくる。今回はx64を使ったが、x32のほうが使い回しできていいと思う。またC#の参照に追加するにはインストールディレクトリから探してこないとダメみたい。僕の環境ではC:\Program Files\System.Data.SQLite\2012\binにSystem.Data.SQLite.dllが入っているので、こいつを追加した。またVisual StudioはExpress 2015を使ったが、Any CPUでは警告が出るために構成マネージャでx64のプラットフォームを設定した。これは2013とかでも同じ。

以下、SQLiteのデータベースを読むためのコード。

using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Text;
using System.Windows.Forms;

namespace FreqMgr
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void OpenButton_Click(object sender, EventArgs e)
        {
            string Path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\SDRSFM";
            string[] FileNames = FileSelect(Path, "FreqMgr.db", "Data Base File|*.db|All File|*.*");

            if (FileNames == null)
            {
                return;
            }

            List<string> tList = new List<string>();

            string[] DataNames = {
                "Id", "Frequency", "Center", "Description", "Mode", "Protocol", "Call Sign", "Service",
                "Shift Enabled", "Shift", "Filter Type", "Filter Bandwidth", "Filter Order",
                "Squelch Enabled", "Squelch", "CW Shift", "Notes", "Date", "Locked", "Flagged"
            };

            StringBuilder sb = new StringBuilder();

            using (SQLiteConnection conn = new SQLiteConnection("Data Source=" + FileNames[0]))
            {
                conn.Open();

                using (SQLiteCommand command = conn.CreateCommand())
                {
                    command.CommandText = "SELECT * FROM Data";

                    using (SQLiteDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            sb.Clear();
                            foreach (string DataName in DataNames)
                            {
                                sb.Append(reader[DataName].ToString() + ",");
                            }
                            tList.Add(sb.ToString().Substring(0, sb.Length - 1));
                        }
                    }
                }

                conn.Close();
            }

            sb.Clear();

            foreach (string DataName in DataNames)
            {
                sb.Append(DataName + ",");
            }
            sb.Remove(sb.Length - 1, 1);

            for (int i = 0; i < tList.Count; i++)
            {
                sb.Append("\n" + tList[i]);
            }

            //MessageBox.Show(sb.ToString());
            Clipboard.SetText(sb.ToString().Replace(',', '\t'));
        }

        private string[] FileSelect(string InitialDirectory, string FileName, string Filter, bool Multiselect = false)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.InitialDirectory = InitialDirectory;
            ofd.FileName = FileName;
            ofd.Filter = Filter;
            ofd.Multiselect = Multiselect;

            if (ofd.ShowDialog() != DialogResult.OK)
            {
                return (null);
            }

            return (ofd.FileNames);
        }
    }
}

データを表示するとこうなる。

とりあえずFrequency,Center,Description,Mode,Filter Bandwidthがあれば事足りそう。Dateは謎なデータだ。日しか使われていないし、UTCではなくローカルタイムのようだ。Tと書いてあるからといってUTC-07でもないらしい。おそらくDateは追加日で表示をソートするのに使ってる程度だと思うので、あんまり気にしなくても良さそう。

あとはSQLiteにデータを書き込むことができれば何とかなりそうだ。