C#でグラフを描く OxyPlotのFunctionSeriesによる方法

.NETフレームワーク用のグラフ描画ライブラリであるOxyPlotを使ってグラフを描く.
その中でもFunctionSeriesクラスを使って与えられた関数のグラフを描く方法を記す.

OxyPlotはWindows Formアプリケーションだけでなく,WPF,Silverlight,Windows8等様々な環境で使える.

Chartコントロールとの比較

Chartコントロールによるグラフの描画方法は以下の別の記事で紹介したことがあるが,比較してOxyPlotの方が良いなと思っている点をあげる.

  • 関数を渡してこの範囲を表示するとかが楽(今回の記事のメイン.詳細は後述.たとえば

y=x^3+2x^2-3-2\le x\le 2の範囲のグラフを描画とか)

  • マウス操作によるグラフの拡大・縮小,移動等の機能が最初からついている
  • ベクター画像できれいにグラフを保存できる(ChartコントロールでもEMFで保存できるがグラフの種類によっては汚い)
  • 配列データ等にバインディングさせて,複数のグラフを描画するとき便利な気がする(今度記事にまとめようと思います)

OxyPlotのインストール

NuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「OxyPlot」を検索.自分に合った環境のパッケージをインストール.(この記事ではOxyPlot.WindowsFormsを使った)

f:id:whoopsidaisies:20140210110630p:plain

Plotコントロールの追加

グラフ描画用のPlotというコントロールが用意されているので使う.

ツールボックスの適当なところで右クリックして「アイテムの選択(I)...」を選択.「.NET Framework コンポーネント」タブの「参照(B)...」ボタンを押して,「(ソリューションフォルダ)\packages\OxyPlot.WindowsForms.yyyy.x.xxx.x\lib\NET45\OxyPlot.WindowsForms.dll」を選択したらOK.

すると,ツールボックスに「Plot」ができるので,Formに配置する.

f:id:whoopsidaisies:20140210111336p:plain

FunctionSeries

OxyPlotには,Func<double, double>のメソッドのグラフを描画してくれるFunctionSeriesクラスがあるのでその使い方を記す.
Func<double, double>という書き方になじみがない人もいるかもしれないので,とりあえず標準正規分布
f\(x\)=\frac{1}{\sqrt{2\pi}}exp\(-\frac{x^{2}}{2}\)
を描くサンプルを以下に示す.

private static double StandardNormalDistribution(double x)
{
    return 1 / Math.Sqrt(2 * Math.PI) * Math.Exp(-x * x / 2);
}

private void Form1_Load(object sender, EventArgs e)
{
    plot1.Model = new OxyPlot.PlotModel { PlotType = OxyPlot.PlotType.XY };
    var series = new OxyPlot.Series.FunctionSeries(
        StandardNormalDistribution,     // 引数double,戻り値doubleの関数
        -3,                             // x座標の最小値
        3,                              // x座標の最大値
        0.01,                           // x座標の刻み幅
        "標準正規分布");                // グラフタイトル
    plot1.Model.Series.Add(series);
}

実行すると以下のようなグラフが表示される.

f:id:whoopsidaisies:20140210114639p:plain

StandardNormalDistributionという引数がdouble型,戻り値もdouble型の関数を定義している.これがFunc<double, double>のメソッドである.
これと,グラフ描画をしたい範囲をFunctionSeriesクラスのコンストラクタに渡してやるだけでグラフを描画してくれて非常に便利である.

ラムダ式による方法

メソッドの部分をラムダ式で表現することも当然出来るので,StandardNormalDistributionメソッドをわざわざ定義しないでも以下のように書くこともできる.(ここではラムダ式の説明は省略.わからない人はここらのへんのページを参照)

    var series = new OxyPlot.Series.FunctionSeries(
        x => 1 / Math.Sqrt(2 * Math.PI) * Math.Exp(-x * x / 2),     // 引数double,戻り値doubleの関数
        -3,                             // x座標の最小値
        3,                              // x座標の最大値
        0.01,                           // x座標の刻み幅
        "標準正規分布");                // グラフタイトル

媒介変数表示された関数のグラフ描画

これも簡単に出来るので例を示す.例として以下のカージオイドを表示させてみる.
x=a\(2\cos\theta - \cos2\theta\)
y=a\(2\sin\theta - \sin2\theta\)

ソースコードは以下のようになる.(ラムダ式使用)

= new OxyPlot.PlotModel { PlotType = OxyPlot.PlotType.XY };
var series = new OxyPlot.Series.FunctionSeries(
    t => 2 * Math.Cos(t) - Math.Cos(2 * t), // x座標の関数
    t => 2 * Math.Sin(t) - Math.Sin(2 * t), // y座標の関数
    0,                                      // tの最小値
    2 * Math.PI,                            // tの最大値
    Math.PI / 32,                           // tの刻み幅
    "カージオイド");                        // グラフタイトル
plot1.Model.Series.Add(series);

実行すると以下のようなグラフが表示される.

f:id:whoopsidaisies:20140210122606p:plain

その他

PlotTypeの設定によって極座標表示とかもできるがそれはまたの機会に.

C#で地球を表示する(vtkTexturedSphereSourceによる方法)

VTKのvtkTexturedSphereSourceで球に世界地図のテクスチャを貼り付けることで地球を表示させる.

テクスチャを使わずにvtkEarthSourceによって表示する方法は以下のページを参照.
C#で地球を表示する(VTKのEarthSourceによる方法) - whoopsidaisies's diary

ActiViz

ActiVizは,3Dコンピュータグラフィックスの可視化ライブラリVTK(The Visualization ToolKit)のC#ラッパである.NuGetからインストールできる.簡単な解説は以下のページを参照.
C#で3Dモデル表示(VTKのC#ラッパActiVizによる方法) - whoopsidaisies's diary

RenderWindowControl

NuGetからActiVizをインストールし,RenderWindowControlをフォームに追加する.

vtkTexturedSphereSource

VTKにはvtkTexturedSphereSourceという,テクスチャを貼り付けることが出来る球オブジェクトのクラスがあるためそれを利用する.ソースコードは以下のようになる.

sing (var sphere = new Kitware.VTK.vtkTexturedSphereSource())
using (var texture = new Kitware.VTK.vtkTexture())
using (var image = new Kitware.VTK.vtkPNGReader())
using (var mapper = new Kitware.VTK.vtkCompositePolyDataMapper())
using (var actor = new Kitware.VTK.vtkActor())
{
    // 球の滑らかさ(数字が多いほど分割数が多く滑らかになる)
    sphere.SetThetaResolution(64);
    sphere.SetPhiResolution(64);
    mapper.SetInputConnection(sphere.GetOutputPort());
    actor.SetMapper(mapper);

    // 世界地図画像の読み込みとテクスチャの設定
    image.SetFileName(@"WorldMap_ja.png");
    texture.SetInputConnection(image.GetOutputPort());                
    actor.SetTexture(texture);

    renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddActor(actor);
}

実行すると以下のように地球が表示される.世界地図画像はウィキペディアにあったものを使った.

f:id:whoopsidaisies:20140209202615p:plain

以下のように拡大表示もできるが,この方法だとベクターグラフィックの読み込みが(たぶん)できないので拡大表示する際は何かしら工夫が必要そう.

f:id:whoopsidaisies:20140209203055p:plain

C#で地球を表示する(VTKのEarthSourceによる方法)

C#でVTKのvtkEarthSourceクラスによって地球を表示させる.

ActiViz

ActiVizは,3Dコンピュータグラフィックスの可視化ライブラリVTK(The Visualization ToolKit)のC#ラッパである.NuGetからインストールできる.簡単な解説は以下のページを参照.
C#で3Dモデル表示(VTKのC#ラッパActiVizによる方法) - whoopsidaisies's diary

RenderWindowControl

NuGetからActiVizをインストールし,RenderWindowControlをフォームに追加する.

vtkEarthSource

VTKにはvtkEarthSourceという,地球が表示されるオブジェクトがあるので使う.ソースコードは以下のようになる.

using (var earth = new Kitware.VTK.vtkEarthSource())
using (var mapper = new Kitware.VTK.vtkCompositePolyDataMapper())
using (var actor = new Kitware.VTK.vtkActor())
{
    mapper.SetInputConnection(earth.GetOutputPort());
    actor.SetMapper(mapper);
    renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddActor(actor);
}

実行すると以下のように地球が表示される.ドラッグで視点変更が出来る.
f:id:whoopsidaisies:20140209185406p:plain

C++でLINQライクな処理(LINQ for C++による方法)

LINQ for C++

LINQは.NET Framework3.5から組み込まれた機能で,C#等の言語でSQLのデータベース操作のようなことを可能とする.「C# is LINQ」と言う方までいほどに便利な機能.

しかし,C++/CLIはLINQに対応する予定はないとのこと.そこで,LINQに似た機能をC++に提供してくれるのがLINQ for C++である.というわけでLINQ for C++を使ってみる.

導入

LINQ for C++はヘッダファイルひとつで実装されているので,以下のページからダウンロードしてきてインクルードするだけで使用できる.NuGetからのインストールも取得可能である.

LINQ for C++ - Home

使用例1

以下に使用例を示す.LINQ for C++を使って,テストの成績のうち国語と英語がともに50点以上の人のリストを取得している.

普通の配列に対して使うことが出来る.コード補完も微妙だし,型明記しなくちゃいけないし,メソッド形式ないしでC#のLINQほど軽快には書けないものの,便利ではある.

#include <cpplinq.hpp>

void main()
{
	using namespace cpplinq;

	struct Seiseki
	{
		char Name[256];
		int Kokugo;
		int Sansuu;
		int Eigo;
		int Rika;
		int Shakai;
	};

	Seiseki seisekiList[] = 
	{
		{"Yamada",98,78,61,83,70},
		{"Tanaka",20,33,16,39,48},
		{"Yoshida",72,65,92,13,26},
		{"Suzuki",49,82,55,11,96},
		{"Sasaki",83,47,61,23,60}
	};
	
	auto result = from_array(seisekiList)
		>> where([](Seiseki s) {return s.Kokugo > 50 && s.Eigo > 50;})
		>> to_vector();

	for (int i = 0; i < result.size();  ++ i)
		printf("%s : 国語%d 英語%d\n", result[i].Name, result[i].Kokugo, result[i].Eigo);
}

結果は以下のように表示される.

Yamada : 国語98 英語61
Yoshida : 国語72 英語92
Sasaki : 国語83 英語61

使用例2

無理やりOpenCVで使ってみる.BRISKで特徴点抽出し,オリエンテーションの向きによって表示する際の色を変えている.

#include <opencv2/opencv.hpp>
#include <cpplinq.hpp>

void main()
{
	using namespace cpplinq;

	auto image = cv::imread("Penguins.jpg");

	auto detector = cv::FeatureDetector::create("BRISK");
	std::vector<cv::KeyPoint> keyPoints;
	detector->detect(image, keyPoints);
	auto key1 = from(keyPoints)
		>> where([](cv::KeyPoint k) { return 0 <= k.angle && k.angle < 90;})
		>> to_vector();
	auto key2 = from(keyPoints)
		>> where([](cv::KeyPoint k) { return 90 <= k.angle && k.angle < 180;})
		>> to_vector();
	auto key3 = from(keyPoints)
		>> where([](cv::KeyPoint k) { return 180 <= k.angle && k.angle < 270;})
		>> to_vector();
	auto key4 = from(keyPoints)
		>> where([](cv::KeyPoint k) { return 270 <= k.angle && k.angle < 360;})
		>> to_vector();

	cv::drawKeypoints(image, key1, image, cv::Scalar(255,0,0), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
	cv::drawKeypoints(image, key2, image, cv::Scalar(0,255,0), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
	cv::drawKeypoints(image, key3, image, cv::Scalar(0,0,255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
	cv::drawKeypoints(image, key4, image, cv::Scalar(255,255,0), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
	cv::imshow("Key Point", image);
	while(cv::waitKey(1) == -1);
}

f:id:whoopsidaisies:20140117010914p:plain

C# 名前付きの色一覧を配列に取得してRGB値をDataGridViewに表示する

Color構造体

.NET FrameworkのColor構造体はRGB値で色の指定もできるが,あらかじめRGB値が指定されていて名前を指定して使える色がある.「Color.Red」とか「Color.Green」といった感じで使うことが出来る.

本記事では,名前が付いた色の一覧を取得してColor構造体の配列に取得,さらにRGB値と色をDataGridViewに表示する方法を紹介する.

名前付きの色の取得

名前付きの色は,Color構造体の静的プロパティとして定義されている.色の一覧をColor構造体の配列に格納するコードは以下のようになる.

var colors = typeof(Color).GetProperties(BindingFlags.Static | BindingFlags.Public)
    .Select(p => Color.FromName(p.Name))
    .ToList();

1行目では,GetPropertiesメソッドを使って,Color構造体のパブリックな静的プロパティの型情報の一覧を取得している.BindingFlags構造体を使って取得するプロパティの検索方法を指定できるが,StaticとPublicを指定することでパブリックな静的プロパティを取得している.

2行目では,LINQSelectメソッドとhttp://msdn.microsoft.com/ja-jp/library/system.drawing.color.fromname(v=vs.110).aspx:Color.FromaNameメソッドを使って色の名前から,その色のColorオブジェクトを生成している.

3行目で,結果をListに変換している.(ListとかArrayとかに変換したほうが後の処理が楽なので)

DataGridViewで表示

取得した色の一覧をDataGridViewコントロールに表示する.

dataGridView1.DataSource = colors;

上記の一行を追加すると以下のように色の名前とRGB値が表示される.
f:id:whoopsidaisies:20140115195242p:plain

表示するプロパティの選択

上の画像だと「IsKnownColor」とかの余計なプロパティも表示されているので,表示しないようにする.

foreach (DataGridViewColumn col in dataGridView1.Columns)
    if (col.Name != "R" && col.Name != "G" && col.Name != "B" && col.Name != "Name")
        col.Visible = false;

DataGridViewの各列はVisibleプロパティによって表示・非表示を切り替えられるので,RGBと名前以外の列を非表示に設定している.

f:id:whoopsidaisies:20140115200155p:plain

セルの色付け

名前とRGB値だけだとどんな色か想像がつきにくいので,セルに色を付ける.ついでに名前が表示されている列を先頭にする.

for (int i = 0; i < colors.Count; ++i)
    dataGridView1["Name", i].Style.BackColor = colors[i];
dataGridView1.Columns["Name"].DisplayIndex = 0;

DataGridViewの各セルStyleプロパティによって色やフォント等の書式を指定出来る.ここではBackColorプロパティによって背景色を指定している.

また,DisplayIndexプロパティによって名前の表示列を先頭にしている.

実行すると以下のように名前のセルに色がついて表示される.(Transparentのところは後ろのウィンドウが表示されている.)

f:id:whoopsidaisies:20140115201854p:plain

ソースコード

上記コードをまとめると以下のようになる.

var colors = typeof(Color).GetProperties(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
    .Select(p => Color.FromName(p.Name))
    .ToList();

dataGridView1.DataSource = colors;

foreach (DataGridViewColumn col in dataGridView1.Columns)
    if (col.Name != "R" && col.Name != "G" && col.Name != "B" && col.Name != "Name")
        col.Visible = false;

for (int i = 0; i < colors.Count; ++i)
    dataGridView1["Name", i].Style.BackColor = colors[i];
dataGridView1.Columns["Name"].DisplayIndex = 0;

C#でOpenCVを使う(C++/CLIによる方法)

OpenCVラッパクラス

C#でOpenCVを手軽に使うには

OpenCvSharpをつかう その17(NuGetで導入) - schima.hatenablog.com
C#でOpenCVを動かす~NuGetでOpenCV.Netをインストール - whoopsidaisies's diary

のページのように,NuGetからC#ラッパをインストールして使う方法があるが,必要な機能がラッピングされていないことがある.

このページでは,C++/CLIでOpenCVの必要な機能だけ持つラッパクラスを自分で作り,C#から呼び出す方法を紹介する.

C++/CLI

通常のC++のライブラリをC#から呼び出そうとするとDllImport属性で宣言が必要だったりして面倒であるが,C++/CLIで作ったDLLであれば参照に追加するだけで使用できる.

手順

手順は以下の通りである.

C#プロジェクト作成

実行されるC#のプロジェクト

C++/CLIのDLL用プロジェクト作成

OpenCVをラップするクラス用のプロジェクト

プロジェクトの参照の設定

作成したクラスをC#から読み込むための設定

OpenCVのインストール

OpenCVのインストール

C++/CLIでラッパクラスの作成

OpenCVによる処理部分の実装

C#のプロジェクトの出力パスの変更

OpenCVのDLLの場所とかの関係上の設定

C#から呼び出す

C#からの呼び出し部分とGUIの実装


以下に各手順の詳細を記す.

C#プロジェクト作成

Visual Studioで新しいプロジェクトを作成する.
f:id:whoopsidaisies:20140112101313p:plain

C++/CLIのDLL用プロジェクト作成

ソリューションを右クリックして,「追加」の「新しいプロジェクト(N)...」でC++/CLIのDLL作成用のプロジェクトを作成する.「Visual C++」の「CLR」の「クラス ライブラリ」を選択.
f:id:whoopsidaisies:20140112101334p:plain

プロジェクトの参照の設定

C++/CLIで作成したDLLをC#から呼び出すための参照設定を行う.C#のプロジェクト内の「参照設定」を右クリックし「参照の追加(R)...」を選択する.開いたダイアログで「ソリューション」の「プロジェクト」を選択し,C++/CLIのプロジェクトの名前のところにチェックを入れる.
f:id:whoopsidaisies:20140112101620p:plain
f:id:whoopsidaisies:20140112102324p:plain

OpenCVのインストール

C++/CLIのプロジェクトを右クリックし「NuGet パッケージの管理」を選択.ダイアログで「オンライン」を選択し,検索窓で「OpenCV」を検索,インストールする.
f:id:whoopsidaisies:20140112101806p:plain

C#のプロジェクトの出力パスの変更

NuGetでインストールしたOpenCVのDLLが,C#プロジェクトで作成した実行ファイルから参照できる位置に配置されないため,実行ファイルの出力パスを変えてやる.C#のプロジェクトを右クリックし「プロパティ(R)」を選択.「ビルド」の「出力パス」を「..\Debug\」に変える(Releaseモードなら..\Release\).
f:id:whoopsidaisies:20140112110351p:plain
この手順を忘れると,実行時に以下のようなエラーメッセージが表示されてOpenCVの呼び出しに失敗する.
f:id:whoopsidaisies:20140112110400p:plain

C++/CLIでラッパクラスの作成

C++/CLIでOpenCVの処理のコードを書く.C++/CLIのプロジェクトを作成したときに色々とファイルが生成されているので,そのうちの「OpenCvClrLibrary.h」(プロジェクト名.h)を以下のようにする.画像をファイルから読み込んで,BRISK特徴点を抽出して表示するサンプルである.

// OpenCvClrLibrary.h

#pragma once
#include <opencv2/opencv.hpp>

using namespace System;

namespace OpenCvClrLibrary {

	public ref class TestClass
	{
	public:
		static void TestMethod()
		{
			auto image = cv::imread("Penguins.jpg");

			auto brisk = cv::BRISK();
			std::vector<cv::KeyPoint> keyPoint;
			brisk.detect(image, keyPoint);
			cv::drawKeypoints(image, keyPoint, image);

			cv::imshow("image", image);
			while (cv::waitKey(1) == -1);
			cv::destroyAllWindows();
		}
	};
}

「ref class」はC++/CLI用のクラス.詳細は省略するが,こういう書き方をすることでC#でそのまま使えるクラスを作れる.
「auto」はc++11から追加された機能で,変数の初期値から型推論を行ってくれる.Visual Studio 2012以降では使えるみたい.横に長くなりがちなOpenCV(に限らないけど)のコードではこれを使えるのは嬉しい.

C#から呼び出す

C#から上記の特徴点抽出処理を呼び出すコードを以下のように書く.

using System;
using System.Windows.Forms;

namespace OpenCvFromCSharp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenCvClrLibrary.TestClass.TestMethod();
        }
    }
}

Buttonコントロールを配置して,クリックすると特徴点が書き込まれた画像が以下のように表示される.

f:id:whoopsidaisies:20140112104341p:plain

C#でAI,PSD,EPS,SVG,PDF等の画像ファイルを開く(ImageMagickのC#ラッパMagick.NETによる方法)

Magick.NET

100種類以上の画像を操作したり表示したりできるライブラリImageMagickのC#ラッパMagick.NETを使って,C#でAI,PSD,EPS,SVG,PDF等の画像ファイルを開く.

インストール

NuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「Magick.NET」を検索してインストール.

f:id:whoopsidaisies:20140108210712p:plain

プラットフォームを変える

プロジェクトのプラットフォームをインストールしたパッケージ(「x64」か「x86」)に変える.「Any CPU」だと動かない場合があるので注意.

サンプルコード

以下にepsファイルを開いて,PictureBoxに表示するサンプルを示す.ファイル名の部分を変えれば,eps以外のファイルも読み込むことが出来る.

var magickImage = new ImageMagick.MagickImage(
    @"blossom tree.eps",
    new ImageMagick.MagickReadSettings() { Density = new ImageMagick.MagickGeometry(300, 300) });
pictureBox1.Image = magickImage.ToBitmap();

ImageMagick.MagickGeometry(300, 300)の数字の部分を大きくすると,AI,EPS,PDF等のベクター画像の読み込み後の解像度を高くすることが出来る.

以下のように,ベクター画像を高解像度にすると拡大してもきれいに表示出来る.
f:id:whoopsidaisies:20140108211811j:plain
f:id:whoopsidaisies:20140108211819p:plain