ITKを使った肺のセグメンテーション(3)

投稿者: | 2021年2月26日

前回(第二回)はK-means法を使って「人体組織」と「人体組織」の領域を分ける方法について説明しました。第一回のおさらいになりますが、以下の方法で肺のセグメンテーションを行います。

(1)胸部CT画像入力
(2)K-means法で人体組織とそうでない部分を分ける
(3)人体組織でない部分(外側)をリージョングローイング法で抽出する
(4)肺野内の空気領域を得る
(5)クロージングで肺内部の空洞を埋める
(6)セグメンテーション完了!

前回までに(2)を説明したので、今回は「(3)人体組織でない部分(外側)をリージョングローイング法で抽出する」方法について説明していきます。ようやくITKの登場です!

(3)では、前回のK-means法により検出した「人体組織ではない部分」のうち、体の外側にある領域を検出し、削除することが目的です。これによって(4)肺野内の空気領域を得ることができます。

図1 リージョングローイングの目的

リージョングローイングは自分で実装することもできますが、ITKにやってもらいます。ITKを用いることで、研究時間を節約できるバグが少ない正確に領域を検出できるなどのメリットがあります。

では実際に説明していきましょう!

目次

リージョングローイング

リージョングローイングとは、シードと呼ばれる起点から「ある条件」をクリアした周辺の領域を同じ領域として拡張させる方法です。ある条件とは、濃度値、平均値、分散、色やテクスチャなどですね。

図2 リージョングローイングのイメージ

一般的なリージョングローイングのアルゴリズムの一例です。この処理を実装すれば領域は拡がっていきます。今回はITKを用いて実装したいと思います。

図3 リージョングローイングのアルゴリズム例
ITKによる実装

それでは、ここからITKを使用して実際にリージョングローイングを実装していきます。C++で実装します。

ITKは基本的にクラステンプレート、つまりプレースホルダの集まりなので、クラステンプレートの変数型を決定し、実体化します。リージョングローイングのフィルタとしてConnectedThresholdImageFilterクラスを使用します。まず3次元short型の画像データを扱うので、typedef itk::Image< short, 3 > ImageTypeと宣言することでデータ型を定義します。入力および出力データ型もImageTypeとして扱いたいので、typedef itk::ConnectedThresholdImageFilter< ImageType, ImageType > ConnectedFilterType; と宣言してリージョングローイングクラスを作成します。

// データ型の設定
typedef itk::Image< short, 3 > ImageType;
// リージョングローイングフィルター
// 入力データ型:ImageType, 出力データ型:ImageType
typedef itk::ConnectedThresholdImageFilter< ImageType, ImageType > ConnectedFilterType;
ConnectedFilterType::Pointer connectedThresholdFilter = ConnectedFilterType::New();

画像はVTKにおいてvtkImageDataクラスのオブジェクトとして取り扱っているので、これをITKにおける画像データクラスオブジェクトに変換します。

// ITKへの入力
ImageImportType::Pointer itkImporter;
// VTKからの出力
vtkImageExport *vtkExporter = vtkImageExport::New();
// VTKへの入力
vtkImageImport *vtkImporter = vtkImageImport::New();
// VTKからITKへの変換
vtkExporter->SetInput( input );
ConnectPipelines( vtkExporter, itkImporter );

いよいよ、リージョンクローイングのフィルタを用います。一番重要です。ここで上限値、下限値とも1を指定しています。なぜなら、リージョングローイングで検出したい領域の値が1であるためです。図3においてのtaとなります。隣接する画素の値がこの条件を満たすならば同領域とします。また、画像の左下をシードとします(図4参照)。左下の座標は(0,0,0)と定義されています。

// 画像データをフィルターに入力する
connectedThresholdFilter->SetInput( itkImporter->GetOutput() );
// 上限値と下限値を指定する
connectedThresholdFilter->SetUpper( 1 );
connectedThresholdFilter->SetLower( 1 );
// 領域内の画素値を指定する
connectedThresholdFilter->SetReplaceValue( 1 );
// シードの座標を指定する
ImageType::IndexType index_seed;
index_seed[0] = 0;
index_seed[1] = 0;
index_seed[2] = 0;
connectedThresholdFilter->SetSeed( index_seed );
// リージョングローイングを実行する
connectedThresholdFilter->Update();
図4 シード

以上でリージョングローイングの処理は終了です。自分で実装するより遥かに簡単です。VTKによって画像表示するならば、以下の処理によりVTKデータに戻す必要があります。

// ITKからの出力
ImageExportType::Pointer itkExporter;
// データ出力
itkExporter->SetInput( connectedThresholdFilter->GetOutput() );
ConnectPipelines( itkExporter, vtkImporter );
vtkImageData* img = vtkImporter->GetOutput()

以下は出力結果です。図1の斜線部分が検出されていますね。(3)人体組織でない部分(外側)をリージョングローイング法で抽出することができました。

図5 リージョングローイングの出力結果

K-means法で検出した領域から、リージョングローイングで検出した領域を取り除けば、(4)肺野内の空気領域を得ることができます。

図6 肺野内の空気領域
まとめ

いかがでしたでしょうか。今回はITKを使ってリージョングローイング処理を実装し、人体ではない部分を取り除きました。今回のポイントは、以下となります。

・ITKは入出力データ型を自分で宣言することで自由に設定できる。

・ITKでの画像処理は「フィルター(画像処理クラス)に画像を通す」という概念で行う。

・フィルターの条件を設定し、Update()を呼び出せば簡単に処理できる。

ITKはデータ型を宣言しなければならないので一見複雑に見えますが、このフィルターの概念によってすっきりと処理の流れを認識できます。

今回の出力結果は、図6のように肺野内の白い部分(空気領域)となっており、肺野内の黒い部分が検出されていません。肺野内の黒い部分は血管などで、これらも検出する必要があります。ではどうやって検出するのか。それが「(5)クロージングで肺内部の空洞を埋める」の処理です。次回は(5)について説明します。

バックナンバー:
第一回
第二回

ページの一番上に戻る

コメントを残す

メールアドレスが公開されることはありません。