1、Grabcut图像分割
代码清单8-21 grabCut()函数原型 void cv::grabCut(InputArray img, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, InputOutputArray fgdModel, int iterCount, int mode = GC_EVAL )
- img:输入的待分割图像,数据类型为CV_8U的三通道图像。
- mask:用于输入、输出的CV_8U单通道掩码图像,图像中像素值的取值范围以及含义在表8-4给出。
- rect:包含对象的ROI区域,该参数仅在mode == GC_INIT_WITH_RECT时使用。
- bgdModel:背景模型的临时数组。
- fgdModel:前景模型的临时数组。
- iterCount:算法需要进行的迭代次数。
- mode:分割模式标志,该参数值可选择范围以及含义在表8-5给出。
该函数实现了GrabCut图像分割算法,函数的第一个参数是待分割的输入图像,要求是CV_8U的三通道彩色图像。第二个参数是掩码矩阵,该参数既用于输入又用于输出,当最后一个参数设置为GC_INIT_WITH_RECT时,该矩阵会被设置为初始掩码,掩码矩阵中具有4个可选择的参数,分别是0(GC_BGD)表示明显为背景的像素、1(GC_FGD)表示明显为前景或者对象的像素、2(GC_PR_BGD)表示疑似背景的像素、3(GC_PR_FGD)表示疑似前景或者对象的像素。最后图像的分割结果也是通过分析掩码矩阵中每个像素的数值进行提取。函数第三个参数是需要进行分割去ROI区域,在ROI区域的外部会被标记为“明显的背景”区域,该参数尽在mode == GC_INIT_WITH_RECT时使用。函数第四个和第五个参数分别是背景模型、前景模型的临时数组,需要注意的是在处理同一图像时,请勿对它进行修改。函数第六个参数是算法进行迭代的次数。函数最后一个参数是分割模式标志,可以选择的参数及其含义在表8-5给出。
为了了解该函数的使用方法以及对图像的分割效果,在代码清单8-22中给出了通过grabCut()函数对图像进行分割的示例程序。程序中首先在原图像中选择ROI矩形区域,之后利用grabCut()函数对该区域分割,计算前景和背景,最后将掩码矩阵中明显是前景和疑似前景的像素点全部输出,程序运行结果如图8-14所示。需要说明的是程序中为了保证绘制矩形框不对图像分割产生影响,在绘制矩形框时对原图像进行了深拷贝。
代码清单8-22 myGrabCut.cpp利用grabCuts方法图像分割 #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main() { Mat img = imread("lena.png"); if (!img.data) //防止错误读取图像 { cout<<"读取图像错误,请确认图像文件是否正确" << endl; return 0; } //绘制矩形 Mat imgRect; img.copyTo(imgRect); //备份图像,方式绘制矩形框对结果产生影响 Rect rect(80, 30, 340, 390); rectangle(imgRect, rect, Scalar(255, 255, 255),2); imshow("选择的矩形区域", imgRect); //进行分割 Mat bgdmod = Mat::zeros(1, 65, CV_64FC1); Mat fgdmod = Mat::zeros(1, 65, CV_64FC1); Mat mask = Mat::zeros(img.size(), CV_8UC1); grabCut(img, mask, rect, bgdmod, fgdmod, 5, GC_INIT_WITH_RECT); //将分割出的前景绘制回来 Mat result; for (int row = 0; row < mask.rows; row++) { for (int col = 0; col < mask.cols; col++) { int n = mask.at<uchar>(row, col); //将明显是前景和可能是前景的区域都保留 if (n == 1 || n == 3) { mask.at<uchar>(row, col) = 255; } //将明显是背景和可能是背景的区域都删除 else { mask.at<uchar>(row, col) = 0; } } } bitwise_and(img, img, result, mask); imshow("分割结果", result); waitKey(0); return 0; }
2、Mean-Shift分割算法
Mean-Shift算法又被称为均值漂移法,是一种基于颜色空间分布的图像分割算法。该算法的输出是一个经过滤色的“分色”图像,其颜色会变得渐变,并且细纹纹理会变得平缓。在Mean-Shift算法中每个像素点用一个五维的向量表示,前两个量是像素点在图像中的坐标,后三个量是每个像素点的颜色分量(蓝、绿、红)。在颜色分布的峰值处开始,通过滑动窗口不断寻找属于同一类的像素点并统一像素点的像素值。滑动窗口由半径和颜色幅度构成,半径决定了滑动窗口的范围,即坐标的范围,颜色幅度决定了半径内像素点分类的标准。这样通过不断地移动滑动窗口,实现基于像素点颜色的图像分割。由于分割后同一类像素点具有相同像素值,因此Mean-Shift算法的输出结果是一个颜色渐变、纹理平缓的图像。
OpenCV 4中提供了实现Mean-Shift算法分割图像的pyrMeanShiftFiltering()函数,该函数的函数原型在代码清单8-23中给出。
代码清单8-23 pyrMeanShiftFiltering()函数原型 void cv::pyrMeanShiftFiltering(InputArray src, OutputArray dst, double sp, double sr, int maxLevel = 1, TermCriteria termcrit = TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 5, 1) 8. )
- src:待分割的输入图像,必须是三通道CU_8U的彩色图像
- dst:分割后的输出图像,与输入图像具有相同的尺寸和数据类型
- sp:滑动窗口的半径
- sr:滑动窗口颜色幅度
- maxLevel:分割金字塔缩放层数
- termcrit:迭代算法终止条件。
该函数基于彩色图像的像素值实现对图像的分割,函数的输出结果是经过颜色分布平滑的图像。经过该函数分割后的图像具有较少的纹理信息,可以利用边缘检测函数Canny()以及连通域查找函数findContours()进行进一步细化分类和处理。函数前两个参数是待分割的输入图像和分割后的输出图像,两个图像具有相同的尺寸并且必须是CV_8U的三通道彩色图像。第三个参数为滑动窗口的半径,第四个参数为滑动窗口的颜色幅度。第五个参数为分割金字塔缩放层数,当参数大于1时构建maxLevel + 1层高斯金字塔。该算法首先在尺寸最小的图像层中进行分类,之后将结果传播到尺寸较大的图像层,并且仅在颜色与上一层颜色差异大于滑动窗口颜色幅度的像素上再次进行分类,从而使得颜色区域的边界更清晰。当分割金字塔缩放层数为0时表示直接在整个原始图像时进行均值平移分割。函数最后一个参数表示算法迭代停止的条件,该参数的数据类型是TermCriteria,该数据类型是OpenCV 4中用于表示迭代算法终止条件的数据类型,在所有涉及到迭代条件的函数中都有该参数,用于表示在满足某些条件时函数将停止迭代并输出结果。TermCriteria变量可以通过TermCriteria()函数进行赋值,该函数的函数原型在代码清单8-24中给出。
代码清单8-24 TermCriteria()函数原型 cv::TermCriteria::TermCriteria(int type, int maxCount, double epsilon )
- type:终止条件的类型标志,可以选择的参数及含义在表8-6中给出。
- maxCount:最大迭代次数或者元素数。
- epsilon:迭代算法停止时需要满足的精度或者参数变化。
该函数可以表示迭代算法的终止条件,主要分为满足迭代次数和满足计算精度两种。函数第一个参数是终止条件的类型标志,其可选参数在表8-6中给出,这几个标志可以互相结合使用,需要注意的是,由于该参数在TermCriteria类中,因此在使用时需要在变量前面添类名前缀。函数第二个参数表示最大迭代次数,在epsilon== TermCriteria::COUNT时发挥作用。函数第三个参数表示停止迭代时需要满足的计算精度,在epsilon== TermCriteria::EPS时发挥作用。
码清单8-25 myPyrMeanShiftFiltering.cpp利用均值漂移法分割图像 #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main() { Mat img = imread("coins.png"); if (!img.data) { cout << "读取图像错误,请确认图像文件是否正确" << endl; return -1; } //分割处理 Mat result1, result2; TermCriteria T10 = TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 10, 0.1); pyrMeanShiftFiltering(img, result1, 20, 40, 2, T10); //第一次分割 pyrMeanShiftFiltering(result1, result2, 20, 40, 2, T10); //第一次分割的结果再次分割 //显示分割结果 imshow("img", img); imshow("result1", result1); imshow("result2", result2); //对图像提取Canny边缘 Mat imgCanny, result1Canny, result2Canny; Canny(img, imgCanny, 150, 300); Canny(result1, result1Canny, 150, 300); Canny(result2, result2Canny, 150, 300); //显示边缘检测结果 imshow("imgCanny", imgCanny); imshow("result1Canny", result1Canny); imshow("result2Canny", result2Canny); waitKey(0); return 0; }
3、分水岭算法
void watershed( InputArray image, InputOutputArray markers );
总的概括一下watershed图像自动分割的实现步骤:
1. 图像灰度化、滤波、Canny边缘检测
2. 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。
3. watershed分水岭运算
4. 绘制分割出来的区域,视觉控还可以使用随机颜色填充,或者跟原始图像融合以下,以得到更好的显示效果。
以下是Opencv分水岭算法watershed实现的完整过程:
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> using namespace cv; using namespace std; Vec3b RandomColor(int value); //生成随机颜色函数 int main( int argc, char* argv[] ) { Mat image=imread(argv[1]); //载入RGB彩色图像 imshow("Source Image",image); //灰度化,滤波,Canny边缘检测 Mat imageGray; cvtColor(image,imageGray,CV_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); //Opencv分水岭第二个矩阵参数 marks=Scalar::all(0); int index = 0; int compCount = 0; for( ; 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); } //我们来看一下传入的矩阵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(); } Vec3b RandomColor(int value) <span style="line-height: 20.8px; font-family: sans-serif;">//生成随机颜色函数</span> { 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,37,算法,参数,图像 From: https://www.cnblogs.com/okmai77xue/p/17306935.html