本文共 4319 字,大约阅读时间需要 14 分钟。
分水岭算法(Watershed Algorithm)是一种经典的数学形态学分割方法。其核心思想是将灰度图像转换为梯度图像,将梯度变化视为高低起伏的山岭,将局部极小值及其邻域视为“集水盆”。通过对图像进行仿射,计算水位上升并最终确定分割线,从而将图像分割为多个连通区域。
传统分水岭算法在处理真实图像时,常常会因噪声或干扰因素的存在而产生过度分割。例如,许多微小的局部极值点会被错误地识别为分割线,导致分割效果毫无实用价值。
为了解决过度分割问题,研究者提出了基于标记(Mark)的分水岭算法。通过人工或计算标记,可以为分水岭算法提供先验知识,指导分割过程。标记图像通常定义灰度层级,确保洪水淹没过程从特定高度开始,从而避免对微小噪声区域进行分割。
OpenCV提供了分水岭算法的接口函数watershed
,该函数接受输入图像和标记图像。标记图像由轮廓组成,每个轮廓有唯一编号,通常通过findContours
函数生成。分水岭算法从标记点开始,根据梯度信息对图像进行分割,区域分界处标记为-1以区分不同区域。
以下是基于OpenCV的分水岭算法实现代码示例:
#include#include #include #include using namespace cv;using namespace std;static void help() { cout << "\nThis program demonstrates the famous watershed segmentation algorithm in OpenCV: watershed()\n" << "Usage:\n" << "./watershed [image_name -- default is ../data/fruits.jpg]\n" << endl; cout << "Hot keys:\n" << "\tESC - quit the program\n" << "\tr - restore the original image\n" << "\tw or SPACE - run watershed segmentation algorithm\n" << "\t\t(before running it, roughly mark the areas to segment on the image)\n" << "\t (before that, roughly outline several markers on the image)\n";}Mat markerMask, img;Point prevPt(-1, -1);static void onMouse(int event, int x, int y, int flags, void*) { if (x < 0 || x >= img.cols || y < 0 || y >= img.rows) return; if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) prevPt = Point(-1, -1); else if (event == EVENT_LBUTTONDOWN) prevPt = Point(x, y); else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) { Point pt(x, y); if (prevPt.x < 0) prevPt = pt; line(markerMask, prevPt, pt, Scalar::all(255), 1, 8, 0); line(img, prevPt, pt, Scalar::all(255), 1, 8, 0); prevPt = pt; imshow("image", img); }}int main(int argc, char** argv) { cv::CommandLineParser parser(argc, argv, "{help h | | }{@input | I:/Learning-and-Practice/2019Change/Image process algorithm/Img/lung2.jpeg | }"); if (parser.has("help")) { help(); return 0; } string filename = parser.get ("@input"); Mat img0 = imread(filename, 1), imgGray; if (img0.empty()) { cout << "couldn't open image " << filename << ". Usage: watershed \n"; return 0; } help(); namedWindow("image", 1); img0.copyTo(img); cvtColor(img, markerMask, COLOR_BGR2GRAY); cvtColor(markerMask, imgGray, COLOR_GRAY2BGR); markerMask = Scalar::all(0); imshow("image", img); setMouseCallback("image", onMouse, 0); for (;;) { char c = waitKey(0); if (c == 27) break; if (c == 'r') { markerMask = Scalar::all(0); img0.copyTo(img); imshow("image", img); } if (c == 'w' || c == ' ') { vector contours; vector hierarchy; findContours(markerMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE); if (contours.empty()) continue; Mat markers(markerMask.size(), CV_32S); markers = Scalar::all(0); int idx = 0; for (; idx >= 0; idx = hierarchy[idx][0], compCount++) { drawContours(markers, contours, idx, Scalar::all(compCount + 1), -1, 8, hierarchy, INT_MAX); if (compCount == 0) continue; } vector colorTab; for (int i = 0; i < compCount; i++) { int b = theRNG().uniform(0, 255); int g = theRNG().uniform(0, 255); int r = theRNG().uniform(0, 255); colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r)); } double t = (double)getTickCount(); Mat tmp = Mat::zeros(markers.size(), CV_8U); markers.convertTo(tmp, CV_8U, 255); imshow("markers", tmp); watershed(img0, markers); t = (double)getTickCount() - t; printf("execution time = %gms\n", t * 1000. / getTickFrequency()); Mat wshed(markers.size(), CV_8UC3); for (int i = 0; i < markers.rows; i++) { for (int j = 0; j < markers.cols; j++) { int index = markers.at (i, j); if (index == -1) { wshed.at (i, j) = Vec3b(255, 255, 255); } else if (index <= 0 || index > compCount) { wshed.at (i, j) = Vec3b(0, 0, 0); } else { wshed.at (i, j) = colorTab[index - 1]; } } } wshed = wshed * 0.5 + imgGray * 0.5; imshow("watershed transform", wshed); } } return 0;}
基于标记的分水岭算法能够显著减少过度分割现象,生成更稳健、实用的图像分割结果。以下是分割前后的对比:
转载地址:http://urrfk.baihongyu.com/