记录一下区域生长法的学习过程,区域生长法是基于区域的分割方法,通过算法自动选取或者交互式选取种子点(即单个像素点),并规定所应用的谓词逻辑,将8邻接或4邻接并满足谓词逻辑的点进行合并,不断迭代,直至不满足谓词逻辑时,完成分割。
最开始在实现这个功能的时候,在网上看了一些别人的代码,发现和自己理解的区域生长法有些出入,再此写下自己所理解的算法代码,仅代表个人意见。
代码如下:
/*
* function: 区域生长法; 递归版本
* input: grayImage 源图灰度图像
* output: SegmentImage 分割完成图像(必须传入全黑单通道图像)
* D_value: 谓词逻辑;灰度差值
*/
void SeedPoint_trace(cv::Mat& SegmentImage, cv::Mat grayImage, int r, int c, int D_value, int rows, int cols)
{
//如果该坐标点未被标记
if (SegmentImage.at<uchar>(r, c) == 0)
{
SegmentImage.at<uchar>(r, c) = 255;
int Seedvalue = grayImage.at<uchar>(r, c); //种子点像素值
for (int i = -1; i <= 1; ++i)
{
for (int j = -1; j <= 1; ++j)
{
//检测该点是否在图像内
if (checkInRang(r + i, c + j, rows, cols))
{
int NeighborValue = grayImage.at<uchar>(r + i, c + j); //邻域点像素值
if (abs(Seedvalue - NeighborValue) <= D_value)
SeedPoint_trace(SegmentImage, grayImage, r + i, c + j, D_value, rows, cols);
}
}
}
}
}
/*
* function: 区域生长法; 非递归版本
* input: grayImage 源图灰度图像
* output: SegmentImage 分割完成图像(必须传入全黑单通道图像)
* D_value: 谓词逻辑;灰度差值
* pt 初始种子点
*/
void SeedPoint_trace2(cv::Mat& SegmentImage, cv::Mat grayImage, cv::Point pt, int D_value, int rows, int cols)
{
//标记初始生长点
SegmentImage.at<uchar>(pt) = 255;
//定义种子点动态集合
vector<cv::Point> growPtVec;
growPtVec.push_back(pt);
while (!growPtVec.empty())
{
cv::Point SeedPt = growPtVec.back(); //返回当前容器最后一个元素
growPtVec.pop_back(); //弹出最后一个元素
int SeedValue = grayImage.at<uchar>(SeedPt); //种子点像素值
cv::Point CurrPt; //当前点
//遍历8邻接
for (int i = -1; i <= 1; ++i)
{
for (int j = -1; j <= 1; ++j)
{
CurrPt.x = SeedPt.x + j;
CurrPt.y = SeedPt.y + i;
//检测该点是否在图像内并且未被标记
if (checkInRang(CurrPt.y, CurrPt.x, rows, cols) && SegmentImage.at<uchar>(CurrPt) == 0)
{
int NeighborValue = grayImage.at<uchar>(CurrPt);
if (abs(SeedValue - NeighborValue) <= D_value)
{
SegmentImage.at<uchar>(CurrPt) = 255;
growPtVec.push_back(CurrPt);
}
}
}
}
}
}
定义了两种版本:递归版本和非递归版本。递归版本看似很简洁,但其性能和非递归版本比起来相差甚远,而且为了防止递归堆栈溢出,还需要在IDE中自定义堆栈区的大小,个人感觉;递归很巧妙,但并不总是很适用。
总结: 当基于阈值分割得不到理想的结果时可以考虑区域生长法,但是此方法的局限性很大,既要考虑谓词逻辑的选取(灰度、颜色、梯度角度等)和设定(终止条件),也要考虑种子点的选取,自动化程度不是很高。而且当区域与区域之间间隔不明显或者渐变过渡时,也容易导致错误分割。
试验结果:
代码中未定义函数参考链接:自适应阈值canny边缘检测