PowerPointのオートシェイプで好きな関数の曲線を作る C# NetOfficeによる方法
オートシェイプできれいな波形を作りたいときがたまにあるのだけど,C#を使って上の画像みたいにsin(x)/xのオートシェイプをわりときれいに作れたので手順をまとめる.
試した環境はPowerPoint2010とVisual Studio 2013.バージョンが違うと少し変わる可能性はあると思う.(わざわざC#でやらずともVBAマクロでも同じことは出来るけど,VBAは苦手なのでC#でやる.VBAでのやり方も後述します)
出来上がったPowerPointファイルを以下からダウンロードできるようにしておいた.
sinc.pptx - Google ドライブ
NetOffice.PowerPointのインストール
以前「C#でExcelファイルを作成・グラフを挿入する NetOfficeによる方法 - whoopsidaisies's diary」で紹介した,NetOfficeという.NET用のライブラリを使うとPowerPoint等にアクセスして色々出来るのでそれを利用する.
NetOfficeはNuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「NetOffice.PowerPoint」を検索してインストール.
ソースコード
以下にソースコードを載せる.スライドを作ってAddCurveメソッドで曲線を追加しているだけである.
AddCurveメソッドの引数として,float[(点の数),2]に座標を格納して渡す.この点の座標を元にベジェ曲線を描くようである.点の数は3n+1個にしないと止まるので注意.
点の座標はスライドの左上が(0,0)でピクセルで指定する.スライドのオブジェクトのPageSetupプロパティにスライドの幅と高さが格納されているのでそれを元に大きさを調整した.
static void Main(string[] args) { // パワーポイントを開く using (var app = new NetOffice.PowerPointApi.Application()) { // プレゼンテーションの作成 var presentation = app.Presentations.Add(); // スライドの追加 var slide = presentation.Slides.Add(1, NetOffice.PowerPointApi.Enums.PpSlideLayout.ppLayoutBlank); // sin(x)/xの座標を得る var sinc = GetSinc(presentation.PageSetup.SlideWidth, presentation.PageSetup.SlideHeight); // 座標からベジェ曲線を描画する // 引数はfloatの二次元配列.座標の数は6n+1個じゃないといけないため注意. slide.Shapes.AddCurve(sinc); // プレゼンテーションの保存 presentation.SaveAs(@"D:\a.pptx"); // プレゼンテーションファイルを閉じる presentation.Close(); // パワーポイントの終了 作成したファイル以外も閉じられるので注意 //app.Quit(); } } /// <summary> /// sin(x)/x関数の座標をスライドの大きさに合わせて拡大, /// </summary> /// <param name="slideWidth">スライドの幅</param> /// <param name="slideHeight">スライドの高さ</param> /// <returns>sin(x)/x関数を描くための点の座標</returns> static float[,] GetSinc(float slideWidth, float slideHeight) { // xが-4π~4πまでのsin(x)/xを求める.601点. var xs = Enumerable.Range(-300, 601).Select(x => x * 4 * Math.PI / 300); var ys = xs.Select(x => x != 0 ? Math.Sin(x) / x : 1); // PowerPointのスライドのサイズに合わせて移動・拡大をする // 中心座標 var cx = slideWidth / 2; var cy = slideHeight / 2; // オブジェクトのだいたいの大きさ var width = slideWidth * 0.8; var height = slideHeight * 0.8; var pptXs = xs.Select(x => x / (4 * Math.PI) * width / 2 + cx).ToArray(); var pptYs = ys.Select(y => -y * height / 2 + cy).ToArray(); // AddCurveメソッドに渡すためにfloatの2次元配列に代入する var sinc = new float[pptXs.Length, 2]; for (int i = 0; i < pptXs.Length; ++ i) { sinc[i, 0] = (float)pptXs[i]; sinc[i, 1] = (float)pptYs[i]; } return sinc; }
実行すると,記事の最初の画像のpptxファイルが作成される.GetSincメソッドの部分を適当な座標を格納をするように変えれば自分の好きな曲線を引ける.
ちなみに出来た曲線はオートシェイプなので,以下の画像のように線の太さとか種類とか色とかも簡単に変えられる.
拡大してもきれい.
VBAマクロでのやり方
記事の最初にも述べたが,C#を使わずともVBAマクロで同じことが出来る.以下に参考になるコードを載せる(ここのサンプル見やすくしただけだけど).
これの点の座標を引きたい曲線の座標にしてやればOK.
Sub DrawCurve() ' 1枚目のスライドのオブジェクトを得る Dim slide As PowerPoint.slide Set slide = ActivePresentation.Slides(1) ' ベジェ曲線を引くための点の座標を配列に格納 ' ここの座標の値を適当に変えてやれば好きな曲線が引ける Dim points(1 To 7, 1 To 2) As Single points(1, 1) = 0 points(1, 2) = 0 points(2, 1) = 72 points(2, 2) = 72 points(3, 1) = 100 points(3, 2) = 40 points(4, 1) = 20 points(4, 2) = 50 points(5, 1) = 90 points(5, 2) = 120 points(6, 1) = 60 points(6, 2) = 30 points(7, 1) = 150 points(7, 2) = 90 ' 曲線の追加 slide.Shapes.AddCurve SafeArrayOfPoints:=points End Sub
OpenCVでDeepFlow
opencv_contribを見てたら,DeepFlowというオプティカルフローのアルゴリズムが実装されていたので勉強ついでに使ってみる.
DeepFlow
DeepFlowはコンピュータビジョンの分野では有名な国際学会ICCV2013でWeinzaepfelらによって発表されたアルゴリズムである.
HAL - INRIA :: [hal-00873592, version 1] DeepFlow: Large displacement optical flow with deep matching
DeepFlowではDeep Matchingという画像のマッチング手法(これも同論文内で提案されている)を用いてオプティカルフローを求めている.
以下の画像にアルゴリズムの全体像がまとめられている.
LEAR - DeepMatchingFlow
パワーポイントの資料はこちら
Deep Matching
Deep Matchingでは,畳み込み,プーリング,サブサンプリングを繰り返しすことで,画像間のQuasi-Denseなマッチング(日本語でなんていうかわからない.準密?)を求める.
Deep Learningで注目を集めているConvolutional Neural Networks(CNN)に似たアプローチとなっている.
Deep Matchingを使うと以下のように非剛体のマッチングもうまく出来るようである.
Jerome Revaud
オプティカルフロー
DeepFlowでは,時間的輝度勾配と空間的輝度勾配の拘束条件に加えて,Deep Matchingで求めた対応とオプティカルフローとの差異も拘束条件として目的関数に入れることで,密でかつ大きな動きにも強いオプティカルフローを得るようである.
目的関数の最小化はSOR法という反復法で行っているようである.
DeepFlowで以下の画像みたいな感じにマッチングとオプティカルフローが得られるらしい.
LEAR - DeepMatchingFlow
OpenCVで動かしてみる
DeepFlowは2014/8/21現在opencv_contribに入っているが,opencv_contribは開発用リポジトリらしいので自己責任で.
ここからソースを落としてきて,OpenCVのCMakeのオプションで「OPENCV_EXTRA_MODULES_PATH」を「(opencv_contribのパス)/modules」としてgenerate,ビルドしてやるとOpenCVにopencv_contrib内のモジュールが組み込まれる.
実際にDeepFlowを動かしてみたソースコードは以下.
DeepFlowのパラメータはここを参照.
#include <opencv2/opencv.hpp> #include <opencv2/optflow.hpp> void main() { using namespace cv; // DeepFlow計算用のインスタンスを生成 auto deepflow = optflow::createOptFlow_DeepFlow(); // 動画読み込み auto capture = VideoCapture("video.mp4"); // 前のフレーム保存 // まだグレースケールしか対応してないようなので変換 Mat prev, tmp; capture >> tmp; cvtColor(tmp, prev, COLOR_RGB2GRAY); while (cv::waitKey(1) == -1) { Mat curr; capture >> tmp; if (tmp.empty()) break; cvtColor(tmp, curr, COLOR_RGB2GRAY); // オプティカルフローの計算 Mat flow; deepflow->calc(prev, curr, flow); // 表示するようにX成分とY成分に分解 Mat flowXY[2]; split(flow, flowXY); // 極座標に変換 Mat magnitude, angle; cartToPolar(flowXY[0], flowXY[1], magnitude, angle, true); // 色相(H)はオプティカルフローの角度 // 彩度(S)は0~1に正規化したオプティカルフローの大きさ // 明度(V)は1 Mat hsvPlanes[3]; hsvPlanes[0] = angle; hsvPlanes[1] = magnitude; hsvPlanes[2] = Mat::ones(angle.size(), CV_32F); // HSVを合成して一枚の画像にする Mat hsv; merge(hsvPlanes, 3, hsv); // 表示 imshow("DeepFlow", hsv); // 前のフレームを保存 prev = curr; } }
結果はこんな感じ.かなり時間かかった.
ちょいちょい真っ赤になるのは仕様なのかバグなのか.
OpenCV3.0.0-alphaの特徴抽出・マッチングまとめ
OpenCV3.0.0-alphaのパッケージが公開されたと話題になっていたので使ってみる.
Change Logを見るとAKAZE特徴量が組み込まれている.
AKAZE特徴量の紹介と他特徴量との比較 - 遥かへのスピードランナー
によるとAKAZE特徴量は,以前のOpenCVでもakaze-opencvというパッケージを入れれば使えたが,OpenCV3.0.0-alphaでは元々組み込まれているのでそのまま使えるようだ.
逆にSIFTやSURFなんかは元々OpenCVで使えたが
OpenCV 3.0.0-alphaでSIFT/SURFを使う | .COM-POUND
によると,opencv_contribに移動されてしまいOpenCVと一緒にビルドしないと使えないようである.
OpenCV3.0.0-alphaでの変更点まとめ
以前当ブログで
OpenCVで画像の特徴抽出・マッチングを行う - whoopsidaisies's diary
にOpenCV2.4.7の画像の特徴抽出あたりをまとめた記事を書いていたので,今回はOpenCV3.0.0-alphaでの変更点をまとめてみる.
特徴点抽出(FeatureDetectorインターフェース)
FeatureDetector | |
---|---|
OpenCV2.4.7 | FAST,FASTX,STAR,SIFT,SURF,ORB,BRISK,MSER,GFTT,HARRIS,Dense,SimpleBlob |
OpenCV3.0.0-alpha | FAST,ORB,BRISK,MSER,GFTT,HARRIS,SimpleBlob,KAZE,AKAZE |
opencv_contrib | STAR,SIFT,SURF |
赤字はOpenCV3.0.0-alphaで追加されたもの.
青字はopencv_contribを入れれば使えるもの.
灰色はOpenCV3.0.0-alphaでなくなってしまってopencv_contribにも入ってないもの.
FASTXとDenseはなくなってしまったようである.
特徴記述(DescriptorExtractorインターフェース)
DescriptorExtractor | |
---|---|
OpenCV2.4.7 | SIFT,SURF,BRIEF,BRISK,ORB,FRIEK |
OpenCV3.0.0-alpha | BRISK,ORB,KAZE,AKAZE |
opencv_contrib | SIFT,SURF,BRIEF,FREAK |
赤字はOpenCV3.0.0-alphaで追加されたもの.
青字はopencv_contribを入れれば使えるもの.
特徴点マッチング(DescriptorMatcherインターフェース)
DescriptorExtractor | |
---|---|
OpenCV2.4.7およびOpenCV3.0.0-alpha | BruteForce,BruteForce-L1,BruteForce-SL2,BruteForce-Hamming,BruteForce-Hamming(2),FlannBased |
DescriptorMatcherに関しては変わりがないようである.
AKAZEを使ってみる
AKAZEを使ってみたかったので,OpenCV3.0.0-alphaで特徴抽出を行ってみる.ついでに他のアルゴリズムとも画像を比較してみる.特徴抽出のソースコードは以下.
cv::String detectorNames[] = {"AKAZE", "KAZE", "FAST", "ORB", "BRISK", "MSER", "GFTT", "HARRIS", "SimpleBlob",}; auto img = cv::imread("Penguins.jpg"); for (int i = 0; i < 9; i++) { auto detector = cv::FeatureDetector::create(detectorNames[i]); std::vector<cv::KeyPoint> keyPoints; detector->detect(img, keyPoints); cv::Mat output; cv::drawKeypoints(img, keyPoints, output, cv::Scalar::all(-1),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); cv::imwrite(detectorNames[i] + ".bmp", output); }
AKAZE
KAZE
FAST
ORB
BRISK
MSER
GFTT
HARRIS
SimpleBlog
C#でDeep Learning(Accord.NETによる方法)
流行りのDeep LearningをC#で試してみる.機械学習やコンピュータビジョン,信号処理等の.NET向けのオープンソースのライブラリであるAccord.NET FrameworkにDeep Learningが実装されているのでそれを使う.
Deep Belief Networks(DBN), Deep Neural Networks(DNNs)とおまけにRestricted Boltzmann Machine(RBM)を単体で動かしてみる.
Deep Learning自体については以下のページ等を参考に.
MIRU2014 tutorial deeplearning
Deep Learning技術の今
ディープラーニング チュートリアル(もしくは研究動向報告)
Accord.NETのインストール
NuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「Accord.Neuro」を検索・インストール.
ソースコード
- Deep Belief Networks(DBN) - 教師あり学習(Back propagation)
- Deep Belief Networks(DBN) - 教師なし学習(Contrastive Divergence)
- Deep Neural Networks(DNNs)
- Restricted Boltzmann Machine(RBM)
を動かすサンプルコードを載せていくが,いずれのサンプルでも以下のusingを書いておくこと.
using Accord.Neuro; using Accord.Neuro.Networks; using Accord.Neuro.Learning; using AForge.Neuro.Learning; using Accord.Neuro.ActivationFunctions; using Accord.Math;
Deep Belief Networks(DBN) - 教師あり学習(Back propagation)
// トレーニングデータ double[][] inputs = { new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 1, 0, 1, 0, 0, 0 }, new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, new double[] { 0, 0, 1, 1, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, }; double[][] outputs = { new double[] { 1, 0 }, new double[] { 1, 0 }, new double[] { 1, 0 }, new double[] { 0, 1 }, new double[] { 0, 1 }, new double[] { 0, 1 }, }; // DBNの生成 var network = new DeepBeliefNetwork( inputsCount: inputs.Length, // 入力層の次元 hiddenNeurons: new int[] { 4, 2 }); // 隠れ層と出力層の次元 // ネットワークの重みをガウス分布で初期化する new GaussianWeights(network).Randomize(); network.UpdateVisibleWeights(); // DBNの学習アルゴリズムの生成 5000回繰り返し入力 var teacher = new BackPropagationLearning(network); for (int i = 0; i < 5000; i++) teacher.RunEpoch(inputs, outputs); // 重みの更新 network.UpdateVisibleWeights(); // 学習されたネットワークでテストデータが各クラスに属する確率を計算 double[] input = { 1, 1, 1, 1, 0, 0 }; var output = network.Compute(input); // 一番確率の高いクラスのインデックスを得る int imax; output.Max(out imax); // 結果出力 Console.WriteLine("class : {0}", imax); foreach (var o in output) { Console.Write("{0} ", o); }
実行結果
class : 0 0.883118949465337 0.115560850111682
Deep Belief Networks(DBN) - 教師なし学習(Contrastive Divergence)
// トレーニングデータ double[][] inputs = { new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 1, 0, 1, 0, 0, 0 }, new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, new double[] { 0, 0, 1, 1, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, }; // DBNの生成 var network = new DeepBeliefNetwork( inputsCount: 6, hiddenNeurons: new int[] { 4, 2 }); // ネットワークの重みをガウス分布で初期化する new GaussianWeights(network).Randomize(); network.UpdateVisibleWeights(); // DBNの学習アルゴリズムの生成 5000回繰り返し入力 var teacher = new DeepBeliefNetworkLearning(network) { Algorithm = (h, v, i) => new ContrastiveDivergenceLearning(h, v) }; var layerData = teacher.GetLayerInput(inputs); for (int i = 0; i < 5000; i++) teacher.RunEpoch(layerData); // 重みの更新 network.UpdateVisibleWeights(); // 学習されたネットワークでテストデータが各クラスに属する確率を計算 double[] input = { 1, 1, 1, 1, 0, 0 }; var output = network.Compute(input); // 一番確率の高いクラスのインデックスを得る int imax; output.Max(out imax); // 結果出力 Console.WriteLine("class : {0}", imax); foreach (var o in output) { Console.Write("{0} ", o); }
実行結果
class : 1 0.529437183967883 0.554114047128916
Deep Neural Networks(DNNs)
// トレーニングデータ double[][] inputs = { new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 1, 0, 1, 0, 0, 0 }, new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, new double[] { 0, 0, 1, 1, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, }; double[][] outputs = { new double[] { 1, 0 }, new double[] { 1, 0 }, new double[] { 1, 0 }, new double[] { 0, 1 }, new double[] { 0, 1 }, new double[] { 0, 1 }, }; // ネットワークの生成 var network = new DeepBeliefNetwork( inputsCount: inputs.Length, // 入力層の次元 hiddenNeurons: new int[] { 4, 2 }); // 隠れ層と出力層の次元 // DNNの学習アルゴリズムの生成 var teacher = new DeepNeuralNetworkLearning(network) { Algorithm = (ann, i) => new ParallelResilientBackpropagationLearning(ann), LayerIndex = network.Machines.Count - 1, }; // 5000回繰り返し学習 var layerData = teacher.GetLayerInput(inputs); for (int i = 0; i < 5000; i++) teacher.RunEpoch(layerData, outputs); // 重みの更新 network.UpdateVisibleWeights(); // 学習されたネットワークでテストデータが各クラスに属する確率を計算 double[] input = { 1, 1, 1, 1, 0, 0 }; var output = network.Compute(input); // 一番確率の高いクラスのインデックスを得る int imax; output.Max(out imax); // 結果出力 Console.WriteLine("class : {0}", imax); foreach (var o in output) { Console.Write("{0} ", o); }
実行結果
class : 0 0.999680477117216 0.00120961077917418
Restricted Boltzmann Machine(RBM)
// トレーニングデータ double[][] inputs = { new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 1, 0, 1, 0, 0, 0 }, new double[] { 1, 1, 1, 0, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, new double[] { 0, 0, 1, 1, 0, 0 }, new double[] { 0, 0, 1, 1, 1, 0 }, }; double[][] outputs = { new double[] { 1, 0 }, new double[] { 1, 0 }, new double[] { 1, 0 }, new double[] { 0, 1 }, new double[] { 0, 1 }, new double[] { 0, 1 }, }; // RBMの生成 var rbm = new RestrictedBoltzmannMachine( inputsCount: 6, hiddenNeurons: 2); // トレーニングデータで学習 var teacher = new ContrastiveDivergenceLearning(rbm); for (int i = 0; i < 5000; i++) teacher.RunEpoch(inputs); // テストデータ double[] input = { 1, 1, 1, 1, 0, 0 }; // 学習されたネットワークで各クラスに属する確率を計算 var output = rbm.Compute(input); // 一番確率の高いクラスのインデックスを得る int imax; output.Max(out imax); // 結果出力 Console.WriteLine("class : {0}", imax); foreach (var o in output) { Console.Write("{0} ", o); }
実行結果
class : 1 0.133832413712274 0.906343089146992
C#でSVM libsvm.netによる方法
SVMのライブラリであるLIBSVMを.NETプロジェクトで使えるようにするlibsvm.netを動かしてみる.
libsvm.netのインストール
NuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「libsvm.net」を検索・インストール.
動作確認
libsvm.netでは,トレーニングデータやテストデータをLIBSVMと同じ形式でファイルから入力できる.データのフォーマットは以下の通りである.
<label> <index1>:<value1> <index2>:<value2> ... <indexN>:<valueN>
LIBSVMの入力データ形式について
<label> <index1>:<value1> <index2>:<value2> ... <indexN>:<valueN>
<label> <index1>:<value1> <index2>:<value2> ... <indexN>:<valueN>
.
.
.
<label> <index1>:<value1> <index2>:<value2> ... <indexN>:<valueN>
ここにLIBSVM用のデータセットが落ちていたので,fourclassというデータセットを使ってみる.
以下に,分類器を動かすソースコード乗せる.
// トレーニングデータの読み込み var training = libsvm.ProblemHelper.ReadProblem("fourclass"); // RBFカーネルを生成 var gamma = 1; var kernel = libsvm.KernelHelper.RadialBasisFunctionKernel(gamma); // C-SVC用のクラスの生成 var c = 1; var svm = new libsvm.C_SVC(training, kernel, c); // テストデータの読み込み(面倒だったのでトレーニングデータをそのまま使ってる) var test = libsvm.ProblemHelper.ReadProblem("fourclass"); for (int i = 0; i < test.l; i++) { // 予測値 var z = svm.Predict(test.x[i]); // 入力ファイルについているラベル var y = test.y[i]; // 予測値と正解値を出力 Console.WriteLine("{0}:{1} {2}", i, z, y); }
トレーニングデータとテストデータは同じものを使った.実行すると,予測値と正解値が出力される.
上記コードではパラメータ選択とかまでは行っていないが,結構簡単に使えそう.他にも回帰 (ϵ-SVR)や外れ値検出 (One-Class SVM) が出来るようである.
C# LINQでFIRフィルタ
LINQを使った,FIRフィルタ演算を行う関数.
ソースコード
/// <summary> /// FIRフィルタをかけたデータを返す /// </summary> /// <param name="data">入力データ</param> /// <param name="coefficients">フィルタ係数</param> /// <returns>FIRフィルタをかけたデータ</returns> List<double> FiniteImpulseResponseFilter( List<double> data, List<double> coefficients) { return Enumerable.Range(0, data.Count - coefficients.Count) .Select(i => data.Skip(i).Take(coefficients.Count) .Zip(coefficients, (d, c) => d * c).Sum()) .ToList(); }
C# LINQで移動平均 - whoopsidaisies's diary
をベースにした.
.Zip(coefficients, (d, c) => d * c).Sum()
の部分で入力データとフィルタ係数の積和演算を行っている.
C# LINQで移動平均
LINQを使った,移動平均を計算する関数.
ソースコード
/// <summary> /// 移動平均を格納した配列を返す /// </summary> /// <param name="data">入力データ</param> /// <param name="num">何個分の平均をとるか</param> /// <returns>移動平均をとったデータ</returns> List<double> MovingAverage( List<double> data, int num) { return Enumerable.Range(0, data.Count - num) .Select(i => data.Skip(i).Take(num).Average()) .ToList(); }
LINQの練習用なので効率が良いかとかは知らない.
Enumerable.Range(0, data.Count - num)
で,0~総データ数-平均を求めるときのデータの個数までの整数のシーケンスを生成する.
data.Skip(i).Take(num).Average()
で,入力データのi番目のデータからnum個データを抽出して平均をとる.
その他
FIRフィルタは以下の記事.
C# LINQでFIRフィルタ - whoopsidaisies's diary