ITK/VTK实现聚类

本文介绍如何使用ITK进行K均值聚类并通过VTK进行可视化。

头文件引入

ITK用于进行聚类分析,VTK用于可视化。itkVector用于定义点的坐标向量,itkListSample用来存储数据样本。vtkActor和vtkPolyDataMapper等类用于渲染和展示聚类后的点云。

#include <itkDecisionRule.h>
#include <itkVector.h>
#include <itkListSample.h>
#include <itkKdTree.h>
#include <itkWeightedCentroidKdTreeGenerator.h>
#include <itkKdTreeBasedKmeansEstimator.h>
#include <itkMinimumDecisionRule.h>
#include <itkEuclideanDistanceMetric.h>
#include <itkDistanceToCentroidMembershipFunction.h>
#include <itkSampleClassifierFilter.h>
#include <itkNormalVariateGenerator.h>
#include <vtkVersion.h>
#include <vtkActor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkSmartPointer.h>
#include <vtkVertexGlyphFilter.h>

生成样本点集

使用ITK的NormalVariateGenerator生成两个正态分布的点集。每个点是一个三维向量,两个点集的均值分别为100和200,标准差为30。

typedef itk::Vector< double, 3 > MeasurementVectorType;  // 定义三维向量
typedef itk::Statistics::ListSample< MeasurementVectorType > SampleType;  // 定义点集样本
SampleType::Pointer sample = SampleType::New();  // 创建样本实例

typedef itk::Statistics::NormalVariateGenerator NormalGeneratorType;
NormalGeneratorType::Pointer normalGenerator = NormalGeneratorType::New();

normalGenerator->Initialize(101);  // 初始化随机生成器

MeasurementVectorType mv;
double mean = 100;
double standardDeviation = 30;

// 生成第一个分布的点集
for (unsigned int i = 0; i < 100; ++i)
{
    mv[0] = (normalGenerator->GetVariate() * standardDeviation) + mean;
    mv[1] = (normalGenerator->GetVariate() * standardDeviation) + mean;
    mv[2] = (normalGenerator->GetVariate() * standardDeviation) + mean;
    sample->PushBack(mv);  // 将生成的点加入样本
}

// 生成第二个分布的点集
normalGenerator->Initialize(3024);
mean = 200;
standardDeviation = 30;
for (unsigned int i = 0; i < 100; ++i)
{
    mv[0] = (normalGenerator->GetVariate() * standardDeviation) + mean;
    mv[1] = (normalGenerator->GetVariate() * standardDeviation) + mean;
    mv[2] = (normalGenerator->GetVariate() * standardDeviation) + mean;
    sample->PushBack(mv);
}

Kd树生成与K均值聚类

使用ITK的Kd树来加速聚类计算。首先,使用WeightedCentroidKdTreeGenerator生成Kd树,接着用KdTreeBasedKmeansEstimator进行K均值聚类。

typedef itk::Statistics::WeightedCentroidKdTreeGenerator< SampleType > TreeGeneratorType;
TreeGeneratorType::Pointer treeGenerator = TreeGeneratorType::New();

treeGenerator->SetSample(sample);  // 设置样本
treeGenerator->SetBucketSize(16);  // 设置桶大小,控制Kd树的深度
treeGenerator->Update();  // 生成Kd树

typedef itk::Statistics::KdTreeBasedKmeansEstimator<TreeGeneratorType::KdTreeType> EstimatorType;
EstimatorType::Pointer estimator = EstimatorType::New();

// 初始聚类中心的参数设置
EstimatorType::ParametersType initialMeans(6);
initialMeans[0] = 0.0; initialMeans[1] = 0.0; initialMeans[2] = 0.0;  // 第一类中心
initialMeans[3] = 5.0; initialMeans[4] = 5.0; initialMeans[5] = 5.0;  // 第二类中心

estimator->SetParameters(initialMeans);  // 设置初始中心
estimator->SetKdTree(treeGenerator->GetOutput());
estimator->SetMaximumIteration(200);  // 设置最大迭代次数
estimator->SetCentroidPositionChangesThreshold(0.0);  // 设置聚类终止条件
estimator->StartOptimization();  // 开始K均值聚类

EstimatorType::ParametersType estimatedMeans = estimator->GetParameters();  // 获取聚类后的中心点

分类与决策规则

使用分类器对点集进行分类,通过SampleClassifierFilter和MinimumDecisionRule将样本根据聚类结果分为两类。

typedef itk::Statistics::DistanceToCentroidMembershipFunction< MeasurementVectorType > MembershipFunctionType;
typedef itk::Statistics::MinimumDecisionRule DecisionRuleType;

DecisionRuleType::Pointer decisionRule = DecisionRuleType::New();
typedef itk::Statistics::SampleClassifierFilter< SampleType > ClassifierType;
ClassifierType::Pointer classifier = ClassifierType::New();

classifier->SetDecisionRule(decisionRule);
classifier->SetInput(sample);  // 输入样本
classifier->SetNumberOfClasses(2);  // 设置分类数量
classifier->Update();  // 执行分类操作

VTK可视化

vtkSmartPointer<vtkPoints> points1 = vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkPoints> points2 = vtkSmartPointer<vtkPoints>::New();

// 根据分类结果将点分配到两个点集中
auto iter = classifier->GetOutput()->Begin();
while (iter != classifier->GetOutput()->End())
{
    if (iter.GetClassLabel() == 100) {
        points1->InsertNextPoint(iter.GetMeasurementVector()[0],
                                 iter.GetMeasurementVector()[1],
                                 iter.GetMeasurementVector()[2]);
    } else {
        points2->InsertNextPoint(iter.GetMeasurementVector()[0],
                                 iter.GetMeasurementVector()[1],
                                 iter.GetMeasurementVector()[2]);
    }
    ++iter;
}

// 渲染分类后的点集
vtkSmartPointer<vtkPolyData> polyData1 = vtkSmartPointer<vtkPolyData>::New();
polyData1->SetPoints(points1);
vtkSmartPointer<vtkPolyDataMapper> mapper1 = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper1->SetInputConnection(glyphFilter1->GetOutputPort());
vtkSmartPointer<vtkActor> actor1 = vtkSmartPointer<vtkActor>::New();
actor1->GetProperty()->SetColor(0, 1, 0);  // 绿色点表示第一类
actor1->GetProperty()->SetPointSize(3);

// 同样设置第二类的点集和渲染属性
vtkSmartPointer<vtkPolyData> polyData2 = vtkSmartPointer<vtkPolyData>::New();
polyData2->SetPoints(points2);
vtkSmartPointer<vtkPolyDataMapper> mapper2 = vtkSmartPointer<vtkPolyDataMapper>::New();
vtkSmartPointer<vtkActor> actor2 = vtkSmartPointer<vtkActor>::New();
actor2->GetProperty()->SetColor(1, 0, 0);  // 红色点表示第二类
actor2->GetProperty()->SetPointSize(3);

// 创建渲染窗口并展示
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderWindow->AddRenderer(renderer);
renderer->AddActor(actor1);  // 添加第一类的点集
renderer->AddActor(actor2);  // 添加第二类的点集
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindowInteractor->SetRenderWindow(renderWindow);
renderWindowInteractor->Start();  // 开始渲染交互

渲染效果

相关推荐
xiaoshiguang33 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡3 小时前
【C语言】判断回文
c语言·学习·算法
别NULL3 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇3 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
yuanbenshidiaos4 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习4 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA4 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo5 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc5 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
游是水里的游6 小时前
【算法day20】回溯:子集与全排列问题
算法