最近做实验需要对二维图像进行连通区域分析,在翻阅资料后发现ITk提供了相应的功能。十分好用。在这里总结一下。
实现连通区域分析细分为两个步骤 1、寻找连通区域 2、连通区域分析。
ITK中使用下面的类寻找标记连通区域
#include "itkConnectedComponentImageFilter.h"
该类输出一个对各连通区域标记过的图像,称之为LabelImage.
连通区域分析使用到的类:
#include "itkLabelImageToShapeLabelMapFilter.h"
看名字就大概知道意思这是一个映射:把LabelImage映射到ShapeLabelImage.它的输出类型为
itk::LabelMap< ShapeLabelObjectType >
ITK文档里找到一副图很好解释LabelMap:
ShapeLabelObject这个类很厉害,ITK中的描述A Label object to store the common attributes related to the shape of the object.
提供了很多形状相关的属性描述,例如面积 周长 质心 等效圆半径等等。下面给出一个ITK WiKI提供的例子:
连通区域分析
由于ITK没有提供可视化功能,我们可以使用VTK将寻找到的连通区域画出来
ShapeLabelObject提供了一个函数接口GetBoundingBox, 可以获取连通区域的包围盒。该函数的返回类型是
InputImageType::RegionType region
结合VTK中的vtkPolyData类画出一个包围盒
主要代码如下:
CreateBoundingBox(InputImageType::RegionType region)
{
InputImageType::IndexType start=region.GetIndex();
InputImageType::SizeType size=region.GetSize();
vtkSmartPointer points=
vtkSmartPointer::New();
points->InsertNextPoint(start[0],start[1],0.0);//points of left bottom
points->InsertNextPoint(start[0]+size[0],start[1],0.0);//points of right bottom
points->InsertNextPoint(start[0]+size[0],start[1]+size[1],0.0);//points of right up
points->InsertNextPoint(start[0],start[1]+size[1],0.0);//points of left up
vtkSmartPointer Bottomline=
vtkSmartPointer::New(); //Bottom
Bottomline->GetPointIds()->SetId(0,0);
Bottomline->GetPointIds()->SetId(1,1);
vtkSmartPointer Rightline=
vtkSmartPointer::New(); //Right
Rightline->GetPointIds()->SetId(0,1);
Rightline->GetPointIds()->SetId(1,2);
vtkSmartPointer Upline=
vtkSmartPointer::New(); //Up
Upline->GetPointIds()->SetId(0,2);
Upline->GetPointIds()->SetId(1,3);
vtkSmartPointer Leftline=
vtkSmartPointer::New(); //Left
Leftline->GetPointIds()->SetId(0,3);
Leftline->GetPointIds()->SetId(1,0);
vtkSmartPointer Maindiagonal=
vtkSmartPointer::New(); //主对角线
Maindiagonal->GetPointIds()->SetId(0,3);
Maindiagonal->GetPointIds()->SetId(1,1);
vtkSmartPointer Deputydiagonal=
vtkSmartPointer::New(); //副对角线
Deputydiagonal->GetPointIds()->SetId(0,2);
Deputydiagonal->GetPointIds()->SetId(1,0);
//draw bounding box
vtkSmartPointer Lines=
vtkSmartPointer::New();
Lines->InsertNextCell(Bottomline);
Lines->InsertNextCell(Rightline);
Lines->InsertNextCell(Upline);
Lines->InsertNextCell(Leftline);
Lines->InsertNextCell(Maindiagonal);
Lines->InsertNextCell(Deputydiagonal);
polyData->SetPoints(points);
polyData->SetLines(Lines);
vtkSmartPointer mapper=
vtkSmartPointer::New();
mapper->SetInputData(polyData);
actor->SetMapper(mapper);
actor->PickableOff();
actor->SetVisibility(false);
actor->GetProperty()->SetColor(1.0,0.0,0.0);
actor->GetProperty()->SetLineWidth(2);
}
结果:
补充:
在获取连通区域的包围盒的时候,其返回类型为InputImageType::RegionType region 被称为图像区域类
ITK文档中这样描述这个region:
A region is defined by two classes: the itk::Indexand itk::Size classes. The origin of the region within the image is defined by theIndex. The extent, or size, of the region is defined by the Size. When an image is created manually, the user is responsible for defining the image size and the index at which the image grid starts. These two parameters make it possible to process selected regions.
The Index is represented by a n-dimensional array where each component is an integer indicating—in topological image coordinates—the initial pixel of the image.
The region size is represented by an array of the same dimension as the image (using theitk::Size class). The components of the array are unsigned integers indicating the extent in pixels of the image along every dimension.
index:表示图像区域起始位置
size:表示图像区域的大小
但没有留意到他们的单位是什么。原文中说:Index——the initial pixel of the image 图像的初始像素。size——the extent in pixels of the image along every dimension 每个维度上像素个数的扩展。
因为不考虑像素间隔(spacing)这样一来其反映的不是实际的物理尺寸size,而只是表示当前维度下像素的个数。
在实际操作时发现,当spacing小于1的时候获得的包围盒位置都向左上角偏移。如图:
spacing=0.3134108
所以在实际计算包围盒的start和size参数时候要乘以一个spacing:
InputImageType::IndexType start1=region.GetIndex();
InputImageType::SizeType size1=region.GetSize();
double start[2],size[2];
for(int i=0;i<2;i++)
{
start[i]=start1[i]*spacing[i];
size[i]=size1[i]*spacing[i];
}
补充 2016-12-5
ShapeLabelObject这个类中有一个成员函数 GetPrincipalAxes();返回值类型是一个变换矩阵(Matrix
仿射变换(Affine Transformation或 Affine Map),又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间的过程。它保持了二维图形的“平直性”(即:直线经过变换之后依然是直线)和“平行性”(即:二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。
一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)接着再加上一个向量(平移)的形式。
那么, 我们能够用仿射变换来表示如下三种常见的变换形式:
如果进行更深层次的理解,仿射变换代表的是两幅图之间的一种映射关系。
而我们通常使用2 x 3的矩阵来表示仿射变换。
考虑到我们要使用矩阵 A 和 B 对二维向量 做变换, 所以也能表示为下列形式:
或者
即:
回归正题, 对于一副二维图像,GetPrincipalAxes()返回值是Matrix
/*!
* \brief ConnectedRegion::GetShortAxesPoints
* 获取等效椭圆长轴的两个端点坐标
* itk中短轴方向与连通区域坐标系(主轴坐标)的x轴平行
* \param i 连通区域索引号
* \param pt QPointF数组 保存了短轴的两个端点坐标
*/
void ConnectedRegion::GetShortAxesPoints(const int i, QPointF pt[]) const
{
//主轴坐标系==》物理坐标系的变换矩阵
const ShapeLabelObjectType::MatrixType matrix=GetPrincipalAxes(i);
//短轴的一半长度
const double halfLenngth=GetEquivalentEllipsoidDiameter(i)[0]/2.0;
//连通区域质心
const ShapeLabelObjectType::CentroidType center=GetCentroid(i);
pt[0].setX(center[0]-halfLenngth*matrix[0][0]);
pt[0].setY(center[1]-halfLenngth*matrix[0][1]);
pt[1].setX(center[0]+halfLenngth*matrix[0][0]);
pt[1].setY(center[1]+halfLenngth*matrix[0][1]);
}