2016年2月6日土曜日

レプEoTechをバラしてみた

7千円くらいのレプEoTechをバラしてみた。



ネジを数本外せば簡単にバラすことができる。基板はメインとスイッチ部の2枚で、その間はフラットケーブルで接続されている。メインの基盤には14ピンのマイコンとその他抵抗やコンデンサなどが実装されている。14ピンのICは表面が汚れていたために拭いてみたが、刻印は消えていて全く読むことができなかった。Pin4がVBAT+に直結、Pin11がVBAT-に直結されている。

LEDの周りはこんな回路になっている。



RED点灯時は2.22VでGND側の抵抗に350mVが、GREEN点灯時は2.85VでGND側の抵抗に60mVがかかっている。REDは6.8mA、GREENは1.2mA程度で点灯していることになる。LEDは550HzのPWMで調光され、最高輝度で99%、最低輝度で5%くらいだった。大抵のLEDは20mAくらいまで流せるだろうから、これもそれくらい流せばかなり高輝度になりそう。もっともチップ抵抗を交換するのはかなり面倒だろうけども。GND側の抵抗値を減らし、GREENの抵抗を増やせば、REDを高輝度化しつつ、GREENを低輝度にしてNV対応、みたいにできるかもしれない。それと、REDがGREENよりも電圧が低いのが気になる。どこかに100Ωくらいの抵抗が入ってるのかもしれない。

EoTechの高輝度化は、当面のところ抵抗のあたりをいじるだけで良さそうだ。それで足りないならLEDを20cdのモノに交換して電流も50mAくらい流してやれば相当に高輝度化されるだろう。バッテリーライフがものすごい短くなるだろうけど。

2016年2月3日水曜日

IR誘導ミサイルのレティクル

初期の赤外線誘導ミサイルは画像としては認識できず、赤外線を点として認識していた。しかし点では偶然に発見できる可能性は低いだろうし、発見できたとしてもすぐにロストしてしまうだろう。そのため実際のミサイルでは焦電型センサの前に扇風機の羽のようなレティクルを置くことで目標の位置を探している。

参照:http://homepage3.nifty.com/kubota01/navigation.htm <レティクル走査方式>

ということで、簡易的ながらどうなるのかを試してみた。といっても焦電型センサや羽を用意するのは大変なので、PCの中で。

今回使用したレティクルは以下の2個。

左と右は基本的に同じだが、右側には中央に半円の不透過、透過エリアが付いている。

以下がそのレティクルで見た目標(緑の丸)の大きさ。左側にレティクルの形状と目標の位置を、右側に見えている目標の大きさのグラフがある。大きさのグラフは縦が大きさ、横が時間で、右に行くほどレティクルが回転し、右端で2周分回転している、という状態。



上から、右側のレティクルで目標が中央、右側のレティクルで目標が中央からほんの少しずれてる、左側のレティクルで目標が視野外側、左側のレティクルで目標が視野の中心付近、となっている。3段目はレティクルの外側なので、中央部の切り欠きの有無は関係ない。
今回はレティクルは固定して、目標をレティクルの中心から同心円で移動した時の目標の大きさをグラフにした。
一番下のグラフはとりあえず無視しとくとして、3段では上に行くほどレティクルの中央よりとなる。ざっと見た感じ、中央に近づくほどピークが小さくなり、レティクルの全周で強度が変わらなくなるようだ。対して外側に目標がいる3段目では、目標の有無によるピークが鋭く出ており、レティクルが不透過の半円部分では目標は全く見えていない。
ということで目標の大きさのピークの形状はレティクルの中心からの距離に相関がありそうだということはわかった。次は目標の位置について。中心からの距離がわかっても、角度がわからないとどこに向かえばいいかがわからない。角度を知るためにはレティクルの半分が隠れているというのがポイント。3段目では2箇所、全くピークが見えないところがある、このピークが見えないところはレティクルの半円が不透過になっている部分に隠れている、と考えることができそうだ。つまりターゲットが長期間見えなくなった時のレティクルの角度と比較することにより、目標がどの方向にいるかがわかる。これで目標までの中心からの角度と、方向を把握することができそうだ。あとはシーカーをジンバルに乗せて、目標がシーカーの中央に来るように修正し、シーカーの角度がゼロになるようにミサイルを動かせば目標の方向に向かって飛ぶことが可能となる。

実際にミサイルに使うレティクルはもっと複雑な形状のようだが、とりあえず考え方はなんとなく分かった。焦電型センサなんて性能を無視すれば1個100円とかで買うことができるから、あとはレティクルを作る方法を考えればロウソクの位置を探す赤外線シーカーくらいは作ることができそうな気がする。レティクルはかなり小さく作る必要があるが、今なら精密な加工機があるからなんとかなるかもしれない。

OpenCvSharpでレンズ補正っぽいことをする

OpenCvでレンズ補正を行うプログラムをC#に移植してみた。といってもほとんど全く同じコードで動くわけだが。OpenCvSharpすぎょい。(ただし本気でSharpにするならいろいろ最適化できるが)

カメラはiBUFFALO マイク内蔵320万画素WEBカメラ F2.2ガラスレンズ搭載モデル ブ ラック BSW32KM03BKを使用した。

修正したサンプル 上が補正済み、下が未補正。


元画像がほとんど歪みのない画像なのであまり違いがわからない。Webカメラすぎょい。

チェッカーボードは本来印刷したりして使うんだろうが、プリンタが無いのでとりあえず画面に表示して代用した。



この画像は24インチの1920x1080液晶で表示することで1マスが25.0mmのROW9,COL18なチェッカーボードとして使用することができる。ROWとCOLはコマの数ではなく、交差しているところの数であることに注意。

補正情報を拾うための画像は適当にいろいろな角度や距離から撮影しておく。距離や向きに多様性がある方がいいと思うが、チェッカーボードが一部でも隠れていると解析に失敗してしまうので、すべての領域が撮影されている必要がある。

情報はXMLで書きだされ、今回はこんな感じになった。

<?xml version="1.0"?>
<opencv_storage>
<intrinsic type_id="opencv-matrix">
  <rows>3</rows>
  <cols>3</cols>
  <dt>f</dt>
  <data>
    1.04094995e+003 0. 6.19313538e+002 0. 1.15699170e+003
    3.98739349e+002 0. 0. 1.</data></intrinsic>
<rotation type_id="opencv-matrix">
  <rows>1</rows>
  <cols>3</cols>
  <dt>f</dt>
  <data>
    -2.04190898e+000 -2.19732761e+000 2.56176054e-001</data></rotation>
<translation type_id="opencv-matrix">
  <rows>1</rows>
  <cols>3</cols>
  <dt>f</dt>
  <data>
    -1.47931931e+002 -1.17953056e+002 6.39266296e+002</data></translation>
<distortion type_id="opencv-matrix">
  <rows>1</rows>
  <cols>4</cols>
  <dt>f</dt>
  <data>
    8.36658627e-002 -6.74404874e-002 -1.06694188e-003 -7.54280773e-005</data></distortion>
</opencv_storage>


さて、とりあえずオプティカルフローでカメラの動きを検出することができるようになった。それからレンズ歪みも補正できるようになった。あとはオプティカルフローでシェイクリダクションをできるようになれば、ウェアラブルカメラの補正とかができて楽しいかな。


2016年2月2日火曜日

OpenCv(Sharp)でオプティカルフロー



OpenCvSharpでオプティカルフローを行ってみた。ソースコードは"実践OpenCV 2.4―映像処理&解析"のオプティカルフロー、178ページのC++のコードをほぼそのまま使用している。
サンプルには検出しやすそうなキーボードを使ってみた。キーボードのバックライトで表示された文字もちゃんと文字として認識できるのが面白い。
それとオリジナルの処理として画像の下側に移動の長さの量を表示している。上の画像は平行移動しているので鋭いスペクトルが出ている。ということで出現回数が多い移動を平均すれば移動方向が把握できる。と思ったのだけど、流石にそんな簡単じゃない。



上の画像は平行移動ではなく、カメラを回転させたサンプル。回転中心が移動量最小で、中心から離れる程に移動量が多くなる。なので鋭いスペクトルは出ず、様々な長さの移動が検出される。
とりあえずシェイクリダクションでもやってみようかと思っていたけど、かなり大変のようだ。とりあえずオプティカルフローで遊ぶという目的は達成したけど、もうちょっといろいろ調べてみよう。

2016年1月31日日曜日

コンソールプログラムで表現を豊かに

CygwinをはじめとするUnixらいくな環境ではコンソールプログラムの文字色や背景色をプログラムで1文字ごとに設定することができる。ということで色の一覧。



1行毎に背景色を、1列毎に文字色を設定している。色は黒、赤、緑、黄、黄緑、青、マゼンタ、シアン、デフォルトがある(でもシアンってどう見ても白だよね)。



この表現がどういうふうに使えるかというと、例えばビットのON/OFFで情報が入ってくるとして、それを文字だけではなく色として表現することができる。上の画像では青がON、赤がOFFで、現在選択しているデータを緑で表している。hjklキーで選択を移動することができ、スペースキーなどで反転することもできる。
この表現は基本的にUnixライク環境専用だが、Windowsでも使うことは可能。また、C#のConsoleでも同様の表示が可能。C#だとシリアルポートが簡単に使えるので、ちょっとした電子機器のステータス表現に使うと便利かもしれない。

2016年1月10日日曜日

STM32F1のGPIO

STM32F1のGPIOマッピング。STM32F103VE向け。STM32F103CBでも使えるはず。

一部のペリフェラルに対するピン。

ハイフンはリマップが存在しない、スラッシュはピン割当が存在しない事を表す。
リファレンスマニュアルによるとSPI3にもリマップが存在するようだが、103VEのPDFには書かれていなかったのでここでも表記していない。

ピンに対するペリフェラル。


STM32F1のGPIOは結構ペリフェラル割り当てられてる気がするけど、表で見ると意外とスカスカ。


STBee F4も発売されたことだし、僕も本格的にF4に移行したいかなと思いつつ、まだSTBeeで足りてるのでわざわざ移行する気力が。もっとも、F4のほうが容量多くて早いのに数百円安いので、64ピンで足りるならF4使ったほうが良いかもしれないけど。

2016年1月9日土曜日

STM32F1のUSARTを割り込みで送信

USARTの送信をポーリングで行う場合、115.2kbaudで32文字送った場合、2.8msec程度かかる。わずかといえばわずかだが、この時間はほとんど無駄にループさせているため、CPUのリソースが浪費されている。一旦データをバッファにコピーし、送信処理を終了してから、割り込みなどを利用してデータを送ればこの時間を200usec未満にすることができる。

1) 変数を用意する

#define USART1_TX_Buff_Size (128)
uint8_t USART1_TX_Buff[USART1_TX_Buff_Size];
uint16_t USART1_TX_Buff_SetCounter = 0;
volatile uint16_t USART1_TX_Buff_GetCounter = 0; // 割り込みで更新するのでvolatileをつける 

USART1_TX_Buff_Sizeでバッファサイズを設定する。その後USART1_TX_Buffでバッファを用意し、SetCounterとGetCounterを用意する。Setは挿入位置、Getは取り出し位置を記録する。GetCounterは割り込みの中で変更されるため、コンパイラの最適化によってwhileによる監視等が削除される場合がある。そのためGetにはvolatileをつけて最適化を回避する。

2) 下位関数を用意する

uint16_t USART1_TX_Buff_SetCounter_Inc(void) {
    uint16_t counter = USART1_TX_Buff_SetCounter;

    counter++;

    if (counter == USART1_TX_Buff_Size) {
        counter = 0;
    }

    return(counter);
}

uint16_t USART1_TX_Buff_GetCounter_Inc(void) {
    uint16_t counter = USART1_TX_Buff_GetCounter;

    counter++;

    if (counter == USART1_TX_Buff_Size) {
        counter = 0;
    }

    return(counter);
}

SetCounter_IncとGetCounter_Incはその名の通りSetとGetをインクリメントする。ただし関数内では変数に反映せず、戻り値として値を返すので関数を呼び出したところで責任をもって変数に反映する必要がある。

3) 割り込みを有効にする

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_ITConfig(USART1, USART_IT_TC, ENABLE);

USARTを初期化する際にNVICも初期化する。USART自体の初期化は前回のエントリを参照。

4) 割り込みハンドラを用意する

void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_TC))
    {
        if (USART1_TX_Buff_SetCounter == USART1_TX_Buff_GetCounter) {
            USART_ITConfig(USART1, USART_IT_TC, DISABLE);
        } else {
            USART_SendData(USART1, USART1_TX_Buff[USART1_TX_Buff_GetCounter]);

            USART1_TX_Buff_GetCounter = USART1_TX_Buff_GetCounter_Inc();

            USART_ClearITPendingBit(USART1, USART_IT_TC);
        }
    }
}

内部処理としてはSetとGetの差が0なら割り込みを停止し、割り込み自体はクリアせずに終了する。データが存在する場合はUSART_SendDataで送信を行い、Getをインクリメントする。その後割り込みをクリアして終了する。

5) 送信用の関数を用意する

void USART1_putc(unsigned char ch) {
    uint16_t inc;
    
    do {
        inc = USART1_TX_Buff_SetCounter_Inc();
    } while (inc == USART1_TX_Buff_GetCounter);

    USART1_TX_Buff[USART1_TX_Buff_SetCounter] = ch;

    USART1_TX_Buff_SetCounter = inc;

    USART_ITConfig(USART1, USART_IT_TC, ENABLE);
}

putcの中ではバッファへのコピーのみを行う。ただし未送信のデータを破壊しないようにdo whileで待機する。その後にバッファへコピーを行い、SetCounterを更新する。最後に割り込みを有効にする。


割り込みはデータの流れが面倒になるのと、ソフトウェアもそれなりに複雑になる。毎度のことだがほんとうに必要かどうかを考えてから実装しよう。