OpenCvSharpをつかう その19(ASP.NET MVCで使う 入門編)

OpenCVC++ではなくC#(.NET Framework)からわざわざ利用する最大のメリットは何か。単純に言語・開発環境的な理由(書きやすいとか)を挙げる人も多いでしょうが、おそらく本質的に最も大きいのは、.NET Frameworkが持つ膨大なクラスライブラリ・周辺技術等を活用できるというところではないでしょうか。

ということで、ここらで.NET Frameworkの真価を発揮したいと思いまして、ASP.NET MVC 4でOpenCvSharpを動かしてみたいと思います。ローカルで動かすので、サーバの用意は必要ありません。

OpenCvSharpが動くことを最優先していますので、MVCのお作法などなどは、テキトーです。私もあまり慣れていないので、勉強がてら書いています。

OpenCvSharpをつかう 記事一覧

つくるもの

画像ファイルを選択してリクエストを投げると、サーバでその画像をCannyエッジ検出して、返してくれるようなサービスを作ります。
今回は、普通に「ビュー」があるWebアプリケーションとして作ります。つまり、出来上がったものはブラウザで触って遊べるものということです。

トップは、ファイル選択と送信ボタンだけのごく単純なページです。送信するファイルとして1つ画像を選びます。ここではWindows7 サンプルピクチャのペンギンを指定しました。
f:id:Schima:20140122164124p:plain

そしてSubmitボタンを押すと、送信された画像に対しグレースケール化・Cannyエッジ検出をして、表示します。
f:id:Schima:20140122184344p:plain

環境構築

Microsoft Visual Studio Express 2013 for Web を使用する前提で、以降説明いたします。必要に応じて、導入してください。単純にインストーラにおまかせするのみです。
http://www.microsoft.com/ja-jp/download/details.aspx?id=40747

なお2013ではなく2012の環境がある方は、おそらくそれでも問題ありません。またProfessional以上のエディションであればWebも含まれています。

ASP.NET MVC Hello World

慣れている方は読み飛ばしてください。
HomeControllerをつくり、Indexのビューを作るまでです。

プロジェクトの作成

「Visual C#」のWebから、ASP.NET MVC 4 Web アプリケーションで新しいプロジェクトを作ります。名前は適当に。
f:id:Schima:20140122165726p:plain

次のダイアログでは、プロジェクトテンプレートを「空」とします。
テンプレートを使った方が早いのですが、初心者的には空の方がためになるはず。

コントローラー(Controller)の作成

MVCのCです。

ソリューションエクスプローラControllersを右クリックして、コントローラーを選択して新規作成します。
f:id:Schima:20140122171439p:plain

名前は必ず、HomeControllerとしてください(もし変える場合は、RouteConfig.csの書き換えも必要です)。テンプレートは、空のMVCコントローラーです。

HomeController.csを作ると、このようなコードが自動生成されます。Indexというアクションメソッドを作ってくれました。これがいわゆるトップページの処理となります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

    }
}

ビュー(View)の作成

MVCのVです。上のコントローラーでは何もせず return View(); していますが、何をViewするかまだ書いていませんので作成します。

  1. ソリューションエクスプローラViewsの下に新しいフォルダーを作ります。名前は必ずHomeとしてください。
  2. 作成したHomeフォルダーを右クリックして、ビューを選択して新規作成します。名前は必ずIndexとしてください。その他の設定はそのままです。

この時点で、ソリューションエクスプローラはこうなっているはずです。
f:id:Schima:20140122203426p:plain

作成された Index.cshtml の中身はこうなっていると思います。これはRazorという構文で書かれていますが、ひとまず基本的にはHTMLです。<body>の中身という扱いで、いろいろと書くことができます。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

これでようやく、ページが表示されるようになりました。デバッグ実行してみましょう。ブラウザが自動的に立ち上がり、以下のように表示されるはずです。
f:id:Schima:20140122174445p:plain

そういえばMVCのVCは作ったけど、M(モデル)は?と思った方、するどいです。しかし今回は手抜きなのでモデルは作りません。ロジックは全部コントローラーに書いてしまいます。あしからず。

OpenCvSharpの導入 (NuGet)

ようやく本題です。その17の要領で、OpenCvSharpをNuGet Package Managerから検索し、インストールします。

注意点として、Windowsアーキテクチャ(32-bit/64-bit)に関わらず、必ず「OpenCvSharp x86」の方を導入してください。
ちゃんと理由がわかっていないのですが、64-bitのWindowsでもx64ではだめっぽいです。普通のIISでは動作を32-bitモードにするかどうか、のような設定があったと記憶していますが、IIS Expressでどうなのかは、知識が足りません。

f:id:Schima:20140122194900p:plain

画像を送信するビュー(Index.cshtml)の作成

Views/Home/Index.cshtml にこのように書き足します。この記事の初めにスクリーンショットで示したような、ファイル送信機能を備えたフォームを作ります。

Html.BeginFormの第1引数がアクション名です。Cannyというアクションに飛ばします。このあと作ります。

第2引数がコントローラ名です。HomeControllerを作っていますので、それを指定しています。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm("Canny", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <input type="file" name="imageData" accept="image/*" /><br />
    <input type="submit" value="Submit" />
}

HomeControllerへのアクション(Canny)の追加

ビューでCannyというアクションへ投げるように設定しましたので、それを作ります。
Controllers/HomeController.csを以下のように書きます。

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using OpenCvSharp;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Canny(HttpPostedFileBase imageData)
        {
            // 入力画像からIplImageを読み込み
            using (var image = IplImage.FromStream(imageData.InputStream, LoadMode.Color))
            {
                // グレースケール化・Cannyエッジ検出
                using (var grayImage = new IplImage(image.Size, BitDepth.U8, 1))
                using (var cannyImage = new IplImage(image.Size, BitDepth.U8, 1))
                {
                    Cv.CvtColor(image, grayImage, ColorConversion.BgrToGray);
                    Cv.Canny(grayImage, cannyImage, 60, 180);

                    // Canny画像をPNGエンコードでバイト配列に変換し、さらにBase64エンコードする
                    byte[] cannyBytes = cannyImage.ToBytes(".png");
                    string base64 = Convert.ToBase64String(cannyBytes);
                    // ビュー変数に設定
                    ViewBag.Base64Image = base64;
                }
            }
            return View();
        }
    }
}

アクションメソッドの引数名は、ビューでのフォームのパラメータ名と合わせます。そうするだけで、ビューで入力されたパラメータを得ることができます。

今回、画像ファイルは"multipart/form-data"で送っています。CannyアクションメソッドではそれをStreamとして受け取り、IplImageに変換しています。IplImage.FromStreamにより簡単にできます。

Cannyの画像は、IplImage.ToBytesでバイト配列に変換し、さらにそれをConvert.ToBase64StringBase64文字列に変換しています。ここもすごく簡単に書けるところで、ちょっと感動して頂けると幸いです。

画像処理結果を見せるビュー(Canny.cshtml)の作成

最後に、Cannyアクションメソッドの結果を表示するビューを作りましょう。

Views/Home/Canny.cshtmlを新規作成し、このように書きます。

@{
    ViewBag.Title = "Canny";
}

<h2>Canny</h2>

<img src="data:image/png;base64,@ViewBag.Base64Image" />

Cannyアクションメソッドで保存しておいた、画像のBase64文字列を、<img>タグのsrcに埋め込んでいます。このようにsrc属性を書くと、そのBase64文字列が表す画像を表示させることができます。

実行

これで終わりです。かなり簡単と思っていただけたでしょうか。
デバッグ実行して、適当な画像を送ってみましょう。この記事の最初で示したように、Cannyエッジ検出の結果が表示されるはずです!
f:id:Schima:20140122202837p:plain

発展形として、WebAPIとして画像処理を公開したり、Ajaxで動的なインタラクションを持つ画像処理Webアプリケーションを作ったり、などなど、夢が広がりますね。

このサンプルで言えば、まずはCannyの2つの閾値をビューから指定できるようにする、といったあたりから始めてみると良いかもしれません。