C#でDICOMファイルからボリュームレンダリング ActiViz(VTK)による方法
C#でActiVizを使って,DICOM形式画像の連番ファイルを読み込みボリュームレンダリングにより3D画像を生成・表示する.
ActiViz
ActiVizは,3Dコンピュータグラフィックスの可視化ライブラリVTK(The Visualization ToolKit)のC#ラッパである.NuGetからインストールできる.簡単な解説は以下のページを参照.
C#で3Dモデル表示(VTKのC#ラッパActiVizによる方法) - whoopsidaisies's diary
ボリュームレンダリング
ActiVizには,DICOM画像を読み込むクラスもボリュームレンダリングを行うクラスも用意されているので,簡単にDICOM画像からの3D画像の表示を行える.以下にサンプルコードを示す.
using (var dicomReader = new vtkDICOMImageReader()) using (var mapper = new vtkFixedPointVolumeRayCastMapper()) using (var opacity = new vtkPiecewiseFunction()) using (var property = new vtkVolumeProperty()) using (var volume = new vtkVolume()) { // DICOM画像が入っているフォルダを指定 dicomReader.SetDirectoryName(@"DICOM"); dicomReader.Update(); // MapperにDICOM画像を読み込んで3Dボリュームへ登録 mapper.SetInputConnection(dicomReader.GetOutputPort()); volume.SetMapper(mapper); // 不透明度と補間アルゴリズムの設定 opacity.AddSegment(0, 0, 3900, 1); property.SetScalarOpacity(opacity); property.SetInterpolationTypeToLinear(); volume.SetProperty(property); // 表示用ウィンドウへの3Dボリュームの登録 renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddVolume(volume); }
VTKのWIKIページにDICOMのサンプルデータがあったので表示させてみた結果を以下に示す.プログラム上では,マウスドラッグにより視点の変更が可能である.
C#で3Dモデル表示(VTKのC#ラッパActiVizによる方法)
ActiViz
C#で3Dモデル表示する方法はたくさんあると思うが,今回は,3Dコンピュータグラフィックスの可視化ライブラリであるVTK(The Visualization ToolKit)の,C#ラッパ「ActiViz」を使った方法について書く.
ActiVizおよびVTKの良さそうな点を以下にざっとあげる.
- オープンソース
- NuGet経由で簡単インストール
- C#のWindows フォーム アプリケーション用のコントロールが用意されている
- 表示するだけなら結構短いコードで書ける
- VTKは次期OpenCVとかPCL(Point Cloud Library)とかでも使われているので,書き方を覚えておくと便利?
という感じで,手軽かつそれなりに汎用性がありそう.
インストール
NuGet経由でインストール可能.プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「ActiViz」を検索.x86とx64があるが,x64だとコントロール追加できなかったりするので,x86が無難かも(自分の環境だけ?他の環境では確認していないので誰か詳しい方教えてくれると助かります).
RenderWindowControlの追加
3D表示用のコントロールが用意されているので追加する.
ツールボックスの適当なところで右クリックして「アイテムの選択(I)...」を選択.「.NET Framework コンポーネント」タブの「参照(B)...」ボタンを押して,「(ソリューションフォルダ)\packages\Activiz.NET.x86.5.8.0\lib\net20\Kitware.VTK.dll」を選択したらOK.
すると,ツールボックスに「RenderWindowControl」ができるので,Formに配置する.
サンプルコード
3Dモデルファイルの読み込み
以下にPLYファイルを読み込んで表示するサンプルを示す.MapperとかActorとか出てくるが,そこら辺の説明はこことかを参照.
using (var reader = new Kitware.VTK.vtkPLYReader()) using (var mapper = new Kitware.VTK.vtkCompositePolyDataMapper()) using (var actor = new Kitware.VTK.vtkActor()) { // 表示させたい3Dモデルファイル reader.SetFileName(@"bun_zipper.ply"); // Mapperにオブジェクトを写像する mapper.SetInputConnection(reader.GetOutputPort()); // ActorにMapperをセットする actor.SetMapper(mapper); // 描画ウィンドウにActorを追加する renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddActor(actor); }
上記コードを,フォームロードイベントとかに書いておくと,以下のように3Dモデルが表示される.(plyファイルはStanford Bunnyを使用)
描画ウィンドウ上で左クリックや右クリックしてドラッグすると,視点の変化を行える.
ply以外のファイル
上記コードの
using (var reader = new Kitware.VTK.vtkPLYReader())
のvtkPLYReaderの部分を変えてやると,ply以外の3Dモデルファイルも読み込める.以下に,その一例を示す.
- vtkOBJReader … wavefrontのobjファイル
- vtkSimplePointsReader … スペース区切りの3D点群データ
- vtkParticleReader … カンマ区切りの3D点群データ
他にも様々な形式に対応している.
複数モデルの表示
複数のモデルファイルを読み込んで表示するサンプルを示す.以下のようにモデルの数だけActorを作ってやるとよい.
private void ShowMultipleModels() { // objファイルからモデルを読み込んでActorを生成する var actor1 = ActorFromObjFile("model1.obj"); var actor2 = ActorFromObjFile("model2.obj"); var actor3 = ActorFromObjFile("model3.obj"); // 描画ウィンドウにActorを追加する renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddActor(actor1); renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddActor(actor2); renderWindowControl1.RenderWindow.GetRenderers().GetFirstRenderer().AddActor(actor3); } private static Kitware.VTK.vtkActor ActorFromObjFile(string filename) { var actor = new Kitware.VTK.vtkActor(); using (var reader = new Kitware.VTK.vtkOBJReader()) using (var mapper = new Kitware.VTK.vtkCompositePolyDataMapper()) { reader.SetFileName(filename); mapper.SetInputConnection(reader.GetOutputPort()); actor.SetMapper(mapper); } return actor; }
ShowMultipleModelsを呼び出すと,以下のように3Dモデルが3つ表示される.(objファイルはPerfumeのグローバルサイトのデータを使用)
その他
VTK/Examples - KitwarePublic
にサンプルコードが豊富に用意されている.
Androidで手乗りPerfume
AndroidでARを使って3DのPerfumeを手のひらに乗せてみる.
AndAR Model Viewer
AndAR Model Viewerというオープンソースで無料のAndroidアプリを使う.AndAR Model Viewerを使うと,Androidのカメラで撮影している映像のARマーカの上にリアルタイムで3Dモデルを投影出来る.3Dモデルはアプリ内で数種類のものが選択可能だが,自分で用意したobj形式の3Dモデルを表示することもできる.
手乗りPerfume
PerfumeのGlobalサイトから,Perfumeメンバーの3Dスキャンデータがobj形式でダウンロードできるのでそれを使う.
ARマーカを印刷してAndAR Model Viewerを起動すると,以下の画像のようにAndroidの画面上でPerfumeが手のひらに乗る.(無料の3Dモデリングソフトblenderを使って3人で一つのobjファイルにしたり,大きさや色を変えている.)
あとは,衣装ちゃんとしたいのと踊らせたい.
開発中のOpenCV 3.0でPerfumeの3Dデータを表示する
OpenCV 3.0.0-dev Vizモジュール
OpenCV 3.0の開発ブランチを見ていたら,Vizという3D表示用のモジュールがあったので使ってみる.
3D表示には,Point Cloud Libraryとかでも使われているVTKというオープンソースのライブラリが使われている.
環境
Windows 7
Visual Studio Professinal 2012
OpenCV ハッシュ値:59cab94fc7a43ad3a39414232fd34d23815b8547
VTKのバージョン:5.10.1(6.0.0だとうまく出来なかった.出来た人教えてください)
Perfumeの3Dデータ表示
Perfume official global websiteで公開されている,Perfumeの3次元スキャンデータを表示するプログラムを作った.ファイルフォーマットはOBJファイルのものを選択.
#include <opencv2/viz.hpp> #include <opencv2/viz/widget_accessor.hpp> #include <vtkPolyDataMapper.h> #include <vtkActor.h> #include <vtkOBJReader.h> using namespace cv; using namespace std; // Objファイルから3Dデータを読み込み可能なウィジェット class WObj : public viz::Widget3D { public: WObj(const string& filename) { // OBJファイルを読み込むクラスの生成 vtkSmartPointer<vtkOBJReader> reader = vtkSmartPointer<vtkOBJReader>::New(); reader->SetFileName(filename.c_str()); reader->Update(); // mapperにOBJファイルからデータを取り込む vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(reader->GetOutputPort()); // actorの設定 vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); // ウィジェットにactorをセットする viz::WidgetAccessor::setProp(*this, actor); } }; int main() { // 表示用ウィンドウの生成 viz::Viz3d perfumeWindow("Creating Widgets"); // Perfumeのウィジェットを生成 string filename1 = "Perfume_Global_Site_Project_003\\model1.obj"; string filename2 = "Perfume_Global_Site_Project_003\\model2.obj"; string filename3 = "Perfume_Global_Site_Project_003\\model3.obj"; WObj objAchan(filename1), objNocchi(filename2), objKashiyuka(filename3); // Perfumeのウィジェットをウィンドウに表示 perfumeWindow.showWidget("Achan", objAchan); perfumeWindow.showWidget("Nocchi", objNocchi); perfumeWindow.showWidget("Kashiyuka", objKashiyuka); // 軸を表示 Affine3dcloud_pose = Affine3f().translate(Vec3f(-500.0f,-400.0f,-60.0f)); perfumeWindow.showWidget("Coordinate Widget", viz::WCoordinateSystem(200), cloud_pose); // 表示のスタート perfumeWindow.spin(); }
VTKの書き方をほぼそのまま使える.実行すると下の画像ような感じで,かしゆか,あーちゃん,のっちが表示される.
視点の移動
視点を移動したり,ウィジェットを移動・回転したりできるので,適当に視点移動をさせてみた.上のコードの「perfumeWindow.spin();」を以下のコードに置き換える.
float angle = 0.0; while(!perfumeWindow.wasStopped()) { angle += CV_PI * 0.01f; // カメラの姿勢を生成 Point3f cam_pos(100+2000*cos(angle),500,3000*sin(angle)); Point3f cam_focal_point(3.0f,3.0f,2.0f) Point3f cam_y_dir(0.0f,1.0f,0.0f); Affine3f cam_pose = viz::makeCameraPose(cam_pos, cam_focal_point, cam_y_dir); // 視点をセット perfumeWindow.setViewerPose(cam_pose); perfumeWindow.spinOnce(1, true); }
実行すると,下の動画のようにウィンドウ内の表示が動く.
C# ComboBoxにenumの値を表示してコントロールのプロパティにバインドする
例として,PictureBoxSizeMode列挙体のメンバをコンボボックスに表示し,選択された値がピクチャボックスのSizeModeプロパティに設定されるようにする.
comboBox1.DataSource = Enum.GetValues(typeof(PictureBoxSizeMode)); comboBox1.DataBindings.Add(new Binding("SelectedItem", pictureBox1, "SizeMode"));
これで,コンボボックスの選択によってピクチャボックスの画像表示方法を変更できるようになる.
C# アンチエイリアシングしないPicturebox
画像がぼやける
下の画像みたいに,10x10画素の画像を拡大してPictureBoxコントロールに標示させたい.
そのまま表示させようとすると,以下みたいにアンチエイリアシングされてぼやけてしまう.
「画像を縮小するとピクセルにグラデーション?がかかる」にあるように一度Graphicsを作成してからInterpolationModeとPixelOffsetModeを指定すればアンチエイリアシングしないで表示はできる.
が,わざわざGraphicsを作成するというのも面倒なので,そうしなくてもアンチエイリアシング処理しないPictureBoxコントロールを作る.
InterpolationModeを指定可能なPictureBox
Graphicsで画像の拡大・回転時の画素の補間方法はInterpolationModeで指定可能だが,これをPictureBoxでも指定できるようにする.
以下のように,PictureBoxを継承したコントロールを作ってやる.InterpolationModeプロパティをNearestNeighborに指定すれば,アンチエイリアシングなしで画像が表示される.
using System; using System.Windows.Forms; using System.Drawing.Drawing2D; using System.ComponentModel; namespace Whoopsidaisies { class InterpolatedPictureBox : PictureBox { private InterpolationMode interpolation = InterpolationMode.Default; [DefaultValue(typeof(InterpolationMode), "NearestNeighbor"), Description("The interpolation used to render the image.")] public InterpolationMode Interpolation { get { return interpolation; } set { if (value == InterpolationMode.Invalid) throw new ArgumentException("\"Invalid\" is not a valid value."); // (Duh!) interpolation = value; Invalidate(); // Image should be redrawn when a different interpolation is selected } } protected override void OnPaint(PaintEventArgs pe) { pe.Graphics.InterpolationMode = interpolation; pe.Graphics.PixelOffsetMode = PixelOffsetMode.Half; base.OnPaint(pe); } } }
OpenCVで背景差分
※追記:OpenCV 3.4.1版の記事は下記リンク。使えるアルゴリズムが増えている。
以降はOpenCV 2.4.7のサンプル。
背景差分
固定カメラで移動物体の検出をするのに有効な手法.OpenCV2.4.7では混合正規分布(Mixture of Gaussian Distribution, MoG)によるアルゴリズム,Godbehere,Matsukawa,Goldbergによるアルゴリズム(GMG)が用意されているので試してみる.
サンプルコード
BackgroundSubtractorMOGクラス,BackgroundSubtractorMOG2クラスおよびBackgroundSubtractorGMGクラスで背景差分の計算ができる.
アルゴリズムによってパラメータ等が違うが,基本的な計算は共通の書き方で使用できる.以下にサンプルコードを示す.
#include <opencv2/opencv.hpp> void main() { cv::VideoCapture cap = cv::VideoCapture("test.avi"); // 背景差分計算用オブジェクトの生成 cv::BackgroundSubtractorGMG backGroundSubtractor; //cv::BackgroundSubtractorMOG backGroundSubtractor; //cv::BackgroundSubtractorMOG2 backGroundSubtractor; while (cv::waitKey(1) == -1) { cv::Mat frame, foreGroundMask, output; // 画像取得 cap >> frame; if (frame.empty()) break; // マスク画像の取得 backGroundSubtractor(frame, foreGroundMask); // 入力画像にマスク処理を行う cv::bitwise_and(frame, frame, output, foreGroundMask); cv::imshow("output", foreGroundMask); } }