之前搜了很多分水岭算法的文章,有许多在我的博文中,但是还没有真正的把分水岭算法的代码看懂和测试。这次重新梳理一下。
一 分水岭算法描述
Watershed Algorithm(分水岭算法),顾名思义,就是根据分水岭的构成来考虑图像的分割。现实中我们可以或者说可以想象有山有湖的景象,那么那一定是水绕山,山围水的情形。当然在需要的时候,要人工构筑分水岭,以防集水盆之间的互相穿透。而区分高山(plateaus)与水的界线,以及湖与湖之间的间隔或都是连通的关系,就是我们可爱的分水岭(watershed)。为了得到一个相对集中的集水盆,那么让水涨到都接近周围的最高的山顶就可以了,再涨就要漏水到邻居了,而邻居,嘿嘿,水质不同诶,会混淆自我的。那么这样的话,我们就可以用来获取边界灰阶大,中间灰阶小的物体区域了,它就是集水盆。
浸水法,就是先通过一个适当小的阈值得到起点,即集水盆的底;然后是向周围淹没也就是浸水的过程,直到得到分水岭。当然如果我们要一直淹没到山顶,即是一直处理到图像灰阶最高片,那么,当中就会出现筑坝的情况,不同的集水盆在这里想相遇了,我们要洁身自爱,到这里为止,因为都碰到边界了;那么即使在相遇时没有碰到最高灰阶的地方,也需要人工构筑分水岭,区分不同的区域。不再上山。构筑属于自己的分水岭在计算机图形学中,可利用灰度表征地貌高。图像中我们可以利用灰度高与地貌高的相似性来研究图像的灰度在空间上的变化。这是空域分析,比如还可以通过各种形式的梯度计算以得到算法的输入,进行浸水处理。分水岭具有很强的边缘检测能力,对微弱的边缘也有较好的效果。这与分水岭扩张的阈值的设置有关系,阈值可以决定集水盆扩张的范围。但自我构筑的能力却不受影响
二 分水岭算法代码
/*====================================================================
函数名: Watershed
功能: 用标记-分水岭算法对输入图像进行分割
算法实现: 无
输入参数说明: OriginalImage --输入图像(灰度图,0~255)
SeedImage --标记图像(二值图,0-非标记,1-标记)
LabelImage --输出图像(1-第一个分割区域,2-第二个分割区域,...)
row --图像行数
col --图像列数
返回值说明: 无
====================================================================*/
void WINAPI CDib::Watershed(unsigned char **OriginalImage, char** SeedImage, int **LabelImage, int row, int col)
{
// using namespace std;
//标记区域标识号,从1开始
int Num=0;
int i,j;
//保存每个队列种子个数的数组
vector<int*> SeedCounts;
//临时种子队列
queue<POINT> quetem;
//保存所有标记区域种子队列的数组,里面放的是种子队列的指针
vector<queue<POINT>*> vque;
int* array;
//指向种子队列的指针
queue<POINT> *pque;
POINT temp;
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
LabelImage[i][j]=0;
}
int m,n,k=0;
BOOL up,down,right,left,upleft,upright,downleft,downright;//8 directions...
//预处理,提取区分每个标记区域,并初始化每个标记的种子队列
//种子是指标记区域边缘的点,他们可以在水位上升时向外淹没(或者说生长)
//pan's words:我的理解是梯度值较小的象素点,或者是极小灰度值的点。
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
{
//如果找到一个标记区域
if(SeedImage[i][j]==1)
{
//区域的标识号加一
Num++;
//分配数组并初始化为零,表示可有256个灰阶
array=new int[256];
ZeroMemory(array,256*sizeof(int));
//种子个数数组进vector,每次扫描则生成一个数组,并用区域标识号来做第一维。灰度级做第二维。
//表示某个盆地区域中某灰阶所对应的点的数目。
SeedCounts.push_back(array);
//分配本标记号的优先队列,256个种子队列,
//表示对应一个灰阶有一个队列,并且每个队列可以存储一个集合的点信息
pque=new queue<POINT>[256];
//加入到队列数组中,对应的是本标记号Num的
vque.push_back(pque);
//当前点放入本标记区域的临时种子队列中
temp.x=i;
temp.y=j;
quetem.push(temp);
//当前点标记为已处理
LabelImage[i][j]=Num;
SeedImage[i][j]=127;//表示已经处理过
//让临时种子队列中的种子进行生长直到所有的种子都生长完毕
//生长