opencv-watershed分水岭算法--图像自动分割法
分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征
其他图像分割方法,如阈值,边缘检测等都不会考虑像素在空间关系上的相似性和封闭性这一概念,彼此像素间互相独立,没有统一性。分水岭算法较其他分割方法更具有思想性,更符合人眼对图像的印象
watershed图像自动分割的实现步骤:
1. 图像灰度化、滤波、Canny边缘检测
2. 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。
3. watershed分水岭运算
4. 绘制分割出来的区域,视觉控还可以使用随机颜色填充,或者跟原始图像融合以下,以得到更好的显示效果
实例
lm.jpg
#include<opencv2/opencv.hpp> #include<iostream> cv::Vec3b RandomColor(int value) //生成随机颜色函数 { value = value % 255; //生成0~255的随机数 cv::RNG rng; int aa = rng.uniform(0, value); int bb = rng.uniform(0, value); int cc = rng.uniform(0, value); return cv::Vec3b(aa, bb, cc); } int main(int argc, char** argv) { cv::Mat image = cv::imread("D:/bb/tu/lm.jpg"); imshow("原图像", image); cv::Mat imageGray; cvtColor(image, imageGray, cv::COLOR_RGB2GRAY);//灰度转换 GaussianBlur(imageGray, imageGray, cv::Size(3, 3), 2); //高斯滤波 Canny(imageGray, imageGray, 80, 150); //边缘检测 std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; findContours(imageGray, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point());//找出轮廓坐标 cv::Mat imageContours = cv::Mat::zeros(image.size(), CV_8UC1); //用来显示轮廓 cv::Mat marks(image.size(), CV_32S); //Opencv分水岭第二个矩阵参数 marks = cv::Scalar::all(0); int index = 0, compCount = 0; for (; index >= 0; index = hierarchy[index][0], compCount++) { //hierarchy[index][0] 表示index轮廓的后一个轮廓的序号--具体看:https://www.cnblogs.com/liming19680104/p/15554815.html //对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点 //marks与imageContours差别就是在颜色的赋值上,marks是不同轮廓赋予不同的值,imageContours是轮廓赋值白色 drawContours(marks, contours, index, cv::Scalar::all(compCount + 1), 1, 8, hierarchy);//绘制轮廓 //注意:不同轮廓颜色不同 drawContours(imageContours, contours, index, cv::Scalar(255), 1, 8, hierarchy); //注意:不同轮廓颜色相同 } watershed(image, marks); //分水岭算法--分割图像 /*第一个参数image:必须是一个8bit 3通道彩色图像矩阵序列 第二个参数markers:它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个 是执行分水岭之前的要求 算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判 断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”, 以做区分 */ //来看一下分水岭算法之后的矩阵marks里是什么东西 cv::Mat afterWatershed; convertScaleAbs(marks, afterWatershed); //位深转化函数,可将任意类型的数据转化为CV_8U //marks是CV_32S 为了显示需要转换一下 imshow("分水岭之后", afterWatershed); //对每一个区域进行颜色填充 cv::Mat PerspectiveImage = cv::Mat::zeros(image.size(), CV_8UC3); for (int i = 0; i < marks.rows; i++) { //maks是分水岭算法之后的区域图 for (int j = 0; j < marks.cols; j++) { int index = marks.at<int>(i, j); if (index == -1) { PerspectiveImage.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 255, 255);//把分界线填充为白色 } else { PerspectiveImage.at<cv::Vec3b>(i, j) = RandomColor(index); } } } imshow("填充之后", PerspectiveImage); //分割并填充颜色的结果跟原始图像融合 cv::Mat wshed; addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed); imshow("融合之后图像", wshed); cv::waitKey(0); return 0; }