算法介紹
區域生長,指將前景種子點擴展爲更大區域的過程。
預備知識
編程工具:VS2010, CMAKE
編程語言:C++
編程庫:ITK
數據結構:棧,通過棧實現區域生長算法
數據:三維的病變視網膜圖像
目的:分割視網膜圖像中的病變區域,即色素上皮層脫離區域。
實現過程
(1) 讀入原圖像,前景種子點圖像;
說明:前景種子點由形態學方法自動獲取,也可以手動標註前景種子點。
(2) 定義區域生長的準則;
(3) 前景種子點入棧;對前景種子點逐個遍歷,在當前種子點的26鄰域內,如果有像素點滿足生長條件,則該像素點入棧;直到棧爲空,則算法結束;
(4) 添加約束條件,對區域生長結果進行限制;
(5) 將區域生長結果疊加在原圖上,查看具體效果。
具體代碼如下,並在VS2010上測試通過:
#include"itkImage.h"
#include"itkImageFileReader.h"
#include"itkImageFileWriter.h"
#include<iostream>
#include<string>
#include <fstream>
#include <stack>
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::ImageFileReader<InputImageType>ReaderType;
typedef itk::ImageFileReader<SeedImageType>SeedReaderType;
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<seedpoint>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::ImageFileWriter<OutputImageType>WriterType;
WriterType::Pointer Writer = WriterType::New();
Writer->SetFileName(argv[3]);
Writer->SetInput(dstImage);
Writer->Update();
return 0;
}
效果圖如下,紅色區域爲區域生長結果。其中,半圓形區域爲目標區域,其他爲誤分割區域。對於誤分割區域,可以用形態學處理算法進行後處理。