C++实现分水岭算法(Watershed Algorithm)

分水岭分割方法(Watershed Segmentation),是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
  分水岭的计算过程是一个迭代标注过程。分水岭比较经典的计算方法是L. Vincent提出的。在该算法中,分水岭计算分两个步骤,一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高排序,然后在从低到高实现淹没过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。
  分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点,即为分水岭。显然,分水岭表示的是输入图像极大值点。因此,为得到图像的边缘信息,通常把梯度图像作为输入图像,即:
  grad(f(x,y))=((f(x-1,y)-f(x+1,y))^2 + (f(x,y-1)-f(x,y+1))^2)^0.5
  式中,f(x,y)表示原始图像,grad(.)表示梯度运算。
  分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时应当看出,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证的。另外,分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。
  为消除分水岭算法产生的过度分割,通常可以采用两种处理方法,一是利用先验知识去除无关边缘信息。二是修改梯度函数使得集水盆只响应想要探测的目标。
  为降低分水岭算法产生的过度分割,通常要对梯度函数进行修改,一个简单的方法是对梯度图像进行阈值处理,以消除灰度的微小变化产生的过度分割。即:
  g(x,y)=max(grad(f(x,y)),gθ)
  式中,gθ表示阈值。
  程序可采用方法:用阈值限制梯度图像以达到消除灰度值的微小变化产生的过度分割,获得适量的区域,再对这些区域的边缘点的灰度级进行从低到高排序,然后在从低到高实现淹没的过程,梯度图像用Sobel算子计算获得。对梯度图像进行阈值处理时,选取合适的阈值对最终分割的图像有很大影响,因此阈值的选取是图像分割效果好坏的一个关键。缺点:实际图像中可能含有微弱的边缘,灰度变化的数值差别不是特别明显,选取阈值过大可能会消去这些微弱边缘。

  下面用C++实现分水岭算法:

#define _USE_MATH_DEFINES 

#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <cfloat>
#include <ctime>
#include <cmath>
#include <cassert>
#include <vector>
#include <stack>
#include <queue> 

using namespace std; 

typedef void              GVVoid;
typedef bool              GVBoolean;
typedef char              GVChar;
typedef unsigned char          GVByte;
typedef short              GVInt16;
typedef unsigned short         GVUInt16;
typedef int               GVInt32;
typedef unsigned int          GVUInt32;
typedef long long            GVInt64;
typedef unsigned long long       GVUInt64;
typedef float              GVFloat32;
typedef double             GVFloat64; 

const GVBoolean GV_TRUE         = true;
const GVBoolean GV_FALSE        = false;
const GVByte              GV_BYTE_MAX = UCHAR_MAX;
const GVInt32              GV_INT32_MAX = INT_MAX;
const GVInt32              GV_INT32_MIX = INT_MIN;
const GVInt64              GV_INT64_MAX = LLONG_MAX;
const GVInt64              GV_INT64_MIN = LLONG_MIN;
const GVFloat32 GV_FLOAT32_MAX     = FLT_MAX;
const GVFloat32 GV_FLOAT32_MIN     = FLT_MIN;
const GVFloat64 GV_FLOAT64_MAX     = DBL_MAX;
const GVFloat64 GV_FLOAT64_MIN     = DBL_MIN; 

class                  GVPoint; 

class GVPoint { 

public:
  GVInt32       x;
  GVInt32       y; 

public:
  GVPoint() : x(0), y(0) { }
  GVPoint(const GVPoint &obj) : x(obj.x), y(obj.y) { }
  GVPoint(GVInt32 x, GVInt32 y) : x(x), y(y) { } 

public:
  GVBoolean operator ==(const GVPoint &right) const {
    return ((x == right.x) && (y == right.y));
  }
  GVBoolean operator !=(const GVPoint &right) const {
    return (!(x == right.x) || !(y == right.y));
  }
}; 

/*
 * <Parameter>
 *   <image> image data;
 *   <width> image width;
 *   <height> image height;
 *   <label out> image of labeled watersheds.
 */
GVVoid gvWatershed(
    const GVByte *image,
    GVInt32 width,
    GVInt32 height,
    GVInt32 *label)
{ 

  // Local constants
  const GVInt32 WSHD = 0;
  const GVInt32 INIT = -1;
  const GVInt32 MASK = -2;
  const GVPoint FICT_PIXEL = GVPoint(~0, ~0); 

  // Image statistics and sorting
  GVInt32 size = width * height;
  GVInt32 *image_stat = new GVInt32[GV_BYTE_MAX + 1];
  GVInt32 *image_space = new GVInt32[GV_BYTE_MAX + 1];
  GVPoint *image_sort = new GVPoint[size];
  ::memset(image_stat, 0, sizeof (GVInt32) * (GV_BYTE_MAX + 1));
  ::memset(image_space, 0, sizeof (GVInt32) * (GV_BYTE_MAX + 1));
  ::memset(image_sort, 0, sizeof (GVPoint) * size);
  for (GVInt32 i = 0; !(i == size); ++i) {
    image_stat[image[i]]++;
  }
  for (GVInt32 i = 0; !(i == GV_BYTE_MAX); ++i) {
    image_stat[i + 1] += image_stat[i];
  }
  for (GVInt32 i = 0; !(i == height); ++i) {
    for (GVInt32 j = 0; !(j == width); ++j) {
      GVByte space = image[i * width + j];
      GVInt32 index = image_stat[space] - (++image_space[space]);
      image_sort[index].x = j;
      image_sort[index].y = i;
    }
  }
  for (GVInt32 i = GV_BYTE_MAX; !(i == 0); --i) {
    image_stat[i] -= image_stat[i - 1];
  } 

  // Watershed algorithm
  GVPoint *head = image_sort;
  GVInt32 space = 0;
  GVInt32 *dist = new GVInt32[size];
  GVInt32 dist_cnt = 0;
  GVInt32 label_cnt = 0;
  std::queue<GVPoint> queue;
  ::memset(dist, 0, sizeof (GVInt32) * size);
  ::memset(label, ~0, sizeof (GVInt32) * size);
  for (GVInt32 h = 0; !(h == (GV_BYTE_MAX + 1)); ++h) {
    head += space;
    space = image_stat[h];
    for (GVInt32 i = 0; !(i == space); ++i) {
      GVInt32 index = head[i].y * width + head[i].x;
      GVInt32 index_l = ((head[i].x - 1) < 0) ? -1 : ((head[i].x - 1) + head[i].y * width);
      GVInt32 index_r = !((head[i].x + 1) > width) ? -1 : ((head[i].x + 1) + head[i].y * width);
      GVInt32 index_t = ((head[i].y - 1) < 0) ? -1 : (head[i].x + (head[i].y - 1) * width);
      GVInt32 index_b = !((head[i].y + 1) > height) ? -1 : (head[i].x + (head[i].y + 1) * width);
      label[index] = MASK;
      if (    (!(index_l < 0) && !(label[index_l] < WSHD))
          || (!(index_r < 0) && !(label[index_r] < WSHD))
          || (!(index_t < 0) && !(label[index_t] < WSHD))
          || (!(index_b < 0) && !(label[index_b] < WSHD))) {
        dist[index] = 1;
        queue.push(head[i]);
      }
    }
    dist_cnt = 1;
    queue.push(FICT_PIXEL);
    while (GV_TRUE) {
      GVPoint top = queue.front();
      GVInt32 index = top.y * width + top.x;
      GVInt32 index_l = ((top.x - 1) < 0) ? -1 : ((top.x - 1) + top.y * width);
      GVInt32 index_r = !((top.x + 1) > width) ? -1 : ((top.x + 1) + top.y * width);
      GVInt32 index_t = ((top.y - 1) < 0) ? -1 : (top.x + (top.y - 1) * width);
      GVInt32 index_b = !((top.y + 1) > height) ? -1 : (top.x + (top.y + 1) * width);
      queue.pop();
      if (top == FICT_PIXEL) {
        if (queue.empty()) break;
        else {
          ++dist_cnt;
          queue.push(FICT_PIXEL);
          top = queue.front();
          queue.pop();
        }
      }
      if (!(index_l < 0)) {
        if ((dist[index_l] < dist_cnt) && !(label[index_l] < WSHD)) {
          if (label[index_l] > WSHD) {
            if ((label[index] == MASK) || (label[index] = WSHD)) label[index] = label[index_l];
            else if (!(label[index] == label[index_l])) label[index] = WSHD;
          } else if (label[index] == MASK) {
            label[index] = WSHD;
          }
        } else if ((label[index_l] == MASK) && (dist[index_l] == 0)) {
          dist[index_l] = dist_cnt + 1;
          queue.push(GVPoint(top.x - 1, top.y));
        }
      }
      if (!(index_r < 0)) {
        if ((dist[index_r] < dist_cnt) && !(label[index_r] < WSHD)) {
          if (label[index_r] > WSHD) {
            if ((label[index] == MASK) || (label[index] = WSHD)) label[index] = label[index_r];
            else if (!(label[index] == label[index_r])) label[index] = WSHD;
          } else if (label[index] == MASK) {
            label[index] = WSHD;
          }
        } else if ((label[index_r] == MASK) && (dist[index_r] == 0)) {
          dist[index_r] = dist_cnt + 1;
          queue.push(GVPoint(top.x + 1, top.y));
        }
      }
      if (!(index_t < 0)) {
        if ((dist[index_t] < dist_cnt) && !(label[index_t] < WSHD)) {
          if (label[index_t] > WSHD) {
            if ((label[index] == MASK) || (label[index] = WSHD)) label[index] = label[index_t];
            else if (!(label[index] == label[index_t])) label[index] = WSHD;
          } else if (label[index] == MASK) {
            label[index] = WSHD;
          }
        } else if ((label[index_t] == MASK) && (dist[index_t] == 0)) {
          dist[index_t] = dist_cnt + 1;
          queue.push(GVPoint(top.x, top.y - 1));
        }
      }
      if (!(index_b < 0)) {
        if ((dist[index_b] < dist_cnt) && !(label[index_b] < WSHD)) {
          if (label[index_b] > WSHD) {
            if ((label[index] == MASK) || (label[index] = WSHD)) label[index] = label[index_b];
            else if (!(label[index] == label[index_b])) label[index] = WSHD;
          } else if (label[index] == MASK) {
            label[index] = WSHD;
          }
        } else if ((label[index_b] == MASK) && (dist[index_b] == 0)) {
          dist[index_b] = dist_cnt + 1;
          queue.push(GVPoint(top.x, top.y + 1));
        }
      }
    }
    for (GVInt32 i = 0; !(i == space); ++i) {
      GVInt32 index = head[i].y * width + head[i].x;
      dist[index] = 0;
      if (label[index] == MASK) {
        label_cnt++;
        label[index] = label_cnt;
        queue.push(head[i]);
        while (!queue.empty()) {
          GVPoint top = queue.front();
          GVInt32 index_l = ((top.x - 1) < 0) ? -1 : ((top.x - 1) + top.y * width);
          GVInt32 index_r = !((top.x + 1) > width) ? -1 : ((top.x + 1) + top.y * width);
          GVInt32 index_t = ((top.y - 1) < 0) ? -1 : (top.x + (top.y - 1) * width);
          GVInt32 index_b = !((top.y + 1) > height) ? -1 : (top.x + (top.y + 1) * width);
          queue.pop();
          if (!(index_l < 0) && (label[index_l] == MASK)) {
            queue.push(GVPoint(top.x - 1, top.y));
            label[index_l] = label_cnt;
          }
          if (!(index_r < 0) && (label[index_r] == MASK)) {
            queue.push(GVPoint(top.x + 1, top.y));
            label[index_r] = label_cnt;
          }
          if (!(index_t < 0) && (label[index_t] == MASK)) {
            queue.push(GVPoint(top.x, top.y - 1));
            label[index_t] = label_cnt;
          }
          if (!(index_b < 0) && (label[index_b] == MASK)) {
            queue.push(GVPoint(top.x, top.y + 1));
            label[index_b] = label_cnt;
          }
        }
      }
    }
  } 

  // Release resources
  delete[] image_stat;
  delete[] image_space;
  delete[] image_sort;
  delete[] dist;
} 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Opencv实现用于图像分割分水岭算法

    目标 • 使用分水岭算法基于掩模的图像分割 • 学习函数: cv2.watershed() 原理   任何一幅灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷.我们向每一个山谷中灌不同颜色的水,随着水的位的升高,不同山谷的水就会相遇汇合,为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝.不停的灌水,不停的构建堤坝直到所有的山峰都被水淹没.我们构建好的堤坝就是对图像的分割.这就是分水岭算法的背后哲理.   但是这种方法通常都会得到过度分割的结果

  • OpenCV图像分割中的分水岭算法原理与应用详解

    图像分割是按照一定的原则,将一幅图像分为若干个互不相交的小局域的过程,它是图像处理中最为基础的研究领域之一.目前有很多图像分割方法,其中分水岭算法是一种基于区域的图像分割算法,分水岭算法因实现方便,已经在医疗图像,模式识别等领域得到了广泛的应用. 1.传统分水岭算法基本原理 分水岭比较经典的计算方法是L.Vincent于1991年在PAMI上提出的[1].传统的分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一像素的灰度值表示该点的海

  • OpenCV半小时掌握基本操作之分水岭算法

    [OpenCV]⚠️高手勿入! 半小时学会基本操作 ⚠️ 分水岭算法 概述 OpenCV 是一个跨平台的计算机视觉库, 支持多语言, 功能强大. 今天小白就带大家一起携手走进 OpenCV 的世界. 分水岭算法 分水岭算法 (Watershed Algorithm) 是一种图像区域分割算法. 在分割的过程中, 分水岭算法会把跟临近像素间的相似性作为重要的根据. 分水岭分割流程: 读取图片 转换成灰度图 二值化 距离变换 寻找种子 生成 Marker 分水岭变换 距离变换 距离变换 (Distan

  • Opencv分水岭算法学习

    分水岭算法可以将图像中的边缘转化成"山脉",将均匀区域转化为"山谷",这样有助于分割目标. 分水岭算法是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中的每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭.分水岭的概念和形成可以通过模拟浸入过程来说明:在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响区域慢慢向外扩展,

  • C++实现分水岭算法(Watershed Algorithm)

    分水岭分割方法(Watershed Segmentation),是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭.分水岭的概念和形成可以通过模拟浸入过程来说明.在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭. 分水岭的计算过程是一个迭代标

  • python数字图像处理之骨架提取与分水岭算法

    骨架提取与分水岭算法也属于形态学处理范畴,都放在morphology子模块内. 1.骨架提取 骨架提取,也叫二值图像细化.这种算法能将一个连通区域细化成一个像素的宽度,用于特征提取和目标拓扑表示. morphology子模块提供了两个函数用于骨架提取,分别是Skeletonize()函数和medial_axis()函数.我们先来看Skeletonize()函数. 格式为:skimage.morphology.skeletonize(image) 输入和输出都是一幅二值图像. 例1: from s

  • python opencv之分水岭算法示例

    本文介绍了python opencv之分水岭算法示例,分享给大家,具体如下: 目标 使用分水岭算法对基于标记的图像进行分割 使用函数cv2.watershed() 原理: 灰度图像可以被看成拓扑平面,灰度值高的区域可以看出山峰,灰度值低的区域可以看成是山谷.向每一个山谷当中灌不同颜色的水.水位升高,不同山谷的水会汇合,为防止不同山谷的水汇合,小在汇合处建立起堤坝.然后继续灌水,然后再建立堤坝,直到山峰都掩模.构建好的堤坝就是图像的分割. 此方法通常会得到过渡分割的结果,因为图像中的噪声以及其他因

  • C++中实现OpenCV图像分割与分水岭算法

    分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征. API介绍 void watershed( InputArray image, InputOutputArray markers ); 参数说明: image: 必须是一个8bit 3通道彩色图像矩阵序列 markers: 在执行分水岭函数watershed之前,必须对第二个参数markers

  • OpenCV-Python使用分水岭算法实现图像的分割与提取

    随着当今世界的发展,计算机视觉技术的应用越来越广泛.伴随着硬件设备的不断升级,构造复杂的计算机视觉应用变得越来越容易了.OpenCV像是一个黑盒,让我们专注于视觉应用的开发,而不必过多的关注基础图象处理的具体细节. 图像分割 了解分水岭算法之前,我们需要了解什么是图像的分割. 在图像的处理过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来.例如,在视频监控中,观测到的是固定背景下的视频内容,而我们对背景本身并无兴趣,感兴趣的是背景中出现的车辆,行人或者其他对象.我们希望将这些对象从视

  • OpenCV图像分割之分水岭算法与图像金字塔算法详解

    目录 前言 一.使用分水岭算法分割图像 1.cv2.distanceTransform()函数 2.cv2.connectedComponents()函数 3.cv2.watershed()函数 二.图像金字塔 1.高斯金字塔向下采样 2.高斯金字塔向上采样 3.拉普拉斯金字塔 4.应用图像金字塔实现图像的分割和融合 前言 主要介绍OpenCV中的分水岭算法.图像金字塔对图像进行分割的方法. 一.使用分水岭算法分割图像 分水岭算法的基本原理为:将任意的灰度图像视为地形图表面,其中灰度值高的部分表

随机推荐