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のサンプルデータがあったので表示させてみた結果を以下に示す.プログラム上では,マウスドラッグにより視点の変更が可能である.

f:id:whoopsidaisies:20131230234937p:plain

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が無難かも(自分の環境だけ?他の環境では確認していないので誰か詳しい方教えてくれると助かります).

f:id:whoopsidaisies:20131228121622p:plain

RenderWindowControlの追加

3D表示用のコントロールが用意されているので追加する.

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

 

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

f:id:whoopsidaisies:20131228121729p:plain

サンプルコード

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を使用)
描画ウィンドウ上で左クリックや右クリックしてドラッグすると,視点の変化を行える.

f:id:whoopsidaisies:20131228123539p:plain

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のグローバルサイトのデータを使用)

f:id:whoopsidaisies:20131228130248p:plain

その他

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ファイルにしたり,大きさや色を変えている.)

f:id:whoopsidaisies:20131227143539j:plain

あとは,衣装ちゃんとしたいのと踊らせたい.

開発中の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の書き方をほぼそのまま使える.実行すると下の画像ような感じで,かしゆか,あーちゃん,のっちが表示される.

f:id:whoopsidaisies:20131220225614p:plain

視点の移動

視点を移動したり,ウィジェットを移動・回転したりできるので,適当に視点移動をさせてみた.上のコードの「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);

}

実行すると,下の動画のようにウィンドウ内の表示が動く.


OpenCV 3.0.0 devでPerfumeの3次元データ表示 - YouTube

C# ComboBoxにenumの値を表示してコントロールのプロパティにバインドする

例として,PictureBoxSizeMode列挙体のメンバをコンボボックスに表示し,選択された値がピクチャボックスのSizeModeプロパティに設定されるようにする.

comboBox1.DataSource = Enum.GetValues(typeof(PictureBoxSizeMode));
comboBox1.DataBindings.Add(new Binding("SelectedItem", pictureBox1, "SizeMode"));

これで,コンボボックスの選択によってピクチャボックスの画像表示方法を変更できるようになる.

C# アンチエイリアシングしないPicturebox

画像がぼやける

下の画像みたいに,10x10画素の画像を拡大してPictureBoxコントロールに標示させたい.
f:id:whoopsidaisies:20131220063327p:plain
そのまま表示させようとすると,以下みたいにアンチエイリアシングされてぼやけてしまう.
f:id:whoopsidaisies:20131220063351p:plain

画像を縮小するとピクセルにグラデーション?がかかる」にあるように一度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版の記事は下記リンク。使えるアルゴリズムが増えている。

whoopsidaisies.hatenablog.com


以降は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);
	}
}