無料ブログはココログ

2016年7月28日 (木)

作りながら学ぶオートガイダーの原理、いよいよオートガイド

いよいよ、完成が近付いてきました。どんどん、いきましょう。

前回までで、赤道儀をコントロールできるところまで来ました。N,S,W,Eのどのボタンを押すと、星がどちらに動くか覚えておきましょう。

それと、赤道儀動作中は、画像の描写が止まってしまうので、今日は前回出したガイド信号出力処理をスレッドにするところから始めます。

1607281

赤枠の部分の変数を定義し、青枠の部分のスレッドと関数を追加します。

変数ですが、次の意味を持ちます。

motorDir モーターの方向を表す、それぞれ'N' 'S' 'E' 'W'で区別する
motorTim モーターを動かす時間、ミリ秒単位
motorMov モーターが動作中はtureにして、2重呼び出しを避ける

MotorThreadは、スレッドです。スレッドなので、画像の描写を邪魔しません。そして、motorMove関数で実際にガイド信号を発し、モーターを動かします。

今後は、motorMov関数で、モーターを動かすようにします。

それでは、次に、ガイド信号を出す部分の処理です。

1607282


場所分かりますか? wxとwyが、ガイド星のずれを表すので、その値が分かった直後に処理を追加します。

senceDecCombo.Value等は、メインウインドウの感度の部分の値です。また100は比例係数で、ここは自身の赤道儀やコントローラに合わせ、実際に試しながら、値を決めてください。100というのはあくまでも例です。

'N' 'S' 'E' 'W'も同じです。方向は赤道儀やコントローラによって違うので、各自調整してください。

方向は、本来、キャリブレーションによって決まるのですが、キャリブはまだやってないので、仮の値です。次回かその次あたりにキャリブの方法を説明します。

また、まだ室内の人工星によるテストと思いますが、部屋が明るいと、露出時間が短すぎ、頻繁にガイド信号が出てしまうので、ガイドレンズの絞りを絞るか、部屋を暗くするなどして、露出時間が長くなるようにしてください。現在カメラの設定は自動露出になっていますが、次回あたりに露出やゲインの設定などの説明をします。

実行してみてください。

1607283

意図的に星をずらし、元に戻ることを確認します。

今日はここまで。

2016年7月15日 (金)

作りながら学ぶオートガイダーの原理、赤道儀の制御、グラフの描写

お待たせしました。更新です。

今日は、ST4ポートによる赤道儀の制御と、ガイドグラフの描写についてお話します。

まず、ST4ポートです。カメラは赤道儀の載せてください。そしてASIのガイドカメラと赤道儀のコントローラをガイドケーブルで接続しておいてください。

アプリケーションの画面にある、N、S、W、Eのボタンを押したとき、赤道儀が動くようにしましょう。

デザイン画面で、各ボタンをダブルクリックすると、対応するイベント関数にジャンプしますので、そこでコードを書きます。こんな感じです。

1607151

1607152


3000というのは、3秒のことです。各自、適当な値にしてください。

プログラムはこれで完了です。実行させ、各ボタンを押して、赤道儀が動くことを確認します。簡単ですね。

さて、前回、ガイド星を選択し、ガイドウインドウにガイド星を表示させるところまで、できました。今日はガイドグラフを表示してみましょう。

まずは、ガイドグラフに使う変数をwhileループの直前に記述します。

1607153

変数の説明をちょっとします。

graphNowXというのは、画像の横方向のずれを表す変数です。同じく
graphNowYというのは、画像の縦方向のずれを表す変数です。ガイドグラフのY軸ではありませんよ。注意してください。ガイドグラフには、画像の横方向と縦方向の2方向のずれを2つの折れ線グラフで表します。

graphPreXというのは一つ前の値です。折れ線グラフを書くとき、一つ前の値が必要なためです。graphPreYも同じです。

graphCountとういのは、ガイドグラフのX軸のことです。ちなみにガイドグラフのY軸は上記、graphNowXとgraphNowYです。

それでは、次に、ガイドグラフを描写する部分を書きます。場所に注意してください。ガイド中モード、つまり if  (guideMode == 2)の中に書いてください。

1607156


やってることは、まず、guideCount=0なら、ガイドグラフを初期化、つまり消しています。そして中心線を書き、各変数を初期化します。

ガイドグラフは、X座標が増える方向が右方向で値域は0から233まで、Y座標が増える方向が下向きで値域は0から119までです。

次に、ガイド星のずれwx、wyを10倍して座標変換しています。したがって、0.1ピクセルのずれを、ガイドグラフでは1ピクセルで表現しています。

さて、記述できたら、実行してみましょう。

1607155

すっかり、オートガイダーらしくなってきました。

次回は、いよいよオートガイドしてみましょう。というか、もうここまで来たら、ほぼ完成でしょう。

2016年6月23日 (木)

作りながら学ぶオートガイダーの原理、フレームワークの作成

さぁ、どんどんいきましょう。みんな~、ついてきてますか?

まずは、前回のバグの修正です。labeling.csを開いてください。最後の方です。

1606231

青枠の、不等号の向きを逆にしてください。星を大きい順に並べるためです。上の図は既に逆向きになっています。まぁ、どちら順に並べてもいいのですが。

そして、赤枠のところ(double)でキャストしてください。これがないと、どちらも整数なので、商が整数に切り捨てられてしまいます。

それでは、本題です。
前回までで、ラベリングクラスを利用して星の認識ができました。ここまでくると、オートガイダーまでもうすぐです。そこで今日は、プログラムの基本的なフレームワークを決めます。プログラムの概略ですね。そんな難しい話ではありません。

オートガイダーの状態は大まかに分けて次の状態があります。

1 停止状態
2 ガイド星選択状態
3 ガイド中
4 キャリブレーション中

この状態を区別するためにguiderModeと言う変数を導入します。

1 guderMode = 0; 停止状態
2 guderMode = 1; ガイド星選択状態
3 guderMode = 2; ガイド中
4 guderMode = 3; キャリブレーション中

プログラムにguderModeの定義を追加します。

1606232

そして、プログラムの大枠ですが、whileループ中の最後にこんな感じで場合分けします。

while (1) {
 。。。
 if (guiderMode == 1) {
  
。。。ガイド星選択時の処理をここに書く
 }
 else if  (guiderMode == 2) {
  
。。。ガイド中の処理をここに書く
 }
 else if  (guiderMode == 3) {
  
。。。キャリブレーション中の処理をここに書く
 }
}

フレームワークが決まったので、実際にプログラムを書いてみましょう。

まずは、必要な変数や領域を定義します。whileループに入る直前に以下の記述をします。

1606233


guideWindowX/Yですが、これは重要な変数です。ガイド中は、画像全体は使いません。ガイド星が決まったなら、その周り、32×32の画像だけをクロップして解析すれば十分です。その32×32の領域の左上の座標を保持する変数が、guideWindowX/Yです。

ガイド中は32×32の領域を表示しますが、その表示のために必要な領域をここで定義しています。

次ですが、whileループの最後の方、前回の記述を削除して、以下のプログラムを追加します。

1606234

赤枠の部分は、ガイド星選択モードの処理です。内容は、ほぼ前回のプログラムと同じです。labelingクラスで星を認識し、四角で囲みます。

この中からガイド星を選ぶのですが、ガイド星の選択はユーザに選ばせるのか、ガイダーが自動で選ぶなら、どう選択するか、ここはガイダーの個性が出るところです。また、ガイド星は一つに限る必要はなく複数でもかまいません。その場合はガイド精度が上がります。

今回は勉強目的なので、具体的にどうするかは、宿題として、この例では、一番大きい星をガイド星とすることにします。つまり番号0番がガイド星になります。

次は、ガイド中の処理です。

1606235

まず、全体画像から32×32の領域を抜き出す、クロップ処理をしています。

次にラベリング処理をして、星の重心を取得しています。この重心の値はガイド修正信号出すとき重要です。16を減算しているのは32×32の左上が原点だからです。

次に、32×32の領域をガイドウインドウに表示していますが、これは全体画像の場合とまったく同じです。

次に、キャリブレーションの処理に続きますが、これは次回以降とします。

最後に、(ガイド)ボタンを押したときに処理を加えましょう。

ソリューションエクスプローラーのForm1.csをダブルクリックして、フォームを出します。

1606236


ガイドボタンをダブルクリックします。

以下の処理を追加してください。

1606237

ここでガイドボタンが押された場合の処理を記述しますが、各モードで処理が異なります。

以上で、プログラムの追加は終わりです。実行してみましょう。

ガイドボタンを押すと、ガイド星が選択され。。。

1606238


青枠が、ガイド星です。一番大きな星です。OKで、ガイドが始まります。

1606239

今日はここまで。

次回は、赤道儀をコントロールしてみましょう。オートガイダーまでもう一息。

2016年6月17日 (金)

C言語ワンポイント講座 ポインタと「実体、参照」

久々のC言語ワンポイント講座です。3か月ぶりかな。

前回は配列についてお話ししました。結論はC言語において配列という概念はなく、単なる領域確保、あるいは簡略記号の意味しかないということです。

いまいちピンときませんが、ポインタを理解すればもっとはっきりします。そこでここからポインタについてお話ししますが、その前に重要な実体か参照か、についてお話します。

一番最初に言ったように、プログラムを理解するには、「静的か動的か」、「実体か参照か」これらの違いを理解することが重要です。これらの区別がつかなければ、プログラムを正しく理解できません。

最近は特にオブジェクト指向のプログラミングが主流で、ますます実体か参照かの区別が重要になってきました。

プログラミングしているとき、オブジェクトが実体なのか参照なのか、ちゃんと区別できています?

たとえば、オートガイダーのプログラムで言えば、

Bitmap bitmap = new Bitmap(640, 480, 640*1, PixelFormat.Format8bppIndexed, framePtr);

でビットマップデータを作成していますが、bitmap自体に画像データが詰まっているわけではありません。bitmap自体が保持してるのは、画像データの番地、つまり参照です。

実体とは、データそのものです。そして参照はその番地です。

扱っているものが実体か参照かではプログラムの意味が違ってきます。

参照なら、実体は別に定義し、その番地で初期化しなければなりません。また必要なくなったら解放しなければいけません。

参照なら、受け渡しているのは番地であって、実体ではありません。ですから、複数の場所から重複してアクセスされる可能性があるので十分注意しなければいけません。

実体の場合は、データの受け渡しは、その都度コピーされるから、他からの影響はあまり意識しません。ただし、データの受け渡しに相当なコストがかかりますから、そこは注意です。

●プログラミング言語と参照

プログラムでは大きなデータとかは、直接やりとりしなく、参照、つまり先頭番地でもってやりとりするのが普通です。ですから、番地を格納できる変数が必要です。

番地は、0x1234番地とか、整数です。ですから番地を扱うにはintとか整数型ではダメなの?と思うかも知れません。

答えはダメです。

たとえば、0x1234番地からデータを持ってこい!

と言ってもプログラムは組めません。正確には、

0x1234番地から2バイトのデータを持ってこい!

が正解です。

つまり、どこどこの番地から○○のデータを持ってこい!

と言わなければだめです。番地とその中にあるデータ型が重要なのです。

しかし、いちいち番地とデータ型を指定するのは面倒なので、番地自身にデータ型の属性を持たせたのが、ポインタ型です。

たとえば、int型へのポインタ型と言った場合、それは単なる番地ではなく中にint型のデータが入っていることを意味します。

つまりint型のポインタ型でアクセスする限り、いつもint型のデータを持ってこい!と言わなくても自動でint型のデータを持ってきます。

中に入ってるデータ型が重要なのです。

このことが良く分かる例題を一つ出します。

pをchar型のポインタ変数とします。pには、100番地が入っているとします。

そうすると

p++;

だとすると、pは101番地になります。

では、pをint型のポインタ変数とします。そうすると、

p++;

は101ではなく104番地になります。

int型は通常4バイトなので、++で4バイト進まないとつじつまが合いません。

同様に

p+5

は105ではなく120です。

この例のようにポインタ型は単なる番地データではなく中に入ってるデータ型によって計算が異なってくるのです。

番地を扱うためには整数型ではなく特別なポインタ型という型が必要なわけです。そして、ポインタ型を格納する変数がポインタ変数です。

「ポインタ」という言葉は曖昧です。このような曖昧な言葉は使うべきではありません。通常はポインタと言った場合、ポインタ型かポインタ変数を意味します。あと、ポインタ定数というのがあります。ポインタ定数には以下のものがあります。

1 変数にアンパサンド &x
2 キャストされた定数 (int *)0x1234
3 文字列 "ABC"
4 配列の名称、関数の名称

これらは、ポインタ型をもつ定数で、コンパイラによって0x1234とか具体的な数値に置き換わります。

ポインタ定数は、ポインタ変数に代入できます。ポインタ変数は定義しただけでは、中身は空っぽなので、これらの定数や他のポインタ変数で初期化しないといけません。

●ポインタ型によるデータのアクセス

プログラミングでは配列をよく使います。オートガイダーのプログラムでもそうです。ところが、C言語には配列というデータ型はないので、ポインタ型を駆使するしかありません。

たとえば、100番地から並んだint型データがあるとします。

==================
100 104 108 112 116
---+--+---+--+---
76   89   56  34   65
-----------------

まずは、int型のポインタ変数pを定義し、先頭番地で100で初期化します。

int *p = (int *)0x100;

ただ、いちいちプログラムで番地を割り当てるのも面倒なので、
通常は配列を定義します。

int array[] = {76, 89, 56, 34, 65};
int *p = array;

配列はこれだけの意味しかありません。単なる領域確保です。

それぞれの要素にアクセスするには次のようにします。

0番目 *p
1番目 *(p+1)
2番目 *(p+2)
3番目 *(p+3)
4番目 *(p+4)
i番目  *(p+i)

ただ、*(p+1)と書いても、良くわからないので、配列のような簡略記号が認めらています。

0番目 p[0]
1番目 p[1]
2番目 p[2]
3番目 p[3]
4番目 p[4]
i番目  p[i]

こっち方が分かりやすいですね。

[ ]はポインタ型に作用する簡略記号です。ポインタ型ならなんでも良いので、たとえば、

"ABCDEF"[3]

のような使い方も可能です。これは文字'D'を表します。

もちろん配列名にも使ってよく、

int array[] = {76, 89, 56, 34, 65};
array[3]

と書いたら、34を意味します。

array[3]は、*(array+3)の簡略記号です。

ではなぜ、わざわざポインタ変数を定義するのか? 

array[3]でアクセスできるなら、なぜわざわざ、

int array[] = {76, 89, 56, 34, 65};
int *p = array;
p[3]

なんてやる必要はないです。実はC言語の入門書には、上記のようなサンプルがいっぱい書かれていますが、プログラム的にはまったく意味がありません。英語の「This is a pen」と同じです。みりゃ分かるって。

私が思うに、ほんとにポインタ変数が必要なのは2つくらいしかないように思えます。

1 ポインタ変数はインクリメントできる

配列名はポインタ定数なのでインクリメントできません。

array++

は、NGです。

したがって、いったん、ポインタ変数に入れて、

int array[] = {76, 89, 56, 34, 65};
int *p = array;
*p++;

とやります。インクリメントによるアクセスは効率が良いので。

2 関数の仮引数として

C言語には配列型はないので、関数に処理を任す場合は、先頭番地を渡すしかありません。

いっぽう、受ける関数の方は、何がくるか分からないので、変数にして置くほかありません。

void func(int *p) {
}

私は、ポインタ変数の利用価値としては、この2点しかないように思えますが、どうでしょうか? ポインタは難しいと言われますが、この2点しか必要ないと分かれば少しは気が楽になります。

また、最近はオブジェクト指向が主流なので、データはnewで動的に確保するようになってきました。そうすると、newで確保したデータの番地を受け取るため、どうしても、ポインタ変数が必要です。

さて、今日はC言語においてのポインタ型変数の役割について話しました。なぜ必要か、それがわかるだけでも理解の助けになるのではないでしょうか。

終わり。

作りながら学ぶオートガイダーの原理、ラベリング実践編

それでは、ラベリング実践編です。実際にプログラミングして動かしてみましょう。私が、ラベリングクラスを作成しましたので、それを利用するだけです。

ラベリングクラスのプログラムの説明は後でします。実は前回理論編で説明したのとは少し違うアルゴリズムを採用しています。詳しいことは後回しにして、とりあえず、動かしてみましょう。

まず、新規にクラスを作成します。
ソリューションエクスプローラーの[AutoGuiderSample]で右クリックして、[追加]->[クラス]を選びます。

1606161

で次のダイアログが出ます。

1606162

ここで、名前のところに、[Labeling.cs]と入力します。

できたファイルを開いて、以下のファイルをコピペしてください。

「Labeling.cs」をダウンロード


ラベリングクラスの使い方を説明します。

●オブジェクトの作成は以下のようにします。

Labeling lab = new Labeling(imageData, 640, 480);

ここで、imageDataは元画像の配列です。640は横幅、480は縦幅です。

●ラベリングの実行は以下のメソッドを実行します。

int maxLabelNo = lab.startLabeling(border);

borderは、星と背景を分ける閾値です。前回の2値化処理で求めましたね。あれです。
maxLabelNoには、認識できた星の数は返されます。

●結果の取得。

ラベリングの結果は以下のメソッドで取得できます。

double wx = lab.getWeightX(i);
double wy = lab.getWeightY(i);
int size = lab.getSize(i);

getWeightX()とgetWeightY()でi番目の星の重心の座標が得られます。
getSizeでi番目の星のサイズが得られます。

なお、星はサイズの大きい順にソートされますので、getSize(0)で一番大きな星のサイズが得られます。

それでは、このクラスを利用して、星を認識してみましょう。前回のプログラムを改造します。

まずは、前回のプログラムのうち、borderを求める部分は残しておいてください。白黒に分けるラスタースキャンの部分は削除します。そして、最後の方に以下の赤枠部分を追加してください。

1606164


追加する場所に注意してください。whileループの最後の方です。

追加したら、模擬星にカメラを向け、実行してみましょう。

1606163_2

どうです。ここまでくるとけっこう感動もんです。

いちおう、クラスの中身も説明しておきます。理論篇のアルゴリズムは分かりやすく1パスで実行できるのが魅力ですが、いざプログラムをしようとするとけっこう難儀します。とくにラベル番号の付け変えでは、関数の再帰呼び出しを利用したり、ちょっと初心者向きでありません。

新しい、アルゴリズムは2パス構成で、一回目のパスでラベル付けを完了し、2回目のパスでサイズや重心計算をします。

説明

1回目のラスタースキャンでのラベル付け方法は理論編の説明とまったく同じです。つまり、

1 隣に0以外の白がない場合は->新しいラベル番号を付ける
2 隣に0以外の白がある場合は->隣の番号のうち小さいものをもらう

ここまではまったく同じですが、ここで一工夫します。2で隣の番号のうち小さい値をもらうのですが、隣が2つある場合です。この場合、小さい値をもらうのですが、大きい値のラベルのラベルテーブルの番号を小さい番号に、書き変えてしまいます。

図で説明しましょう。

まずは理論編での続きです。2番のラベルを付けるとこまでは同じです。

1606165

ただし、今度は、2パス構成なのでsXとsYの項目は必要ありません。またサイズとΣの計算は2パス目でやるので、一回目は0のままです。

さて、問題は次です。

1606166

ここで座標(3,2)ですが、お隣さんは二人います。1と2のラベルをもつお隣さんです。ここで小さいほうのラベル番号1をもらいます。そして大きいほうのラベル2ですが、ラベルテーブルの2番目の項目のラベル番号をなんと1に書き換えてしまいます。

16061612

つまり見かけ上はラベル番号2ですが、実質1ということです。

プログラムで説明すると次のようになります。

16061614

labelTable[another].labelNo = num;のところがテーブルを書き換えているところです。

このように、全部ラベリングすると次のようになります。

1606167


左のラベルの図は理論編とまったく同じです。ところが右のテーブルが違います。

たとえば、見かけのラベル5ですが、テーブルの項目5を見ると、ラベル番号4とあります。さらに項目4をみると3とあります。項目3は3なので、ここで置き換えが終了します。

つまりみかけラベル5は実質ラベル3です。

では、2回目のラスタースキャンに入ります。ここではサイズとΣを加算していきますが、見かけのラベル番号ではなく、実質のラベル番号で行うことが味噌です。

プログラムはこんな感じ。

1606169

while(no != labelTable[no].labelNo)
{
no = labelTable[no].labelNo;
}

のところが見かけのラベルを実質のラベルに変換してるとこです。

最後にとびとびになったテーブルを詰めます。

16061613


16061610
配列の項目番号とラベル番号が同じとこだけ、ほんとの情報です。それ以外は抜け殻です。ですからそこを詰めます。またサイズ4未満の星は除外しています。

以上がプログラムの説明でうす。残りは見れば分かると思います。

今日はここまで、次回はガイド星の選択とクロッピングについてです。これも重要なので見逃せません。お楽しみに。

2016年6月14日 (火)

作りながら学ぶオートガイダーの原理、ラベリング理論編

すっかり月刊誌となったこのコーナー。SS-one AutoGuiderの出荷も一段落したので、これからペース上げます。

さて、前回は、星と背景を分ける閾値を求めました。これ以上なら星(白)、これ以下なら背景(グレー)とします。

で、星の固まりを認識して番号を付ける作業のことをラベリングといいます。こんな感じ。

1606141_3


ラベリングによって何が分かるかというと、次のことが分かります。

1星の数
2星のサイズ
3星の明るさの平均
4星の重心位置
5星の周長
6星の円形度

今回は1と2と4だけわかるアルゴリズムを説明しますが、プログラムを理解できれば、3は割と簡単に求まります。5,6はまた別のアルゴリズムが必要です。

このラベリングができれば、星の重心位置が分かりますので、もう即、オートガイダーが作れます。

ではこのラベリング方法を説明します。今日は理論編で次回、実践編です。

1 まず、元画像と同じサイズの配列を用意し、0で初期化します。元画像とラベリング用の2つのデータがあることに注意してください。説明では2つを重ねて表示します。元画像は値を絶対変更しません。

それと、ラベリングされた星の情報を保存するテーブル(配列)も用意します。

1606142_2

テーブルの項目の意味は次の通りです。
●No ラベリングNo
●Size 星のサイズ
●sX 最初に星を見つけた位置のX座標
●sY 最初に星を見つけた位置のY座標
●ΣX 星の各画素のX座標をすべて足したもの(重心を求めるため)
●ΣY 星の各画素のY座標をすべて足したもの(重心を求めるため)

2 次に、ラスタースキャンをして星を見つけます。最初に星を見つけたら、番号を割り振ります。最初の星なので1ですね。そしてその座標をテーブルに保存します。Σも、わすれずに。こんな感じ。

1606143_2


3 さらにラスタースキャンをします。しかし、今度は次の決まりを守ってください。

●白を見つけたら、4方向の隣にも0以外の白がないか探す。ただし、右隣と下隣はまだスキャン前なので当然ありません。したがって、左隣と上隣を探すだだけで十分。

●隣にも0以外の白があったら、新しい番号を割り当てないで、隣の番号のうち最小の番号を割り当てる。

さぁ、ラスタースキャンを続けます。座標(2,2)で白を見つけます。左と上に0以外の白がないので、新しい番号2を割り当てます。

1606144

4 さらにつづけます。今度は(3,2)に白を見つけますが、上が1の白なので、新しい番号を割り当てないで、この番号1を割り当てます。

1606145

ここでテーブルのNo1を更新します。サイズを+1し、ΣX、ΣYも座標を足しこみます。sXとsYは不変です。

5 このように全画像をスキャンすると以下のようなテーブルが完成します。

1606146

6 しかし、このテーブルを見ればわかるように一つの星に複数の番号が割り当てられています。そこで、各番号の最初の白の座標に注目します。sXとsYの座標の白です。

1606147

たとえば、2番の場合、(2,2)の隣に白1があります。そこで2番をすべて1番に置き換えます。こんな感じ。

1606148

そして、テーブルも2番を削除して、1番のサイズ、Σに値を足しこみます。

7 このように最後の番号までやると、星が完全に分離できます。

1606149

8 ただ、テーブルがとびとびの値になるので、テーブルの番号を付け変えて間を詰めます。

16061410

これでラベリング完了です!!。

これで何が分かるかというと。

●星の数 = 2個
●星のサイズ
1の星=8
2の星=13
●星の重心
1の星=(19/8, 10/8)=(2.375, 1.25)
2の星=(78/13, 78/13)=(6.0, 6.0)

重心は(ΣX/size, ΣY/size)であることに注意してください。

これで星の位置が計算できました。今日はここまで。次回は、これをプログラミングします。
ただ、一からコーディングするのはけっこう大変なので、私が、ラベリングクラスを用意します。このクラスを使い方を覚えて頂ければ十分です。ただ、今日説明した理論をしっかり理解して頂ければ、このクラスの読解は容易です。

2016年5月 2日 (月)

作りながら学ぶオートガイダーの原理、2値化処理

すっかり月一の更新となったこの連載。これでも更新は大変なんですよ。ただ、着実に進めていきます。たまには応援よろしくお願いします。めげますよ。

さて、前回はラスタースキャンについて学びました。画像処理はほとんどすべてこのラスタースキャンです。今日は画像認識で重要な2値化処理です。

2値化処理とは、画像を、注目している部分(天体写真なら星)を白、それ以外(天体写真なら背景)を黒の2値に分ける処理です。

ただ、ここで注意して頂きたいのは、画像認識では実際に画像を白黒にする処理はしません。重要なのは、その境界値(border)を求めることです。つまり、ある値以上なら、星、それ以外なら背景。その値はいくつなのか?

●borderの求め方。

この境界の求め方ですが、天体写真のある特性を利用します。つまりある特性とは、

「天体写真は画像全体に対して、星の占める部分が少なく、ほとんど背景である」

別の言い方をすると、

「天体写真のヒストグラムはほぼすべて背景である」

さて、以下はおなじみヒストグラムです。この山はすべて背景と思ってください。そうすると、星と背景の境界は矢印の部分あたりになります。

1605027_2

さて、ここで数学の時間です。この山をガウス分布とすると、山の中心は平均値avg。そこから分散をfrcとすると、この山がavg±n*frcの中に入る割合は、(nは自然数)

avg±frc -> 68.27%
avg±2frc -> 95.45%
avg±3frc -> 99.73%

16050242

となります。これを見るとavg+3frcを境界として問題なさそうです。ただ、実際はもっと余裕を見て、avg+4frcを境界(border)とすることにします。

それでは実践です。前回のプログラムを修正して、画像の2値化処理をやってみましょう。

1605024

赤枠の部分が変更または、新規の部分です。

2回ラスタースキャンをしていて、一回目のラスタースキャンで、平均および分散を求め、そこから境界値を求めています。そして、2回目のラスタースキャンで、白黒に分けています。

●実行結果

2値化処理前
1605025

2値化処理後
1605026

さて、今日はここまで、だんだん核心に近いづいてきました。次回はいよいよ画像認識の基礎の基礎。ラベリングについてお話しします。これがむずい。説明するのも大変だ。

2016年4月12日 (火)

ラズベリーパイ・プログラミング入門 Qtクリエーターの使用

前回、ラズパイのLinuxデスクトップ画面をパソコンからVNC接続するとこまで説明しました。今日は、プログラム開発環境の説明です。

あらかじめ、Qt Creatorというツールがインストールされていますので、使ってみましょう。

1 ラズベリーのメニューから[Programming]⇒[Qt Creator]を起動します。

1604089

2 Qt Creatorが起動したら、プロジェクトを新規作成します。メニューの[File]⇒[New File or Project]を選択します。

1604088

3 ダイアログが出ますので、[Appkication]から[Qt Gui Applivation]を選択します。

16040810

4 ここでプロジェクト名を入力しますが、名前はなんでもいいです。Sampleというプロジェクトは既にあるかも知れません。他の名前にします。

16040811

5 これで、プロジェクトが作成されました。メインファイルを開いてみましょう。左側の[Edit]をクリックして、ファイルツリーからmain.cppをクリックします。これがmainファイルです。

16040812

6 まずはビルドしてみましょう。メニューの[Build]⇒[Build All]を選択します。

16040813

7 ビルドしただけでは、まだ実行されません。実行ファイルが指定されていないからです。そこで実行ファイルを登録します。

左の[Project]をクリックし、上のタブから[Run Setting]を選びます。

16040814

8 [Deployment]のAddボタンから、[Deploy Configuration]を選びます。

16040815

9 次に[Run]の[Add]から[Custom Executable]を選びます。

16040816

10 図の[Brows..]をクリックして実行ファイルを選択します。

16040817

11 ダイアログが出ますので、下図にあるフォルダの中を開きます。フォルダ名がやたら長いので注意です。[Sample]フォルダではないです。

16040818

12 ここでプロジェクト名と同じファイル名を選択します。これが実行ファイルです。拡張子はありません。

16040819

13 左下の実行ボタンをクリックしますと、プロジェクトが実行されます。まだWindowが開くだけです。

16040820

14

これでプロジェクトの作成から実行までの流れです。

なお、すでに作成したプロジェクトを開くには次のようにします。
メニューから[File]⇒[Open File or Project]を選び、プロジェクト名と同じフォルダを開きます。

16040821_2


15 次に、プロジェクト名+拡張子[.Pro]のファイルを開けばOKです。

16040822


今日はここまで。

2016年4月 8日 (金)

ラズベリーパイ・プログラミング入門

お待たせしました。ラズベリーパイの話です。

さて、ご存知のようにSS-one AutoGuiderはラズベリーパイを使用しています。ラズベリーパイは単独でパソコンとしての機能を備えていて、ここでプログラミングすることができます。

そこで、まずは、ラズベリーパイ上でプログラムを作成し、動かすまでの方法を説明します。SS-one AutoGuiderは必須です。普通の基板を買ってきてもダメです。SS-one AutoGuiderはすでにプログラミングに必要な開発環境をセットアップしていて、すぐに開発を始めることができます。このレベルまでセットアップするのも容易ではありません。

さて、ラズベリーパイでプログラム開発をする場合は以下の3通りの方法があります。

1 ラズベリーパイを単独のパソコンのように扱う
この場合、ラズベリーパイにHDMIモニタ、キーボード、マウスを付け、あたかも一台のパソコンのように扱う。

2 ラズベリパイを有線LANに接続し、パソコンからリモートアクセスする

3 ラズベリパイを無線LANに接続し、パソコンからリモートアクセスする

最初は1の方法が分かりやすいのでこれでいこうと思ったのですが、モニタを一台占有してしまうのです。開発中は調べものなどでパソコンも必要なので不便と言えば不便です。モニタ切り替え器の使用もありますが。

そこで、2の方式を採用することにしました。

必要なハードウェア
1 SS-one AutoGuider
2 セルフパワーのUSBハブ(ACアダプターつきのもの)
3 Windowesパソコン用のキーボード
4 USB-LANアダプタ

4のUSB-LANアダプタはいろいろ試したのですが、けっこう相性問題があります。接続できないものもありました。私が動作確認できたものに、
サンワサプライ USB3.0LANアダプタ LAN-ADUSBRJ45G
があります。

ちなみに、ラズベリーパイとかLinuxの世界では訳のわからないトラブルはWindowsの比ではありません。ネットには情報があふれていますが、責任のある情報は皆無なので、ちょっとしたレビジョンの違いなどで動かなかったり、うまくいかないことはもうざらです。
というか、一発でものごとを進む方が珍しいです。

必要なソフトウェア
1 ターミナルソフト TeraTerm
https://osdn.jp/projects/ttssh2/

2 VNCビューアー VNCViewr
https://www.realvnc.com/download/viewer/

以上2つのソフトをダウンロードしてパソコンにインストールしてください。

接続方法
SS-one AutoGuiderにセルフパワーのUSBハブをつけ、そこにLANケーブルとキーボードを接続します。
LANケーブルは、ルーターなどに接続します。

16040823_2


IPアドレスの確認
まず最初にやることはルーターがラズベリーパイに割り当てたIPアドレスの確認です。
1 SS-one AutiGuderに電源を入れ、起動する
2 オープニング画面が出るので、(ShuttDown)->(Sysdtem)でシステムに戻る
3 ここで、モニタのアイコンを指が細い棒でダブルクリックします。

16040824_2

4 ターミナル画面が開くので、「ifconfig」と入力し、リターンキーを押す

16040825

16040826

5 ここで、[eth0]と出ればネットにつながっています。[lo]だけならLANにつながっていません。また、ここでIPアドレスを確認してください。赤枠の部分です。

IPアドレスが確認できたらメモっておいてください。キーボードはもう必要ないので、抜いてかまいません。

パソコンからTeraTermで接続
IPアドレスが分かったので、パソコンからTeraTermsで接続してみましょう。TeraTermを起動し、メニューの[ファイル]⇒[新しい接続]を選びます。

1604081

ここでTCP/IPを選択し、IPアドレスを入力します。途中警告画面が出ますが、「続行」で続けます。そうするとユーザ名とパスワードを求められます。

1604082

ここで、ユーザ名に[pi]、パスワードに「ssone123]と入力します。これでラズパイに接続できます。

1604083

この画面はラズパイのコマンド画面です。たとえば「ls」と打ち込めば、ファイル一覧を見ることができます。Linuxのコマンドを知っていれば、これだけでなんでもできます。

ただテキストベースなので、現在のGUIプログラミング環境に慣れた人は、やはり不便でしょう。GUI環境にしたいと思います。そこで、今度はパソコンからVNC接続しますが、その前に、ラズベリーパイの方で、VNCサーバーを起動しておく必要があります。

コマンドラインから「sudo tightvncserver」と入力します。

1604084


次にパソコンでVNC Viewerは起動します。ここでIPアドレスを入力しますが、最後に「:1」コンマ1を入れてください。

1604085_2

途中警告画面が出ますが、「続行」します。そうすると、パスワードを求められますので、「ssone123」と入力してください。

1604086

これでラズベリーパイのWindowsシステムに接続できました。おなじみのオープニング画面が出ますので、[ShuttDown]⇒[System]でシステムに戻ります。

1604087


今日はここまで。なお、終了するにはイチゴの[Menu]から[ShuttDown]を選んでください。

次回はいよいよプログラミングです。C言語知ってれば大丈夫。

2016年4月 7日 (木)

作りながら学ぶオートガイダーの原理、ラスタースキャン

前回までで、画像のキャプチャと表示ができたと思います。今日は画像処理や画像認識で必要となる基本、ラスターキャンについてお話します。

カメラから取り込んだ画像は以下のように二次元配列の形式で収まっています。ここでWidthは横幅(ピクセル)、Heightは縦幅(ピクセル)です。

1604071
この例はモノクロで1ピクセル=1バイトになりますが、カラーの場合は3バイトでRGBの順に並びます。

さて、この画像をいじる場合は、左から右へ、上から下へと操作していきます。これをラスタースキャンといいます。今、座標(x,y)のピクセルに注目すると、この2次元配列をラスタースキャンの通り、一列にならべた1次元配列と考えて、次のようにあらわすことができます。

imageData[y*Width + x]

ここでimageDataはバイト型の一次元配列です。さて、ラスタースキャンをforループで書くと以下のようになります。

//ラスタースキャンの公式
for (int y = 0; y < Height; y++) {
   for (int x = 0; x < Width; x++) {
      imageData[y*Width + x] = ......
   }
}

それでは、このラスタースキャンを使って、画像の反転処理を前回のプログラムに追加してみましょう。赤枠の部分が反転処理です。

1604072

なお、青枠の部分ですが、前回のプログラムの間違えです。直しておいてください。最初の青枠2行は記述場所のミスです。whileループの外に出します。

ラスタースキャン分かりましたか? 次回は、2値化処理です。

«作りながら学ぶオートガイダーの原理、ひな型の公開