2018年1月15日月曜日

「独習 C# 新版」レビュー

先月中旬(2017/12/15)に久々にC#の解説書が発売になりました。
山田祥寛 著「独習C# 新版」です。前書「独習C# 第3版」の発売から7年ぶりに新版が出版されました。C#のバージョンも4.0から7.1までと最新の言語仕様ついてまで解説されています。

数々のC#本は発売されていますが、言語仕様についての解説がされている本はあまり多くはないと思います。

第1章から第4章
 C#についての基本的な説明です。変数や制御構文などプログラミング言語の使用についての解説です。他の言語の経験があればそれほど難しい内容ではないと思います。

第5章、第6章
 標準ライブラリ、コレクションといったC#(あるいは.Net)独自機能についての解説になります。かならずしもC#だけというわけではないですが、この章をよめば.net frameworkが提供している便利な機能を利用するのに参考になるでしょう。

第7章から第9章
 C#におけるオブジェクト指向プログラミンについての解説がされています。結構ボリュームがあって一度読んだだけでは理解できないかもしれません。しかし、丁寧な説明がされているので何度か読み直せばしだいに理解できると思います。

第10章
 ラムダ式、LINQといった使いこなせるとコーディングの効率を上げることができる機能についての解説がされています。

第11章
 マルチスレッド処理、ダイナミック型、イベントなどWindowsアプリケーションを作成するときによく使う機能についての解説がされています。


すでに他の言語の経験がある人は前半は軽く読み飛ばしても大丈夫だと思います。第5章以降はC#独自の機能も入ってくるのでじっくり腰を落ち着けて読み進めるのがいいでしょう。サンプルプログラムも基本短い(たぶん一番長くても見開き2ページ分まで)ものが多いので解説を読みながら、プログラムの確認をすることができます。また図表もたくさんあるので大量の解説文とにらめっこしながら読み進めるということもありません。一度で内容を理解できなくても繰り返し、読み進めれば理解を深めることができるのではないでしょうか。

 この本ではWPF/UWPといったGUIアプリケーションの解説はほとんどありません。基本的にサンプルプログラムはコンソールアプリケーションです。よってWindows用アプリケーションを作成したい場合には他の入門書を参考にする必要があります。
 C#の言語仕様についてより詳しく勉強したい人にお勧めします。また、現在MSでは大量のサンプルプログラムをGitHub上で公開しています。それらのサンプルプログラムを読む際に本書を手元に置いておくと、プログラムの内容についてより理解ができるのではないでしょうか。


2016年1月13日水曜日

Windows10 IoT Core アップデート 履歴

しばらくこちらへの投稿はしていなかったのですが、Windows10 IoT Coreに関してメモ的な内容はこちらにも書いていこうかと思っています。

ちょうど先日(2016年1月11日付)アップデートの告知がありました。


これを機会にIoTCoreのリリース履歴を簡単にまとめてみたいと思います。


2015年4月29日 Preview版限定リリース

2015年5月12日 修正リリース

2015年6月24日 最終候補版リリース

2015年7月29日 正式版リリース(Build10240)

2015年9月25日 Septemberリリース(Build10531) 

2015年10月15日 Octoberリリース(Build10556) シリアルポートが使えるようになった。

2015年12月4日 Decemberリリース(Build10586) 国内のWifiアダプターが利用できるようになった。DragonBoard410cに対応

2016年1月11日 Januaryリリース(Build10586.63)<--現在はここ


正式版リリースからほぼ1か月~1か月半でアップデートがありますね。


2015年4月5日日曜日

RaspBerryPi2でプライベートクラウドに挑戦(2)---LXCのインストール

RasPi2でクラウド環境を構築していくわけですが、どのOSSで構築するのか検討した結果CloudStackにしました。また、仮想化環境についてはRasPi2の性能も考えてLXCでやってみることにします。

まず、CloudStackをインストールする前に仮想化環境のチェックのためLXCをインストールしてみました。

OSはUbuntuに変更しています。OSイメージはこちらから入手しました。

LXCのインストールは特別なことはなく、「sudo apt-get install lxc」でインストールできます。

Raspbianでも同じようにインストールできるのですが、仮想環境をスタートするときに「/cgroupがない」といって怒られるので、前もって

「sudo mkdir /cgroup」
「sudo mount -t cgroup cgroup /cgroup」

を実行してください。

さて、UbuntuでのLXCですが、なぜか「lxc-list」コマンドがありません。「lxc-ls」コマンドは使えるので困ることはあまりないのですけども。Raspbianでは両方のコマンドが使えます。

テンプレートについてはUbuntuの方が多くあるようです。

現時点では以下のようになっています。
---------------------------------------------------------------------------
linaro@raspberry:/usr/share/lxc/templates$ ls -al
total 352
drwxr-xr-x 2 root root  4096 Apr  4 08:52 .
drwxr-xr-x 6 root root  4096 Apr  4 08:52 ..
-rwxr-xr-x 1 root root 10789 Oct 10 04:54 lxc-alpine
-rwxr-xr-x 1 root root 13625 Oct 10 04:54 lxc-altlinux
-rwxr-xr-x 1 root root 10786 Oct 10 04:54 lxc-archlinux
-rwxr-xr-x 1 root root  9446 Oct 10 04:54 lxc-busybox
-rwxr-xr-x 1 root root 29293 Oct 10 04:54 lxc-centos
-rwxr-xr-x 1 root root 10150 Oct 10 04:54 lxc-cirros
-rwxr-xr-x 1 root root 14576 Oct 10 04:54 lxc-debian
-rwxr-xr-x 1 root root 17427 Oct 10 04:54 lxc-download
-rwxr-xr-x 1 root root 47561 Oct 10 04:54 lxc-fedora
-rwxr-xr-x 1 root root 27808 Oct 10 04:54 lxc-gentoo
-rwxr-xr-x 1 root root 13961 Oct 10 04:54 lxc-openmandriva
-rwxr-xr-x 1 root root 13869 Oct 10 04:54 lxc-opensuse
-rwxr-xr-x 1 root root 40371 Oct 10 04:54 lxc-oracle
-rwxr-xr-x 1 root root 11837 Oct 10 04:54 lxc-plamo
-rwxr-xr-x 1 root root  6851 Oct 10 04:54 lxc-sshd
-rwxr-xr-x 1 root root 25489 Oct 10 04:54 lxc-ubuntu
-rwxr-xr-x 1 root root 12401 Oct 10 04:54 lxc-ubuntu-cloud
---------------------------------------------------------------------------

初回仮想環境を作成するときは約10分ほどかかります。2回目以降はキャッシュからコピーするので数十秒で作成できるようになります。

操作に関してはこちらのサイトを参考にさせていただきました。
http://www.server-world.info/


2015年4月2日木曜日

RaspBerryPi2でプライベートクラウドに挑戦(1)

RaspberryPi2(以下RasPi2)が発売されて、すぐに購入してみました。なんとなく今は3台手元にあります。
特に使い道もなくしばらく箱に入った状態だったのですが、どうせ複数台あるならクラスター作ったりHPCっぽくしてみたりできないかなと色々調べてみるとOpenStackやCloudStackなどでプライベートクラウドを作ることができそうなので、挑戦してみることにしました。

クラウドといえば使ったことがあるのはMicrosoftAzureだけなので、OSSのクラウド環境については勉強しつつやっていくことになります。

最初はRasPi2がどれくらいの性能(RasPiB+より6倍速いらしい)があるのか気になったので、ベンチマークを行ってみました。

ベンチマークの対象はRasPi2,Edison,MicrosoftAzure VM Basic A0~A3です。
RasPi2には最新のRaspbianをインストールしました。Edisonは標準Linuxをインストールしています。
AzureについてはDebianのイメージが標準で用意されていないので、デプロイ王子こと廣瀬一海氏が作成したDebianイメージを利用させていただきました。

ベンチマークにはUnixBenchの最新版を使っています。

各マシンはOSをインストール後必要なアップデートを行ったのみの状態です。
マシンごとのアーキテクチャが全く違うので、DhrystoneとWhetstoneおよびSystem Benchmarks Index Scoreのみ比較しています。

以下は表とグラフです。





グラフを見るとRasPi2はAzureA0とほぼ同じくらいの性能になるようです。
簡易なWebサーバぐらいなら余裕でインターネットに公開できそうです。

今日はここまでとします。
次回はプライベートクラウドを構築するのに必要なOSSについて書いてみたいと思います。

2015年2月15日日曜日

Galileo Gen2 + WindowsIoT + node.js

RaspberryPi2でWindowsIoTが対応するみたいなのでRspberryPi2をほしいのですが、いまだに手に入れられません・・・

さて、1月27日にWindowsIoTのアップデートがあって、node.js(Win32アプリが色々動く)を動作させる方法が公開されたのでやってみました。

手順はこちらで公開されています。

実際にやってみました。

1.最新版のWindows imageを用意します。最新のイメージは2014/11/21の日付です。それとapply-bootmedis.cmd(2015/2/5)もダウンロードしておきます。microSDにイメージを展開して起動するのを確認しましょう。起動メディアの作成方法はこちらが参考になります。


2.Windowsが起動したら、適当なところにフォルダを作成します。「C:\node」と作成しました。Telnetでもいいですが、GalileoWatcherで右クリックして「Open Network Share」を選ぶとすぐにCドライブにアクセスできるのでそちらから作成するのが色々楽です。

3.nodejs.orgからWindows32bit版のnode.exeをダウンロードして、先ほど作成したフォルダへコピーしておきます。

4.最新のnpm zipファイルをこちらからダウンロードして、展開してから中身をnode.exeと同じフォルダにコピーします。(ダウンロードできる最新のnpmのバージョンは1.49でした、しかし実際には2.6以上のバージョンに上がっているのでインストール後「npm update」しておきましょう。問題なければ最新版にアップデートできます。)

5.Win32アプリを動作させるのに必要な「User32.dll」をビルドします。予めVisualStudio Express 2013 for WindowsDesktop等を用意してください。こちらからファイル一式をダウンロードして展開後ビルドしましょう。出来上がった「User32.dll」をnode.exeと同じフォルダにコピーします。

6.telnetでGalileoにアクセスして、C:\nodeフォルダに移動します。次にコマンドライン上で
  「mkdir %systemroot%\system32\config\systemprofile\AppData\Roaming\npm」
  「setx path "%path%;c:\node" /M」
  「shutdown /r /t 0」

7.再起動後node.jsが使えるようになっています。

8.「npm update」でnpmを最新版にしておきましょう。

これ以降は通常のWindowsでnode.jsを使う場合と同じです。
expressをインストールして動作することも確認できました。

Node.js Sampleページではインストール後にLチカの動作させていますね。(自分はまだ試していません)

node.jsが動作するようになって色々サーバーを動かすことが可能になりますね。

2014年11月6日木曜日

Edisonでデスクトップ表示(VNC接続)

先日、EdisonでDebian(ubilinux)が動いたとツイートしたら今までにないくらいリツイートされて驚きました。

さて、EdisonはすでにLinuxボード化になっているのですが、やはりデスクトップ表示もしてみたいということでやってみました。もちろんEdisonにはモニター出力がありませんので、リモートデスクトップでやってみることにしました。

こちらのIntelのGalileoにVNC接続するを参考にさせていただきました。

構成としてはX Window環境+Xvfb(仮想ディスプレイ)+VNCサーバーです。

X Window環境(デスクトップ)としてLXDEにしてみました。

コマンドラインからデスクトップ環境を「apt-get install lxde」でインストールできます。

続いて仮想ディスプレイ「Xvfb」も「apt-get install xvfb」でインストールします。

VNCサーバーとしてx11vncを「apt-get install x11vnc」でインストールします。

以上でインストールは完了しました。

次に実行方法ですが、

最初に仮想ディスプレイを立ち上げます。
コマンドラインで
「export DISPLAY=:1」
「Xvfb :1 -screen 0 1024x768x24 &」

続いて、Windowマネージャーを起動します。LXDEにはOpenBoxが含まれているので
「openbox &」

次にVNCサーバーを起動します。
「x11vnc -display :1 -bg -nopw -listen 0.0.0.0 -xkb」

途中フォントがないとか色々表示されますがそのままでも大丈夫でした。
これでEdison上でデスクトップ環境が動くようになりました。

あとはホストPCからVNCクライアントで接続すればデスクトップが表示されます。

最初は真っ黒ですが、右クリックするとメニューが表示されます。


ブラウザーを立ち上げてみました。

とりあえずEdisonでデスクトップ環境が使えることを確認できました。
自動起動などの設定はあまり詳しくないので試していません。

標準のYocto Linuxでも同じ手順(LXDEが動くかどうかはわかりませんが)でできると思います。(もう一度Yocto Linuxに戻してもいいのですが、eMMCの書き換え寿命が気になるのであまりやりたくないのです。Edisonモジュールだけ追加で購入しようかな)

次はWebカメラを接続してブラウザに表示できるか試してみたいと思います。


2014年11月2日日曜日

Debian(ubilinux) on Edison

Edisonの公式LinuxイメージはYocto Linuxになります。ベースは Ångstrom Linuxでしょうか。
公式イメージでは比較的コンパクトな構成になっているので、意外と使いにくい(エディタはViとか、パッケージ管理システムはopkgとか)と思います。

既にDebian(Wheezy)ベースのイメージが公開されています。

実際にインストールしてみました。
環境はWindowsからのインストールです。

-----------------------------------------------------------------------
準備
-----------------------------------------------------------------------
ubilinuxのサイトからイメージファイルをダウンロードします。
(Galileo用もあるみたいです。後日試してみようかと思います。)
ダウンロードしたファイルはgz形式なので7zip等で展開しましょう。
次にファームウェア更新用のプログラム(dfu-util.exe)をダウンロードします。
ダウンロードしたファイルをイメージファイルを展開したフォルダへ移動しておきます。

こちらのサイトからダウンロードしてください。

-----------------------------------------------------------------------
更新の実行
-----------------------------------------------------------------------
EdisonをUSBケーブルでPCに接続しておきます。(途中経過を見るのに2本とも接続してもいいでしょう)

コマンドプロンプトを管理者権限で起動します。

イメージファイルを展開したフォルダへ移動します。

「flashall.bat」を実行します。

リブートするように指示が表示されるので、一旦Edisonの電源を切ってからもう一度入れなおします。



しばらくするとフラッシュメモリの書き換えが始まります。このときシリアルコンソールを開いておくと作業の経過を見ることができます。


書き換えが終わるとしばらくして自動的にリブートします。

起動するとログインプロンプトが表示されるので、ユーザー「root」、パスワード「edison」でログインします。

-----------------------------------------------------------------------
ネットワークの設定
-----------------------------------------------------------------------
イメージを書き込んだ直後はWifiの設定が完了していないので、Wifiで接続できるようにします。

「/etc/network/interfaces」ファイルを以下のように編集します。

10行目 「#auto wlan0」->「auto wlan0」
13行目 「wpa-ssid Emutex」->「wpa-ssid 自分のアクセスポイントのSSID」
14行目 「wpa-psk passphrase」->「wpa-psk 自分のアクセスポイントの共有キー」

保存してから念のためにリブートします。

再起動時にDHCPサーバーが見つかればネットワークの設定は完了です。


あとは apt-get update & apt-get upgrade を実行してシステムの更新や、rootパスワードの変更などをしておきましょう。

ここまでくれば、あとはDebianが動くLinuxマシンと一緒です。サーバーにするもよし、GPIOも利用できるのでセンサーを付けてみるもよし(ただしArduinoIDEで動くかどうかは試していません。)

純正Linuxと同じくpythonやNode.jsはインストール済みなのですぐにサーバーとして使うことはできそうです。

-----------------------------------------------------------------------
その他
-----------------------------------------------------------------------
現在わかっている問題点(?)として、標準LinuxではホストPC上にディスクがマウントされるのがドライバーが読み込めなくてマウントされなくなります。今のところ特に不都合はなさそうなのですが・・・・

もし、標準Linuxに戻したい場合には公式サイトから最新の「Edison Yocto complete image」をダウンロードして展開しておきます。展開したフォルダに「dfu-util.exe」をコピーしてから管理者コマンドプロンプトで「flashall.bat」を実行すれば元に戻すことができます。手順は一緒になります。


以上 EdisonにDebianをインストールする手順でした。

Edisonの初期設定メモ

Edisonの初期設定については、すでにいろいろなサイトで説明されています。
参考にさせていただいたサイトのリストです。 基本的にWindowsでのセットアップです。

・Edison開発方法 
  初期設定の方法やBrakeoutボードのピンアサイン表など参考になります。

・Edison - Software Downloads 
  Intel Edisonの公式ダウンロードページです。
  初期設定に必要なソフトウェアはここからダウンロードしましょう。

・KEI SAKAKI's PAGE.
  ファームウェアの更新について説明が掲載されています。

・switch-science Edison wiki
  スイッチサイエンスさんのEdisonWikiページです。ここでEdison購入しました。


あとはターミナルソフトとマイクロUSBケーブルを2本用意すれば初期設定は問題なくできると思います。

メモ:
個人的にちょっとはまったのがここ・・・
COMポートが2つ出ていますが、シリアル接続は「USB Serial Port」を選択します。
はじめ「Intel Edison Virtual Com Port」で接続しようとして何も表示されずに焦りました。



Intel Edison

Galileoに続いてEdisonを購入しました。

購入したのは「Intel Edison Breakout Board Kit」です。
これはEdisonメインモジュール+拡張ボードの組み合わせです。
これ以外に「Intel Edison Kit for Arduino」もありますが、Galileoと大体同じようなものですね。

実際開発環境もArduinoIDEなどはGalileoと共通のようですが、BreakoutBoardの場合はGPIOなどはピンソケットなどをボード上にはんだ付け等行わないと使えません。

なのでLinuxサーバ的な使い方を色々試したいと思います。(そのうちGPIOなども使ってみるかもしれませんが)

箱の大きさはGalileoと比べて随分と小さいです。

GalileoGen2と並べてみました。左側がメインモジュール、右側がBreakoutボードです。

2014年8月22日金曜日

Intel Galileo Gen2 on Windows

今日はKINECTからちょっと離れてCPUボードの話題です。

ここ数年で1万円くらいまでで手に入るCPUボードは飛躍的に増えてきました。まぁ、増えたのはARM搭載のボードがほとんどのような気もしますけど。

実際今手元にあるのはRasberryPi,mbedボード,Arduinoなどなど。

そして今回はIntel Galileo Gen2を購入してみました。

大きな違いはCPUがx86 Pentium互換ということです。(詳しい仕様はこちら)
標準ではLinux+Arduinoが動作(この場合Linux上でArduinoをエミュレートしている?)します。

で、x86CPUでメモリもそこそこ搭載(DDR3-256MB)ならWindowsも動くかもと考えたどうかは知りませんが、MicrosoftではGalileo用Windowsの提供を開始しました。(ただし、現在はまだβ?正式版ではありません)

Galileo用Windowsを手に入れるにはこのサイトにレジストすると手に入れることができます。

実際のインストールはこちらのサイトを参考にさせていただきました。

特に問題もなく無事インストールできました。


Galileo Watcherで右クリックしてコンテキストメニュー内の「Open Network Share」を選択するとGalileoのCドライブを開くことができます。


もちろんPCからファイルの作成やコピーなども可能です。
たぶんEXE単体のコマンドラインプログラムなら普通に動きそうです。

System32フォルダ内をざっと見た感じでは基本的なコマンドラインプログラムは一通りありました。

まだ開発版なので空っぽのフォルダなんかもありますが、これから色々できそうな感じです。
USBデバイスとかつながるのかな?Webカメラでもつないでみようかと思います。



2014年7月23日水曜日

KINECT V2  音声シャッター(SDK2.0β,C#)

FaceBookのグループでスナップショットを撮るときクリックするのが大変という話があったので、音声認識でスナップショットをとれるようにしてみました。
SDK2.0βのColorBasics-WPFサンプルに音声認識部分を追加しています。

今回もKinectAudioStream.csをプロジェクトに追加しています。

変更はnamespaceとMainWindow.xaml.csだけです。

MainWindow.xaml.cs

//------------------------------------------------------------------------------
// <copyright file="MainWindow.xaml.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.ColorBasics.speech
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Kinect;
    using Microsoft.Speech.AudioFormat;
    using Microsoft.Speech.Recognition;

    /// <summary>
    /// Interaction logic for MainWindow
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        /// <summary>
        /// Size of the RGB pixel in the bitmap
        /// </summary>
        private readonly uint bytesPerPixel = 0;

        /// <summary>
        /// Active Kinect sensor
        /// </summary>
        private KinectSensor kinectSensor = null;

        /// <summary>
        /// Reader for color frames
        /// </summary>
        private ColorFrameReader colorFrameReader = null;

        /// <summary>
        /// Bitmap to display
        /// </summary>
        private WriteableBitmap colorBitmap = null;

        /// <summary>
        /// Intermediate storage for receiving frame data from the sensor
        /// </summary>
        private byte[] colorPixels = null;

        /// <summary>
        /// Current status text to display
        /// </summary>
        private string statusText = null;

        /// <summary>
        /// 音声ストリーム
        /// </summary>
        KinectAudioStream convertStream = null;

        /// <summary>
        /// 音声認識エンジン
        /// </summary>
        SpeechRecognitionEngine engine=null;

        /// <summary>
        /// 日本語音声認識エンジンのID
        /// </summary>
        const string engin_id = "SR_MS_ja-JP_Kinect_11.0";


        /// <summary>
        /// Initializes a new instance of the MainWindow class.
        /// </summary>
        public MainWindow()
        {
            // get the kinectSensor object
            this.kinectSensor = KinectSensor.GetDefault();

            // open the reader for the color frames
            this.colorFrameReader = this.kinectSensor.ColorFrameSource.OpenReader();

            // wire handler for frame arrival
            this.colorFrameReader.FrameArrived += this.Reader_ColorFrameArrived;

            // create the colorFrameDescription from the ColorFrameSource using Bgra format
            FrameDescription colorFrameDescription = this.kinectSensor.ColorFrameSource.CreateFrameDescription(ColorImageFormat.Bgra);

            // rgba is 4 bytes per pixel
            this.bytesPerPixel = colorFrameDescription.BytesPerPixel;

            // allocate space to put the pixels to be rendered
            this.colorPixels = new byte[colorFrameDescription.Width * colorFrameDescription.Height * this.bytesPerPixel];

            // create the bitmap to display
            this.colorBitmap = new WriteableBitmap(colorFrameDescription.Width, colorFrameDescription.Height, 96.0, 96.0, PixelFormats.Bgr32, null);

            // set IsAvailableChanged event notifier
            this.kinectSensor.IsAvailableChanged += this.Sensor_IsAvailableChanged;

            // open the sensor
            this.kinectSensor.Open();

            // set the status text
            this.StatusText = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.NoSensorStatusText;

            // use the window object as the view model in this simple example
            this.DataContext = this;

            // initialize the components (controls) of the window
            this.InitializeComponent();


            ////////////////////////////////////////////////////////////
            //音声認識関連
            ////////////////////////////////////////////////////////////

            //音声入力設定
            IReadOnlyList<AudioBeam> audioBeamList = kinectSensor.AudioSource.AudioBeams;
            Stream audioStream = audioBeamList[0].OpenInputStream();

            //audioStreamのビット変換
            convertStream = new KinectAudioStream(audioStream);

            //音声認識エンジン設定
            engine = new SpeechRecognitionEngine(engin_id);

            //認識するワード
            var word = new Choices();

            word.Add("チーズ");
            word.Add("キャプチャ");
            word.Add("シャッター");
           
            //グラマービルダー
            var gb = new GrammarBuilder();

            //言語設定
            gb.Culture = engine.RecognizerInfo.Culture;

            //グラマービルダーに単語を登録
            gb.Append(word);

            //グラマー作成
            var g = new Grammar(gb);

            //認識エンジンにグラマーを登録
            engine.LoadGrammar(g);

            //イベントの登録
            engine.SpeechRecognized += engine_SpeechRecognized;
            engine.SpeechRecognitionRejected += engine_SpeechRecognitionRejected;

            //オーディオストリームの変換をアクティブにする
            convertStream.SpeechActive = true;

            //認識エンジンに入力設定
            engine.SetInputToAudioStream(convertStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            //非同期で連続認識の開始
            engine.RecognizeAsync(RecognizeMode.Multiple);

        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void engine_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void engine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            //認識信頼度の閾値
            //(認識信頼度 -1<0<1 -1が低信頼度 0が標準 1が高信頼度 数字が大きいほど認識率が厳しくなる)
            const double ConfidenceThreshold = 0.6;

            if (e.Result.Confidence >= ConfidenceThreshold)
            {
                //認識した単語を表示
                if (this.colorBitmap != null)
                {
                    // create a png bitmap encoder which knows how to save a .png file
                    BitmapEncoder encoder = new PngBitmapEncoder();

                    // create frame from the writable bitmap and add to encoder
                    encoder.Frames.Add(BitmapFrame.Create(this.colorBitmap));

                    string time = System.DateTime.Now.ToString("hh'-'mm'-'ss", CultureInfo.CurrentUICulture.DateTimeFormat);

                    string myPhotos = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

                    string path = Path.Combine(myPhotos, "KinectScreenshot-Color-" + time + ".png");

                    // write the new file to disk
                    try
                    {
                        // FileStream is IDisposable
                        using (FileStream fs = new FileStream(path, FileMode.Create))
                        {
                            encoder.Save(fs);
                        }

                        this.StatusText = string.Format(Properties.Resources.SavedScreenshotStatusTextFormat, path);
                    }
                    catch (IOException)
                    {
                        this.StatusText = string.Format(Properties.Resources.FailedScreenshotStatusTextFormat, path);
                    }
                }

            }
        }

        /// <summary>
        /// INotifyPropertyChangedPropertyChanged event to allow window controls to bind to changeable data
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets the bitmap to display
        /// </summary>
        public ImageSource ImageSource
        {
            get
            {
                return this.colorBitmap;
            }
        }

        /// <summary>
        /// Gets or sets the current status text to display
        /// </summary>
        public string StatusText
        {
            get
            {
                return this.statusText;
            }

            set
            {
                if (this.statusText != value)
                {
                    this.statusText = value;

                    // notify any bound elements that the text has changed
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("StatusText"));
                    }
                }
            }
        }

        /// <summary>
        /// Execute shutdown tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            if (this.colorFrameReader != null)
            {
                // ColorFrameReder is IDisposable
                this.colorFrameReader.Dispose();
                this.colorFrameReader = null;
            }

            if (this.kinectSensor != null)
            {
                this.kinectSensor.Close();
                this.kinectSensor = null;
            }
        }

        /// <summary>
        /// Handles the user clicking on the screenshot button
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void ScreenshotButton_Click(object sender, RoutedEventArgs e)
        {
            if (this.colorBitmap != null)
            {
                // create a png bitmap encoder which knows how to save a .png file
                BitmapEncoder encoder = new PngBitmapEncoder();

                // create frame from the writable bitmap and add to encoder
                encoder.Frames.Add(BitmapFrame.Create(this.colorBitmap));

                string time = System.DateTime.Now.ToString("hh'-'mm'-'ss", CultureInfo.CurrentUICulture.DateTimeFormat);

                string myPhotos = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

                string path = Path.Combine(myPhotos, "KinectScreenshot-Color-" + time + ".png");

                // write the new file to disk
                try
                {
                    // FileStream is IDisposable
                    using (FileStream fs = new FileStream(path, FileMode.Create))
                    {
                        encoder.Save(fs);
                    }

                    this.StatusText = string.Format(Properties.Resources.SavedScreenshotStatusTextFormat, path);
                }
                catch (IOException)
                {
                    this.StatusText = string.Format(Properties.Resources.FailedScreenshotStatusTextFormat, path);
                }
            }
        }

        /// <summary>
        /// Handles the color frame data arriving from the sensor
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Reader_ColorFrameArrived(object sender, ColorFrameArrivedEventArgs e)
        {
            bool colorFrameProcessed = false;

            // ColorFrame is IDisposable
            using (ColorFrame colorFrame = e.FrameReference.AcquireFrame())
            {
                if (colorFrame != null)
                {
                    FrameDescription colorFrameDescription = colorFrame.FrameDescription;

                    // verify data and write the new color frame data to the display bitmap
                    if ((colorFrameDescription.Width == this.colorBitmap.PixelWidth) && (colorFrameDescription.Height == this.colorBitmap.PixelHeight))
                    {
                        if (colorFrame.RawColorImageFormat == ColorImageFormat.Bgra)
                        {
                            colorFrame.CopyRawFrameDataToArray(this.colorPixels);
                        }
                        else
                        {
                            colorFrame.CopyConvertedFrameDataToArray(this.colorPixels, ColorImageFormat.Bgra);
                        }

                        colorFrameProcessed = true;
                    }
                }
            }

            // we got a frame, render
            if (colorFrameProcessed)
            {
                this.RenderColorPixels();
            }
        }

        /// <summary>
        /// Renders color pixels into the writeableBitmap.
        /// </summary>
        private void RenderColorPixels()
        {
            this.colorBitmap.WritePixels(
                new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
                this.colorPixels,
                this.colorBitmap.PixelWidth * (int)this.bytesPerPixel,
                0);
        }

        /// <summary>
        /// Handles the event which the sensor becomes unavailable (E.g. paused, closed, unplugged).
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Sensor_IsAvailableChanged(object sender, IsAvailableChangedEventArgs e)
        {
            // on failure, set the status text
            this.StatusText = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.SensorNotAvailableStatusText;
        }
    }
}


説明:
 前回の音声認識サンプルとほぼ同じです。スナップショットを撮るルーチンもClickイベント部分と同じです。
 慣れてくれば簡単にイベント部分を音声認識イベントに変更できそうです。

2014年7月20日日曜日

KINECT V2で音声認識(SDK2.0β C#)

7月15日からKINECT for Windows V2が発売されました。SDK2.0もパブリックベータ版が同時に公開されています。

今回はハードが大幅に変更になっているのでSDKも色々変わっています。

ColorやDepthの解説はぼちぼちブログやIT系メディアで紹介されていますが、音声認識関連はなかなか出てこないです。

取り急ぎSDKのSpeechサンプルを参考にして簡単なプログラムを作ってみました。

開発環境は

Windows8.1Pro 64bit
Visual Studio Pro 2012

です。

その他 KINECT SDK2.0 及び SpeechPlatform SDK 11.0,LanguagePack_jaJPもインストールしてください。

サンプルプログラムはC#+WPFで作成しています。

最初にプロジェクトを新しく作成してから、SDK2.0βのSpeech Basics-WPFプロジェクトよりKinectAudioStream.csファイルをインポートして、namespaceを変更してください。

MainWindow.xaml
<Window x:Class="speech_jp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" Closing="Window_Closing">
    <Grid>
        <TextBox x:Name="tb1" HorizontalAlignment="Left" Height="30" Margin="10,82,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="497" FontSize="20"/>
        <TextBox x:Name="tb2" HorizontalAlignment="Left" Height="30" Margin="10,153,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="497" FontSize="20"/>
        <Label Content="認識した単語" HorizontalAlignment="Left" Margin="10,52,0,0" VerticalAlignment="Top" Width="107"/>
        <Label Content="信頼度" HorizontalAlignment="Left" Margin="10,123,0,0" VerticalAlignment="Top" Width="74"/>

    </Grid>
</Window>

説明:
 テキストボックスを2つ配置しています。


MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.Kinect;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;

namespace speech_jp
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        //KINECT
        KinectSensor kinect=null;

        //AudioStream
        KinectAudioStream convertStream=null;

        //音声認識エンジン
        SpeechRecognitionEngine engine;

        //日本語音声認識エンジンのID
        const string engin_id = "SR_MS_ja-JP_Kinect_11.0";

        //英語音声認識エンジンのID
        //const string engin_id = "SR_MS_en-US_Kinect_11.0";

        /// <summary>
        ///
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //KINECTの準備
            try
            {
                kinect = KinectSensor.GetDefault();
                if (kinect == null)
                {
                    throw new Exception("KINECTを開けません。");
                }

                //KINECTの使用開始
                kinect.Open();

                //音声入力設定
                IReadOnlyList<AudioBeam> audioBeamList = kinect.AudioSource.AudioBeams;
                Stream audioStream = audioBeamList[0].OpenInputStream();

                //audioStreamのビット変換
                convertStream = new KinectAudioStream(audioStream);


            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                Close();
            }

            //音声認識エンジン設定
            engine = new SpeechRecognitionEngine(engin_id);

            //認識するワード
            var word = new Choices();

            word.Add("ハンバーグ");
            word.Add("ラーメン");
            word.Add("焼肉");
            word.Add("すし");
            word.Add("スパゲッティ");
            word.Add("卵焼き");
            word.Add("ぎゅう丼");
            word.Add("うどん");
            word.Add("からあげ");

            //グラマービルダー
            var gb = new GrammarBuilder();

            //言語設定
            gb.Culture = engine.RecognizerInfo.Culture;

            //グラマービルダーに単語を登録
            gb.Append(word);

            //グラマー作成
            var g = new Grammar(gb);

            //認識エンジンにグラマーを登録
            engine.LoadGrammar(g);

            //イベントの登録
            engine.SpeechRecognized += engine_SpeechRecognized;
            engine.SpeechRecognitionRejected += engine_SpeechRecognitionRejected;

            //オーディオストリームの変換をアクティブにする
            convertStream.SpeechActive = true;

            //認識エンジンに入力設定
            engine.SetInputToAudioStream(convertStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            //非同期で連続認識の開始
            engine.RecognizeAsync(RecognizeMode.Multiple);
           
        }

        void engine_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            tb1.Text = "認識できません。";
            tb2.Text = "";
        }

        void engine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            //認識信頼度の閾値
            //(認識信頼度 -1<0<1 -1が低信頼度 0が標準 1が高信頼度 数字が大きいほど認識率が厳しくなる)
            const double ConfidenceThreshold = 0.6;

            if (e.Result.Confidence >= ConfidenceThreshold)
            {
                //認識した単語を表示
                tb1.Text = e.Result.Text;
                tb2.Text = e.Result.Confidence.ToString();
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            //終了処理
            if (kinect != null)
            {
                kinect.Close();
                kinect = null;
            }
        }
    }
}



説明:
 大きく変わったのはマイクで録音した時のオーディオフォーマットが変更になっているところです。V1の時はPCM 16bitだったのがV2ではPCM 32bit floatになっています。音声認識エンジンへの入力フォーマットはPCM 16bitの必要があるので32bitfloat->16bitへの変換が必要になります。
 今回はSDKのサンプルプログラムから変換用クラスをそのままインポートして利用しています。
 また、音声ストリームの指定方法が少し変更になっていますね。AudioBeam配列から選択するように変わりました。
 音声認識部分についてはSpeechPlatformのバージョンは以前と変わっていないので特に変更はありません。


入力音声を配列で取得できるということは複数人の音声を識別できそうです。
現在はまだそこまで調べていないので何とも言えないのですが。できるならイントロクイズとかに使えるかもしれないですね。

2014年4月24日木曜日

TechCrunchHackathon Osakaに参加してきました。画像センシングコンポ(HVC)について

4月12-13日にかけて開催されたTechCrunchHackathon Osakaに参加してきました。
その時に作成したプロダクトに使ったオムロン製 画像センシングコンポ(HVC)について使用した感想を述べたいと思います。

まず、ボードについてはここを参照してください。
オムロンの組み込み向けライブラリOKAO Visionをプロセッサに組み込んでカメラモジュールと合わせて搭載したボード(以下HVC)です。
カメラで取り込んだ映像を解析し、結果を出力します。このボードひとつで顔検出、人体検出、性別推定、年齢推定、視線推定、、目つむり推定、顔向き推定、表情推定、手(のひら)検出、顔認証の各データが取得できます。

ボードとの入出力はUART3.3Vのシリアル通信になります。
評価ボードにはUSB変換基板も付属しているのでPCとの接続は簡単にできます。(変換基板からUSB経由で5V電源を供給しています。)
別で5V電源を供給できればマイコンボードとの接続も簡単かと思います。

今回はチームメイトがMicroUSB変換ケーブルを持っていたので、Nexus7と接続を試してみましたが問題なく接続できました。USBHost機能を持ったAndroid端末も接続できるようです。

さて、実際に使用するときにはプログラムからシリアル通信でコマンドコードを送ることになります。
コマンドコードと受信データはバイトデータなので、それらに対応した開発言語ならどれを使っても開発できます。
サンプルはWindowsデスクトップアプリでした。ハッカソン参加者で使っていた言語はC#、python、JAVA(Android)といったところでしょうか。

センシングの性能としてはボードから1.5~2mくらいまでが解析できる距離(顔認識系)のようです。これはカメラモジュールの性能にもよる可能性があるので、モジュールを交換できるならもう少し距離が延びるかもしれません。

一人分の顔認識にかかる時間は約1秒未満くらいでしょうか、瞬時とはいかないようです。
ボードからは認識時に使った画像データも取得できます。サンプルプログラムではリアルタイムで画像表示して解析データを出力できていたので、もう少し時間があれば色々試せたのかもしれません。

今回使う機会がなかったのは顔認証機能でしたが、マニュアルをちらっと読んでみただけなのですが、認証用の画像データ(もしかすると特徴点データ?)はボード上のフラッシュメモリーに登録するようになっているようです。

センシング機能をボード上ですべて行っているので非力な端末と接続しても、十分な処理速度を保てるのではないでしょうか。

いくつか希望を述べるとすると

1.10種類のアルゴリズムすべてではなくて、機能別バージョンがあってもいいかなと思います。
人体検出+顔検出、顔検出+顔認識(推定系)、全部ありバージョンなどなど・・・顔認証ありバージョンとなしバージョンでもいいかも。 用途として顔認証はなくてもいい場合も十分あると思います。

2.ぜひ企業向けだけではなく個人向けにも販売してほしいです。今回は幸運にも評価ボードを貸していただけたのでよかったのですが、結構興味を持っている個人もいると思います。開発キット7万円はちょっと高いかな・・・・少しでも安くなればいいのですが。

以上簡単ですが、HVCを使ってみた感想でした。






2014年4月16日水曜日

TechCrunchHackathon Osakaに参加してきました。

先週末の土曜日から日曜日にかけて大阪のグランフロントにある大阪イノベーションハブで行われたTechCrunch Hackathon Osakaに参加してきました。

ハードウェア系のハッカソンということで2日間で提供されるハードウェアやWebAPIを利用して、IoTになるプロダクトを作っていきます。

今回結成したチーム(当初6人でしたが、1人途中で用事のため抜けたので最終的に5人)ではセンサーカメラ(オムロン HVC)と音楽API(Gracenote)で何かできないかなということで色々アイデアを考えていました。

最終的には「独居老人を見守ることができるミュージックプレイヤー」を作るということになりました。

全体の構成イメージは下図のようになります。


動作内容としては

1.HVCカメラで対象者を認識し、顔の表情や年齢のパラメーターをサーバーに送信。
2.サーバーで受信したパラメーターを元に年代、ムードなどのクエリを生成してGracenote APIからプレイリスト、ジャケット画像、音源URL等のデータを取得。
3.サーバー上でGracenoteAPIから取得したデータを基にプレイヤー用HTMLを生成して、プレイヤーへ送信。
4.プレイヤーは受信したHTMLを表示。
5.動作中は特定のトリガー(今回はあらかじめ決めていたモーション)で緊急事態を認識してモバイルサービスにアラートを送信。
6.モバイルサービスから緊急受信用のアプリへアラートを送信。

このような構成になりました。

HVCとAndroidタブレットの接続(UART)は当初PocketDuinoを使う必要があるかなと思っていたのですが、HVCに付属の変換基板でそのまま接続することができました。USBホスト機能のある機種はコネクターを合わせるとそのまま接続できます。
ただし、Androidの場合はそのままではシリアル通信ができないのでライブラリとしてPocketDuinoと一緒に公開されているPhysicaloid Libraryを使っています。
HVCを使ったチームのうちAndroidと接続していたのは我々のチームだけのようでした。

次にセンサーから得られた情報を如何にしてGracenoteAPIのクエリにするのかを検討しました。最終的には15種類(年代3種類、感情5種類)のパターンで検索しています。

APIから得られたプレイリストを基にジャケット画像と音源URL(今回はyoutube)を検索してからプレイヤー向けのHTMLを生成してプレイヤーに返すようにしています。

緊急時のアラートについてはプレイヤーが特定の条件を検知した場合にAzureモバイルサービスにアラートを送信してiPhoneアプリで通知を受け取り表示およびアラート音がなるようにしました。

デモのときにアラートがならなかったのが残念です。(多分ネットワークが重く通知が届かなかったのが原因だと思います。)

今回なんとか2日間で動作するプロダクトを作れたのもチームが偶然にも、それぞれ得意分野が違っていて上手くできたのだと思います。一部Android担当された方にタスクが集まってしまった感じではありましけれども。

今回作ったプロダクトをこのままで終わるのはもったいないと思うので何らかの形で続けられたらと、チームメンバーとも話をしています。

後ほど他のチームのプロダクトについても感想を書いてみたいと思います。

2014年2月5日水曜日

SQLServerで地図データ(緯度経度)を検索

先日FaceBookでDB上の地図座標を検索したいという話題がでていたので、SQLServerでやり方を調べてみました。
実現したいのは「現在地の座標から500m(任意の範囲)内の登録地点を検索する」となります。

SQLServer2008から空間データ用にgeometry型とgeography型がサポートされるようになりました。
geometry型は平面上の座標データ(一般的なxyグラフ)を扱います。
geography型は楕円体上の座標データを扱うので緯度経度データはこちらを利用することになると思います。

まずはサンプルテーブルを作成します。

CREATE TABLE T_TEST
(
 id int IDENTITY(1,1) NOT NULL,
 name nvarchar(50) ,
 address nvarchar(50) ,
 geo geography
)

次にサンプルデータを挿入します。今回は弊社近辺の銀行の所在地にしてみました。

INSERT INTO T_TEST(name,address,geo)
     VALUES('りそな銀行明石支店',
'明石市本町1丁目2-26',
geography::STGeomFromText('POINT(134.991151 34.647505)',4326))
INSERT INTO T_TEST(name,address,geo)
     VALUES('みずほ銀行明石支店',
'明石市大明石町1丁目5-1',
geography::STGeomFromText('POINT(134.993259 34.64798)',4326))
INSERT INTO T_TEST(name,address,geo)
     VALUES('三井住友銀行明石支店',
'明石市大明石町1丁目5-4',
geography::STGeomFromText('POINT(134.993299 34.647755)',4326))
INSERT INTO T_TEST(name,address,geo)
     VALUES('東京三菱UFJ銀行明石支店',
'明石市本町1丁目1-34',
geography::STGeomFromText('POINT(134.993251 34.647223)',4326))
INSERT INTO T_TEST(name,address,geo)
     VALUES('百十四銀行明石支店',
'明石市本町2丁目1-26',
geography::STGeomFromText('POINT(134.989622 34.647607)',4326))

geography型へのデータの挿入は「geography::STGeomFromText('POINT([経度] [緯度])',4326)」となります。(「経度」と「緯度」の間は半角スペースです。間違えるとエラーになります。)

------------------------------------------
追記
------------------------------------------
geography型へのデータの挿入は「geography::POINT([緯度],[経度],SRID)」でも可能です。
こちらのほうが直接的でわかりやすいかと思います。
------------------------------------------
追記終わり
------------------------------------------

SQLServerでのgeography型データは.NET 共通言語ランタイム (CLR) のデータ型として実装されているのでgeography::STGeomFromTextメソッドで座標を変換しています。「4326」はSRID (spatial reference ID) になります。(詳しく調べていないのですが固定値として指定しています。)


これで検索する準備ができました。

登録されているデータで任意の位置から特定範囲(この場合は円内)に含まれる地点を検索するには任意の位置座標とデータの位置座標の距離を求めることによって検索することができます。

距離(単位はメートルです)はgeography型のSTDistanceメソッドで求めることができます。

次のようにクエリを発行すると任意の座標から各データの位置データまでの距離が計算されます。

--任意の位置座標を代入する変数
declare @g geography

--任意の位置座標データとしてJR明石駅の位置座標を使用しています。
set @g=geography::STGeomFromText('POINT(134.993164 34.649096)',4326)

select name,address,@g.STDistance(geo) as distance from T_TEST

上記のクエリに以下の条件を加えると任意の範囲に含まれるデータを検索できます。

--200m以内にある地点を検索
where @g.STDistance(geo)<200

以上の内容をストアドもしくはユーザー定義関数としてSQLServerに登録して呼び出せるようにしておけば任意の位置データと検索したい範囲の距離を与えて検索することができるようになります。






2013年12月31日火曜日

Windows7で音声合成できない・・・・

先日Windowsで音声合成するサンプルを書きましたが、Windows7では動作させることができません。
Windows7標準の音声合成機能はなぜか日本語音声合成エンジンの提供が通常では行われていないので、プログラムから標準機能のみでの音声合成はできないことになります。

Microsoftの方針としてはSpeechPlatformをインストールして利用してほしいようです。
実際Office2010以降の読み上げ機能もSpeechPlatformをインストールするように説明があります。

音声認識に関してもサンプルプログラムは動きませんでした。

次回はちょっと予定を変更して、SpeechPlatformのインストールに関して書いてみたいと思います。

2013年12月27日金曜日

Windowsで音声認識(Choices+GrammarBuilder)

先日の音声合成に続いて、Windows標準の音声認識機能を使ってみます。

標準の音声認識機能は学習方式と辞書方式どちらにも対応しているようなのですが、今回は辞書方式で認識させます。

音声合成の場合と同じく特に用意するものはありません。

さっそくサンプルプログラムです。コンソールアプリケーションです。

using System;
using System.Speech.Recognition;

namespace SpeechRecognized_Sample
{
    class Program
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {

            //認識エンジン(既定のロケールを使用)
            using (SpeechRecognitionEngine sre = new SpeechRecognitionEngine())
            {

                //イベント登録
                sre.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(sre_SpeechRecognized);

                //語彙登録
                Choices words = new Choices(new string[] { "晴れ", "曇り", "雨", "雪" });

                //GrammarBuilderインスタンス
                GrammarBuilder gb = new GrammarBuilder("今日の天気は");

                //GrammarBuilderインスタンスに語彙を追加
                gb.Append(words);

                //Grammarインスタンスを作成
                Grammar g = new Grammar(gb);

                //Grammarインスタンスをロード
                sre.LoadGrammar(g);

                Console.Write("認識開始" + "\n");

                //入力ソース(既定のマイク)
                sre.SetInputToDefaultAudioDevice();

                //非同期で認識開始
                sre.RecognizeAsync(RecognizeMode.Multiple);

                while (true)
                {
                    Console.ReadLine();
                }
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            Console.WriteLine("認識しました:" + e.Result.Text + " 信頼度:" + e.Result.Confidence);
        }
    }
}


説明:
「プロジェクト」>「参照の追加」>「アセンブリ」>「フレームワーク」内の「System.Speech」のチェックを忘れないでください。
今回辞書への登録方法としてChoicesクラスとGrammarBuilderクラスを使っています。
Choicesクラスに認識させたい単語を文字列配列として与えます。
その後Choicesオブジェクトを引数としてGrammarBuilderクラスに与えます。
次にGrammarクラスにGrammarBuilderオブジェクトを与えます。
最後に認識エンジンにGrammarオブジェクトをロードして辞書に登録します。

サンプルではGrammarBuilderインスタンスを作成するときに文字列を与えています。
このようにすることで「今日の天気は」+「Choicesクラスに登録した単語」の組み合わせの文章を登録できます。

音声合成の場合より少し複雑ですが、手順がわかればそれほど難しくはないと思います。

次回はSRGSを使って認識させるサンプルの予定です。


2013年12月25日水曜日

Windowsで音声合成

先日大阪でDev(ice)LOVE デバイス祭りという勉強会に参加しました。
「KINECTを使った音声対話」という内容で話をさせていただいたのですが、その時のスライドに対応したサンプルプログラム+αを何回かに分けて書いていきたいと思います。

ちなみにスライドはこちらにアップロードしています。

まず最初に音声合成のサンプルプログラムです。
勉強会の時はSpeechPlatform11をインストールした状態での説明でしたが、Windows標準の状態でもできますので特に何も用意する必要はありません。(もちろん開発環境は必要です^^;)

サンプルプログラムの動作確認は Windows8 Pro+VS C# 2012 Express for Desktopですが、
Windows7やC#2010でも動くと思います。.NET Framework3.5以上なら大丈夫でしょう。もしかするとXPでも動くかもしれないです。

さっそくプログラムです。コンソールアプリとなります。

using System;
using System.Speech.Synthesis;

namespace Speak_Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            //音声合成エンジン
            SpeechSynthesizer ss = new SpeechSynthesizer();
           
            //出力を既定のスピーカーに設定
            ss.SetOutputToDefaultAudioDevice();

            //音声出力
            ss.Speak("こんにちは,今日はようこそいらっしゃいました。");

            //キー入力待ち
            Console.ReadKey();
           
        }
    }
}

あらかじめ「プロジェクト」>「参照の追加」>「アセンブリ」>「フレームワーク」内の「System.Speech」にチェックを入れてください。

2行目の「System.Speech.Synthesis」でWindows標準の音声合成機能を利用できるようにしています。

しゃべらせるだけならこれだけです。ひらがな、カタカナ、漢字も使えます。英語も標準で使えます。
多国語対応ですが、別に言語パックなどをインストールしなければいけないでしょう。

2013年8月2日金曜日

LeapMotion サンプル その1 (WPF+VB)

KINECTよりすごい(かも)と言われているLeapMotionを手に入れました。
2013/7/22に一般発売でしたが、21日に注文して27日には到着しましたので比較手に入手はしやすいようです。

さて、LeapMotionで何ができるのかと簡単に説明するとセンサー上の手指の位置や移動の情報を高精度で取得できるガジェットです。

開発環境はC++,C#,JAVA,Javascript,Python等色々あります。OSはWindows7以降,MacOS X等(Linuxはまだβ版ドライバ)です。

SDKも一通り揃っているので、開発しやすいと思います。

ただ、SDK内のサンプルプログラムはWPF向けではないので、VB(開発環境には載っていないけれども.NetFramework3.5以降対応なので問題なし)+WPFのサンプルプログラムを作ってみました。

内容は指一本をセンサー上で動かすとCanvas上のポインター(Ellipse)が指先と同じように動きます。
LeapMotionはSDK上に仮想スクリーンを生成する機能を持っているので、それを利用しています。

早速コードをと行きたいところですが、まずは開発環境を整えましょう。

環境についてはこちらのブログを参照するのが早いと思います。

Natural Software
 http://www.naturalsoftware.jp/blog/8389

C#向けですが、VBも同じです。C++とかMacでの環境設定の説明もあるので他の記事も参考になると思います。

それではプログラムコードです。

まずはXAMLのソースです。画面構成はテキストボックスを4つと、Canvasを1つ配置しています。
Canvasのサイズは適当に変えても大丈夫です。

MainWindow.xaml
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="900" Width="1228">
    <Grid>
        <Canvas Name="Canvas1" Height="800" Width="1200" Margin="10,50,82,19"/>
        <TextBox Name="textbox1" HorizontalAlignment="Left" Height="23" Margin="85,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="80"/>
        <TextBox x:Name="textbox2" HorizontalAlignment="Left" Height="23" Margin="245,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="80"/>
        <TextBox x:Name="textbox3" HorizontalAlignment="Left" Height="23" Margin="358,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="80"/>
        <TextBox x:Name="textbox4" HorizontalAlignment="Left" Height="23" Margin="470,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="80"/>
        <Label Content="Frame ID:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="70"/>
        <Label Content="Finger ID:" HorizontalAlignment="Left" Margin="170,10,0,0" VerticalAlignment="Top"/>
        <Label Content="X:" HorizontalAlignment="Left" Margin="330,10,0,0" VerticalAlignment="Top"/>
        <Label Content="Y:" HorizontalAlignment="Left" Margin="443,10,0,0" VerticalAlignment="Top"/>
    </Grid>
</Window>

次にプログラムコードです。
MainWindow.xaml.vb

Imports Leap
Class MainWindow
    Private leap As New Controller
    Private windowWidth As Integer = 1200       ''仮想スクリーンの幅
    Private windowHeight As Integer = 800       ''仮想スクリーンの高さ
    Public Sub New()
        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()
        ' InitializeComponent() 呼び出しの後で初期化を追加します。
        ''イベント登録
        AddHandler CompositionTarget.Rendering, AddressOf UpdateFrame
    End Sub
    Private Sub UpdateFrame(sender As Object, e As EventArgs)
        Dim frame As Leap.Frame = leap.Frame
        Dim finger As Finger = frame.Fingers.Frontmost                      ''一番センサーに近い指を選択
        Dim stabilizedPosition As Vector = finger.StabilizedTipPosition
        Dim iBox As InteractionBox = leap.Frame.InteractionBox
        Dim normalizedPosition As Vector = iBox.NormalizePoint(stabilizedPosition)
        Dim x As Double = normalizedPosition.x * windowWidth
        Dim y As Double = windowHeight - normalizedPosition.y * windowHeight
        ''テキストボックスに各情報をセット
        textbox1.Text = frame.Id
        textbox2.Text = finger.Id
        textbox3.Text = CInt(x)
        textbox4.Text = CInt(y)
        ''Canvasのクリア
        Canvas1.Children.Clear()
        ''指を認識していないときは何もしない
        If finger.Id = -1 Then
        Else
            Dim el As New Ellipse
            Canvas1.Children.Add(el)
            With el
                .Margin = New Thickness(CInt(x), CInt(y), 0, 0)
                .Fill = New SolidColorBrush(Colors.Green)
                .Width = 10
                .Height = 10
            End With
        End If
    End Sub
End Class



WPFを使う場合に問題になるのが、要素の書き換え(この場合はCanvas)とセンサーからのデータ取得を同一スレッドで行う必要があることになります。

今回はCompositionTarget.Renderingイベントに合わせて、センサーからデータを取得しています。
CompositionTarget.RenderingはWPFの描画プロセス中に発生するイベントです。

プログラム内の描画ルーチン(UpdateFrame)は最低限のことしかしていません。本来であれば指先をロストした時の処理なども必要になると思います。(なので指先がセンサーの認識範囲外に移動するとポインターが暴れます。)

次はこのプログラムにタッチエミュレーションを加えてみようと思います。


2013年6月27日木曜日

Node.jsからローカルのSQLServerに接続

最近はNode.jsとDB接続に関して色々調べています。
Node.jsと組み合わせるDBはNosql系のDBが多いですが、今回はSQLServerでやってみました。

MS純正のNode用ドライバは以前から提供されているのですが、日本語での情報がほとんどないため結構苦労しました。

今回はHyper-V上のWindows7にSQLServer2012ExpressとNode.jsをインストールしてみました。

環境:
・Windows7 Pro SP1 32bit
・SQLServer 2012 Express x86版
・Node.js v0.8.25 32bit

その他
・Express 3.3.0 + ejs

また node-sqlserverのコンパイルに必要なのが
・Visual C++ 2010 Express
・Python 2.7

となります。


導入手順

1.Node.jsのダウンロードとインストールをします。今回は0.8系の最新版を選択しました。
  (最新安定版の10.0系ではnode-sqlserverがコンパイルできませんでした。)

2.Visual C++ 2010 ExpressとSQLServer2012Expressをダウンロードして、インストールします。
  SQLServerはネットワーク接続できるようにファイアウォール等の設定をしています。

3.Python2.7をダウンロードしてインストールします。

4.SQLServerに「test」データベースを作成し、「T_M商品」テーブルを作成します。
  内容は下の図を参照してください。データも適当に入れておきます。
  



5.C:\に「node」フォルダを作成し、この中にアプリケーションを配置するようにします。

6.Node.jsコマンドプロンプトを起動してからC:\nodeフォルダに移動します。

7.コマンドプロンプトでc:\node> npm install -g express と入力してExpressをインストールします。

8.c:\node> express -e test と入力してtestフォルダにアプリケーションの雛形を作成します。
  testフォルダには以下のように雛形が作成されます。
  

9.package.jsonファイルをエディタで以下のように編集します。

-------------------------------------------------------- 
{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.3.0",
    "ejs": "*",
    "node-sqlserver": "*" <------------赤字部分を追加
  }
}
--------------------------------------------------------
このときファイルの文字コードに気をつけましょう。UTF-8で保存しておかないと正常に動作しない可能性があります。以下すべてのファイルの文字コードがUTF-8になるようにしてください。

10.コマンドプロンプト上でtestフォルダに移動してから c:\node\test> npm install と入力する
   と先ほどのpackage.jsonの内容にしたがってインストールが行われます。このとき途中で
   黄色のワーニングメッセージが出るかもしれませんが、コンパイル環境が問題なければ
   そのまま問題なく終了します。

11.問題なくインストールができるとtestフォルダにはnode_modulesフォルダが追加されています。
   

12.node_modules>node-sqlserver>build>Releaseフォルダ内のsqlserver.nodeファイルを
   node_modules>node-sqlserver>libフォルダ内にコピーします。

13.testフォルダにあるapp.jsファイルを以下のように編集します。

途中から
----------------------------------

app.get('/', routes.index);
app.get('/users', user.list);
app.post('/',routes.index);  <-----赤字の部分を追加

----------------------------------
以下は変更なし


14.test>routesフォルダにあるindex.jsファイルを以下のように編集します。

-------------------------------------------------------------------------------
exports.index = function(req, res){
if (req.body.code) {
var sql=require('node-sqlserver');
var cn_str="Driver={SQL Server Native Client 11.0};Server=(local);Database=test;Uid=ユーザー名;Pwd=パスワード;";
sql.query(cn_str,"select * from T_M商品 where 商品コード='" + req.body.code + "'",function(err,rst) {
if (err) {
res.render('index', { title: 'ERROR' });
console.log(err);
}
res.render('index2',{ title: 'Result',
    rst: rst
});
});
}
else {
res.render('index', { title: '検索' });
}
};
-------------------------------------------------------------------------------


15.test>viewsフォルダにあるindex.ejsファイルを以下のように編集します。

-------------------------------------------------------------------------------
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>検索</h1>
      
      <form method="POST" action="/">
        コード検索: <input type="text" name="code" size="50">
        <input type="submit" value="検索実行">
      </form>
  </body>
</html>
-------------------------------------------------------------------------------


16.test>viewsフォルダにindex2.ejsファイルを以下の内容で追加します。

-------------------------------------------------------------------------------
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
 
    <h1>Data</h1>
 
<form method="POST" action="/">
        コード検索: <input type="text" name="code" size="50">
        <input type="submit" value="検索実行">
</form>

<P>

<table border="1">
 <tr>
<th>商品コード</th>
<th>商品名</th>
<th>標準価格</th>
 </tr>

 <% for(var i=0;i<rst.length;i++){ %>
<tr>
 <td> <%= rst[i]['商品コード'] %> </td>
 <td> <%= rst[i]['商品名'] %> </td>
 <td> <%= rst[i]['価格'] %> </td>
</tr>
 <% } %>

</table>
  </body>
</html>
-------------------------------------------------------------------------------


17.アプリケーションを動かしてみます。コマンドプロンプトでc:\node\testフォルダに移動して
   c:\node\test> node app.jsと入力します。
   問題がなければ「Express server listeng on port 3000」と表示されます。


18.ブラウザで「http://localhost:3000/」にアクセスすると以下のように表示されます。


19.試しにコードを入力して検索を実行すると以下のような結果になりました。



まだSelect文のテストしかしていませんが、このようにNode.js+SQLServerの組み合わせでアプリケーションが作成できました。

Node.jsはまだまだ勉強中なのでたいしたサンプルではないですが、少しでも参考になればと思います。