在做项目中用到了有种子像素的图像分割问题,找到了古老的分水岭算法,看了很多博客,都是直接调用了opencv的函数,对原算法的原理实现与原理解释的很不到位。因此,研究了opencv中分水岭函数的实现原理,参考了阿里云开发者麦克老狼的博客。
原始图像和Mark图像,它们的大小都是32*32,分水岭算法的结果是得到两个连通域的轮廓图。
初始的mark图像数据如下,黄色的部分为我们的第一个mark区域,值为255,用来标记背景区域,第二个区域为褐红色的区域,值为128,第三个绿色的区域,值为64。
opencv分水岭算法描述如下:
初始化mark矩阵,生成最初的注水区域。
1.设置mark图像的边框值为-1(标记背景区域)
2. 标记每个mark区域的边界为-2(初始填充点)
3. 对于mark图像一个像素值,如果它本身值为0,但上下左右四邻域有一个像素值不为0,则把该点按照(最小邻域灰度差值)放入相应的队列。
举例说明:如下图像素点,它的mark值为0,但左和上像素值不为0,此时,我们求原始图像中对应像素的灰度差值,计算方式如下面公式,其中I原图像的灰度值,彩色图像为三个颜色值,OpenCV中使用三个通道差分的最大值,下标L表示左,R表示右,T表示上,B表示下,abs表示取绝对值:
min(abs(I-IL),abs(I-IT),abs(I-RR),abs(I-IB))
上图中指定的像素,它的高度值显然为0,所以我们把(2,2)点放入高度为0的队列中(总共有256个队列,对应0-255的高度)
初始化阶段完成后,我们得到下面的mark图,并把-2对应的边界像素点,按照其对应的灰度差值放入相应的队列。
之后就进入了递归注水过程,递归过程描述如下:
for(; ; )
{
1、从0到255扫描灰度差值队列,找到一个像素,则弹出该标记,并退出扫描。从低处填充,邻域像素的高度用与已标记点的灰度差表示,故优先填充所有种子点差值最小的像素。
2、如果该像素的四邻域中存在两个不同的非0值,表示该点为两个注水盆地的边缘,即分水岭线,在mark图像中标记该点为-1。(两个区域的交汇处,标记为区域之间的分界线)
3、扫描该点的四邻域,是否存在为0的mark域,存在的话这把该邻域点按照与该点的灰度差值,放入相应的队列。
4、直到队列中无像素,退出。
}
经过上述的递归过程,最后我们得到的mark图像如下所示,其中绿色格子的-1即为所有的分水岭边界:
上述递归过程总是从最小的灰度差0到最大255扫描的,因此总能够保证与已标记像素灰度差最小的邻域像素参与标记。
参考: