2013/10/30

とりあえずJoystick

Kです。テスト用プログラムにJoystickを使いたいので、C#からDirect Inputを使用してボタンの判定を行うクラスを作ってみました。


動作との関連づけは、各デバイスクラスにonbuttonメソッドがあり、全てのコントローラからのデータはこいつが受け取ります。設定により登録されたデバイスに対して、登録されたボタンが押されたりするとそのボタンと任意の文字列を渡す仕組みです。アナログスティックについては、常に通知する仕組みです。

joystick.setButtons(new Byte[] { 1, 3, 2, 0 }, 4);
によってキー割り当てを変更しています。
これは、左が1, 上が2, 下が3, 右が4となっているコントローラにおいて、
上から時計回りに0, 1, 2, 3と割り当てるための記述です。
また、上記4つのボタン以外は受け付けません。


using System;
using System;
using IWshRuntimeLibrary;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectInput;
using System.Windows.Forms;

using System.Collections.Generic;

class Form1 : Form {

}

class Program {
   public static void Main() {
      Device device;
      Joystick joystick;
      Form1 form;

      form = new Form1();

      device = serachDevice(form.Handle);

      if(device != null) {
         Joystick.Command jc;

         System.Console.WriteLine("デバイス見付かった");
         joystick = new Joystick(device);

         joystick.setButtons(new Byte[] { 1, 3, 2, 0 }, 4);

         jc = new Joystick.Command(0, "△");
         jc.onbutton += onButton;
         joystick.setCommand(jc);

         jc = new Joystick.Command(1, "○");
         jc.onbutton += onButton;
         joystick.setCommand(jc);

         jc = new Joystick.Command(2, "×");
         jc.onbutton += onButton;
         joystick.setCommand(jc);

         jc = new Joystick.Command(3, "□");
         jc.onbutton += onButton;
         joystick.setCommand(jc);
      }
      Application.Run(form);
   }

   private static void onButton(int no, string message) {
      System.Console.WriteLine("ID={0} / mes={1}", no, message);
   }

   private static Device serachDevice(System.IntPtr hwnd) {
      DeviceList dl;
      Device joystick = null;

      dl = Manager.GetDevices(DeviceClass.GameControl, EnumDevicesFlags.AttachedOnly);

      foreach(DeviceInstance dev in dl) {
         joystick = new Device(dev.InstanceGuid);
         joystick.SetCooperativeLevel(hwnd, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);

         // 最初に見つかったジョイスティックを操作対象とする
         break;
      }

      if(joystick != null) {
         joystick.Acquire();
      }

      return joystick;
   }
}

/*
 * ### ゲーミング用Joystickクラス ###
 * 特定のコマンドを検出したら、登録されたデリゲートに通知する。
 * setButtons(int[] )で、
 */
class Joystick {
   // pulibc
   public delegate void onButton(int no, string message);

   public Joystick(Device joystick) {
      this.joystick = joystick;

      // タイマーの作成・起動
      var mytimer = new System.Threading.Timer(new System.Threading.TimerCallback(onTimer),
         this, 0, 10);

      // ボタンルール・ボタンカウントの初期設定
      button_rule = new byte[128];
      button_number = 0;

      button_counter = new int[128];
      button_repeat_counter = new int[128];

      // コマンドリストの作成
      commands = new List<Command>();
   }

   public void setButtons(byte[] button_rule, int button_number) {
      button_rule.CopyTo(this.button_rule, 0);

      this.button_number = button_number;
   }

   public void setCommand(Command command) {
      commands.Add(command);
   }

   // private
   private Device joystick;
   private int button_number;
   private byte[] button_rule;
   private int[] button_counter, button_repeat_counter;
   private List<Command> commands;

   private static void onTimer(object sender) {
      Joystick joystick = (Joystick)sender;

      // ポーリング
      joystick.joystick.Poll();
      JoystickState js = joystick.joystick.CurrentJoystickState;

      // ボタンの検出
      byte[] buttons = js.GetButtons();

      // ここでのiは再定義した仮想番号
      // 仮想番号→実Joystick番号に変換した後データを受け取る
      for(int i = 0; i < joystick.button_number; i++){
         if(buttons[joystick.button_rule[i]] == 0x80) {
            // 通常単押しまでカウント
            if(joystick.button_counter[i] >= 5) {
               // 長押しの検出
               if(joystick.button_repeat_counter[i] > 1) {
                  foreach(Command command in joystick.commands) {
                     if(command.checkRepeatCommand(i)) {
                        command.onbutton(i, command.message);
                     }
                  }

                  joystick.button_repeat_counter[i] = 0;
               }
               else {
                  joystick.button_repeat_counter[i]++;
               }

               // 1回目だけ単押し
               if(joystick.button_counter[i] == 5) {
                  foreach(Command command in joystick.commands) {
                     if(command.checkCommand(i)) {
                        command.onbutton(i, command.message);
                     }
                  }
               }
            }

            joystick.button_counter[i]++;
         }
         else {
            joystick.button_counter[i] = 0;
            joystick.button_repeat_counter[i] = 0;
         }
      }
   }

   // 仮想番号を登録または、アナログスティックの登録
   public class Command {
      // public
      public onButton onbutton;
      public string message;

      public enum analog_direction {
         RX, RY, LX, LY, ZX, ZY, NONE
      };

      public Command(int no, string message) {
         this.no = no;
         this.is_repeat = false;
         this.ad = analog_direction.NONE;
         this.message = message;
      }

      public Command(int no, bool is_repeat, string message) {
         this.no = no;
         this.is_repeat = is_repeat;
         this.ad = analog_direction.NONE;
         this.message = message;
      }

      public Command(analog_direction ad, string message) {
         this.no = 0;
         this.is_repeat = false;
         this.ad = ad;
         this.message = message;
      }

      public bool checkCommand(int no) {
         // アナログスティックの場合は無条件
         if(ad != analog_direction.NONE) {
            return true;
         }
         
         // コマンドの照会
         return this.no == no;
      }

      public bool checkRepeatCommand(int no) {
         // コマンドの照会
         return this.no == no && is_repeat;
      }

      // private
      private int no;
      private bool is_repeat;
      private analog_direction ad;
   }
}

2013/10/25

to ka  です。
CADで試作品の角材を作ってみました。

2013/10/24

浜松基地に行ってきました

Contrailです。

サークル活動とは関係ありませんが、日曜日に大学の北にある空自の浜松基地で航空祭があったので行ってきました。

あいにくの天候で、予定されていたF-15の機動飛行やブルーインパルスの演技が中止になってしまいました。ただ、気になっていたF-15のエンジンノズルの機構を見ることができたので、それは良かったです。

来月の岐阜基地の航空祭も天候がよければ見に行きたいと考えています。

また、来月9,10日にあるテクノフェスタにサークルで展示をすることになっていて、それに向けて現在活動中です。

2013/10/23

C#からRubyを実行★IronRuby★

Kです。本日2回目の投稿です。というのも、テーマごとに1つの記事にすべきなので1日に何回投稿しても構いません。メールがうざったい人は管理できる人に言ってリストから削除して貰って下さい。尚、私は削除しました。笑

さて、本題のIronRubyに関して、まずは動機を説明しましょう。先日の操縦テストプログラムの要望によって、スクリプトファイルで処理した結果をデバイスに送信する必要が出てきました。従って、C#から数値の配列を渡し、Rubyが処理し、その結果を数値の配列で受け取る必要が出てきました。

次に、必要な環境について説明しましょう。IronRubyはC#上でRubyのソースコードを解釈し、実行するプログラムです。これを組み込めば、上記の目的は達成できるでしょう。さて、これを使うには次の4つのdllファイルを参照に追加する必要があります。
  • IronRuby.dll
  • IronRuby.Libraries.dll
  • Microsoft.Dynamic.dll
  • Microsoft.Scripting.dll
これらは、公式サイトからダウンロードできます。尚、Microsoft.Dynamic.dllについてはdynamicを使用しない場合は要らないようです。(適当)

とりあえずネット上から探してコードを実行してみました。

using System;
class Program {
 public static void Main() {
  var engine = IronRuby.Ruby.CreateEngine();
  var scope = engine.CreateScope();

  engine.ExecuteFile("./test.rb", scope);
  var ss = engine.CreateScriptSourceFromString("foo()");
  ss.Execute(scope);

  int res = engine.Execute<int>("fooo", scope);
  Console.WriteLine(res);
 }
}
test.rbの中身は以下の通りです。ちなみにRubyの流派ではreturnを書きません。
def foo
 $x = 10
end

def fooo
 return $x
end
これを実行すると、10と表示されます。

中身を追っていきます。 IronRuby.Ruby.CreateEngine();でRubyのインタプリタエンジンを作成します。

次に、engine.CreateScope();で変数などを記録するフィールド(適当)を作成します。このscopeが異なると別々の領域で実行されることになってしまいますので注意。

次に、engine.ExecuteFile("./test.rb", scope);でファイル都市コープを指定して実行します。実行された結果、値が返ってきますが、今回はメソッドの定義しかしていないのでその必要はありません。

 次に、var ss = engine.CreateScriptSourceFromString("foo()");でfooというメソッドを呼び出すプログラムを作成し、そのプログラムを変数ssに代入しています。これは次の行のとおりss.Execute(scope);と実行できます。ループして実行する場合は一度プログラムを解釈した後にまとめてExecuteしたほうが速かったです。

 最後にint res = engine.Execute<int>("fooo()", scope);でfoooメソッドを実行しました。結果をintで受け取り、ごにょごにょしています。文字列の場合はstringを、配列の場合はRubyArrayを指定します。

 その後いろいろ調べていたら、 次のコードでも実行できることが分かりました。但し参照にMicrosoft.CSharpを追加する必要があります。
using System;

class Program {
 public static void Main() {
  var rt = IronRuby.Ruby.CreateRuntime();
  dynamic t1 = rt.UseFile("./test.rb");
  dynamic t2 = rt.UseFile("./test2.rb");

  t1.foo();
  t2.bar();

  Console.WriteLine(t1.fooo());
 }
}
 Rubyは次の2ファイルです。1つめがtest.rbです。
def foo
 $x = 10
end

def fooo
 return $x
end
2つめがtest2.rbです。
def bar
 $x = 20
end

さてこの実行結果は20を表示します。

まず、var rt = IronRuby.Ruby.CreateRuntime();でRubyのランタイムを作成します。(適当)次にdynamic t1 = rt.UseFile("./test.rb");でソースコードを読み込み、解析結果をt1に代入します。このとき、dynamicを指定します。これは動的に動作が変わるためです。そしてt1.foo();で実行します。

この方法ではRuby内のメソッドをC#のメソッドで呼び出せます。更に、1つめのサンプルに比べてかなり高速です。これを使うしかありませんね!!

ファイルを分割しても、同じスコープを共有されるためグローバル変数が上書きされました。

デザインを変えてみた

Kです。Bloggerのデザインを変えてみました。

初め、クラシックテンプレートで編集していましたが、細かいことが出来ないことに気づいたので(「タグ」があまりじゃないか。)適当にテンプレートを選択しました。こいつのせいで2時間くらい無駄にしました。ああ最悪!

これでも不満はばっちりなんですけどね。まず、ブログ全体の幅がpx指定じゃないといけないこと。今時どの端末で見ているかもわからないのにどうして%指定じゃないのでしょうか。px固定とか10年前かっての!他にも投稿者と時刻の表示がおかしい点や上げればきりがないのですが;

更に、bloggerに限らず段落分けの機能がないことが非常に不満です。未だにbr要素で改行を表現しているんですよ? もう2013年ですよ。 10年m(ry

いいですか、今時のブログは意味づけが出来るようなエディタであるべきです。基本は段落とリスト、表で構成されており、必要ならば強調、リンク、削除など付け加えればいいのです。

もういろいろ面倒になってきたのでこれでいいや。あ、デザイン変更するとシンタックスハイライトのコードまで削除されちゃうから注意ね!

2013/10/22

操縦テストプログラムの仕様

こんばんはKです。いろいろなロボットを制御するにあたって、ひとまずPC上からコントローラーなど使って動かせるように汎用的なプログラムを作る予定です。できれば今週中に仕上がるといいな

仕様は以下の通り :

自由操作プログラム仕様

1. 本ソフトの目的
本ソフトはPC上からマイコンに対してデータを送るプログラムである。
即ち、ある入力x_nに対して出力y_n = f(x_n)を出力するプログラムである。

例えば、コントローラAの○ボタンを押すことにより、内部の制御データa1の値をα1にセットする、
その結果、y_1 = βα1γを出力するといった具合である。

2. 入力インタフェース
・マウス
・キーボード
・ジョイスティック
を想定する。これらの入力データは内部の別の記号σ_nに割り当てられる。

デジタル入力に対しては、
・長押し
・単押し
が存在する。内部ではσ_nがそれらを判定できる。

アナログ入力に対しては、
・数値読み取り
が存在する。内部ではσ_nがデータを担う。

3. 出力データ
出力データ列βα_nγは、入力σ_nに対して適切なデータ列α_nを生成し、その他デバイスの要求するプロトコル
に変換したものである。
βα_nγ = d_n(αn)となる写像d_nをデバイス設定と称する。

今回は簡単のため、デバイスは最大で1つまで同時に使用可能ということにしよう。

4. プログラム全体の流れ
(1) PCに接続されているジョイスティックを全て取得
(2) ジョイスティックを識別する設定ファイルを読み込み、関連づける
(3) デバイスの設定を読み込み、ジョイスティックの各ボタンに関連づける
(4) ジョイスティックの入力に対して、デバイスが関連づけられていればデバイスに挙動を委ねる
(5) 操作が終了したらデバイスの設定に基づき、データを生成してデバイスに送る。

4-1. システム上の課題
(1) ジョイスティックの認識と取り扱いが必要
(2) 設定ファイルの形式やその取り扱いが必要
(3) (2)と同様
(4) 特になし
(5) COMポートを用いた通信が必要

4-2. より汎用的にするためには
デバイスとの通信は究極の所、内部の持つ生の設定値α_nを変換して出力データ列βα_nγにすることである。
この変換するプログラムをプログラム中に書いては任意のデータ処理が出来ない。
従って何らかのスクリプトにして外部に追いやることが必要である。
即ち本ソフトのなすべきことは、GUI上でコントローラーやキーボードなどを用いて
データを送りつけることである。

これを実現するためにはWSHを利用したプログラムを書く必要がある。
今回はWSHを利用する。

5. GUI
メイン画面はデータの出力状況を表示する。データとは、例えばPWMを出力するボードなら各ポートの値である。
究極的には数値データしかないのだが、要望があればよりわかりやすい表現を用いる。

設定画面は、ジョイスティックとそれに対する関連づけ画面と、デバイスの設定画面に大別される。

・ジョイスティックとそれに対する関連づけ画面
現在接続されているジョイスティックが一覧で表示され、使用する場合その設定を行う。
まず、ジョイスティックに名前を付ける。この名前を用いて他の設定をする。
次にボタンに名前を付ける。正確には、ボタンに名前を付けて使用リストに追加する。
directinputを使って、使用リストの中を常に監視し、ボタンが押されたり長押しされたり値が変更されたりしたときに、関連づけられたデバイスにメッセージを投げつける。

・デバイスの設定画面
デバイスの入力に対して出力をするスクリプトを指定する。
そのほか、デバイスの通信設定だとか、デバイス名だとかも取得できる。

本ソフトを使用するデバイスとしては、
データ8bitストップ1bitパリティなしの他に、
・デバイスIDの取得
・起動回数の取得
が出来ることを推奨している。


6. クラス
Input 入力を担う
    GameController コントローラー。使用ボタンを管理する
        Button ボタン。ここにデバイスが関連づけられる
    Keyboard キーボード。ここにもデバイスが関連づけられる。
    Mouse マウス。ここにもデバイスが関連づけられる。

Device
    BasicDevice スクリプトを利用しないシンプルなデバイス
                # 出力データは8bitを1つとして、任意バイト数送りつける。フレーム単位*で送る。
    AdvancedDevice スクリプトを利用するデバイス

Script スクリプト処理を管理する。
Setting 設定データの管理



2013/10/21

便利なソフト★かちゃかちゃうぃんどうず★

お久しぶりです。Kです。

今回はみなさんにマウスを極力使わないで作業効率を高める便利なソフトウェアを紹介したいと思います。キーボードとマウスを行ったり来たりするのは時間がかかります。多少の時間だと思っても、その回数が多くなればなるほどいらいら……。慣れれば作業効率が上がります。

1. bluewind
このソフトは、他のソフトを呼び出すためのランチャーです。よく使うソフトを〈コマンド〉として登録して、それを入力することによって実行できます。
もちろん、コマンドは全て入力する必要はなく、数文字で特定できれば確定されます。
そのほか、c:\……と打てば、そのフォルダを表示できたり、再起動のコマンドやシャットダウンのコマンドなど登録できます。
例えばCtrl + Spaceをこのbluewindに割り当てれば、Ctrl + Spaceを押せば入力欄が出てきて、起動したいソフトの名前の先頭数文字を打てば起動してくれます。
応用すれば、「google あいうえお」と入力すればgoogleで検索、「cal 100+100」と打てば200と計算可能、なんてことも出来ます。ぱっとすぐに何かしたいときに大変便利です。

2. あふ
このソフトは、2画面ファイラーです。2つのディレクトリが画面上に表示されており、ファイル名や日付、サイズなどが一覧表示になっています。キーボード上から例えば、space keyで選択し、Dキーで削除、Mキーで反対側のディレクトリへ移動、Cキーで反対側にコピーなどとファイル操作が可能です。各キーの設定は細かく出来、例えば他のソフトウェアに現在のカーソル上のファイルを渡して処理することなどが可能です。
 私は、例えばmfindというソフトを使ってファイル内の文字列の置換といった処理をしたりします。ディレクト以下にあるファイルの文字列を一括で置換あるいは検索が出来便利です。
Migemoが使えるのもポイントです。ディレクトリ上に大量にファイルがある場合、目的のファイルを探すのには苦労しますが、先頭の名前が分かるのなら、ローマ字入力で平仮名片仮名はもちろん、漢字までも検索してすぐにカーソルが移動します。
ディレクトリ移動は親のディレクトリに移動したり、このディレクトリに移動したり、ブックマークを設定してその中から選ぶことが出来たりします。ただ、やはり特定のディレクトリにジャンプしたい場合があります。bluewindを使っても実現可能ですが、少し検索機能が貧弱なのと、ディレクトのみに特化したい場合があります。また、左ウィンドウ・右ウィンドウのどちらかにジャンプしたい場合があります。こんなときは3. fennrirを使うといいでしょう。あふから例えばJキーでfenrirを起動するようにします。そして、例えばEnterキーで現在のfenrirに入力されているディレクトリをあふの左メニューに、Ctrl + Enterで右メニューにするように設定します。これでパソコン上のフォルダをあふですぐに開けるようになります。
ファイルが色別に出来ることは当然ですが、マスク機能が便利です。特定の拡張子のみ表示することが出来ます。私は;キーで現在のカーソル上のファイルの拡張子のみを表示、:で解除、/でファイルのマスクを入力(例えば.pngのみ表示したければ*.pngとする)としています。これらは自分で自由にカスタマイズできます。(する必要があります。)
そのほか、画像ビューアとしても使えたり、ファイルを実行するとき、コマンドラインから実行が出来、CUIなアプリケーションも引数を簡単に指定して実行できたり、圧縮ファイルを展開できたり、展開せずに中身だけ閲覧できたり、属性や更新日時の変更が出来たり、おそらく創造することは何でも出来るでしょう。

足りない機能があれば、他のスクリプト言語やソフトウェアを組み合わせることで無限の可能性を秘めています。



3. fenrir
2.あふで述べたとおり、あふとともに用いるととても便利なランチャー兼ファイラーのソフトです。(もちろんfenrir自体はあふとは全く関係がありません。)
このソフトはパソコン上のディレクトリやファイルを検索してインデックスに記録します。そのため、検索が非常に高速で目的のファイルやディレクトリを一発で 探してくれます。migemoが使えるのでローマ字でも(変換せずに)検索できます。

4. Yet Another Mado tsukai no Yuutsu
このソフトはキー入力のカスタマイズを行うものです。例えば、←キーが押しづらい人は、Alt + Hで直接置換してしまうことができます。つまり、←キーを押す代わりにAlt + Hとおせば、Windows上では←キーと認識されます。このような単純な置換だけでなく、内部でトグル出来たり、2ストローク(例えばCtrl押しながらHを押した後にAを押すといった一連の流れを1つのコマンドとする)が出来たりと非常にカスタマイズ性が高い。
はっきり言ってこのソフトに慣れると他の環境で何も出来なくなりますので注意。


5. ckw-mod
このソフトはコマンドプロンプトを便利にした。WIndowsでもコマンドを入力することはしばしばあります。そんなとき、日本語を入力したくてもAlt + 半角全角を押さなければならないし、コピーは選択してコピーを押さないといけません。
このckw-modを使えば、フォントが自由に選べたり、色(のセット)を自由に変更できたり、操作が簡単になったり、シェル(6. NYAOSをお勧めします)を選べたり自由にカスタマイズできます。
例えばあふで、現在のディレクトリをカレントディレクトリとしてckw-modを開くコマンドをVとしておくととても便利です。

6. NYAOS
cmd.exeのコマンドを拡張したcmd.exeの本体の代替です。ckw-cmdはその外枠に過ぎず、nyaosを使って処理をします。スタックによるファイルの操作や強力な履歴機能、lsコマンド、エイリアスや関数などで強力なコマンド実行が出来る。ただ、私はlsコマンドしか使っていないw (あふがあるので余り出番がない)
たぶん、サークルではipconfigとpingくらいしかお世話になりませんが;

7. サクラエディタ
カスタマイズが隅々まで出来るテキストエディタです。各ファイルの設定からツールバーは当然、メニューバーや本当に細かい全てのキーの割り当て、右クリックメニューとなんでも設定可能。マクロをJScriptで書くことが出来るので好きな処理を自由に追加できます。文字の検索・置換に正規表現(鬼車)が使えるため、検索・置換は非常に強力。文字の強調にも正規表現が使えるため、例えば「\(円マーク)から始まってアルファベットが続く文字列を青で強調表示」といった設定が可能。即ち、IDEのような色分けを設定できます。
入力補助機能も充実。補完入力は、設定ファイル(例えば拡張子が.htmlの場合……など)によって保管ファイルから保管する文字列を選択可能。入力途中で 完成形が出てくるので全部入力しなくても良くなります。
そのほか、バックアップ、Grepによるファイルの検索、Diffによるファイルの差分、文字を打つたびに検索するインクリメンタルサーチ(Migemo併用でローマ字で日本語を検索できます)、タグファイルを作成してタグジャンプ、矩形選択などが出来ます。
マクロと併用することにより8. TeXの開発環境とすることができます。例えば編集中そのソースをコンパイルするマクロを書くことによって、Ctrl + Shift + Cでコンパイルを実行するように割り当てればすぐに実行できます。当然、結果をpdfで閲覧なども可能です(別のpdfビューアが必要です)。



8. TeX
TeXは文章や画像を張るための領域、数式などの図をきれいに整形してdviやpdfなどのファイルに出力するソフトです。きれいな数式が書けたり、自動で図形を並べてくれたりとwordとは違った点で便利なソフトです。レポートなど決まった書式に従って文章をマークアップしていく場合に大変重宝します。
マクロを使うことにより拡張可能で、既にそれらを使った便利なスタイルファイルが用意されています。これらを利用すればソースファイルを読み込んで取り入れたり、センター試験風の描画が出来たり、関数グラフが書けたり思うままです。
機能についてはここでは列挙しきれないほど豊富で、書籍も参考Webサイトも豊富なので必要に応じて参照して下さい。





 9. charu3
クリップボード管理ソフト。貼り付けた彝文字を登録しておいて、例えばShift2回押して呼び出すなどとすることにより、定型文の貼り付けが可能。
このソフトがいいのは、マクロとして選択文字列を取得してごにょごにょできたり、シェルの実行として、他のアプリケーションに文字を渡したりできる点です。これにより強力なその場編集機能がどのソフトでも使えることになります。
もちろんクリップボードとしての機能もあり、スタック・キュー形式のクリップボード管理が可能です。例えば、A, Bとコピーしてから2回貼り付けするとB, Aと貼り付けられたり、A, Bと貼り付けられたり出来るわけです。


10. ruby
これはスクリプト言語Rubyです。強力な文字列処理ができ、高級なbatch fileを作ったり、ちょっとした出力が欲しい場合に活用できます。
例えば、64サンプルのsinのテーブルが欲しい場合、
64.times do |i|
print (Math.sin(i/64.0 * Math::PI) * 128 + 128).to_i
print ", "
end
などとして出力を得ます。

これらのソフトにより作業がより楽しく、効率的になったらうれしいです☆

2013/10/20

C#で画像処理-WEBカメラの使用-

Lv_ZEROです。

前回予告したように今回はUSB接続のWEBカメラから画像を読み込む方法を紹介していきたいと思います。
windows環境でUSB接続のWEBカメラを使用するには2通りの方法があります。1つは"DirectShow"を使う方法で、もう一つは"VFW(video for windows)"を使う方法です。"AForge.NET"では前者の"DirectShow"を使ってWEBカメラから画像を読み込む方法が実装されています。

それではプログラムの説明をしていきます。

ライブラリを使用するために「プロジェクト(P)」→「参照の追加(R)」から
AForge.Video.dll
AForge.Video.DirectShow.dll
をプロジェクトに追加しておいてください。さらに、
using AForge.Video;
using AForge.Video.DirectShow;
もプログラムに追加しておいてください。

WEBカメラを使用する時のプログラムの流れは以下のようになります。
①接続されている全てのビデオデバイスの取得
//ビデオ入力デバイスのみ取得
FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
②使用するビデオデバイスのオブジェクト生成
//VideoDevices中のデバイス1つを選択し、そのオブジェクトを生成
int Index;
VideoCaptureDevice videoSource = new VideoCaptureDevice(videoDevices[Index].MonikerString);
//最初に見つかったビデオデバイスを使用する場合、以下のようにIndex = 0とすればよい
VideoCaptureDevice videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
③フレームサイズなど、ビデオデバイスの設定
//ビデオデバイスの提供する機能一覧を配列に格納
VideoCapabilities[] videoCapabilities = videoSource.VideoCapabilities;
//提供されているフレームサイズをコンボボックスのアイテムに追加
foreach ( VideoCapabilities capabilty in videoCapabilities )
{
    comboBox.Items.Add( string.Format( "{0} x {1}", capabilty.FrameSize.Width, capabilty.FrameSize.Height ) );
}
comboBox.SelectedIndex = 0;
//コンボボックスで選択されているのフレームサイズに設定
videoSource.VideoResolution = videoCapabilities[comboBox.SelectedIndex];
//このプログラムのままだと"SelectedIndex"が常に0となってしまうので、別のメソッドで設定するするように
④新しいフレームが来たら発生するイベントの登録
//新しいフレームが来たら"video_NewFrame"という名のメソッドが呼ばれる
//"=="でなく"+="になっている点に注意
videoSource.NewFrame += new NewFrameEventHandler(video_NewFrame);
⑤ビデオデバイスの起動
//ビデオデバイスの起動
videoSource.Start();
⑥新しいフレームが来たら処理(繰り返し)
//新しいフレームが来たら実行されるメソッド
private void video_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    //新しいフレーム"eventArgs.Frame"のコピーを"Bitmap"に格納して扱う
    Bitmap img = (Bitmap)eventArgs.Frame.Clone();
    //ここで画像処理などを行う
    pictureBox.Image = img;//画像を"pictureBox.Image"に表示
}
⑦ビデオデバイスの停止
//ビデオデバイスの停止
videoSource.SignalToStop();
//完全に停止するまで待つ
videoSource.WaitForStop();
//これ以上使用しないならオブジェクトを空にする
videoSource = null;

細かいメソッドの役割は以上です。これを組み合わせてプログラムを作ります。
まだ紹介していないメソッドなどもありますが、基本的にこれだけ分かっていれば、自分で調べて実装することもできるでしょう。

最後に、実際にwindowsフォームで作ったプログラムを公開しておきます。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//名前空間の省略
using AForge.Video;
using AForge.Video.DirectShow;

namespace CSharpで画像処理
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //接続されている全てのビデオデバイス情報を格納する変数
        private FilterInfoCollection videoDevices;
        //使用するビデオデバイス
        private VideoCaptureDevice videoDevice;
        //ビデオデバイスの機能を格納する配列
        private VideoCapabilities[] videoCapabilities;

        //ウィンドウが生成された時
        private void Form1_Load(object sender, EventArgs e)
        {
            comboBox2.Enabled = false;
            button1.Enabled = false;
            button2.Enabled = false;
        }

        //"デバイス取得"ボタンが押された時
        private void button3_Click(object sender, EventArgs e)
        {
            videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (videoDevices.Count != 0)
            {
                comboBox1.Items.Clear();
                foreach (FilterInfo device in videoDevices)
                {
                    comboBox1.Items.Add(device.Name);
                }
                comboBox1.SelectedIndex = 0;
                comboBox2.Enabled = true;
                button1.Enabled = true;
            }
            else
            {
                comboBox1.Items.Clear();
                comboBox1.Items.Add("デバイスが見つかりません");
                comboBox1.SelectedIndex = 0;
            }
            
        }

        //デバイスが変更された時
        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            videoDevice = new VideoCaptureDevice(videoDevices[comboBox1.SelectedIndex].MonikerString);
            videoCapabilities = videoDevice.VideoCapabilities;
            comboBox2.Items.Clear();
            foreach (VideoCapabilities capabilty in videoCapabilities)
            {
                comboBox2.Items.Add(string.Format("{0} x {1}", capabilty.FrameSize.Width, capabilty.FrameSize.Height));
            }
            comboBox2.SelectedIndex = 0;
            videoCapabilities = videoDevice.VideoCapabilities;
            comboBox2.Items.Clear();
            foreach (VideoCapabilities capabilty in videoCapabilities)
            {
                comboBox2.Items.Add(string.Format("{0} x {1}", capabilty.FrameSize.Width, capabilty.FrameSize.Height));
            }
            comboBox2.SelectedIndex = 0;
        }

        //フレームサイズが変更された時
        private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
        {
            videoDevice.VideoResolution = videoCapabilities[comboBox2.SelectedIndex];
        }

        //"起動"ボタンが押された時
        private void button1_Click(object sender, EventArgs e)
        {
            videoDevice.NewFrame += new NewFrameEventHandler(videoDevice_NewFrame);
            videoDevice.Start();
            button1.Enabled = false;
            button2.Enabled = true;
            button3.Enabled = false;
            comboBox1.Enabled = false;
            comboBox2.Enabled = false;
        }

        //新しいフレームが来た時
        void videoDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            Bitmap img = (Bitmap)eventArgs.Frame.Clone();
            //ここで画像処理
            pictureBox1.Image = img;
        }

        //"停止"ボタンが押された時
        private void button2_Click(object sender, EventArgs e)
        {
            if (videoDevice.IsRunning)
            {
                videoDevice.SignalToStop();
                videoDevice.WaitForStop();
            }
            pictureBox1.Image = null;
            button1.Enabled = true;
            button2.Enabled = false;
            button3.Enabled = true;
            comboBox1.Enabled = true;
            comboBox2.Enabled = true;
        }

        //ウィンドウを閉じる時
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (videoDevice.IsRunning)
            {
                videoDevice.SignalToStop();
                videoDevice.WaitForStop();
                videoDevice = null;
            }
        }

        

        
    }
}

本来ならtry-catch構文でエラー回避するべきですが、コードが見ずらくなると思ったので実装していません。

これでUSB接続のWEBカメラから画像を読み込むことができましたので動画像処理も可能となりました。
次からは"AForge.NET"での画像処理を試していきたいと思います。

2013/10/18

C#で画像処理-AForge.NETの導入-

Lv_ZEROです。

C#で画像処理と言えば"OpenCV Sharp"が有名ですが、個人的には導入が面倒だったり、うまくいかなかったりと導入が大変でした。そこで、導入が簡単で、尚且つ、高性能な画像処理系のオープンソースはないかと探していたところ"AForge.NET Framework"というのを見つけました。

"AForge.NET"はコンピュータビジョンと人工知能の分野で開発や研究のために開発されたC#のためのフレームワークだそうです。主な機能は以下の内容のようです。

  • 画像処理(Kinectも対応しているようだ)
  • ニューラルネットワーク
  • 遺伝的アルゴリズム
  • 機械学習
  • ファジィ論理
  • Lego Mindstormなどのためのライブラリ

機能だけ見るとロボット工学において必要なものばかりです。しかしながら、少ししか使用していないので何が出来て、どれくらいの性能なのか、まだわかりません。情報も少なく、英語のドキュメントが唯一の手がかりな感じですが、少しずつ使い方を覚えていきたいと思います。

"AForge.NET"のホームページは以下のURLから行けます。(もちろん英語です)
http://www.aforgenet.com/

それでは、"AForge.NET"の導入方法を紹介します。

1.公式HPから"AForge.NET Framework"をダウンロードします。

2.ダウンロードしたファイルを解凍し、「Release」フォルダの中にあるdllから必要なものだけプロジェクトフォルダの中にコピーします(もちろんコピーしなくてもできます)。

3."Visual Studio"の「プロジェクト」→「参照の追加」→「参照」をクリックして、必要なdllを選択し、プロジェクトに追加できたら導入完了です。

超簡単でしたね。これでライブラリの使い方さえわかれば直ぐに開発が始められます。

"AForge.NET"による画像処理のイメージフォーマットは"Bitmap"です。しかしながら、一丸に"Bitmap"といえど、8bpp,16bpp,24bpp,32bppなどといったようにフォーマットが異なります。
"AForge.NET"ではグレースケールなら8bpp、RGBなら24bppとフォーマットを定めているようです。そこで適切なフォーマットに変換してくれるメソッドがあります。
// 画像の読み込み
System.Drawing.Bitmap image = (Bitmap) Bitmap.FromFile( fileName );
// 適切なフォーマットに変換
AForge.Imaging.Image.FormatImage( ref image );
あとは適切なフォーマットになったBitmapを使って色々処理をしていく形になります。

今日はここら辺で、次回はUSBカメラから画像を取り込む方法を紹介したいと思います。