Advertisement

opencv(c++)------图像分割(阙值、自适应阙值、grabCut、floodfill、wathershed)

阅读量:

The text describes various image segmentation techniques, including threshold-based methods (e.g., binary, adaptive thresholding), edge-based methods (e.g., Sobel, Canny operators), region-based methods (grabCut, floodFill), and watershed-based segmentation. Each method is accompanied by code examples and explanations of their underlying principles and applications. The text emphasizes the importance of choosing the appropriate segmentation method based on the specific characteristics of the image and the desired outcome.

图像分割

图像分割算法主要包含以下几种类型:以阈值为基础的分割方法、以边缘为基础的分割方法、以区域为基础的分割方法、利用神经网络进行分割的方法以及基于聚类的分割方法,共计五种类型。

1. 基于阙值的分割方法

该分割方法主要依赖于图像灰度直方图信息来获取用于分割的阈值。

在这里插入图片描述

1.1 固定阈值分割

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
复制代码
    #include<iostream>
    #include<opencv.hpp>
    using namespace cv;
    using namespace std;
    
    int main() {
    	Mat img;
    	img=imread("丝丝.jpg");
    	cvtColor(img,img,COLOR_BGR2GRAY);
    	Mat timg,timg2,timg3,timg4,timg5,timg6,timg7,timg8;
    	threshold(img,timg,100,255,THRESH_BINARY);
    	threshold(img, timg2, 100, 255, THRESH_BINARY_INV);
    	threshold(img, timg3, 100, 255, THRESH_OTSU);
    	threshold(img, timg4, 100, 255, THRESH_TOZERO);
    	threshold(img, timg5, 100, 255, THRESH_TOZERO_INV);
    	threshold(img, timg6, 100, 255, THRESH_TRUNC);
    	threshold(img, timg7, 100, 255, THRESH_MASK);
    	threshold(img, timg8, 100, 255, THRESH_TRIANGLE);
    	imshow("二值化阙值",timg);
    	imshow("二值化反阙值",timg2);
    	imshow("自适应二值化", timg3);
    	imshow("二值化0阙值",timg4);
    	imshow("二值化反0阙值", timg5);
    	imshow("THRESH_TRUNC", timg6);
    	imshow("THRESH_MASK", timg7);
    	imshow("THRESH_TRIANGLE", timg8);
    	
    	waitKey(0);
    }

1.2. 自适应阈值

自适应阈值

复制代码
    void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue,
                      int adaptiveMethod, int thresholdType, int blockSize, double C)

以下是改写后的文本

复制代码
    #include<iostream>
    #include<opencv.hpp>
    using namespace cv;
    using namespace std;
    
    int main() {
    	Mat img,gray;
    	img=imread("build.jpg");
    	imshow("原图", img);
    	cvtColor(img,gray,COLOR_RGB2GRAY);
    	imshow("灰度图", gray);
    	Mat thimg;
    	threshold(gray, thimg,100, 255, THRESH_BINARY);
    	imshow("阈值图",thimg);
    	Mat adimg;
    	adaptiveThreshold(gray, adimg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY,5 , 3);
    	imshow("自定义阈值图",adimg);
    	waitKey(0);
    
    }

2. 彩色图像分割

HSV:色调、饱和度、亮度。

**inrange()**具备二值化功能,可用于单通道和三通道图像彩图的二值化处理。**threshould()**仅适用于灰度图的二值化处理,不适用于彩色图像的二值化。

复制代码
    void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst)
在这里插入图片描述
复制代码
    #include<iostream>
    #include<opencv.hpp>
    using namespace cv;
    using namespace std;
    
    int main() {
    	Mat img=imread("219.jpg");
    	imshow("原图", img);
    	Mat hsv;
    	cvtColor(img, hsv, COLOR_RGB2HSV);
    	imshow("hsv", hsv);
    	Mat outimg;
    	inRange(hsv,Scalar(0,0,106),Scalar(180,255,255),outimg);
    	imshow("阙值图",outimg);
    
    	waitKey(0);
    
    
    }

案例:

复制代码
    #include <opencv.hpp>
    #include <iostream>
    using namespace std;
    using namespace cv;
    
    //输入图像
    Mat img;
    //灰度值归一化
    Mat bgr;
    //HSV图像
    Mat hsv;
    
    //色相
    int hmin = 0;
    int hmin_count = 360;
    int hmax = 180;
    int hmax_count = 180;
    
    //饱和度
    int smin = 0;
    int smin_count = 255;
    int smax = 255;
    int smax_count = 255;
    
    //亮度
    int vmin = 106;
    int vmin_count = 255;
    int vmax = 255;
    int vmax_count = 255;
    
    //输出图像
    Mat dst;
    
    //回调函数
    void callBack(int, void*)
    {
    	//输出图像分配内存
    	dst = Mat::zeros(img.size(), img.type());
    	//掩码
    	Mat mask;
    	inRange(hsv, Scalar(hmin, smin, vmin), Scalar(hmax, smax, vmax), mask);
    	//掩模到原图的转换
    	for (int r = 0; r < bgr.rows; r++)
    	{
    		for (int c = 0; c < bgr.cols; c++)
    		{
    			if (mask.at<uchar>(r, c) == 255)
    			{
    				dst.at<Vec3b>(r, c) = bgr.at<Vec3b>(r, c);
    			}
    		}
    	}
    	//输出图像
    	imshow("dst", dst);
    }
    
    int main(int argc, char** argv)
    {
    	//输入图像
    	img = imread("test.jpg");
    	imshow("原图", img);
    	if (!img.data || img.channels() != 3)
    		return -1;
    	bgr = img.clone();
    	//颜色空间转换
    	cvtColor(bgr, hsv, COLOR_BGR2HSV);
    	//定义输出图像的显示窗口
    	namedWindow("dst", WINDOW_GUI_EXPANDED);
    	//调节色相 H
    	createTrackbar("hmin", "dst", &hmin, hmin_count, callBack);
    	createTrackbar("hmax", "dst", &hmax, hmax_count, callBack);
    
    	//调节饱和度 S
    	createTrackbar("smin", "dst", &smin, smin_count, callBack);
    	createTrackbar("smax", "dst", &smax, smax_count, callBack);
    
    	//调节亮度 V
    	createTrackbar("vmin", "dst", &vmin, vmin_count, callBack);
    	createTrackbar("vmax", "dst", &vmax, vmax_count, callBack);
    
    	callBack(0, 0);
    
    	waitKey(0);
    	return 0;
    }

3. 基于边缘的分割方法

常用的边缘检测算法包括:roberts算子、laplace算子、sobel算子、prewitt算子、kirsch算子、Log算子、Canny算子等。这些算法在图像处理领域具有广泛的应用,其中canny算子以其高精度的边缘检测效果而备受推崇。具体方法可参考本人博客:opencv(c++)图像滤波器模块----锐化滤波总结。

代码演示:

复制代码
    #include<iostream>
    #include<opencv.hpp>
    using namespace cv;
    using namespace std;
    
    int main() {
    	Mat img, gray_img;
    	img=imread("219.jpg");
    	imshow("原图", img);
    
    	GaussianBlur(img, img, Size(3, 3), 0, 0);
    	cvtColor(img, gray_img, COLOR_RGB2GRAY);
    	imshow("高斯滤波图", gray_img);
    
    	Mat sobelx, sobely, dst;
    	Sobel(gray_img, sobelx,CV_16S, 1, 0, 3,1,1,BORDER_DEFAULT);
    	Sobel(gray_img, sobely, CV_16S, 1, 0, 3,1,1,BORDER_DEFAULT);
    	convertScaleAbs(sobelx, sobelx);
    	convertScaleAbs(sobely, sobely);
    	addWeighted(sobelx, 0.5, sobely, 0.5, 0, dst);//0为偏移
    	imshow("sobel", dst);
    
    
    	Mat scharrx, scharry;
    	Scharr(gray_img,scharrx,CV_16S,1,0,1.0,1,BORDER_DEFAULT);//x或y方向梯度,求偏导一阶偏导
    	Scharr(gray_img, scharry, CV_16S, 0, 1, 1.0, 1, BORDER_DEFAULT);
    	convertScaleAbs(scharrx, scharrx);
    	convertScaleAbs(scharry, scharry);
    	addWeighted(scharrx, 0.5, scharry, 0.5, 0, dst);
    	imshow("scharr",dst);
    
    	Mat canny_gray;
    	Canny(gray_img,canny_gray,100,255,3,false);
    	imshow("canny", canny_gray);
    
    	Mat lp;
    	Laplacian(gray_img, lp, -1, 3);
    	imshow("laplacian", lp);
    
    	waitKey(0);
    }

4. grabCut算法分割图像

grabcut算法基于图像的明暗度和细节信息,实现了最佳前景与背景分割。然而,其计算效率相对较低,输出结果具有较高的准确性。

GrabCut函数说明:该算法通过多级分割策略实现图像背景的精确分离,其核心原理在于结合区域生长与边界检测的动态平衡。函数原型:GrabCut的函数结构基于图割模型,通过迭代优化实现目标区域的精确提取。

复制代码
    void cv::grabCut( const Mat& img, Mat& mask, Rect rect,
             Mat& bgdModel, Mat& fgdModel,
             int iterCount, int mode )

img ——待分割的源图像,必须为8位、3通道(CV_8UC3)的图像,在处理过程中,源图像不会被修改;
mask ——掩膜图像,其作用是初始化掩膜信息。若采用用户交互设定前景与背景的方式,则需将相关信息存储在mask中,随后传递给grabCut函数。处理结束后,mask将保存分割结果。mask仅能取以下四种预定义的值:
背景(GCD_BGD,值为0);
前景(GCD_FGD,值为1);
可能的背景(GCD_PR_BGD,值为2);
可能的前景(GCD_PR_FGD,值为3)。
若未进行手动标记(GCD_BGD或GCD_FGD),则分割结果将仅包含(GCD_PR_BGD或GCD_PR_FGD);
rect ——限定分割区域的矩形窗口,仅该区域内的图像部分将被处理;
bgdModel ——背景模型,若参数为null,函数将自动创建一个bgdModel。该模型必须为单通道浮点型(CV_32FC1)图像,且其尺寸限定为1行×13列×5行;
fgdModel ——前景模型,若参数为null,函数将自动创建一个fgdModel。该模型必须为单通道浮点型(CV_32FC1)图像,且其尺寸限定为1行×13列×5行;
iterCount ——迭代次数,必须为正整数;
mode ——grabCut函数的操作模式,可选值包括:
GC_INIT_WITH_RECT(=0),通过矩形窗口进行初始化;
GC_INIT_WITH_MASK(=1),通过掩膜图像进行初始化;
GC_EVAL(=2),执行分割操作。

GrabCut的使用方法如下:
首先,通过矩形窗口或掩膜图像对grabCut进行初始化;
随后,执行分割操作;
若对结果不理想,可在掩膜图像中指定前景或背景,再进行分割;
最后,基于掩膜图像中的前景或背景信息完成分割任务。

为了将图像标记为前景区域,我们可以使用compare操作,将两个矩阵叠加生成一个掩码矩阵。 compare操作会对两个图像进行逐像素比较。

在这里插入图片描述
复制代码
    #include<opencv.hpp>
    #include <iostream>
    using namespace std;
    using namespace cv;
    
    int main() {
    	Mat src = imread("test1.jpg");
    	imshow("原图",src);
    	Mat mask,bg,fg,mask1;
    	Rect rect(84, 84, 450, 318);
    	grabCut(src,mask,rect,bg,fg,1,0);
    	compare(mask,GC_PR_FGD,mask1,CMP_EQ);
    	imshow("result", mask1);
    	Mat foreground(src.size(), src.type(),Scalar(255,255,255));
    	imshow("foreground1", foreground);
    	src.copyTo(foreground, mask1);//copyTo有两种形式,此形式表示result为mask
    	imshow("foreground", foreground);
    	waitKey(0);
    
    	waitKey(5000);
    }

5. floodfill漫水填充分割

floodFill算法,即漫水填充法,是图像处理中一种常用的区域填充方式。它通过自动识别与种子像素相连的区域,并采用指定的颜色对这些区域进行填充,从而实现对图像部分的标记或区分。这种技术不仅能够有效标记图像区域,还能通过调整可连通像素的上下限以及连通方式,获得多样化的填充效果。简单来说,该方法首先自动识别与起始点相连的区域,随后将该区域的所有像素替换为指定的颜色,从而实现对目标区域的填充。

复制代码
    void FloodFill( CvArr* image, 
    				CvPoint seed_point, 
    				CvScalar new_val,
                CvScalar lo_diff=cvScalarAll(0), 
                CvScalar up_diff=cvScalarAll(0),
                CvConnectedComp* comp=NULL, 
                int flags=4, CvArr* mask=NULL );
复制代码
    void floodFill(InputOutputArray image, 
              InputOutputArray mask,
              Point seedPoint,
              Scalar newVal,
              CV_OUT Rect* rect=0,
              Scalar loDiff = Scalar(),
              Scalar upDiff = Scalar(),
              int flags = 4 );

输入图像:输入的1-或3-通道、8位或浮点数图像。函数的操作会修改输入图像,除非选择CV_FLOODFILL_MASK_ONLY选项(见下文)。
起始种子点:起始的种子点。
新像素值:新的重新绘制像素值。
低差值:当前像素与其领域像素或待加入该领域的种子像素的负差(较低差值)的最大值。对8位彩色图像,它是一个压缩值。
高差值:当前像素与其领域像素或待加入该领域的种子像素的正差(较高差值)的最大值。对8位彩色图像,它是一个压缩值。
结构体指针:指向用于填充重绘区域的结构体指针。该结构体由函数用重绘区域的信息填充。
操作选项:函数的操作选项。低位比特包含连通值(默认为4或8),高位比特可以是0或以下开关选项的组合:

  • CV_FLOODFILL_FIXED_RANGE:如果设置,则考虑当前像素与种子像素之间的差,否则考虑当前像素与其相邻像素的差(范围为浮点数)。
  • CV_FLOODFILL_MASK_ONLY:如果设置,则函数不填充原始图像(忽略新像素值),但填充掩模图像(此时掩模必须非空)。
    掩模:运算掩模,应为单通道、8位图像,其大小比输入图像image大两个像素点。若非空,则函数使用并更新掩模,因此用户需自行初始化掩模内容。填充操作不会经过掩模的非零像素。例如,使用边缘检测输出作为掩模可以阻止填充边缘。在多次函数调用中使用同一掩模可防止填充区域重叠。注意:由于掩模比目标图像大,mask中与输入图像(x,y)像素点对应的点具有(x+1,y+1)坐标。
复制代码
    #include<opencv.hpp>
    #include <iostream>
    using namespace std;
    using namespace cv;
    
    int main() {
    	Mat src = imread("test2.jpg");
    	imshow("【原始图】", src);
    	Rect rect;
    	floodFill(src,Point(50,300),Scalar(155,255,55),&rect,Scalar(20,20,20),Scalar(20,20,20),8);
    	imshow("【效果图】", src);
    
    	waitKey(0);
    }

6. 分水岭(wathershed)分割法

分水岭概念是基于图像进行三维可视化处理,同时结合地形学进行解释。我们需要明确一个点的位置信息及其灰度级。考虑三种情况:

属于局部极小点,可能位于一个最小值平面上,该平面上的所有点均为局部极小点。
当水从任一点释放时,最终会下落到一个唯一的最低点。
当水处于边缘时,水会等概率流向多个这样的最低点,满足条件2的点的集合被称为汇水盆地或分水岭。
满足条件3的点的集合构成了地形表面的分水岭线,通常称为分割线或分水线。
分水岭算法对噪声极为敏感,容易导致过度分割。在OpenCV中,分水岭算法通过在原算法基础上增加一步预处理步骤来改善这一问题。

在这里插入图片描述
复制代码
    #include<opencv.hpp>
    #include <iostream>
    using namespace std;
    using namespace cv;
    
    Vec3b RandomColor(int value)  //生成随机颜色函数 
    {
    	value = value % 255;  //生成0~255的随机数
    	RNG rng;
    	int aa = rng.uniform(0, value);
    	int bb = rng.uniform(0, value);
    	int cc = rng.uniform(0, value);
    	return Vec3b(aa, bb, cc);
    }
    
    int main() {
    	Mat image = imread("test2.jpg");    //载入RGB彩色图像
    	imshow("Source Image", image);
    
    	//灰度化,滤波,Canny边缘检测
    	Mat imageGray;
    	cvtColor(image, imageGray, COLOR_RGB2GRAY);//灰度转换
    	GaussianBlur(imageGray, imageGray, Size(5, 5), 2);   //高斯滤波
    	imshow("Gray Image", imageGray);
    	Canny(imageGray, imageGray, 80, 150);
    	imshow("Canny Image", imageGray);
    
    	//查找轮廓
    	vector<vector<Point>> contours;
    	vector<Vec4i> hierarchy;
    	findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    	Mat imageContours = Mat::zeros(image.size(), CV_8UC1);  //轮廓	
    	Mat marks(image.size(), CV_32S,Scalar(0,0,0));   //Opencv分水岭第二个矩阵参数
    	int compCount = 0;
    	for (int index=0; index >= 0; index = hierarchy[index][0], compCount++)
    	{
    		//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点
    		drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);
    		drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);
    	}
    	imshow("显示轮廓", imageContours);
    
    	//我们来看一下传入的矩阵marks里是什么东西
    	Mat marksShows;
    	convertScaleAbs(marks, marksShows);
    	imshow("marksShow", marksShows);
    	imshow("轮廓", imageContours);
    	watershed(image, marks);//分水岭
    
    	//我们再来看一下分水岭算法之后的矩阵marks里是什么东西
    	Mat afterWatershed;
    	convertScaleAbs(marks, afterWatershed);
    	imshow("After Watershed", afterWatershed);
    
    	//对每一个区域进行颜色填充
    	Mat PerspectiveImage = Mat::zeros(image.size(), CV_8UC3);
    	for (int i = 0; i < marks.rows; i++)
    	{
    		for (int j = 0; j < marks.cols; j++)
    		{
    			int index = marks.at<int>(i, j);
    			if (marks.at<int>(i, j) == -1)
    			{
    				PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
    			}
    			else
    			{
    				PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);
    			}
    		}
    	}
    	imshow("After ColorFill", PerspectiveImage);
    	//分割并填充颜色的结果跟原始图像融合
    	Mat wshed;
    	addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);
    	imshow("AddWeighted Image", wshed);
    
    	waitKey(5000);
    }

全部评论 (0)

还没有任何评论哟~