Hatena::ブログ(Diary)

まちみのな@はてな

2014-12-04

妄想からおうちハックへ

この投稿は、おうちハック Advent Calendar 2014の4日目です。昨日は、 Kadecot さんのルンバはなぜ可愛いかでした。人間は、なぜルンバに心動かされるのかについて様々な文献を挙げて考察されており、大変刺激を受けました。

身の回りのモノにあれこれと想像を膨らますという試みは、日本では古くから行われてきました。たとえば、日本最古のラノベである古事記には万物には神が宿っている(八百万の神)といった記載があります。おうちハックは、これまで妄想の産物でしかなかったものを現実のものとしていくテクノロジーともいえるでしょう。

2014年8月31日に開催されたおうちハック発表会のまとめにあるように、家の中でずっと使われるものにはいくつか求められる特性があると思います。おうちの中に住人の生活に何らかの形でかかわりを持つ電子的なデバイスを、例えば800個設置するとしましょう。800個あれば、住人の一挙手一投足にフィットした未来的な生活ができそうです。一方、いくつかの問題点が浮き上がってきます。


1.電源

皆さんのお宅にはデバイスを駆動するためのコンセントの口あるいはUSBの口が何個あるでしょうか。800個以上あるご家庭はなかなか少ないのではないでしょうか。数個であれば特に問題にならない(かもしれない)デバイスも数が増えてくると電源を用意するだけでも大変です。また、電源ケーブルの重さが 1本 10g だったとしても、800本あれば 8kg と結構な重さになります。もちろん長さや占める体積も相当なものです。これを解決するためには、ケーブル類をなくせばよいのですが、その方法には、(1)電源がすでに供給されているものに追加で埋め込む(普通の人には難しい)、(2)周囲の環境からエネルギーをとりだして動作する(太陽電池駆動のおもちゃとかありますね)、(3)電池で数年以上動くようにする(壁掛け時計などすでにあるものもあります)、などのアプローチがあります。


2.操作

次に、操作について考えてみましょう。800個のデバイスの操作はどうすればよいでしょうか。もしそれらのデバイスの使い方がすべて異なるとすれば住人にとってその操作を覚えることは苦痛でしかありません。最初は覚えようとするかもしれませんが、すぐに使われなくなってしまいます。これに対応するには、(1)同じ操作で対応できるようにする(バーコードリーダーみたいなもの)、(2)住人の日常の動作を自然にとらえて動作するもの(自動ドアなど)、(3)人間にアプローチするのと同じ手段で対話できるもの(音声認識など)といった方法があります。


3.目的

テクノロジーを中心に考えてしまうと、それをどう使おうかという発想になってしまいがちですが、そもそも、なんでデバイスを置くのでしょうか。筆者の場合は、ミクさんの五感を家の中に張り巡らせることで、ミクさんに常に監視されたいという動機ではじまっています。


詳しくはこちらのスライドをご覧ください。



明日もおうちハック Advent Calendar 2014の枠をいただいたので、上で議論したことを実装するための技術の一例として、TWE-Lite DIP を使ったおうちモニタリングについて実装方法を中心にご紹介したいと思います。

2013-12-16

GDI+ で描画&保存あれこれ

このエントリーは「C# Advent Calendar 2013」の16日目のエントリーです。

前日は hiroyuki_mori さんの「デリゲート〜ラムダ式の歴史を辿って」でした。


みんな大好き System.Drawing

Windows Forms 世代ではお馴染みの GDI+ のお話です。Windows XP ももうオワコンというのにまだ GDI+ ですか?といいたいところですが、ちょっとした画像生成にはまあ便利です。その中でも画像の生成・加工を中心にまとめてみました。Dispose! Dispose! Dispose!

まずはコマンドプロンプト

Windows Forms のプロジェクトは必須ではありません。たとえば、コマンドプロンプトで画像を生成する場合は以下のようになります。Visual Studioコマンドプロンプトのプロジェクトを作成し、参照設定に System.Drawing を追加して、Program.cs を下記のように書き直してください。

using System.Drawing;
using System.Drawing.Text;
class Program
{
    static void Main(string[] args)
    {
        Bitmap bmp = new Bitmap(430, 100);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.Clear(Color.DarkBlue);
            Font font = new Font("メイリオ", 50);
      g.TextRenderingHint = TextRenderingHint.AntiAlias; // ClearType を無効に
            g.DrawString("こんにちは!", font, Brushes.White, new PointF(0, 0));
            g.DrawRectangle(Pens.Red, new Rectangle(10, 10, 410, 80));
            font.Dispose();
        }
        bmp.Save("HelloWorld.png", System.Drawing.Imaging.ImageFormat.Png);
        bmp.Dispose();
    }
}

上記の出力はこんな感じです。

f:id:ksasao:20131216200027p:image

ソースコードが短くて素敵ですね。IDisposable な Bitmap や Font は Dispose をお忘れなく。


フォントベクトル化する

フォントベクトルデータがほしい時があります。そういう場合には、GraphicsPath を使います。

using System.Drawing;
class Program
{
    static void Main(string[] args)
    {
        Bitmap bmp = new Bitmap(430, 100);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            // アンチエイリアスをかける
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

            g.Clear(Color.DarkBlue);

            // 文字描画に GraphicsPath を利用
            using (var gp = new System.Drawing.Drawing2D.GraphicsPath())
            {
                Font font = new Font("メイリオ", 50);
                float pSize = font.SizeInPoints * g.DpiY / 72;
                gp.AddString("こんにちは!",
                    font.FontFamily, (int)font.Style,
                    pSize,  // サイズ変換が必要
                    new PointF(0, 0),
                    StringFormat.GenericDefault);

                // 曲線と制御点が必ずしも重ならない 3次 ベジェ スプライン
                // を直線に変換
                gp.Flatten();
                // 制御点に丸を描画
                foreach (var p in gp.PathPoints)
                {
                    g.FillEllipse(Brushes.White, new RectangleF(p.X - 2, p.Y - 2, 5, 5));
                }
                // 輪郭を描画
                g.DrawPath(Pens.White, gp);

                font.Dispose();
            }
            g.DrawRectangle(Pens.Red, new Rectangle(10, 10, 410, 80));
        }
        bmp.Save("HelloWorld.png", System.Drawing.Imaging.ImageFormat.Png);
        bmp.Dispose();
    }
}

f:id:ksasao:20131216201611p:image

曲線上に制御点があるとは限らないので、Flatten で曲線を直線に変換しています。

縁取り文字を描く

一旦、ベクトルデータにすることで縁取り文字も簡単に描画できます。方法は、

  1. 太いペンを用意して文字の輪郭を描画する
  2. 文字を塗りつぶす

です。2番目を省略すれば、輪郭のみの文字も描画できます。

using System.Drawing;
class Program
{
    static void Main(string[] args)
    {
        Bitmap bmp = new Bitmap(430, 100);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            // アンチエイリアスをかける
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.Clear(Color.DarkBlue);

            // 文字描画に GraphicsPath を利用
            using (var gp = new System.Drawing.Drawing2D.GraphicsPath())
            {
                Font font = new Font("メイリオ", 50);
                float pSize = font.SizeInPoints * g.DpiY / 72;
                gp.AddString("こんにちは!",
                    font.FontFamily, (int)font.Style,
                    pSize,  // サイズ変換が必要
                    new PointF(0, 0),
                    StringFormat.GenericDefault);

                // 1. 文字の輪郭を描画
                Pen pen = new Pen(Brushes.Red, 10); // 輪郭の色・太さ
                pen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round; // 文字にトゲが生えないように
                g.DrawPath(pen, gp);

                // 2. 文字を塗りつぶしで描画
                g.FillPath(Brushes.White, gp);
                font.Dispose();
                pen.Dispose();
            }
            g.DrawRectangle(Pens.Red, new Rectangle(10, 10, 410, 80));
        }
        bmp.Save("HelloWorld.png", System.Drawing.Imaging.ImageFormat.Png);
        bmp.Dispose();
    }
}

f:id:ksasao:20131216034511p:image

既定では文字の端々に意図しないトゲが出てしまうので、System.Drawing.Drawing2D.LineJoin.Round で直線の曲がり角を丸めておくとよい感じになります。


ベクトル形式(.emf)で保存する

PowerPointIllustrator 向けに生成したデータはベクトルデータとして渡したいところです。ベクトルデータの形式として、.emf があります。書き方は Bitmap に描画するときとほとんど同じ感じなのですが、Saveメソッドが、.emf の書き出しをサポートしていない(.emfで書き出したつもりが、中身を見るとPNG形式・・・)ので、ひと手間が必要です。

using System;
using System.Drawing;
using System.Drawing.Imaging;

class Program
{
    static void Main(string[] args)
    {
        Bitmap dummy = new Bitmap(1, 1);
        Graphics gd = Graphics.FromImage(dummy);
        IntPtr ipHdc = gd.GetHdc();

        // 保存ファイル名、画像サイズを指定して初期化
        Metafile metafile = new Metafile("Hello.emf", ipHdc, new Rectangle(0, 0, 100, 100), MetafileFrameUnit.Pixel);

        // 描画の仕方は Bitmap の時と同じ
        using (Graphics g = Graphics.FromImage(metafile))
        {
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.FillEllipse(Brushes.Red, new Rectangle(5, 5, 70, 70));
            g.FillEllipse(Brushes.Cyan, new Rectangle(25, 25, 70, 70));
        }

        // Disposeを忘れずに
        metafile.Dispose();
        gd.ReleaseHdc(ipHdc);
        gd.Dispose();
        dummy.Dispose();
    }
}

f:id:ksasao:20131216034510p:image:w640

このようにして出力したファイルは、PowerPoint などで開くと、ちゃんと頂点の編集ができます。Illustrator の場合はそのまま編集できますが、PowerPointの場合は何度かグループ化を解除してください。また、色に透明色(α値が254以下)を指定すると意図しない感じになります。

圧縮率を指定してJPEGで保存

LINQで少しだけすっきり。

using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        Bitmap bmp = new Bitmap(100, 100);

        // 円を描画
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.Clear(Color.White);
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.FillEllipse(Brushes.Red, new Rectangle(5, 5, 70, 70));
            g.FillEllipse(Brushes.Cyan, new Rectangle(25, 25, 70, 70));
        }
        
        // JPEGの圧縮率を指定して保存
        long quality = 95L; // 0(高圧縮) - 100(高画質) の値を long 型で指定

        // JPEGコーデックを取得
        ImageCodecInfo jpgEncoder = (
            from c in ImageCodecInfo.GetImageEncoders()
            where c.FormatID == ImageFormat.Jpeg.Guid
            select c).FirstOrDefault();

        EncoderParameters encParams = new EncoderParameters(1);
        encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);

        bmp.Save("sample.jpg", jpgEncoder, encParams);

        encParams.Dispose();
        bmp.Dispose();
    }
}

Bitmapをバイト配列として操作

C#3以降ならこんな感じで割とすっきり。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        Bitmap bmp = new Bitmap(256, 256);

        // Bitmap をバイト配列として操作
        BitmapBytes(bmp, (data, width, height) =>
        {
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int p = (x + y * width) * 4;

                    // このあたりはお好みで
                    data[p + 3] = 255;     // A
                    data[p + 2] = (byte)x; // R
                    data[p + 1] = (byte)y; // G
                    data[p] = 128;         // B
                }
            }
        });

        bmp.Save("sample.png", ImageFormat.Png);
        bmp.Dispose();
    }

    /// <summary>
    /// ビットマップをbyte配列として操作
    /// </summary>
    /// <param name="bmp">32bppArgb の Bitmap</param>
    /// <param name="userMethod">Bitmap のbyte配列操作処理</param>
    private static void BitmapBytes(Bitmap bmp, Action<byte[], int, int> userMethod)
    {
        // 32bitARGB Bitmap をbyte配列に変換 
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        System.Drawing.Imaging.BitmapData bmpData =
            bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
            PixelFormat.Format32bppArgb);
        IntPtr ptr = bmpData.Scan0;
        int bytes = bmp.Width * bmp.Height * 4;
        byte[] rgbValues = new byte[bytes];
        Marshal.Copy(ptr, rgbValues, 0, bytes);

        // ユーザー定義のBitmap配列操作処理
        userMethod(rgbValues, bmp.Width, bmp.Height);

        // byte配列をBitmap に書き戻し
        Marshal.Copy(rgbValues, 0, ptr, rgbValues.Length);
        bmp.UnlockBits(bmpData);
    }
}

出力結果はこんな感じ。

f:id:ksasao:20131216224947p:image

まとめ

.NET のグラフィックスライブラリがもっと充実して使いやすくなってくれるといいですね!というか、XNAなき今、3D関係はどうなってしまうの?

それでは、良い GDI+ ライフを!

明日はmatarilloさんです!

Java8とC# - 猫とC#について書くmatarilloの雑記

2010-07-19

Silverlight 4 で Webカメラキャプチャ & JPEG としてローカル保存

Silverlight 4 では、DirectShow でやろうとするとそれなりに面倒な、Webカメラが対応している解像度、フレームレートの一覧が VideoCaptureDevice.SupportedFormats であっさり取れてしまいます。.NET Framework 本家にも実装してほしいですね。

f:id:ksasao:20100719233516p:image

Silverlight 4 の標準ライブラリでは、JPEG 画像が生成できないので、Silverlight 界隈では、ほぼデファクトスタンダードとなっている FJCoreJPEG 画像を生成しています。

利用方法は以下のような感じ。

// 保存ボタンが押されたら、保存ダイアログを開き、
// WriteableBitmap を Jpeg に変換して保存
private void buttonSave_Click(object sender, RoutedEventArgs e)
{
    bool? dialogResult = this.saveDialog.ShowDialog();

    if (dialogResult == true)
    {
        // WriteableBitmap を JPEGに変換して保存
        byte[] fileBytes = CreateImageAsJpeg(_bmp, 80);

        // ユーザーのボタン操作によってファイル保存処理を
        // 行わないとセキュリティ例外
        using (Stream fs = (Stream)this.saveDialog.OpenFile())
        {
            // ユーザーが指定したファイル名で保存
            fs.Write(fileBytes, 0, fileBytes.Length);
            fs.Close();
        }
    }
}

CreateImageAsJpeg の中身はこのような感じ。http://blogs.msdn.com/b/davrous/archive/2009/12/18/silverlight-4-tutorial-adding-avatar-support-to-the-wcf-ria-services-business-template.aspx のコードを若干修正。

/// <summary>
/// ビットマップをJpegのバイト列へ変換します
/// </summary>
/// <param name="bitmap">変換元画像</param>
/// <param name="quality">圧縮品質(0:最高圧縮率〜100:最高品質)</param>
/// <returns>Jpegエンコードされたバイト列</returns>
private byte[] CreateImageAsJpeg(WriteableBitmap bitmap, int quality)
{
    int width = bitmap.PixelWidth;
    int height = bitmap.PixelHeight;
    int bands = 3;
    byte[][,] raster = new byte[bands][,];

    for (int i = 0; i < bands; i++)
    {
        raster[i] = new byte[width, height];
    }

    for (int row = 0; row < height; row++)
    {
        for (int column = 0; column < width; column++)
        {
            int pixel = bitmap.Pixels[width * row + column];
            raster[0][column, row] = (byte)(pixel >> 16);
            raster[1][column, row] = (byte)(pixel >> 8);
            raster[2][column, row] = (byte)pixel;
        }
    }

    ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
    FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

    // JPEG形式でエンコード
    MemoryStream stream = new MemoryStream();
    FluxJpeg.Core.Encoder.JpegEncoder encoder
        = new FluxJpeg.Core.Encoder.JpegEncoder(img, quality, stream);
    encoder.Encode();

    // MemoryStream の先頭に移動
    stream.Seek(0, SeekOrigin.Begin);

    // MemoryStream の内容を配列にコピー
    byte[] binaryData = new Byte[stream.Length];
    long bytesRead = stream.Read(binaryData, 0, (int)stream.Length);
    return binaryData;
}

画像の生成そのものは、クライアントPC上のみで行っているので、サーバ側は静的なファイルを置くだけでOKです。

ただ、セキュリティの理由で、保存用ダイアログに、デフォルトの保存ファイル名を指定できないようです。

2010-05-16

C# と .NET Micro Framework ではじめるマイコンプログラミング

マイコン程度のスペックで動作する、.NET Micro Framework というエディションがあります。これを利用することで、マイコンプログラミングC# で行えるようになります。

.NET Micro Framework が動作する Arduino とピン互換なボード FEZ Domino が日本でも入手できるようになったので、早速試してみました。このボードは、microSD カードの読み書きや USB ホストにも対応しています。

D

USBケーブルが刺さっていますが、これは電源用であり、別途電源を用意すればスタンドアロンで動作します。もちろん、USBケーブルをつないだ状態であれば、ブレークポイントを設定しながら実機上でデバッグすることも可能です。

上記の動画のアプリのために記述したソースコードはこれだけ。.NET Micro Framework の標準ライブラリのみで記述してみました。

f:id:ksasao:20100516145351p:image

.NET Micro Framework は、初期設定さえしてしまえば、通常の Visual Studio での .NETアプリ開発とほとんど変わらない感覚で扱うことができます。Visual Studio ではおなじみのコード補完機能 IntelliSense なども、もちろん有効ですので、キーを打った回数は全体の文字数の1/3くらいかと思います。

以下に、簡単ですが導入までの手順をまとめてみました。動作は、Windows 7 (x64) 上で確認しています。

デバイスの入手から開発環境の構築まで

1. デバイスの入手

今回は http://tinyclr.jp/products/fez-domino.htm から購入しました。


2. Visual C# 2008 Express SP1 の入手

現時点の .NET Micro Framework は Visual Studio 2010 には対応していないようなので、

http://www.microsoft.com/express/downloads/#2008-Visual-CS

で、Visual Studio 2008 Express タブ -> Visual C# 2008 Express Edition を選択し、Japanese を選択してダウンロードインストールします。

f:id:ksasao:20100516144948p:image


3. .NET Micro Framework 4.0 SDK の入手

http://www.microsoft.com/downloads/details.aspx?FamilyId=77dbfc46-14a1-4dcf-a809-eda7ccfe376b&displaylang=en

からダウンロードインストールします。


4. FEZ Domino SDK の入手

http://www.tinyclr.com/dl/

f:id:ksasao:20100516144947p:image


から Software Development Kit (SDK) を入手し、インストールします。


5. デバイスドライバインストール

デバイスマネージャーから手動でドライバインストールします。

f:id:ksasao:20100516144946p:image

ドライバのファイルは以下にあります。

(x86の場合)

C:\Program Files\GHI Electronics\GHI NETMF v4.0 SDK\USB Drivers\

(x64の場合)

C:\Program Files (x86)\GHI Electronics\GHI NETMF v4.0 SDK\USB Drivers\

インストールが完了すると、下図のように表示されます。

f:id:ksasao:20100516144945p:image


6. 動作確認

ボードと通信ができているかどうかを確認するためのツールとして、MFDeploy.exe が用意されています。MFDeploy.exe は以下の場所にインストールされています。

(x86の場合)

C:\Program Files\Microsoft .NET Micro Framework\v4.0\Tools

(x64の場合)

C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.0\Tools

f:id:ksasao:20100516144944p:image

起動すると、以下のような画面が表示されます。

f:id:ksasao:20100516144943p:image

Device で USB を選択し、 Ping ボタンをクリックすると、ボードが正常であれば、

Pinging... TinyCLR

といった応答が返ります。異常がある場合には、Error が返ります。

以上で開発環境の設定は終了です。


開発手順

1. プロジェクトの作成

Visual C# 2008 Express を起動し、[ファイル(F)] -> [新しいプロジェクト(P)] を選択すると、以下の画面が表示されます。ここで、Console Application を選択します。

f:id:ksasao:20100516151357p:image


2. プロジェクトの設定

[プロジェクト(P)] -> [.. のプロパティ] を選択し、.NET Micro Framework タブを選択します。ここで、Deployment の項目を図のように設定します。

f:id:ksasao:20100516151839p:image

これで F5 キーを押すと記述したプログラムが実機上で動作するようになります。


まとめ

いやはや、便利な時代になりましたね。以下、参考サイトです。

2009-11-21