opencv学习——分水岭算法

在很多实际应用中,我们需要分割图像,分割方法有多种经典的分割方法:

1 常见图像分割方法:

1、基于边缘检测的方法:
此方法主要是通过检测区域的边缘进行分割,利用区域之间的特征的不一致性,首先检测图像中的边缘点,然后按照一定的方法把这些边缘点进行全部连接起来,从而构成分割区域。图像中的边缘通常是灰度,颜色或者纹理,其中基于灰度的方法很普遍,许多边缘检测算子利用灰度来检测图像的梯度,Roberts 算子、Laplace 算子、Prewitt 算子、Sobel 算子、Rosonfeld算子、Kirsch 算子以及Canny 。边缘检测算法比较适合边缘灰度值过渡比较显著且噪声较小的简单图像的分割。对于边缘比较复杂以及存在较强噪声的图像,则面临抗噪性和检测精度的矛盾。若提高检测精度,则噪声产生的伪边缘会导致不合理的轮廓:若提高抗噪性,则会产生轮廓漏检和位置偏差。

2.阈值分割方法
阈值分割是最古老的分割技术,也是最简单实用的。许多情况下,图像中目标区域与背景区域或者说不同区域之间其灰度值存在差异,此时可以将灰度的均一性作为依据进行分割。阈值分割即通过一个或几个阈值将图像分割成不同的区域。阈值分割方法的核心在于如何寻找适当的阈值。最常用的阈值方法是基于灰度直方图的方法,如最大类间方差法(OTSU)、最小误差法、最大熵法等。此类方法通常对整幅图像使用固定的全局阈值,如果图像中有阴影或亮度分布不均等现象,分割效果会受到影响。基于局部阈值的分割方法对图像中的不同区域采用不同的阈值,相对于全局阈值方法具有更好的分割效果,该方法又称为自适应阈值方法。

3.区域生长

  区域生长方法[46]也是一种常用的区域分割技术,其基本思路是首先定义一个生长准则,然后在每个分割区域内寻找一个种子像素,通过对图像进行扫描,依次在种子点周围邻域内寻找满足生长准则的像素并将其合并到种子所在的区域,然后再检查该区域的全部相邻点,并把满足生长准则的点合并到该区域,不断重复该过程直到找不到满足条件的像素为止。该方法的关键在于种子点的位置、生长准则和生长顺序。

4.分水岭算法

  是以数学形态学作为基础的一种区域分割方法。其基本思想是将梯度图像看成是假想的地形表面,每个像素的梯度值表示该点的海拔高度。原图中的平坦区域梯度较小,构成盆地,边界处梯度较大构成分割盆地的山脊。分水岭算法模拟水的渗入过程,假设水从最低洼的地方渗入,随着水位上升,较小的山脊被淹没,而在较高的山脊上筑起水坝,防止两区域合并。当水位达到最高山脊时,算法结束,每一个孤立的积水盆地构成一个分割区域。由于受到图像噪声和目标区域内部的细节信息等因素影响,使用分水岭算法通常会产生过分割现象,分水岭算法一般是作为一种预分割方法,与其它分割方法结合使用,以提高算法的效率或精度。

2 定义

分水岭算法(watershed algorithm)可以将图像中的边缘转化为“山脉”,将均匀区域转化为“山谷”,在这方面有助于分割目标
分水岭算法:是一种基于拓扑理论的数学形态学的分割方法。把图像看作是测地学上的拓扑地貌,图像中的每一个点像素值的灰度值表示该点的海拔高度,每一个局部极小值及其影响的区域称为“集水盆”,集水盆的边界可以看成分水岭。在每一个局部极小值表面刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢的向外扩展,在两个集水盆汇合处构建大坝,形成分水岭。

迭代标注过程:

  1. 排序过程:对每个像素的灰度级进行从低到高的排序
  2. 淹没过程:对每一个局部最小值在h阶高度的影响域采用先进先出结构判断及标注。

3.实现算法:watershed()函数

这些标记的值可以使用findContours()函数和drawContours()函数由二进制的掩模检索出来
函数原型:

void watershed(InputArray image, InputOutputArray markers)
第一个参数:输入图src,需要8位三通道的彩图;
第二个参数较复杂:“markers中存储了图像的大致轮廓,<span style="color:#ff0000;">32位单通道图像</span>,并且以值1,2,3..分别表示各个components.markers通常由函数结合使用来获得。markers相
当于watershed()运行时的种子参数。markers中,不属于轮廓(outlined regions)的点的值应置为0.函数运行后,
图像中的像素如果是在由某个轮廓种子生成的区域中,那么其值就置为这个种子的编号,如果像素不在轮廓种子生成的区域中(边界),则置为-1。

4 示例程序

#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp> 
#include <iostream> 
using namespace cv; 
using namespace std; 
//定义窗口标题定义的宏 
#define WINDOW_NAME "【程序窗口】" 
//全局变量 
Mat g_maskImage, g_srcImage; 
Point prevPt(-1, -1); 
//全局函数 
static void on_Mouse(int event, int x, int y, int flags, void *); 
//main()函数 
int main() 
{ 
//载入原图并显示,初始化掩模和灰度图
 g_srcImage = imread("1.jpg", 1); 
 imshow(WINDOW_NAME, g_srcImage); 
 Mat srcImage, grayImage; 
 g_srcImage.copyTo(srcImage); 
 cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY); 
 cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR); 
 g_maskImage = Scalar::all(0); 
 //设置鼠标回调函数 
 setMouseCallback(WINDOW_NAME, on_Mouse, 0); 
 //轮询按键,进行处理 
 while (1)
  { 
  //获取按键 
  int c = waitKey(0); 
  //若按键为ESC,退出 
  if ((char)c == 27) 
  break; 
  //回复原图 
  if ((char)c == '2')
   { 
   g_maskImage = Scalar::all(0); 
   srcImage.copyTo(g_srcImage);
    imshow("image", g_srcImage); 
    } 
    //检测的值为1或者空格,则进行处理
     if ((char)c == '1' || (char)c == ' ') 
     {
      //定义参数
       int i, j, compCount = 0; 
       vector<vector<Point> > contours; 
       vector<Vec4i>hierarchy; 
       //寻找轮廓
        findContours(g_maskImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
         if (contours.empty()) 
         continue;
          Mat maskImage(g_maskImage.size(), CV_32S); 
          maskImage = Scalar::all(0); 
          //循环绘制出轮廓 
          for (int index = 0; index >= 0; index = hierarchy[index][0], compCount++)
           { 
           drawContours(maskImage, contours, index, Scalar::all(compCount + 1), -1, 8, hierarchy, INT_MAX);
            } 
            if (compCount == 0) continue;
             //生成随机颜色 
             vector<Vec3b>colorTab; for (int i = 0; i < compCount; i++) 
             { 
             int b = theRNG().uniform(0, 255); 
             int g = theRNG().uniform(0, 255); 
             int r = theRNG().uniform(0, 255); 
             colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r)); 
             }
              //计算处理时间并输出到窗口中 
              double dTime = (double)cvGetTickCount(); watershed(srcImage, maskImage);
               dTime = (double)getTickCount() - dTime; 
               printf("\t 处理时间 = %gms\n", dTime * 1000. / getTickFrequency()); 
               //双层循环,将分水岭图像遍历存入watershedImage中 
               Mat watershedImage(maskImage.size(), CV_8UC3);
                for (int i = 0; i < maskImage.rows; i++)
                 { 
                 for (int j = 0; j < maskImage.cols; j++)
                  { int index = maskImage.at<int>(i, j);
                   if (index == -1) 
                   { 
                   watershedImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255); 
                   }
                    else if (index <= 0 || index > compCount) 
                    { 
                    watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
                     } 
                     else
                      { 
                      watershedImage.at<Vec3b>(i, j) = colorTab[index - 1];
                       }
                        } 
                        } 
                        //混合灰度图和分水岭效果图并显示最终的窗口 
                        watershedImage = watershedImage*0.5 + grayImage*0.5;
                         imshow("watershed transform", watershedImage);
                          } 
                          }
                           return 0; 
                           }
                            //鼠标消息回调函数 
                            static void on_Mouse(int event, int x, int y, int flags, void *)
                             {
                              //处理鼠标不在窗口中的情况
                               if (x < 0 || x > g_srcImage.cols || y < 0 || y >= g_srcImage.rows) 
                               return; 
                               //处理鼠标左键相关的消息
                                if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) 
                                prevPt = Point(-1, -1); 
                                else if (event == EVENT_LBUTTONDOWN)
                                 {
                                  prevPt = Point(x, y);
                                   } 
                                   //鼠标左键按下并移动,绘制白色的线条 
                                   else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) 
                                   { 
                                   Point pt(x, y); 
                                   if (prevPt.x < 0) 
                                   prevPt = pt; 
                                   line(g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0);
                                    line(g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0); 
                                    prevPt = pt;
                                     imshow(WINDOW_NAME, g_srcImage); 
                                     } 
                                     }

猜你喜欢

转载自blog.csdn.net/xu1129005165/article/details/85090339