Opencv-C++笔记 (19) : 分水岭图像分割

2023-11-29 01:30

本文主要是介绍Opencv-C++笔记 (19) : 分水岭图像分割,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、基于距离变换与分水岭的图像分割
    • 1、图像分割
    • 2、距离和变换与分水岭
      • 距离变换常见算法有两种
      • 分水岭变换常见的算法
    • 3、距离变换API函数接口
    • 4、watershed 分水岭函数API接口
      • 步骤
    • 5、代码

一、基于距离变换与分水岭的图像分割

1、图像分割

图像分割(Image Segmentation)是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。
根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 - KMeans

2、距离和变换与分水岭

距离变换常见算法有两种

1、不断膨胀/ 腐蚀得到
2、基于倒角距离

分水岭变换常见的算法

分水岭法(Meyer)是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。该算法的实现可以模拟成洪水淹没的过程,图像的最低点首先被淹没,然后水逐渐淹没整个山谷。当水位到达一定高度的时候将会溢出,这时在水溢出的地方修建堤坝,重复这个过程直到整个图像上的点全部被淹没,这时所建立的一系列堤坝就成为分开各个盆地的分水岭。分水岭算法对微弱的边缘有着良好的响应,但图像中的噪声会使分水岭算法产生过分割的现象。
————————————————

基于浸泡理论实现

3、距离变换API函数接口

距离变换用于计算图像中每一个非零点像素与其周围最近的零点像素之间的距离,返回的值保存了每一个非零点与最近零点的距离信息;在图像上的体现为图像上越亮的点,代表了离零点的距离越远。

void distanceTransform( 
InputArray src,  
OutputArray dst,
OutputArray labels,
int distanceType,
int maskSize,
int labelType=DIST_LABEL_CCOMP
)

(1)src是单通道的8bit的二值图像(只有0或1)
(2)dst表示的是计算距离的输出图像,可以使单通道32bit浮点数据
(3)distanceType表示的是选取距离的类型,可以设置为
DIST_USER User defined distance
DIST_L1=1 distance = |x1-x2| + |y1-y2
DIST_L2 the simple euclidean distance
DIST_C distance = max(|x1-x2|,|y1-y2|)
DIST_L12 L1-L2 metric: distance =2(sqrt(1+x*x/2) - 1))
DIST_FAIR distance = c^2(|x|/c-log(1+|x|/c)),c = 1.3998
DIST_WELSCH distance = c2/2(1-exp(-(x/c)2)), c= 2.9846
DIST_HUBER distance = |x|<c ? x^2/2 :c(|x|-c/2), c=1.345
(4)maskSize表示的是距离变换的掩膜模板,可以设置为3,5或CV_DIST_MASK_PRECISE,对 CV_DIST_L1 或CV_DIST_C 的情况,参数值被强制设定为 3, 因为3×3 mask 给出5×5 mask 一样的结果,而且速度还更快。
DIST_MASK_3 mask=3
DIST_MASK_5 mask=5
DIST_MASK-PRECISE
(5)labels表示可选输出2维数组;
(6)labelType表示的是输出二维数组的类型,8位或者32位浮点数,图像是单一通道,并且大小与输入图像一致

4、watershed 分水岭函数API接口

void watershed( InputArray image, InputOutputArray markers );

参数说明

(1)参数 image,必须是一个8bit3通道彩色图像矩阵序列。
(2) 输入或输出32位单通道的标记,和图像一样大小。(输入高峰轮廓标记);在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。

算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。


步骤

1、将白色背景变成黑色-目的是为后面的变换做准备
2、使用filter2D与拉普拉斯算子实现图像对比度提高,sharp(锐化)
3、转为二值图像通过threshold
4、距离变换
5、对距离变换结果进行归一化到[0~1]之间
6、使用阈值,再次二值化,得到标记
7、腐蚀得到每个Peak - erode
8、发现轮廓 – findContours
9、绘制轮廓- drawContours
10、分水岭变换 watershed
11、对每个分割区域着色输出结果
————————————————

5、代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>using namespace std;
using namespace cv;int main(int argc, char** argv) {char input_win[] = "input image";char watershed_win[] = "watershed segmentation demo";Mat src = imread("D:/vcprojects/images/cards.png");// Mat src = imread("D:/kuaidi.jpg");if (src.empty()) {printf("could not load image...\n");return -1;}namedWindow(input_win, CV_WINDOW_AUTOSIZE);imshow(input_win, src);// 1. change backgroundfor (int row = 0; row < src.rows; row++) {for (int col = 0; col < src.cols; col++) {if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {src.at<Vec3b>(row, col)[0] = 0;src.at<Vec3b>(row, col)[1] = 0;src.at<Vec3b>(row, col)[2] = 0;}}}namedWindow("black background", CV_WINDOW_AUTOSIZE);imshow("black background", src);// sharpenMat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);Mat imgLaplance;Mat sharpenImg = src;filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);src.convertTo(sharpenImg, CV_32F);Mat resultImg = sharpenImg - imgLaplance;resultImg.convertTo(resultImg, CV_8UC3);imgLaplance.convertTo(imgLaplance, CV_8UC3);imshow("sharpen image", resultImg);// src = resultImg; // copy back// convert to binaryMat binaryImg;cvtColor(src, resultImg, CV_BGR2GRAY);threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU);imshow("binary image", binaryImg);Mat distImg;distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);normalize(distImg, distImg, 0, 1, NORM_MINMAX);imshow("distance result", distImg);// binary againthreshold(distImg, distImg, .4, 1, THRESH_BINARY);Mat k1 = Mat::ones(13, 13, CV_8UC1);erode(distImg, distImg, k1, Point(-1, -1));imshow("distance binary image", distImg);// markers Mat dist_8u;distImg.convertTo(dist_8u, CV_8U);vector<vector<Point>> contours;findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));// create makersMat markers = Mat::zeros(src.size(), CV_32SC1);for (size_t i = 0; i < contours.size(); i++) {drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1);}circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);imshow("my markers", markers*1000);// perform watershedwatershed(src, markers);Mat mark = Mat::zeros(markers.size(), CV_8UC1);markers.convertTo(mark, CV_8UC1);bitwise_not(mark, mark, Mat());imshow("watershed image", mark);// generate random colorvector<Vec3b> colors;for (size_t i = 0; i < contours.size(); i++) {int r = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int b = theRNG().uniform(0, 255);colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));}// fill with color and display final resultMat dst = Mat::zeros(markers.size(), CV_8UC3);for (int row = 0; row < markers.rows; row++) {for (int col = 0; col < markers.cols; col++) {int index = markers.at<int>(row, col);if (index > 0 && index <= static_cast<int>(contours.size())) {dst.at<Vec3b>(row, col) = colors[index - 1];}else {dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);}}}imshow("Final Result", dst);waitKey(0);return 0;
}

输入原图像和锐化图像
在这里插入图片描述

原图和黑背景图(背景应为黑色)
在这里插入图片描述

threshold转化的二值化图片和距离变换结果图
在这里插入图片描述

距离变换结果图和二值化图像
在这里插入图片描述

这篇关于Opencv-C++笔记 (19) : 分水岭图像分割的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/430953

相关文章

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以