18- OpenCV:基于距离变换与分水岭的图像分割

目录

1、图像分割的含义

2、常见的图像分割方法

3、距离变换与分水岭介绍

4、相关API

5、代码演示


1、图像分割的含义

图像分割是指将一幅图像划分为若干个具有独立语义的区域或对象的过程。其目标是通过对图像进行像素级别的分类,将图像中不同的区域或对象分离出来,以便进一步分析、处理或理解图像。

简单来说:就是将图像分割成不同的对象,如下图所示,右边将图像的背景和马匹分割开。

(1)图像分割(Image Segmentation)是图像处理最重要的处理手段之一。

(2)图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。(规则也就是不同的算法,算法不同可能会得到不同的结果 )

(3)根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 - KMeans。

(4)图像分割在计算机视觉和图像处理领域具有广泛的应用,例如目标检测、图像编辑、医学影像分析等。它可以帮助我们识别图像中的不同物体、提取感兴趣的区域、分析物体的形状和结构等。

2、常见的图像分割方法

(1)基于阈值的分割:根据像素的灰度值与预先设定的阈值进行比较,将像素分为不同的类别。这种方法简单直观,适用于图像中目标与背景之间有明显差异的情况。

(2)区域生长法:从种子点开始,根据像素之间的相似性逐渐扩展区域,直到满足某个停止准则。该方法适用于图像中存在连续的区域或对象。

(3)边缘检测法:通过检测图像中的边缘信息,将图像分割为不同的区域。常用的边缘检测算法包括Canny边缘检测、Sobel算子等。

(4)基于图论的分割:将图像表示为一个图,通过最小割或最大流等算法将图像分割为多个区域。这种方法可以考虑到像素之间的空间关系和相似性。

(5)基于深度学习的分割:利用深度神经网络模型,如U-Net、Mask R-CNN等,对图像进行像素级别的分类,实现精细的图像分割。

3、距离变换与分水岭介绍

1、距离变换常见算法有两种:

(1)不断膨胀/ 腐蚀得到 ;

(2)基于倒角距离

2、分水岭变换常见的算法:基于浸泡理论实现

4、相关API

1、cv::distanceTransform 距离转换

distanceTransform(

InputArray src,

OutputArray dst, // dst输出8位或者32位的浮点数,单一通道,大小与输入图像一致

OutputArray labels, // 离散维诺图输出

int distanceType, // DIST_L1/DIST_L2,

int maskSize, // 3x3,最新的支持5x5,推荐3x3

int labelType=DIST_LABEL_CCOMP

)

2、cv::watershed 分水岭

cv::watershed(

InputArray image,

InputOutputArray markers

)

5、代码演示

代码流程的主要步骤:

(1)将白色背景变成黑色-目的是为后面的变换做准备

(2)使用filter2D与拉普拉斯算子实现图像对比度提高,sharp

(3)转为二值图像通过threshold

(4)距离变换

(5)对距离变换结果进行归一化到[0~1]之间

(6)使用阈值,再次二值化,得到标记

(7)腐蚀得到每个Peak - erode

(8)发现轮廓 -- findContours

(9)绘制轮廓- drawContours

(10)分水岭变换 watershed

(11)对每个分割区域着色输出结果

cpp 复制代码
#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("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 background
	for (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);

	// sharpen
	Mat 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 binary
	Mat 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 again
	threshold(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 makers
	Mat 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 watershed
	watershed(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 color
	vector<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 result
	Mat 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;
}

效果展示:

(1)加载图像

(2)change background 去背景

(3)Sharp 锐化

(4)二值距离变换 convert to binary

(5)二值腐蚀 Peaks

(6)标记 makers

(7)分水岭变换 perform watershed

(8)着色效果 fill with color

相关推荐
肥猪猪爸29 分钟前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
大白要努力!29 分钟前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
LZXCyrus1 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
我感觉。1 小时前
【机器学习chp4】特征工程
人工智能·机器学习·主成分分析·特征工程
YRr YRr1 小时前
深度学习神经网络中的优化器的使用
人工智能·深度学习·神经网络
DieYoung_Alive1 小时前
一篇文章了解机器学习(下)
人工智能·机器学习
夏沫的梦1 小时前
生成式AI对产业的影响与冲击
人工智能·aigc
goomind2 小时前
YOLOv8实战木材缺陷识别
人工智能·yolo·目标检测·缺陷检测·pyqt5·木材缺陷识别
只怕自己不够好2 小时前
《OpenCV 图像基础操作全解析:从读取到像素处理与 ROI 应用》
人工智能·opencv·计算机视觉
幻风_huanfeng2 小时前
人工智能之数学基础:线性代数在人工智能中的地位
人工智能·深度学习·神经网络·线性代数·机器学习·自然语言处理