2016年3月23日水曜日

Foretrex401のインサートナット



Foretrex401のインサートナットを埋め込んでいる部分が割れてしまった。樹脂が薄いのと、圧入してあるので、比較的強度が低そう。ネジは2本あるので、1本が外れたくらいで即落下ということはないけど、やっぱ使いづらいと思う。一応ボルトの頭側は残ってるので、ネジ自体が落下するような自体にはなってないが、片方が保持されてないから反対側が割れるのも時間の問題だろう。人間の手首に巻く程度ならそんなに引っ張られることもないだろうし、まず人間が痛いのでFx自体が破壊されるほどにはならないと思うが、M4クレーンストックに巻いてるとカドで簡単にベルクロが引っ張られるので注意が必要。
どうやって修復するかな。とりあえず脱脂してアロンアルファを塗ってみようか。

2016年3月22日火曜日

u-bloxのGPSを買ってみた

今まで使ってきたGPSモジュールはストロベリーリナックスで売ってる10Hzモジュールとかだったけど、今回試しにu-bloxのモノを買ってみた。



amazonで売ってるNEO-6Mというモノ。
この基盤は5Vを突っ込んでオンボードLDOで3.3Vを作って供給するようになってるが、3.3Vの後ろについてる逆流防止ダイオードのドロップが大きくてモジュールが動作できないみたい。ということでダイオードはジャンパワイヤで殺しておいた。そもそもこのダイオードって付ける場所おかしいんじゃないかと思うのだが。もしかしたら外部から3.3Vを供給した時にLDOに逆電圧が掛からないような目的なのかもしれない。5V供給時に動かないので全くの無駄なわけだが。
コネクタには5Vinと3.3VoutとGND、通信用にUARTの2本とI2Cの2本で計7本が出ているが、結構重要なPPS端子が出ていない。ということでこのモジュールもPPSを出すように改造しておいた。PPS端子(データシート的にはTimePulse端子)は3番ピンに出ているが、ちょうどバックアップバッテリが邪魔でハンダ付けするのは大変な場所だった。なので一旦バックアップバッテリを外してからPPSを通し、その後バックアップバッテリを戻すという手順で作業した。
結構ノイズで汚染されてる自室でも付属のパッチアンテナでたまに即位できるので、このモジュールは結構有能かもしれない。外部アンテナもSMAで接続できるので、気が向いたら外部アンテナを買ってベランダに設置してみようかと思ってる。


2016年3月17日木曜日

リミット有りのエンコーダ入力

前回のエントリでは、エンコーダの位置を絶対位置で取り出しました。しかし、この方法では音量調整に使おうとすると、音量を下げて0を通過すると最大に戻るという問題点があります。ということで一定範囲に数値を収める方法について。

まずエンコーダの初期化(RCC,GPIO,TIM)は基本的に前回と同じです。ただし、TIM_SetAutoreloadの引数には0xFFFFを渡します。そうすることによりTIMの出力は0-65535の範囲を取ります。
次にカウント値を取り出すわけですが、TIMx->CNTはint_16でキャストして取り出します。そうすると正回転の時に正の値、逆回転の時に負の値を取り出すことができます。後はこの符号ありの数値を積算していけば、電源ONの時点からの絶対位置を取り出すことができます。
しかしそのままではせいぜい32bitや64bitの範囲でしか動作しません。なので積算後に範囲外に出た場合は範囲内に戻るようにします。
また、CNT値は取り出した後にちゃんと0に初期化してやります。そうすれば次回に取り出すときは、今回から次回の間に変更されたパルス数を読むことができます。

static int Value = 0;

if (TIM3->CNT) {
    Value += (int16_t)TIM3->CNT;
    TIM3->CNT = 0;

    if (Value > +100) { Value = +100; }
    if (Value < -100) { Value = -100; }

    printf("value:%4d\n", Value);
}

実際にはこのような処理になると思います。適当なタイマ割り込みやスレッドでこのような処理を行えば、上の例では+100から-100の間の数値として読み込むことができます。
他の使用方法として、タイマ割り込みなど正確な周期でTIMx->CNTの読み込みとリセットを行えば、周期とカウント値で速度を計測することも可能です。

STM32F1のエンコーダインターフェースモード

STM32F1のTIMxについてるエンコーダインターフェースボードを試してみた。

テストに使用したエンコーダはこれ
http://akizukidenshi.com/catalog/g/gP-00292/
1回転24パルスの機械接点タイプのエンコーダだ。

ソースコード
void TIM3_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_SetAutoreload(TIM3, 24 * 2 - 1);
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI1, TIM_ICPolarity_Falling, TIM_ICPolarity_Falling);
    TIM_Cmd(TIM3, ENABLE);
}

最初はRCCでクロックの供給を行う。次にGPIOを初期化する。今回はマイコンでプルアップを行い、エンコーダはGNDに落とした。TIM3のCH1、CH2はGPIOA6、7に接続されているので、GPIOAの6と7をIPUとして初期化する。次にTIM_SetAutoreloadでカウンタの上限を指定する。またTIM_EncoderInterfaceConfigで動作モードを指定する。最後にTIM_Cmdで動作を開始させる。

TIM_EICの引数にはカウントモード(TIM_EncoderMode_TIhoge)と極性(TIM_ICPolarity_hoge)を渡す。
カウンタモードはTI1ならCH1のエッジでカウント、TI2ならCH2のエッジでカウント、TI12ならCH1とCH2それぞれのエッジでカウントを行う。
極性はRisingで立ち上がり、Fallingで立ち下がりエッジを使うことになると思うが、カウンタは立ち上がり、立ち下がりエッジの両方でカウントを行うため、実質的にはあまり違いはないと思う。ただ今回はプルアップで最初のエッジが立ち下がりのため、Fallingを指定した。

それとTIM_SAで指定した24*2-1という数字だが、これはエンコーダのパルス数(1回転24パルス)に立ち上がりと立ち下がりエッジで2回分、それから0始まりのために-1となる。カウントモードでTI12を指定した場合は24*4-1とする。この設定でわかるように、エンコーダは1回転24パルス出力でも、マイコンからはその2倍(あるいは4倍)として認識することができる。1回転24パルスのエンコーダでも実際には96パルスという比較的高分解能として使えるわけだ(ただしヒューマンインターフェースの場合は1クリックの途中で値が変わるのは非常に気持ち悪いが)。

エンコーダでカウントアップしていき上限に達した場合は0にリセットされ、カウントダウンで0を下回った場合はTIM_SAで指定したカウント値に飛ぶ。そのためカウント値の監視がカウンタのリセットに間に合わない場合にはもっと大きな数値を指定するとか工夫する必要があると思う。音量の調整などに使用するために範囲を0-50とかにすると、音量を下げて0になった次の瞬間に50に飛んで最大音量となるため、この辺りはうまく処理する必要があると思う。


機械式のロータリーエンコーダはチャタリングが酷く、以前にソフトウェアで読んだ時も素早く回すととても使いものにならないような結果になった。今回も同じような結果を予想していたが、CRフィルタも何も使用せず、しかもマイコンの外部部品はエンコーダのみでプルアップ抵抗もマイコン内蔵という悪条件ながら、ほとんど読み込みエラーが発生しなかった。操作ダイヤルなど、相対的な動作で構わない用途にはほぼ問題なく使用できると思われる。モーターの絶対位置を計測したい等の用途では何らかの対策が必要だと思うが、モーターの回転数を測るような、0.5パルスずれた程度ではあまり問題にならない用途では十分だと思われる。


ところで、STM32F1リファレンスマニュアルには「エンコーダインタフェースモードは、単に方向選択を含む外部クロックとして動作します」と書いてある。ということはCH1のパルスでカウントし、CH2でインクリメントとデクリメントを切り替えられるのか?と思って試してみたが、そういう動作はできないようだ。少なくともエンコーダインターフェースボードではノイズとして処理されてしまうらしい。
もしもこのような動作が可能であればステッピングモータドライバに対するパルス出力(回転方向の1bitとステップ数のパルス)のような信号を受け取ることができると思ったんだけど、それはできないみたい。

2016年3月15日火曜日

DIGAのダビング

DIGAにはいくつかの録画モードがあるが、地味に便利な機能にニュース番組録画というものがある。例えばNHKに設定しておけば、NHKで放送されているニュース番組がある程度録画されて、3日前後保存されるという機能。昨日何があったのかとかをサクッと知りたい時には遡ってニュース番組を見ればいいし、ローカルニュースで身近なネタが放送されていたということを後になって知ることもある。

話は変わるが、DIGAの別の録画モードとして、おまかせ録画というものがある。これは事前に設定したキーワード等がヒットした番組を自動で録画して、10に比ほど保存されるという機能。これで録画された番組は10日後に自動的に削除されるが、リモコンのサブメニューから「おまかせ録画から除外」を選ぶと自動削除が行われなくなる。なので自動で勝手に録画させておいて、気に入った番組は残す という使い方ができる。

話を戻して、ニュース録画だが、こちらもローカルネタなどで残したい番組があった時に、おまかせ録画と違って自動削除が行われないようにすることができない(気がする)。サブメニューを開いて除外することもできないし、番組編集からロックをかけて強制的に削除不可にすることもできない。
ということで、残す方法を考える必要がある。とりあえず僕が試して成功した方法は、ダビングして残しておく、という方法。しかし赤ボタンから入れるかんたんダビングではダメだ。
ニュース番組を残す場合、スタートボタンからかんたんメニューの「残す」を選択し、「詳細ダビング」に入る。詳細ダビングではダビング方向の設定で、ダビング元にHDDを、ダビング先にHDDを選択する(デフォルトではダビング先はBD/DVDと光学ドライブが選択されている)。ダビング先を選ぶとその後の設定が出てくる。3番のリスト作成から件のニュース番組を選択する。あとは「ダビング開始」を選択し、「ダビングする」を選べばHDDの中にダビングすることができる。
ダビングが終了すると、元の番組はダビング10のカウントがデクリメントされ、新たにダビング1で番組が作られる。新しく作られた方は自動削除されないから、残しておくことができる。データとしては通常の番組だから、不要な部分を番組編集で部分削除することもできる。


DIGAはあまり大きな問題はなく使用できている(1つ問題があるとすれば、当初想定していたよりも遥かに多く録画をしてしまい、あっという間にHDDが一杯になった点。新たにHDDレコーダを買う人は予算の範囲で最大のHDD容量を選択することをおすすめする)。
しかし、問題はないにしろ不満はいくつかある。まぁそのあたりはうまく付き合っていく必要があるだろう。

とりあえずDIGAに直接TCP/IPで接続して番組一覧を獲得するとか、番組表を取得するとか、番組録画を設定できるAPIが公開されたらすっげー便利になると思うんだけどね。

2016年3月11日金曜日

物体追跡

OpenCvで物体を追跡するヤツを作っています。最終的な目的はWebカメラ(or UVCなキャプチャデバイス)からリアルタイムに動画を読み込んで電動雲台で追尾することですが、とりあえず今は動画ファイルを読み込んで追尾しています(OpenCvは動画とカメラをほぼ同じように扱えるので便利)。

とりあえず最低限の動作は実装できたので、試しにロケットの動画を読み込ませてみました。手持ちにロケットの映像が無かったのでyoutubeから。


そして結果がこちら。




打ち上げ直後は綺麗に追尾できていますが、上空に上がって煙が太陽に照らされると煙自体を追尾するようになってしまいました。二値化の閾値をギリギリにするとうまく追尾できますが、値に余裕が無いので現地でリアルタイムに設定するのは結構大変だと思います。
解像度256x256でダイナミックレンジ広くて12bitくらいで輝度情報だけ出てくるUSBカメラがあれば良いんだけど。インダストリアルでそんなの無いかな。

画像自体は256x256にリサイズした後に処理しているので、FHDに比べれば情報量は多くありませんが、それでもCore i5 3.2GHzのマシンで18fps前後が限界です。Core i5 2.3GHzのノートでは10fpsも出ませんでした。早いCPUを積んだデスクトップならリアルタイムでトラッキングできると思いますが、射場にそんなの持ち込むわけにもいかないので、どうにか高速化する必要があると思います。
GeForce搭載のハイエンドノートとかもありますから、お金があるチームはそれを使うのが手かも。値段もTOUGHBOOKより安いですしw。

他には、例えばAIM-9X 画像誘導ミサイルの解像度は128x128ピクセルで、これでも10km程度先の戦闘機をLockできるはずですから、解像度自体はその程度でも十分なのかもしれません。ただし望遠レンズで視野が狭いですから、追尾用のカメラは小型軽量なモノを独立した高速なジンバルに載せてやる必要があります。


いろいろ課題は有りますが、少しずつ進めていこうと思っています。

2016年3月9日水曜日

C#のPixelFormat変換

前回はPixelFormat8に対するエラーの対策として、画像の保存方法を変更するという手段で対応した。しかし今回、プログラム内で対処する必要に迫られたので変換するメソッドを作ってみた。

static Bitmap convert(Bitmap Src)
{
    if (Src.PixelFormat != PixelFormat.Format8bppIndexed)
    {
        throw (new Exception());
    }
            
    ColorPalette palette = Src.Palette;
    Bitmap Dst = new Bitmap(Src.Width, Src.Height, PixelFormat.Format32bppArgb);

    BitmapData SrcData = Src.LockBits(new Rectangle(0, 0, Src.Width, Src.Height),
        ImageLockMode.ReadOnly, Src.PixelFormat);

    BitmapData DstData = Dst.LockBits(new Rectangle(0, 0, Dst.Width, Dst.Height),
            ImageLockMode.WriteOnly, Dst.PixelFormat);

    IntPtr SrcAdr = SrcData.Scan0;
    IntPtr DstAdr = DstData.Scan0;
            
    for(int y = 0; y < Src.Height; y++)
    {
        for(int x = 0; x < Src.Width; x++)
        {
            Color color;

            {
                int pos = x + SrcData.Stride * y;

                byte index = Marshal.ReadByte(SrcAdr, pos);
                color = palette.Entries[index];
            }

            {
                int pos = x * 4 + DstData.Stride * y;

                Marshal.WriteByte(DstAdr, pos + 0, color.B);
                Marshal.WriteByte(DstAdr, pos + 1, color.G);
                Marshal.WriteByte(DstAdr, pos + 2, color.R);
                Marshal.WriteByte(DstAdr, pos + 3, color.A);
            }
        }
    }
            
    Src.UnlockBits(SrcData);
    Dst.UnlockBits(DstData);

    return (Dst);
}

コードを読めばわかると思うが、入力はFormat8bppIndexedのみで、出力はFormat32bppArgbとなる。違うフォーマットに対応するには2段forをそれぞれに作る必要があり、FormatNNに対応するだけでも少なくとも196種類の分岐が必要となる。さすがに使う用もないのに実装するのも面倒なので今回は8bppIndexedから32bppArgbのみの実装とした。
ピクセルフォーマットの変換って地味に重要なのでC#に付属してても良さそうな気がするんだけど。特に8bppは今でも使われる部分はあるだろうし、Grapicsに使うには8bppは不可だし。

OpenCvでコントラスト調整

OpenCvSharpでコントラストを調整したいのでググったが、どうやらできないみたい。

ゴリラになる知識: コントラスト調整

ということでCv.Scaleを使って調整してみた。
まず256の範囲で獲得したヒストグラムをbins[]に突っ込んでおく。そしてminに一番暗い輝度を、maxに一番明るい輝度を設定する。今回はminを0に、maxを255に近づけるような処理を行う。

int min = 0, max = bins.Length - 1;

for (int i = 0; i < bins.Length; i++)
{ if (bins[i] > 0) { min = i; break; } }

for (int i = bins.Length - 1; i >= 0; i--)
{ if (bins[i] > 0) { max = i; break; } }

float scale = 256f / (max - min);
float shift = -min * scale;

Cv.Scale(img2, img2, scale, shift);

Cv.ScaleではSrc, Dst, Scale, Shiftを引数に渡す。処理としてはまずScaleでn倍に拡張し、その後でShiftを加算している。コントラストを調整したい場合はminとmaxの差が256になるようにScaleで拡張する。その後にShiftでminを0に近づけたいわけだが、minもScaleでn倍にされているので、引数に渡すShiftもscaleでn倍にして渡す。

結果


上が入力画像、下がコントラスト調整後の画像。左がカラー、右がグレースケールの画像となる。グレースケールはカラーをグレースケールに変換した後にコントラストを調整している。調整の係数はグレースケールのヒストグラムをカラーにも使用した。


ということでコントラストの調整は結構簡単にできるっぽい。ただ自動でコントラストを拡張したい場合は予めヒストグラムを獲得しておく必要がある。OpenCvのヒストグラム獲得は結構面倒なので、そのあたりが面倒。

カラーのコントラストを単純に調整すると色味に違和感が出る気がする。まぁこれは見る人の問題なのでそこら辺は好きにいじるように。

2016年3月7日月曜日

C#のGraphicsでException

C#でBitmapを読み込んでGraphicsを作ると"インデックス付きのピクセル形式をもつイメージからグラフィックス オブジェクトを作成することはできません。"という例外が投げられる。



例外が発生したBitmapのPixelFormatを見るとFormat8bppIndexedとなっている。これは8bitのカラーパレットを使用した画像を読み込んでいるためと思われる。C#側で対応しようと思ったのだけど、画像フォーマットの変換とかは見当たらなかった。ということでとりあえず画像側で対応した。



Paint.NetでPNGを保存する際に自動検出を使うと、モノクロの画像(というか8bitパレットで対応できる画像?)は8bitで保存される。保存する際に24bitとか32bitで画像を保存してやるとGraphicsの作成が成功する。

画像のラベリングと重心

画像のラベリングを行い、ラベリングした結果の重心を表示する というモノを作ってみました。試しに鳥の写真を読み込ませてみます。



上が元画像、下が結果です。元画像は実際の写真をモノクロ化し色を逆転した画像を使用しています(元の画像は逆光で鳥が黒く抜けているため、白検出のために)。ラベリングした結果の枠を灰色で、重心を赤色で、物体の面積を緑で表示しています。

この方法で鳥を追尾する場合、鳥が羽ばたくと見かけ上の面積が変わりますから、一番大きい物体を追尾する といった単純な方法ではうまく動かないと思います。位置関係を解析したうえで、連続して一つの物体を識別する といった処理が必要かもしれません。

熱赤外線画像誘導なんかは一番大きい物体(飛行機)を追いかければ良い気もしますが、追いかけられる方は単純なフレアでは回避できなくなるので大変そうです。小さなフレアをバラまいたところでせいぜい処理時間が増える程度の妨害しかできない気がする。熱画像対策だと燃料をダンプして着火して広範囲に高温をバラ撒くとかのほうが良いのかも?


とりあえず、背景に雲とか余計な物体が無い、綺麗な環境での画像ではちゃんと物体の検出ができるので、あとは実環境向けの処理をどうするかですね。実環境でWebカメラとかをソースにして飛行機とかを検出できるようになれば、飛行機とかロケットも追尾できるかもしれません。

2016年3月6日日曜日

画像の重心





ふと思い立って画像の重心位置を計算して遊んでいる。上からブルーインパルスのT-4、空自?のUH-60、ボーイング777の写真を読ませたところ。
処理としては 1) カラー写真のモノクロ化(人間の感度に合わせてWB化) 2) 画像の二値化(任意の閾値) 3) 重心位置の計算 という感じ。また1の前に処理しやすいように512x512にリサイズを行っている。
背景ホワイトと背景ブラックがあるが、逆光気味の場合は背景ホワイトとなり、順光で撮影した場合は背景ブラックとなる。カメラの自動追尾とかを行うには背景Wと背景Bの自動検出を行う必要がありそう。もちろん輝度とか閾値も自動で調整する必要があるのでその辺りもうまくやる必要がある。

カメラの自動追尾に使う場合、T-4やUH-60は綺麗に中心を検出することができている。しかしB777は飛行機雲が明るいため、機体だけではなく、飛行機雲までを含めた重心が計算されている。ラベリングを行って一番大きな目標を捉える、といった処理を行う場合でも、飛行機雲と機体が連続しているので不可能っぽい。飛行機雲は結構モコモコしてるので、画像認識とかやるなら直線を探して、それらを追尾したほうが良いのかも。

USBカメラとかOpenCVとかを使えばリアルタイムに画像を読み込んで重心を計算する といったことができるようになるので、その結果を電動雲台に流し込んで自動追尾 とかもできるようになるかもしれない。ただし実際の環境は背景ノイズがかなり問題になりそう。雲が薄い空を飛ぶヘリコプターとかは結構認識しやすそうだけど、巡航高度の旅客機だとか、地上を移動する物体を検出するのは大変そうだ。