算法介绍
区域生长,指将前景种子点扩展为更大区域的过程。
预备知识
编程工具:VS2010, CMAKE
编程语言:C++
编程库:ITK
数据结构:栈,通过栈实现区域生长算法
数据:三维的病变视网膜图像
目的:分割视网膜图像中的病变区域,即色素上皮层脱离区域。
实现过程
(1) 读入原图像,前景种子点图像;
说明:前景种子点由形态学方法自动获取,也可以手动标注前景种子点。
(2) 定义区域生长的准则;
(3) 前景种子点入栈;对前景种子点逐个遍历,在当前种子点的26邻域内,如果有像素点满足生长条件,则该像素点入栈;直到栈为空,则算法结束;
(4) 添加约束条件,对区域生长结果进行限制;
(5) 将区域生长结果叠加在原图上,查看具体效果。
具体代码如下,并在VS2010上测试通过:
#include"itkImage.h"
#include"itkImageFileReader.h"
#include"itkImageFileWriter.h"
#include
#include
#include
#include
using namespace std;
int main(int argc, char* argv[])
{
char* srcImagePath = argv[1];
char* seedImagePath = argv[2];
char* dstImagePath = argv[3];
char* sur1Path = argv[4];//视网膜分层结果
char* sur12Path = argv[5];//视网膜分层结果
typedef itk::Image<unsigned short,3>InputImageType;
typedef itk::Image<unsigned short,3>OutputImageType;
typedef itk::Image<unsigned char,3>SeedImageType;
InputImageType::Pointer srcImage = InputImageType::New();
OutputImageType::Pointer dstImage = OutputImageType::New();
SeedImageType::Pointer seedImage = SeedImageType::New();
typedef itk::ImageFileReaderReaderType;
typedef itk::ImageFileReaderSeedReaderType;
ReaderType::Pointer reader = ReaderType::New();
SeedReaderType::Pointer readerSeed = SeedReaderType::New();
reader->SetFileName(argv[1]);//原图像
readerSeed->SetFileName(argv[2]);//前景种子点图
reader->Update();
srcImage = reader->GetOutput();
readerSeed->Update();
seedImage = readerSeed->GetOutput();
//允许种子点图像与源图像尺寸不同
InputImageType::SizeType imgSize = srcImage->GetLargestPossibleRegion().GetSize();
SeedImageType::SizeType seedImgSize = seedImage->GetLargestPossibleRegion().GetSize();
//结果图开辟内存
OutputImageType::IndexType index;
index[0]=0;
index[1]=0;
index[2]=0;
OutputImageType::SizeType size;
size[0]=imgSize[0];
size[1]=imgSize[1];
size[2]=imgSize[2];
OutputImageType::RegionType region;
region.SetIndex(index);
region.SetSize(size);
dstImage->SetRegions(region);
dstImage->Allocate();
struct seedpoint
{
int x;
int y;
int z;
};
stack seedS;
seedpoint point;
//读入原数据
//unsigned short *srcData = new unsigned short[imgSize[0]*imgSize[1]*imgSize[2]];
//通过GetBufferPointer()获取数据后,如果使用delete[] srcData 会导致程序崩溃
//所以不使用new开辟内存,借助与ITK内存管理机制来实现
const unsigned short* srcData = srcImage->GetBufferPointer();//ITK数据转换为C++数据
//memset初始化结果图指针,第三个参数为内存单元大小(字节数)
unsigned short* resultData = new unsigned short[imgSize[0]*imgSize[1]*imgSize[2]];
memset(resultData, 0, sizeof(unsigned short) * imgSize[0] * imgSize[1] * imgSize[2]);
//前景种子点入栈
const unsigned char* seedData = seedImage->GetBufferPointer();//ITK数据转换为C++数据
for(int k=0; k < seedImgSize[2]; k++)
for(int j = 0; j < seedImgSize[1]; j++)
for(int i = 0; i < seedImgSize[0]; i++)
{
if(seedData[k*seedImgSize[0]*seedImgSize[1]+j*seedImgSize[0]+i] == 255)//255为前景灰度值
{
point.x = i;
point.y = j;
point.z = k;
seedS.push(point);
}
}
//初始化,将原图中所有像素点都标记为没有被遍历过
bool*flag = new bool[imgSize[0]*imgSize[1]*imgSize[2]];
memset(flag, false, sizeof(bool) * imgSize[0] * imgSize[1] * imgSize[2]);
//区域生长实现
unsigned short intensity = 13000;//前景平均灰度值
unsigned short threshold = 3000;//前景与背景灰度值差异
while(!seedS.empty())//栈为空则推出循环
{
seedpoint temppoint;
point=seedS.top();//取栈顶元素
seedS.pop();//元素弹栈
flag[point.z*imgSize[0]*imgSize[1]+point.y*imgSize[0]+point.x] = true;//标记为已遍历
resultData[point.z*imgSize[0]*imgSize[1]+point.y*imgSize[0]+point.x] = 255;//标记为亮区域
//图像边界出的像素点不进行处理
if((point.x >= 1) && (point.x <= (imgSize[0] - 2)) &&
(point.y >= 1) && (point.y <= (imgSize[1] - 2)) &&
(point.z >= 1) && (point.z <= (imgSize[2] - 2)))
{
int x = point.x;
int y = point.y;
int z = point.z;
for(int i = -1; i <= 1; i++)
for(int j = -1; j <= 1; j++)
for(int k = -1; k <= 1; k++)
{
if(flag[(z + i) * imgSize[0] * imgSize[1] + (y + j) * imgSize[0] + x + k] == false &&
srcData[(z + i) * imgSize[0] * imgSize[1] + (y + j) * imgSize[0] + x + k] >= intensity - threshold &&
srcData[(z + i) * imgSize[0] * imgSize[1] + (y + j) * imgSize[0] + x + k] <= intensity + threshold &&
(abs(srcData[(z + i) * imgSize[0] * imgSize[1] + (y + j) * imgSize[0] + x + k] -
srcData[z * imgSize[0] * imgSize[1] + y * imgSize[0] + x]) < threshold))
{
resultData[(z + i) * imgSize[0] * imgSize[1] + (y + j) * imgSize[0] + x + k] = 255;//亮区域
temppoint.x = x + k;
temppoint.y = y + j;
temppoint.z = z + i;
seedS.push(temppoint);//新种子点入栈
flag[(z + i) * imgSize[0] * imgSize[1] + (y + j) * imgSize[0] + x + k] = true;//标记为已遍历
}
}
}
}
delete[] flag;//用完后立即释放资源
flag = NULL;//避免野指针
//读入视网膜分层数据,对区域生长结果后处理
ifstream fin1, fin12;
int (*sur1)[512] = new int[64][512];
int (*sur12)[512] = new int[64][512];
fin1.open(sur1Path, ios::in);
fin12.open(sur12Path, ios::in);
for(int z = 0; z < 64; z++)
for(int x = 0; x < 512; x++)
{
fin1>>sur1[z][x];
fin12>>sur12[z][x];
}
fin1.close();
fin12.close();
//C++数据转ITK数据
OutputImageType::IndexType newvoxelIndex;
for(int z = 0; z < imgSize[2]; ++z)
for(int y = 0; y < imgSize[1]; ++y)
for(int x = 0; x < imgSize[0]; ++x)
{
newvoxelIndex[0] = x;
newvoxelIndex[1] = y;
newvoxelIndex[2] = z;
if(y < sur1[z][x] || y > sur12[z][x])//依据视网膜分层结果对区域生长结果进行约束
dstImage->SetPixel(newvoxelIndex, 0);
else
dstImage->SetPixel(newvoxelIndex, resultData[z*size[0]*size[1]+y*size[0]+x]);
}
delete[] sur1;
sur1 = NULL;
delete[] sur12;
sur12 = NULL;
delete[] resultData;
resultData = NULL;
//输出结果图
typedef itk::ImageFileWriterWriterType;
WriterType::Pointer Writer = WriterType::New();
Writer->SetFileName(argv[3]);
Writer->SetInput(dstImage);
Writer->Update();
return 0;
}
效果图如下,红色区域为区域生长结果。其中,半圆形区域为目标区域,其他为误分割区域。对于误分割区域,可以用形态学处理算法进行后处理。