前回(第二回)はK-means法を使って「人体組織」と「人体組織」の領域を分ける方法について説明しました。第一回のおさらいになりますが、以下の方法で肺のセグメンテーションを行います。
(1)胸部CT画像入力
(2)K-means法で人体組織とそうでない部分を分ける
(3)人体組織でない部分(外側)をリージョングローイング法で抽出する
(4)肺野内の空気領域を得る
(5)クロージングで肺内部の空洞を埋める
(6)セグメンテーション完了!
前回までに(2)を説明したので、今回は「(3)人体組織でない部分(外側)をリージョングローイング法で抽出する」方法について説明していきます。ようやくITKの登場です!
(3)では、前回のK-means法により検出した「人体組織ではない部分」のうち、体の外側にある領域を検出し、削除することが目的です。これによって(4)肺野内の空気領域を得ることができます。
リージョングローイングは自分で実装することもできますが、ITKにやってもらいます。ITKを用いることで、研究時間を節約できる、バグが少ない、正確に領域を検出できるなどのメリットがあります。
では実際に説明していきましょう!
目次
リージョングローイングとは、シードと呼ばれる起点から「ある条件」をクリアした周辺の領域を同じ領域として拡張させる方法です。ある条件とは、濃度値、平均値、分散、色やテクスチャなどですね。
一般的なリージョングローイングのアルゴリズムの一例です。この処理を実装すれば領域は拡がっていきます。今回は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();
以上でリージョングローイングの処理は終了です。自分で実装するより遥かに簡単です。VTKによって画像表示するならば、以下の処理によりVTKデータに戻す必要があります。
// ITKからの出力 ImageExportType::Pointer itkExporter; // データ出力 itkExporter->SetInput( connectedThresholdFilter->GetOutput() ); ConnectPipelines( itkExporter, vtkImporter ); vtkImageData* img = vtkImporter->GetOutput()
以下は出力結果です。図1の斜線部分が検出されていますね。(3)人体組織でない部分(外側)をリージョングローイング法で抽出することができました。
K-means法で検出した領域から、リージョングローイングで検出した領域を取り除けば、(4)肺野内の空気領域を得ることができます。
いかがでしたでしょうか。今回はITKを使ってリージョングローイング処理を実装し、人体ではない部分を取り除きました。今回のポイントは、以下となります。
・ITKは入出力データ型を自分で宣言することで自由に設定できる。
・ITKでの画像処理は「フィルター(画像処理クラス)に画像を通す」という概念で行う。
・フィルターの条件を設定し、Update()を呼び出せば簡単に処理できる。
ITKはデータ型を宣言しなければならないので一見複雑に見えますが、このフィルターの概念によってすっきりと処理の流れを認識できます。
今回の出力結果は、図6のように肺野内の白い部分(空気領域)となっており、肺野内の黒い部分が検出されていません。肺野内の黒い部分は血管などで、これらも検出する必要があります。ではどうやって検出するのか。それが「(5)クロージングで肺内部の空洞を埋める」の処理です。次回は(5)について説明します。