对三维ITK数据的连通区域进行提取并分割染色

ITK三维连通区域提取

  • 对三维ITK数据的连通区域进行提取并分割染色
    • 使用场景
    • ITK连通区域提取步骤
    • 核心代码
    • 注意事项:

对三维ITK数据的连通区域进行提取并分割染色

使用场景

在进行图像处理时,当需要分割图像中的较大连通区域时,可以使用ITK的连通区域提取功能。比如从人体脑部CT提取与脑骨连接的骨头区域而去除周围噪点信息。

ITK连通区域提取步骤

  1. 读取三维图像
  2. 对图像进行二值化
  3. 连通区域提取: itk::ConnectedComponentImageFilter,该步骤完成后,相当于图像被分成多个小的连通区域,但各个连通区域没有可供区分的信息。
  4. 保留指定属性的指定个数的连通区域: itk::LabelShapeKeepNObjectsImageFilter,该filter只保留满足要求的连通区域,比如 按照像素个数排序,像素个数最大的三个区域。此时图像仍不能按照需求检索。
  5. 给图像打上标签,并对连通区域进行属性估算: itk::LabelImageToShapeLabelMapFilter,给每个连通区域贴上一个标签(下标),使用该下标唯一对应该连通区域。该filter还会评估该连通区域的属性,比如几何中心,物理尺寸,边界信息等。本步骤为查看选定的连通区域所做的操作,非必须步骤。
  6. 对保留下来的连通区域上色,itk::RescaleIntensityImageFilter, 第4步LabelShapeKeepNObjectsImageFilter保留下了目标连通区域的位置信息,本步为该区域染上颜色。注意本步骤染色会将不同的连通区域赋予不同的体素值,如果连通区域过多,可能导致部分图像不可见或不明显可见。
  7. 遍历染色后的图像,将非背景色的像素位置的体素值,替换为原图像对应位置的体素值,使图像保留目标连通区域位置信息的同时,恢复该位置的实际像素值。

核心代码

global.h

//global.h
#pragma once
#include 
#include 
#include 
#include "ImportLibITK5_0_1.h"
#include "itksys/SystemTools.hxx"

namespace dcmHandler
{
    typedef signed short    PixelType;
    typedef itk::Image< PixelType, 2U > DcmImage2DType;
    typedef itk::Image< PixelType, 3U > DcmImage3DType;
}

#define DCMHANDER_NAMESPACE_BEGIN namespace dcmHandler{
#define DCMHANDER_NAMESPACE_END }

分割线

//DcmExtractingConnectedRegions.h
#pragma once
#include "global.h"
#include "itkConnectedComponentImageFilter.h"
#include "itkLabelShapeKeepNObjectsImageFilter.h"
#include "itkRescaleIntensityImageFilter.h"
#include "itkLabelImageToShapeLabelMapFilter.h"
#include "itkBinaryThresholdImageFilter.h"

/**
 * Abstract connected region acoording to "NUMBER_OF_PIXELS"
 * of single region.
 */

DCMHANDER_NAMESPACE_BEGIN

class ConnectedRegionsAbstract
{
public:
    ConnectedRegionsAbstract();
    ~ConnectedRegionsAbstract();

private:
    using LabelType = unsigned short;
    using ShapeLabelObjectType = itk::ShapeLabelObject< LabelType, 3U >;
    using LabelMapType = itk::LabelMap< ShapeLabelObjectType >;
    using I2LType = 
        itk::LabelImageToShapeLabelMapFilter< DcmImage3DType, LabelMapType>;
    using RescaleFilterType = 
        itk::RescaleIntensityImageFilter<DcmImage3DType, DcmImage3DType>;
    using ConnectedComponentImageFilterType = 
        itk::ConnectedComponentImageFilter <DcmImage3DType, DcmImage3DType >;
    using LabelShapeKeepNObjectsImageFilterType = 
        itk::LabelShapeKeepNObjectsImageFilter< DcmImage3DType >;
    using IteratorType3D = itk::ImageRegionIterator<DcmImage3DType>;
    using FilterType = itk::BinaryThresholdImageFilter
        <dcmHandler::DcmImage3DType, dcmHandler::DcmImage3DType>;

    /** deep copy ITK image data*/ 
    template<typename TPixel, unsigned int Dim>
    void deepCopyItkImageData(const itk::Image<TPixel, Dim>* src,
        itk::Image<TPixel, Dim>* dst)
    {
        if (!src || !dst) {
            return;
        }
        dst->CopyInformation(src);
        dst->SetMetaDataDictionary(src->GetMetaDataDictionary());
        dst->SetRegions(src->GetLargestPossibleRegion());
        dst->Allocate();
        std::copy_n(src->GetBufferPointer(),
            src->GetPixelContainer()->Size(), dst->GetBufferPointer());
    }

    template<typename TPixel, unsigned int Dim>
    typename itk::Image<TPixel, Dim>::Pointer deepCopyItkImageData(
        const itk::Image<TPixel, Dim>* data)
    {
        using ImageT = itk::Image<TPixel, Dim>;
        typename ImageT::Pointer ret = ImageT::New();
        deepCopyItkImageData(data, ret.GetPointer());
        return ret;
    }

public:
    void setInputData(DcmImage3DType::Pointer inputData);
    void setBinaryLowerThresholdValue(
        const DcmImage3DType::PixelType& lowerValue );
    void setRegionsNumberTokeep(const unsigned int num);

    /** set the factory to multiply maximum size of single connected region
     *  to exclude the region which much smaller than maximum connected region size. 
     */
    void setFactoryToMaxSizeToKeep(const double factory = 0.0);

    /** set the munber of pixel size to delete,the region size smaller than 
     * this value will be dropped.
     */
    void setSizeToDelete(const unsigned long long pixelNum);

    bool updata();

    DcmImage3DType::Pointer getResult();

public:
    DcmImage3DType::Pointer m_dcmData;
    DcmImage3DType::PixelType m_binaryLowerThresholdValue;
    DcmImage3DType::Pointer m_resultData;
    unsigned int m_RegionNumberToKeep;
    double m_factoryToMaxSizeToKeep;
    unsigned long long m_numberOfMinSizeToDelete;
};

DCMHANDER_NAMESPACE_END

分割线 ================

//DcmExtractingConnectedRegions.cpp
#include "DcmExtractingConnectedRegions.h"
DCMHANDER_NAMESPACE_BEGIN

ConnectedRegionsAbstract::ConnectedRegionsAbstract()
{
}

ConnectedRegionsAbstract::~ConnectedRegionsAbstract()
{
}

void ConnectedRegionsAbstract::setInputData(DcmImage3DType::Pointer inputData)
{
    m_dcmData = inputData;
}
void ConnectedRegionsAbstract::setBinaryLowerThresholdValue(
    const DcmImage3DType::PixelType& lowerValue)
{
    m_binaryLowerThresholdValue = lowerValue;
}
void ConnectedRegionsAbstract::setRegionsNumberTokeep(const unsigned int num)
{
    m_RegionNumberToKeep = num;
}

void ConnectedRegionsAbstract::setFactoryToMaxSizeToKeep(const double factory)
{
    m_factoryToMaxSizeToKeep = factory;
}
void ConnectedRegionsAbstract::setSizeToDelete(const unsigned long long pixelNum)
{
    m_numberOfMinSizeToDelete = pixelNum;
}
bool ConnectedRegionsAbstract::updata()
{
    auto data = deepCopyItkImageData(m_dcmData.GetPointer());

    FilterType::Pointer BinaryFilter = FilterType::New();
    BinaryFilter->SetInput(data);
    BinaryFilter->SetOutsideValue(0);
    BinaryFilter->SetInsideValue(3000);
    BinaryFilter->SetUpperThreshold(4096);
    BinaryFilter->SetLowerThreshold(m_binaryLowerThresholdValue);

    ConnectedComponentImageFilterType::Pointer connected
        = ConnectedComponentImageFilterType::New();
    connected->SetInput(BinaryFilter->GetOutput());
    std::cout << "connected->GetObjectCount() " <<
        connected->GetObjectCount() << std::endl;

    LabelShapeKeepNObjectsImageFilterType::Pointer labelShapeKeepNObjectsImageFilter
        = LabelShapeKeepNObjectsImageFilterType::New();
    labelShapeKeepNObjectsImageFilter->SetInput(connected->GetOutput());
    labelShapeKeepNObjectsImageFilter->SetNumberOfObjects(m_RegionNumberToKeep);
    labelShapeKeepNObjectsImageFilter->SetAttribute(
        LabelShapeKeepNObjectsImageFilterType::LabelObjectType::NUMBER_OF_PIXELS);

    I2LType::Pointer i2l = I2LType::New();
    i2l->SetInput(labelShapeKeepNObjectsImageFilter->GetOutput());
    i2l->SetComputePerimeter(true);
    try
    {
        i2l->Update();
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }

    LabelMapType* labelMap = i2l->GetOutput();
    std::cout << " has " << labelMap->GetNumberOfLabelObjects() << " labels." << std::endl;

    for (unsigned int n = 0; n < labelMap->GetNumberOfLabelObjects(); ++n)
    {
        std::cout << "number " << n << ": " << std::endl<<
            " label: "<< labelMap->GetNthLabelObject(n)->GetLabel() << std::endl<<
            " size: " << labelMap->GetNthLabelObject(n)->GetNumberOfPixels() << std::endl<< std::endl;
    }

    int maxsize = 0;
    for (unsigned int n = 0; n < labelMap->GetNumberOfLabelObjects(); ++n)
    {
        ShapeLabelObjectType* labelObject = labelMap->GetNthLabelObject(n);
        if (labelObject->GetNumberOfPixels() > maxsize)
        {
            maxsize = labelObject->GetNumberOfPixels();
        }
    }
    std::cout << "maxsize= " << maxsize << std::endl;

    unsigned int countToDel = 0;
    for (unsigned int n = 0;  n < labelMap->GetNumberOfLabelObjects(); ++n)//注意越界
    {
        std::cout << "label " << n << " " <<
            labelMap->GetNthLabelObject(n)->GetNumberOfPixels() << std::endl;

        ShapeLabelObjectType* labelObject = labelMap->GetNthLabelObject(n);
        if (labelObject->GetNumberOfPixels() < m_factoryToMaxSizeToKeep * maxsize
            || labelObject->GetNumberOfPixels() < m_numberOfMinSizeToDelete)
        {
            ++countToDel;
            std::cout << "delete label..." <<
                labelObject->GetLabel() << std::endl<< std::endl;
        }
    }
    std::cout << std::endl << "deleted " <<
        countToDel << " labels totally" << std::endl;
    std::cout << "keep region number at last: " <<
        m_RegionNumberToKeep - countToDel << std::endl;
    labelShapeKeepNObjectsImageFilter->
        SetNumberOfObjects(m_RegionNumberToKeep - countToDel);

    RescaleFilterType::Pointer rescaleFilter = RescaleFilterType::New();
    rescaleFilter->SetOutputMinimum(0);
    rescaleFilter->SetOutputMaximum(3000);
    rescaleFilter->SetInput(labelShapeKeepNObjectsImageFilter->GetOutput());
    try
    {
        rescaleFilter->Update();
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << std::endl;
        return false;
    }

    auto resultData = deepCopyItkImageData(m_dcmData.GetPointer());
    auto resclaedImage = rescaleFilter->GetOutput();
    DcmImage3DType::RegionType 
        imageRegion = resclaedImage->GetBufferedRegion();
    IteratorType3D dataItSrc(resclaedImage, imageRegion);
    dataItSrc.GoToBegin();
    while (!dataItSrc.IsAtEnd())
    {
        if (dataItSrc.Get() <= 0)
        {
            resultData->SetPixel(dataItSrc.GetIndex(), 0);
        }
        ++dataItSrc;
    }
    m_resultData = resultData;
    return true;
}

DcmImage3DType::Pointer ConnectedRegionsAbstract::getResult()
{
    return m_resultData;
}
DCMHANDER_NAMESPACE_END

注意事项:

没弄清为什么保留的连通区域个数经常比设定的数目多一个,后面再研究。
完整代码及测试数据集(为spine web开源数据集)下载:
https://download.csdn.net/download/assjaa/14930215

你可能感兴趣的:(医学图像处理,c++,计算机视觉)