2017年9月17日日曜日

C#でエンコーディング変換

 基本的にC#はエンコーディングの事を考えなくても良いのだけど、今回諸事情でUInt16で表現された文字を別のエンコードにする必要があったので、試してみた。

 今回はShift JISとUnicodeの変換を行った。

static UInt16 ShiftJis2Unicode(UInt16 ShiftJisChar)
{
 byte[] buff2 = Encoding.Unicode.GetBytes(
  new[] {
   Encoding.GetEncoding("Shift_JIS").GetChars(
    new [] {
     (byte)(ShiftJisChar / 256),
     (byte)(ShiftJisChar % 256),
    }
   )[0]
  });

 return ((UInt16)(buff2[0] | buff2[1] << 8));
}

static UInt16 Unicode2ShiftJis(UInt16 UnicodeChar)
{
 byte[] buff2 = Encoding.GetEncoding("Shift_JIS").GetBytes(
  new[] {
   Encoding.Unicode.GetChars(
    new[] {
     (byte)(UnicodeChar % 256),
     (byte)(UnicodeChar / 256),
    }
   )[0]
  });

 return ((UInt16)(buff2[0] << 8 | buff2[1]));
}

 Shift JISはASCIIと互換性があり、また半角カナも1バイトで表現できる。が、今回はマルチバイト文字しか考えていないので、シングルバイト文字を入れると問題が起きるはず。
 Unicodeは文字にかかわらず2バイトで表現するので、たぶん動くはず。

 UnicodeとUTF-8とかの変換は、また別の問題なので今回は扱わない。

 ちなみにC#のcharはUnicodeらしく、intに入れるとUnicodeのインデックスになる。intをcharにキャストするとUnicodeで文字化される。今回はEncoding.Unicode.GetCharsとか使ったけど、Unicode対ならcharのキャストでも良いかも。

 他のエンコーディングとの変換も同じようにすれば動くはずだけど、それぞれバイトオーダーや固定長・可変長みたいなバリエーションがあるので、ちょっと面倒。

 今回は文字単位での変換だったけど、文字列の変換だともっと簡単にできるみたいね(未確認)。

2017年9月14日木曜日

PNGファイルシグネチャ

 ソース:PNG 仕様書:原理 12.12.PNGファイルシグネチャ
 ソースの文章を理解できるなら、以下の文章は読まなくていい。

 
 PNGファイルには最初の1バイトが0x89で、次の7バイトが"PNG\r\n\x1A\n"となっている。これの理由について。

 まず最初の0x89だが、これは2進数だと10001001になる。当時、上位1bitを捨てて、7bitで転送するシステムが有ったため、これを通したPNGデータは最初の1バイトが0x09となり、データが破損していることが確認できる。
 また、ASCIIコードは0x00から0x7Fの範囲を使用するので、ソフトウェアによっては明らかにASCIIコードでないファイルとして、テキストデータと誤認されることを防ぐことができる。とはいえ、他の文字コードでは1文字目が何らかの文字として誤認されることもある。例えば、Shift JISでは"\x89P"の一連の文字(0x8950)は’臼’(ウス)の文字が割り当てられている。そのため、日本向けWindowsのメモ帳でPNGファイルを開くと、先頭は"臼NG"で始まる。

 次の"PNG"の3文字は、誤ってPNGファイルをテキストエディタで開いてしまった際に、"PNG"の文字列を表示することにより、PNGデータだと人間に通知することができる。ただし、上記の通り、システムのエンコードによっては正しくPNGと表示されない場合もある。

 次の"\r\n"は、ファイルシステムによって"\r\n"を"\n"に変換されたことを検出することができる。またその後の"\n"も、ファイルシステムによって"\n"が"\r\n"に変換されたことを検出することができる。
 これは、OSによって異なる改行コードの違いをファイルシステムで吸収する際に発生する問題を検出することができる。例えば、C言語のfopenでテキストファイルとして開くと、\r\nと\nの変換が行われる(バイナリファイルとして開けば変換は行われない)。

 その間にある、'\x1A'は、一部のファイルシステムではEnd of Fileを意味し、この文字を検出したシステム(テキストエディタ)はそれ以降のファイルの読み込みを終了する場合がある。

 PNGのファイルシグネチャはこの8バイトだが、場合によっては続けて4バイト程度を読み出す場合もある。
 この4バイトはIHDRチャンクのサイズで、32bitのネットワークバイトオーダーで13が記録されている。これは0x00 0x00 0x00 0x0Dの4バイトだが、システムによってはヌル文字(0x00)を省略する場合があり、その場合は00 00 00 0Dを正しく読めないため、データが破損していることが確認できる。


 以上、PNGのファイルシグネチャの話でした。
 古いフォーマットを見てると当時のシステムの特徴とか出てきて面白いよね。

2017年9月13日水曜日

PNG

 C#で64bit(16bit x 4ch)のPNGを吐き出すライブラリを作っていました。といっても、まだライブラリと言うほどまとまってはいませんが。

 気付いた点とか。

・フィルタについて。
 フィルタは1バイト毎に行われる。1色16bitでも、フィルタは1バイト単位で行われる。なので8bitでも16bitでも同じ関数を使いまわすことができる(1行の幅を、画素数ではなく、バイト数で持たせる)。

追記:2017/09/15
 フィルタの計算量は、None<Sub<Up<Average<Paethという感じか。Noneは計算量ゼロ。SubはUpより少し少ない(ソースコード量とトレードオフ)。
 フィルタは各行毎に設定されるので、forで行を回し、その中でフィルタに応じてforで列を回すことになる。
 Subはfor[y]の中で直接for[x-1]を回せるので、計算量が最小(Noneを除くと)。
 Upはfor[y]の中でy==0をチェックし、trueならNoneとして扱う。それ以外はfor[x]で回すので、ifがあること、1列多く回すこと、等からSubよりも計算量が若干多い。
 Averageはfor[y] for[x]の中で2個のifがあり、また四則演算も多いので遅くなる。ただ、四則演算を除けば、分岐予測が間違わない限りはかなり早いと思う。分岐が必用なのはx==0とy==0のときだけなので、これもソースコード量とトレードオフかな。
 Paethもfor[y] for[x]のループだが、中にifが6個ある。内3個はx==0、y==0、x==y==0なので、予測分岐がミスらなければ早く、ソースコード量とトレードオフになる。一方、残りの3個はピクセル値によって変化するので、それなりの確率で予測が外れることになる(if-else if-else ifなので、1回目で通ればハザードは少なくて済む。が、コレばっかりは画像次第)。
 予測分岐については、CPUとかコンパイラの判断になるだろうから、こればっかりはどうしようもない気がする。

 フィルタリングは、エンコードは減算、デコードは加算で、計算式自体は共通。ただしループの向きが違うので、そこはエンコードとデコードで作り分ける必要がある。
 ループの中でテーブルに対して関数の加算・減算を行う、という流れが楽でいい。ただし計算内容で必要な情報が多いので、関数を呼ぶとスタック操作が発生する。計算式自体はかなり少ないので、インライン展開を期待するか、あるいはべた書きで同じコードを2箇所に書いてしまうか。

・圧縮について。
 PNGの圧縮はDeflateのラッパーであるzlibを使う。が、なぜかDeflateでもpaint.netで読めた(詳細未確認)。


 とりあえず、64bitRGBAのPNGを保存することができるようになった。IDATの圧縮がなんとなくわかってきたので、多倍長独自チャンクも似たような感じで作れそう。

 PNGにはテキストを保存できるチャンクがあるので、中間データとして使う際は、ここにファイルの内容を入れると良さそう。
 基本的にNULL文字終端っぽいので、PNGで使われるテキスト2組の後ろに、バイナリデータを入れられないかと考えている。ソフトウェアで中間データを扱う際に、続くデータチャンクの内容を知らせるためのモノ。
 バイナリデータを入れるのがマズければ、テキスト形式である程度種類を識別させて、続くデータチャンクにバイナリデータを入れても良い。

 以前に考えた時は、IDATが8bit、idBTが16bit、idCTが32bit、のようにしようと思っていたが、コレだとPNGのフォーマットにより8bitデータが1個しか持てない。これはちょっとまずいと思うので、もうちょっと違う形式にする必要がありそう。ま、idATとかでも良いのだけど。
 あと、データチャンクの符号をどうするか。ベクトルデータとかを扱うなら、当然符号なしはマズイわけで、とはいえすべてを符号ありとして扱うと画像データを扱う時にマズイわけで。3文字目の大文字小文字で分けてもいいけど、いちおう予約されてる以上はいじらずに置いたほうが良い気がする。
 例えばidATがu8bit、idBTがs8bit、idCTがu16bit、idDTがs16bit、みたいな感じだとどうだろうか。この形式でも最大65536bitまでの値を持てるから、30年先くらいは安心して使えると思う。


 とりあえず、中間データをPNGに埋め込むライブラリは、PNG画像に中間データを注入する、あるいは中間データを抽出するような動きになると思う。もしくは、画像はBitmapかImageにして扱うとか。画像処理ライブラリまで作り込むのは面倒なので。
 ま、そのあたりは追々。

追記:2017/09/14
 よく考えれば、というか、普通に考えれば、PNGパケットからデータを抜き出す際に、画像を別に取り出す必要性は皆無なんだよな。だってC#のImageなりBitmapで開けば画像は取り出せる。ということは、PNGパケットからデータを取り出す部分と、PNGパケットにデータを埋め込む処理を作ればいい。

C#でDeflateをMemoryStreamで

 C#でMemoryStreamを使って(ファイル操作をせずに)Deflateの圧縮をしようと思ったら、結構穴にハマったのでメモ。

byte[] src = new byte[1024],dst;

for(int i = 0; i < src.Length; i++)
{
 src[i] = (byte)(i % 256);
}

using (MemoryStream ms = new MemoryStream())
using (DeflateStream ds = new DeflateStream(ms, CompressionMode.Compress, true))
{
 ds.Write(src, 0, src.Length);
 ds.Dispose();
 ms.Position = 0;

 dst = new byte[ms.Length];
 ms.Read(dst, 0, dst.Length);
}

src = dst;

using (MemoryStream msSrc = new MemoryStream())
using (MemoryStream msDst = new MemoryStream())
using (DeflateStream ds = new DeflateStream(msSrc, CompressionMode.Decompress))
{
 msSrc.Write(src, 0, src.Length);
 msSrc.Position = 0;

 ds.CopyTo(msDst);
 msDst.Position = 0;

 dst = new byte[msDst.Length];
 msDst.Read(dst, 0, dst.Length);
}

 圧縮と展開、両方のソース。

 圧縮はCompressionMode.Compressで行う。
 DeflateStreamのコンストラクタで第3引数を省略するとfalseになるが、この場合は圧縮が終わった時点で第1引数に指定したStreamをCloseする。この動作は、FileStreamを使ったときには問題にならないが、MemoryStreamを使った場合は、あとから読めなくなる。なので、trueを指定してStreamがCloseされないようにする。
 また、DeflateStreamは、StreamがCloseされた際に圧縮を行うらしい。ということで、DeflateStream.Dispose()で強制的に閉じておく。
 そして、StreamのPositionはStreamの最後に移動しているので、あらかじめ0に戻しておく。
 最後に、Stream.Lengthを使って圧縮後の容量を確認し、Stream.Readでメモリに戻す。

 展開はCompressionMode.Decompressで行う。
 この際は読み込みStreamは閉じられてもかまわないので、第3引数は省略可能。
 まずはSrcStreamにデータをコピーする。DeflateStreamは入力StreamのPositionを戻さないので、自分でSrcStream.Position=0にしておく。
 そしてDeflateStream.CopyToでDstStreamにデータを移動する。CopyToやReadでDeflateStreamを読み出した際に、SrcStreamからの展開を行うらしい。
 またCopyToで読み出すと、Positionも移動するので、ここでも0にしておく。
 最後に、DstStreamのLengthで容量を確認し、Readでメモリに戻す。


 とりあえず、上記のサンプルデータだとちゃんと展開はできることを確認している。

 最初、入力に乱数を使っていて、全く圧縮されなく悩んだ。Deflateはハフマン符号圧縮と違い、過去のメッセージと同じデータを探してくるので、乱数のような同じメッセージが複数回出てこないようなデータに対しては圧縮率が極めて低い。
 今回のサンプルのような、同じメッセージが複数回出てくるようなデータに対しては、かなり圧縮率が良い。例えば、今回のサンプルデータは0x00から0xFFまでの256バイトのメッセージが4回出てくる。最初の1回は圧縮できないが、残りの3回は「nバイト前から長さlバイトと同じ」という情報があれば良いので、その分を圧縮できる。今回は1024バイトが280バイトまで圧縮された。

/*
 そう考えると、PNGの圧縮効率ってあんまり高くない気がする。ピクセルデータを直接ハフマン符号圧縮したほうが効率良さそう。当時のコンピュータの処理能力とか、そういう制限だったんだろうなぁ。
 PNGは浮動小数点演算とかいらない分、JPEGよりは組み込み向けといえるのか? どうだろう。JPEGはブロック単体で展開できるはずだけど、PNGを展開するには最大で圧縮窓全体をカバーするメモリ(=32KiB)が必要になりそう。PNGはフォーマットや圧縮方法にバリエーションが多いので、自前でライブラリ書くのは面倒だな。
 最近扱った組み込みの画像を扱う処理だと、マイコン内の処理が8bitIndexedだったので、BMP8bppIndexed限定にした。コレなら読み込みプログラムも凄まじく簡単に書ける。JPEGだとマイコン内で減色アルゴリズムを走らせる必要がある。PNGは8bppIndexedも保存できるけど、大抵のドローソフトでは8bppPNGでは保存できない気がする。結局、リソースの制限された組み込み系では8bppIndexedBMPが楽だと思う。読み込み早いし。プログラム楽だし。大抵のドローソフトで保存できるし。
*/

2017年9月12日火曜日

STM32/FreeRTOSでCPU使用率的な

 STM32のTIMを使ってFreeRTOSのCPU使用率的なパルスを出す。

 まずTIMxをPWM1で初期化する。プリスケーラは500kHzくらいになるように設定。カウンタは最大(65535)に設定する。1周約0.13秒くらいになる。パルス幅は1に設定し、約2usecくらいのパルスを作る。
 FreeRTOSのIdleHookでTIMx.CNT = 0を行う。これにより、IdleHookが呼ばれてから2usecのパルスが出力される。
 優先度が0より高いタスクが実行中の場合、IdleHookが呼ばれなくなるので、最後にIdleHookが呼ばれてから2usec以降はパルスがLowとなる。
 つまり、このGPIOを見ていればMCUがアイドル状態の割合を知ることができる。
 2usec未満の処理であれば出力されないが、168MHzのコアでは2usecに300命令くらいしか処理できないし、OSのタスクスケジューラでも結構な命令数が必用なので、2usecくらいの分解能でも充分だと思う。

 この方法だとTIM1個とGPIO1本が必用で、マイコン内でアイドル率を知ることができないが、結構信頼性の高いデータだと思う。
 あとはZEROPLUSのロジアナでデューティー比を見れれば最高なんだけど、LAP-Cではデューティー比は見れないらしい。デジオシだとデューティー比を表示できるけど、あんまり信頼性高くない。

 もう一本タイマを使えるなら、TIMxをIdleHookでクリアしてアイドル率のパルスを作り、TIMyをGatedして、10msec毎にTIMyのカウンタを確認して、という感じだろうか。これならマイコン内で使用率を知ることができる。それにGPIOも必要ない。

銃撃ち比べ

 なろうで現代兵器チート的なの読んでると、主人公が銃を持つ時に、安全管理は重要だぜっ!みたいなスタンスの割に、結構簡単に銃のセーフティーを外している描写が見受けられます。討伐系のクエスト受けて森に入って、物音がしたら安全装置解除、とか。おそらくは即応性を高めるための描写ですが、本当に事前にセーフティーを解除したら早くなるの? ってことで、試してみました。
 といっても、統計的には相当酷い計測方法なので、あくまで参考程度に。そもそも僕は武器操作マスターチートとか一切無い素人だしね。

 計測方法は、Stealth-Target ST17のタクティカルターゲットで65枚のターゲットを撃ち終わるまでの時間です。銃はM4を使用しました。弾倉は30発入りを3本使用し、エマージェンシーリロードを使用しています。


 今回はハイレディで1セットごとにセーフティを使用する撃ち方と、ハイレディで最初から最後までセーフティをセミにして撃つ方法を試しました。他に依託射撃(セーフ有りのみ)とローレディ(セーフ有りのみ)も計測しました。

 結果
1) ハイレディ/セーフティ使用:160秒/70発(5発ミス)
2) ハイレディ/セーフティ未使用:166秒/76発(11発ミス)
3) 依託/セーフティ使用:145秒/74発(9発ミス)
4) ローレディ/セーフティ使用:180秒(62枚で時間切れ)/68発(6発ミス)

 という感じでした(先頭の数字は射撃順)。
 セーフティを使うほうが早いですが、後に撃ったセーフティ未使用は、疲れてきたためにミスファイアが増えて時間が遅くなったものと思われます。
 三脚を使った依託射撃をまじめにやったのは初めてなので、調整が合わずにちょっと撃ちづらかったです。もうちょっと早くなるかも。でも銃の先端が固定されてしまうと、照準を変えるのに体の位置を動かす必要があるので、立射よりはかなり撃ちづらいです。
 ハイレディは銃の重心をあまり移動させることがないので、重量物を持ってる程度の疲れしかありません。一方、ローレディは銃を大きく移動させるため、サポートハンドがかなり疲れます。

 ということで、簡単に計測した限りでは、セーフティの有無では有意差は無い気がします。
 機会があれば、セーフティ未使用→セーフティ使用で時間を計測して、平均を取ったほうが良いかな、と思っています。


 今回は操作性のいいM4を使いましたが、89式小銃とかだとどうなるんだろう。アレも慣れてる人は操作が凄まじく早いので、89式の操作に慣れてる人に計測してもらいたいなぁ。セーフ・セミ・3点・フルのセレクタの写真が出てきている以上、セーフ・フル・3点・セミは遅い(あるいは同じ速度でも面倒)ってことなんでしょうけども。


 そもそも、何らかのイベントでセーフティを解除する、という描写は、「このキャラクタは安全装置を使用しているんだ」という描写をするのが最大の目的だと思います。なので即応性とかどうでもいいのでわ

 個人的には使う銃をコロコロ変える主人公は信用ならないわけですが、でも操作マスターチートあれば良いのかなー。いや、チーム内で弾薬の共通化すらできてない主人公はダメだッ。そういうこと考えてるから楽しく読めないんだろうなぁ。

メモ:STM32F4 USBブートローダーで"USB デバイスが認識されません"

 STM32F4のシステムメモリからUSB DFUで起動しようとした時に"USB デバイスが認識されません"「このコンピュータに最後に接続された USB デバイスが正しく機能していないため、Windows によって認識されません」という警告。
 この場合、STMのペリフェラルに外部がマスターになるバスが通信を行っていないか確認すること。
 僕の場合は、相手の都合を考えずにUSART3にPCからデータを送り続けるソフトを動かしていたのが原因。この場合、STMはUSART3からファームウェアを更新するモードに入ってしまい、USBのポートは初期化されるのにUSBデバイスとして動作しないため、PCから警告が出る。もちろん、USB DFUで更新することもできない。

 STMのアプリケーションノート AN2606(Rev31)によると、デバイスによって異なるが、USART1, USART2, USART3, I2C1, I2C2, I2C3, I2C4, SPI1, SPI2, SPI3, SPI4, CAN1, CAN2 からファームウェアの更新が行える(もっとあるかも)。
 ブートローダーはの優先順位は、USART > I2C > SPI > CAN > USB となっているらしい(デバイスによってペリフェラルの有無が異なったり、場合によっては優先度の違いがあるかも)。
 USARTでデータをブロードキャストするような使い方をする場合、ブートモードに入るときにはバスが切断されるような工夫をする、あるいは、ブートローダーで使用されないUSARTでデータを受信する、といった使い方が必用そう。

 CANでファームウェア書き換えってのは、車載とかを想定してるんだろうなぁ。どうやってデバイスを指定するんだろうか。あとどうやってブートローダを起動するんだろうか。ファームウェアはバス上に流せばいいとしても、マイコンがブートローダーを起動しなきゃいけないから、完全にリモートで操作するのは難しそう。CANから特定のデータを受け取ったらブートローダーモードでリブートする、みたいなプログラムが入ってるんだろうか。

 ということで、USB DFUが起動しない場合は回りの怪しいデータの流れを気にしてみよう、という話でした。
 気がつくまでに結構手間取ったので、危ないところはマーキング。

2017年9月11日月曜日

Luaで日時の計算

 Luaではos.timeで数値を取り出すことができ、os.dateで数値から人間が扱いやすい数字(グレゴリオ暦)に変換する。また、os.timeにグレゴリオ暦のテーブルを渡すと、数値が帰る。

function printDate(d)
 print(d.year .. "/" .. d.month .. "/" .. d.day .. " " .. d.hour .. ":" .. d.min .. ":" .. d.sec)
end

t = 1234567890
d = os.date("*t", t)
printDate(d)

d = {
 year = 2001,
 month = 9,
 day = 9,
 hour = 1,
 min = 46,
 sec = 40,
}
t = os.time(d) + 9 * 60 * 60
print(t)

 このスクリプトは
2009/2/14 8:31:30
1000000000
のようになる。
 一見、UNIX時間のようだが、これは環境依存のようだ。

 Lua言語のライブラリ関数#os.timeによると、「os.timeの戻り値は、os.dateおよびos.difftimeの引数としてのみ、意味を持ちます」と書いてあり、os.dateとos.difftime以外には使えないことが示唆されている。
 公式のマニュアルにも、似たようなことが書いてある。
 マニュアルによると「In POSIX, Windows, and some other systems, this number counts the number of seconds since some given start time (the "epoch")」(POSIX、Windows、およびその他のシステムでは、この番号は、指定された開始時刻(「エポック」)からの秒数をカウントします)ということで、とりあえずはUNIX時間として扱っちゃって大丈夫だと思う。

 マイコンで上記1234567890を文字列にすると"2009/2/13 23:31:30"という風になる。上記の結果とは異なるが、マイコンはUTC+0で実行され、PCはUTC+9で実行されたことによる違い。

 マイコンで日時を返すには、"int _gettimeofday(struct timeval *tv, struct timezone *tz)"を実装すればいい。今回試した限りでは、tzはnullが指定されていた。
 Luaはgmtimeとlocaltimeを使うらしいので、タイムゾーンの決定はCライブラリ側で行われているっぽい。Cのタイムゾーンは変更できるらしいのだが、ちょろっと調べても方法がわからなかった。

 日時の処理はかなり泥沼な感じなので、できれば近寄らずにいたい。

 とりあえず、Luaで日時を扱うなら、os.timeでsecに変換して、操作して、os.dateで日時に戻して、という感じで扱えそう(環境依存)。


 マイコンのLuaで時間を扱いたいけど、RTCのバックアップ電池を実装するスペースがない。有ってもコイン電池ケースなんて持ってないからできないんだが。
 せっかくSTM32F4のRTCは人間に扱いやすいようになってるのにね。って、それだとUNIX時間に変換したりしなきゃいけないから面倒なんだ。こうなると、STM32F1の32bitカウンタのほうが便利な気がするなぁ。
 マイコンのRTCモジュール、64bitカウンタとかで、通常動作時はMHzを分周して、スリープ時は32.768kHzの水晶で、みたいな機能になって欲しい。あと外部の1PPSで水晶を補正する機能とか欲しい。

Anker SoundBuds Lifeを買ってみた

 Anker SoundBuds Lifeという、Bluetoothのネックバンド型イヤホンを買ってみた。さっき届いたのでメモ。

 ちょっと古いiOS端末でも問題なく使えてる。OSのミュージックアプリや、Amazon Musicで曲送りとかできる。
 再ペアリングでWindowsに接続すれば、音はすべて聴ける。
 ただし、Windows Media Playerは曲送りができない。一時停止・再生は非アクティブでも可能。iTunesは非アクティブでも曲送りができる。一時停止・再生は非アクティブではできない。
 PCでは使えないことは無いが、フルに使うにはモバイルOSのほうが相性がいい。

 到達距離は、自室の中を歩き回るくらいなら問題ない。木造なら隣の部屋でも大丈夫かも。別の階だとかなり途切れる。

 ネックバンド型なので、首にかけるタイプ。ライフルのスリングと共存させるのはちょっとむずかしそう。カメラの2ポイントスリングみたいな、左右の移動がないタイプはギリギリ大丈夫かな。ただ配線が細いので、巻き込まれると簡単に千切れそう。

 ボタンがちょっと硬いので、再ペアリングのような同時長押し操作はちょっと厳しい。が、無理というほどではない。
 ステータスLEDが左側の先端についているが、首にかけた状態で見るのは厳しい。

 最初にニュースサイトのサムネで単体の画像を見てカッコイイと思って、人間が装着してる画像を見てかっこ悪いなぁと思ったけど、実際に使ってると、自分からは見えないので、人からの視線を気にしないなら問題ない。某うどんよりはマシだと思う。

 IPX5なので、多少の防水性はある。風呂の中で音楽聞くのとかに良いかなーと思いつつ、買っていきなり水場に持ち込むのは勇気がいるので、しばらくはやらないかなぁ。

 多少の飛んだり走ったり、くらいであれば大きな問題はない。ただ走ってるとだんだん回転していくので、時々位置を直す必要はある。
 歩いていればネックバンドが跳ねることはないが、今度は風切り音が聞こえてくる。
 あと周りの音が聞きづらいので、歩きながら着用する時は要注意。


 とりあえず、ファーストインプレッションはこんなところ。

追記:2017/09/15
 ネックバンド左側、ケーブルの付け根からその後ろのLマークのあたりを指で触ると、音量が変化してしまう不具合が出ている。右側のイヤホンには問題はなく、もっぱら左側だけで発生している。
 断線のような雰囲気ではないのだけど、レビューで断線とかそういうのが多いのは、これが原因かも。
 雰囲気としてはテルミンみたいな感じかな。静電容量で音量が変化してる感じ。電子ボリュームICの左側の部品実装をミスってコントロールのインピーダンスが高いのかな、とかそういう感じの挙動。電子ボリュームがアナログで制御されるはずないだろうし、相当な高周波じゃないと手の影響なんて受けないだろうけど。
 Bluetoothの不具合という感じではないんだよな。なんせ右側には問題がないので。左右が別になってるタイプなら、左側だけRFがマズったのかとかあるけど、ネックバンド型ならRFは1個しか無いし、コレがダメなら右側も影響受けるだろうし。
 とりあえず原因は不明。まぁ僕の使い方だと大きな問題になるほどじゃないので、しばらくは様子見かな。18ヶ月サポートなのであんまり急がなくても良さそう。

 あと電池の持ちについて。おもったよりも持たないかも。丸1日使う分には問題ないと思うけど、まだ大丈夫だろ、と思って1日充電するのサボるといきなり切れる。泊まりでどっか行くときはモバイルバッテリー持って行かないとダメだねぇ。あぁ、これがマッチポンプか。


2017年9月10日日曜日

STM32/FreeRTOSでLua

 とりあえずSTM32F4のFreeRTOSでLuaのはろーわーるどが動いたのでメモ。

 まずソースファイルをすべて追加する。
 環境によってはコレだけで動くと思う。


 追記:2017/09/11
 僕の環境でmallocが使えるようになりました。ってことでLuaのソースコードそのままで動くようになりました。あとprintfの浮動小数点出力も正常になりました。

 いちおう、mallocじゃなく、FreeRTOSのpvPortMallocを使った方法も残しておきます↓。


道具とか

 普段使ってる道具。主にはんだ付け回り。

妄想:PNG独自チャンク


2017年9月9日土曜日

C#でAPNGを作る

 64bppPNGを作ろうと調べてた際に、APNGが結構簡単に作れるらしいというのがわかったので、試しにC#で生成してみた。

 サンプル画像を貼りたいけど、BloggerはAPNG非対応らしいので画像は無し。


 今回はとりあえず動けばいいという考えで作ったので、あんまりソースきれいじゃない。でもAPNGを作るのはそんなに難しくない。今回は本当に最低限しか実装してないので、別の用途に使おうとすると大変かも。

 とりあえずGraphicsでサンプル画像を作ってるが、たぶんファイルから読み込んだ画像でも問題ないはず。入力形式はBitmapになってれば良いので、BMPでもJPGでもPNGでも何でも問題ないはず。

 今回はフレーム待ち時間は決め打ちで、ソースコードに書き込んでいる。delay_num, delay_denというのがそれで、1フレームあたりの待ち時間はdelay_num / delay_den [sec]になる。num=1, den=8なら1フレーム0.125秒(8fps)になる。
 この方法だと、30fps(0.0333...sec)みたいな秒で循環小数になるディレイでもある程度は正確に表記できる。そういえばJPGだったかのEXIFもこういうフォーマットだった気がする。あとMS FSXのAPIにもこういうのあったね。マルチメディアではよくあるフォーマットなのかも。
 APNGでは16bit2個なので、1フレームあたり約15マイクロ秒から18時間くらいまで設定できる。
 本来、ディレイはフレームごとに設定できるので、ある1フレームだけ表示時間を増やすとか、そういう表現もできる。

 APNGはあくまでPNGのラッパーなので、おそらくPNGが対応する形式はすべて対応できる。階調のある透過や、24bitフルカラーも使える。たぶん64bitでも。もちろんPNGとして圧縮されるので、ファイル容量も(ちょっとしたアニメーションなら)大きくなりすぎることはない。
 Bitmapのコンストラクタで32bitを指定して、背景に透過色を設定すると、Chromeでは背景が市松模様になるので、たぶんちゃんと透過も処理できてる。

 WikipediaのAPNGのページを見てみると、最初のフレームをアニメーションに使うか否かを設定できたりするらしいが、今回参考にしたページにはそういうのは見当たらなかった(見落としてるだけかも)。
 もしかしたら、IDAT単体ならサムネイルにIDATを使ってアニメーションには使用せず、fcTLとIDATを並べるとIDATもアニメーションに使う、みたいなことなのかも。
 まぁ、ちゃんと使うなら本来の仕様を確認した方がいい。

 ソースはそこそこ長い(300行ちょっとある)ので、続きを読むからどうぞ。

2017年9月6日水曜日

Luaのコードを文字列として持つ

 Luaのコードをファイルから読むサンプルはよくあるが、それじゃただのインタプリタなので、自分で処理を書く意味がない。ということで、とりあえず変数にコードの文字列を持つようにしてみた。

#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

// [スクリプト化可能なアプリケーションに Lua を組み込む](https://www.ibm.com/developerworks/jp/linux/library/l-embed-lua/index.html)

int main(void)
{
    static const char *codes[] = {
        "print(\"hello world\")",
        "abc=123+456\n"
        "print(abc)\n",
    };
    const int codeCount = sizeof(codes) / sizeof(codes[0]);

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    int i;
    for (i = 0; i < codeCount; i++)
    {
        const char *code = codes[i];

        if (luaL_loadbuffer(L, code, strlen(code), "code"))
        {
            fprintf(stderr, "lua couldn't parse '%s': %s.\n", code, lua_tostring(L, -1));
            lua_pop(L, 1);
            continue;
        }

        if (lua_pcall(L, 0, 1, 0))
        {
            fprintf(stderr, "lua couldn't execute '%s': %s.\n", code, lua_tostring(L, -1));
            lua_pop(L, 1);
            continue;
        }

        lua_pop(L, lua_gettop(L));
    }

    lua_close(L);

    return (0);
}

 makefileは前回と同様。
 このサンプルではスクリプト言語の利点が皆無だが、変数に入った文字列を処理できるようになったというのが大きな進化点。これならマイコンのシリアルコンソールで受け取ったコードを処理したりができる。マイコンのFlashに入れたコードを走らせることだってできるはず。

 ちなみに、このサンプルではLuaのコードを文字列で書いているが、luacでバイナリに変換したコードでも処理できる。ただしバイナリだとstrlenが使えないので、そのあたりがちょっと面倒。もしかしたらヘッダとかに容量書いてあるのかもしれないけど。
 まぁ、これくらいのコードだとASCIIよりバイナリのほうが容量がでかいという事態になるので、バイナリを使う利点はあんまりないが。
 LuaはコンパイラとVMのバージョンが違うと動かないらしいので、マイコンでLuaを走らせて誰かにコードを書いて貰う場合は、そのあたりも色々考える必要がありそう。

CでLua

 マイコンでインタープリタつかいたいなーと思ってLuaを試してみた。
 公式ページのダウンロードからソースを落としてきてビルドするだけ。楽。



今回は全部まとめてビルドしてるので、ちょっと時間がかかる。4-6秒くらい。

 lua.c/luac.cはバイナリとして配布されてる実行ファイルのmainが入っている。当然、これを含めてmakeすると重複定義エラーになる。

 今回は動作確認だけなので簡単な処理だけ作ってみた。スタンドアロン動作だし。
 ちゃんと使うならちゃんと使い方を調べにゃ。でも結構簡単にいろいろできそう。

 STM32F4で動くLuaVMとかやりたいな。HALをもうちょっと抽象化してLuaで実行できるようにしたりして。

追記:2017/09/09
 l_hoge関数の戻り値が1だけど、これは0にしなきゃいけないみたい。
 C関数の戻り値は、関数の戻り値の数を渡す必要がある。今回は戻り値がないので0を返す。
 Luaは関数呼び出しの際にスタックが1個作られ、引数がスタックに積まれる。また戻り値もスタックに積まれる。引数が3個、戻り値が2個の場合、スタックは5段となり、Cの戻り値は2になる。この際、print(C関数())のようにするとスタックの上2個が表示される。Cの戻り値に6を指定すると、戻り値の2個、引数の3個、そして不定値の1個の変数が表示された。この不定値は何らかの定数かもしれないが、調べてない。

2017年9月4日月曜日

アンドロメダ銀河


 いくつかの処理を変更して、入力は前回と同じデータを使用しました。
 ただ、今回は新たにパターンノイズのデータを追加で使用しています。パターンノイズは本撮影後に、暗そうな地面に向けて撮影した映像を使用しています。
 あらかじめ動画を静止画に変換し、全静止画の加算を行った後、フレーム数で割った値をパターンノイズのデータとして使いました。

 本撮影データは静止画が7500枚ですが、まず読み込んだデータを無加工でOPM処理に回し、マッチングができれば再び読み込んで、パターンノイズを除去してから位置の変更と加算を行います。
 すべての画像の加算が終わったら一旦バイナリで書き出し、48bppPNGに変換した後に、Lr5で輝度などを調整してjpgで書き出しています。

 パターンノイズの除去が強すぎるのか、特にノイズが多い領域が暗くなっています。16bitで書き出してLrで調整しているというのが大きいんでしょうが、アンドロメダ銀河も見やすくなってる気がします。

 今回は中間データとして独自フォーマットのバイナリを使いました。Width, Height, Chが任意町で、Depthは32bit固定です。ヘッダに幅高さCh数が入ってるだけで、あとはバイナリなので扱うのは楽ですが、中間データは50MBくらいあります。
 C#のZipなりDeflateなりで圧縮掛けても良いんでしょうが、画像データだから単純に圧縮しただけなら圧縮率は悪いだろうなぁ。かといって複雑な圧縮を行うのも面倒だし、そもそもPC内で使うだけのデータなので容量も数十GBとかにならなければ問題ないですし。


 気軽に遊べる程度のソフトウェアでは、だいぶ良いところまで作り込んだと思ってるので、最後に1回くらいちゃんと撮影したら、もう満足かな、という気がしています。
 でもこれから満月になるので、あんまり良いコンディションじゃないんだよなぁ。

C#で64bitPNG

 C#で64bitPNGを出したかったので、やってみた。

using (Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format64bppArgb))
{
    BitmapData bmd = bmp.LockBits(
        new Rectangle(Point.Empty, bmp.Size),
        ImageLockMode.WriteOnly,
        PixelFormat.Format64bppArgb);

    for (int y = 0; y < bmp.Height; y++)
    {
        for (int x = 0; x < bmp.Width; x++)
        {
            int l = y + x;
            byte[] data = BitConverter.GetBytes((UInt16)l);

            int pos = x * 8 + bmd.Stride * y;

            Marshal.WriteByte(bmd.Scan0, pos + 0, data[0]);
            Marshal.WriteByte(bmd.Scan0, pos + 1, data[1]);
            Marshal.WriteByte(bmd.Scan0, pos + 2, data[0]);
            Marshal.WriteByte(bmd.Scan0, pos + 3, data[1]);
            Marshal.WriteByte(bmd.Scan0, pos + 4, data[0]);
            Marshal.WriteByte(bmd.Scan0, pos + 5, data[1]);
            Marshal.WriteByte(bmd.Scan0, pos + 6, 255);
            Marshal.WriteByte(bmd.Scan0, pos + 7, 255);
        }
    }

    bmp.UnlockBits(bmd);

    bmp.Save("./log.png", ImageFormat.Png);
}

 まずBitmapのコンストラクタにPixelFormat.Format64bppArgbを指定する。これで64bitの画像が作れる。
 あとは色を載せるだけだが、今回は直接値を書き込んでいる。
 試してないが、たぶんSetPixelはダメだと思う。Color.FromArgbだと256以上は例外が投げられるはずなので、16bit画像は作れないはず(未確認)。
 画像を保存する際は、ImageFormat.Pngで保存すれば、16bitのPNG(16bit*RGBA =64bit)で保存される。

 ちなみに、このサンプルは下位9bitだけを使ってグレースケール画像を生成している。8bit換算で下位約2bitしか操作していないから、普通に表示しても何も見えない。

 これをpaint.netで輝度をいじると以下のような感じ。
下位8bitが切り捨てられて、上位8bitのみが使用されている。

 Lr5で輝度をいじると以下のような感じ。
てきとーに調整したのでガタガタだが、ちゃんとグレースケールで表示されてる。つまり、少なくとも下位8bitの内の数bitは使用されている。


 ということで、天体写真合成で16bitの出力ができる目処が立ったので、あとから輝度を調整しやすくなった。
 もともと自前で48bitPNGを出すライブラリを作ろうと思ってて、PNGって面倒くさいな~と思ってたところなので、.Net単体で簡単に64bitが出せたので楽になった。
 BitmapDataを使わなきゃいけないのは面倒だけど、そもそもBitmap.SetPixelはクッソ遅いので、どっちにしろ自前でBitmapDataの処理は作らにゃいかん。

 僕としては96bitPNGが欲しいところw。 16bitだと256枚加算まではロスレスだが、それ以上の枚数を重ねると情報が劣化してしまう。96bitだと8bit画像を16777216枚まで重ねられるので、当面は心配いらない。いちおうPNGのヘッダは1024bppまで対応しているっぽいけど、.Netは64bppが上限のはず。
 一般的なユーザーでは32bppPNGで充分だろうけども、データ処理の中間データとして使うならやはり128bppPNGくらいあると心強い。あとはIntだけじゃなくてFloatを持てたりとか、1/3/4ch以外にも、任意のch数を持てたりとかすると、学術データの標準フォーマットとして使えると思うんだけど。
 APNGのフォーマットは調べたことがないが、このコンテナに入れれば2次元画像をもう1次元増やせるので、3次元空間に4次元のベクトルをもたせることができる。現状だと、3次元空間に16bitの4次元ベクトルを入れるのが限界かな。それでも大したもんだが。16bit整数から浮動小数点に変換する処理とかを入れればさらに情報量は増やせる。
 APNGだとWebブラウザでも表示できるから、3次元空間の3次元ベクトルデータをざっくりと確認するのにWebブラウザで開けばいい、という利点もあるかも?
 PNG、クッソ古い規格なのに、拡張性やばい。

妄想:PRNマスキングテープ


2017年9月3日日曜日

CでStack

 たまに欲しくなるスタック(LIFO)のコード。

typedef struct
{
    uint16_t size;
    uint16_t width;
    uint16_t count;
    void *end;
} Stack_t;

void stack_Init(Stack_t *stack, uint16_t size, uint16_t width, void *buff)
{
    stack->size = size;
    stack->width = width;
    stack->count = 0;
    stack->end = buff + size * width;
}

int stack_Push(Stack_t *stack, void *data)
{
    return (stack->count < stack->size ? memcpy(stack->end - ++stack->count * stack->width, data, stack->width), 1 : 0);
}

int stack_Pop(Stack_t *stack, void *data)
{
    return (stack->count ? memcpy(data, stack->end - stack->count-- * stack->width, stack->width), 1 : 0);
}

int stack_Peek(Stack_t *stack, void *data, uint16_t index)
{
    return (stack->count > index ? memcpy(data, stack->end - ++index * stack->width, stack->width), 1 : 0);
}

int stack_PeekTop(Stack_t *stack, void *data, uint16_t index)
{
    return (stack->count > index ? memcpy(data, stack->end - (stack->count - index) * stack->width, stack->width), 1 : 0);
}


Add 2017/09/04
 stack_Peek関数

Add 2017/09/05
 stack_PeekTop関数
 Peek関数はスタックの底から、PeekTopは上からインデックスが始まる。PeekTopで0を指定すると最後に入れたデータが帰る。Peekで0を指定すると最初に入れたデータが帰る。PeekTopはPeekでもStack.Countとか使えば同じ動作になるのでPeekのみ作ってたが、やっぱり一発で上から取り出せるほうが便利なのでPeekTopも追加した次第。

アンドロメダ銀河






 運良く晴れてたので、アンドロメダ銀河を撮ってみました。
 上から動画からキャプチャ、動画変換後、変換後のヒストグラムを操作、撮影中、静止画で撮影、という感じです。
 動画はISO20万、シャッター1/4で5分ほど撮影しました。静止画はISO10万、シャッター4秒です。
 さすがにISO20万だとノイズは相当なもんですが、平均後はかなりノイズが減っています。とはいえあんまり綺麗に写ってないですね。SNRが改善してNFが下がったことによって暗い部分も浮き上がってきてはいますが、なんともイマイチな感じ。
 ISO10万で4秒だと、ノイズは多少改善しますが、星はかなり流れています。銀河は全体的に明るいのであんまり流れてるのはわからないかな。あと動画と静止画では撮影後の処理が違うので、静止画はさらに綺麗になってる感じ。動画で撮影するより、1/5sec, ISO40万くらいで2万枚くらいインターバル撮影したほうが良さそう。天体ならサイレント撮影でも充分だからシャッター機構の寿命も気にしなくていいし。問題はインターバル撮影ができないことだけど。


 動画処理後であんまり明るくならないのは、8bitにスケーリングする際に他の星が明るすぎるためというのが大きいと思いますが、もともとの輝度が低いというのも影響しそうです。ISO20万でここまでノイズ減らせるなら、ISO40万でも良いかも。
 あと雲が出てきたので5分ほどで撮影を終了しましたが、コンディションが良ければ20分位撮影したかった。
 全部で7500フレームくらいですが、合成は1時間程度で終了していたようです。8時間位かかるかなと思って寝てる間に回してたんだけど。。これは望遠鏡で拡大したことにより恒星が疎になり、ラベリングやマッチングの負荷が軽くなったのが要因かもしれません。20分の動画でも同じような密度なら4倍ですから、4時間で済みます。感度を上げて恒星が増えれば処理時間も増えるでしょうが、それでも8時間もあれば終わるはずです。

 あとはパターンノイズがかなり気になります。左側上下や、フレーム上下左のピンク色は熱雑音によるものだと思います。ランダムな位置にあるピクセル単位のパターンノイズは合成中に除去されますが、面積の大きいパターンノイズは除去しきれないので、これは何らかの対策が必用そうです。


 とはいえ、5万円の入門用天体望遠鏡(8cm手動経緯台)でここまで撮れるのは面白いです。1分位の間隔でだいたいの位置に合わせれば良いので、完全手動追尾みたいな苦労もないし、ライブビューでも銀河が見えるので位置を合わせるのはとても楽だし。
 でももうちょっと改善の余地がありそうなので、なんとかしたいところ。
 ま、市場にあるソフトウェアを使えば簡単にもっと綺麗にできるんでしょうが、目的は天体写真撮影じゃなくてソフトウェア作成なので、こんな調子で。