做形态学方法的团队_图像分割实战-分水岭分割方法和GrabCut 算法
1. 分水嶺分割方法
它是依賴于形態(tài)學(xué)的,圖像的灰度等級不一樣,如果圖像的灰度等級一樣的情況下怎么人為的把它造成不一樣?可以通過距離變換實(shí)現(xiàn),這樣它們的灰度值就有了階梯狀的變換。風(fēng)水嶺算法常見的有三種方法:(1)基于浸泡理論的分水嶺分割方法;(2)基于連通圖方法;(3)基于距離變換的方法。OpenCV 中是基于距離變換的分割方法,就相當(dāng)于我們的小山頭(認(rèn)為造成的)。
基本的步驟:
例子1 粘連對象分離和計(jì)數(shù)。
例子代碼:
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; void test() {Mat srcImg;srcImg = imread("pill_002.png");if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", srcImg);Mat grayImg, binaryImg, shiftedImg;//做濾波,使圖像更加平滑,保留邊緣,類似于雙邊濾波pyrMeanShiftFiltering(srcImg, shiftedImg, 21, 51); namedWindow("shifted", CV_WINDOW_AUTOSIZE);imshow("shifted", shiftedImg);cvtColor(shiftedImg, grayImg, COLOR_BGR2GRAY); //轉(zhuǎn)為灰度圖像//二值化threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU); namedWindow("binary", CV_WINDOW_AUTOSIZE);imshow("binary", binaryImg);//距離變換Mat distImg;distanceTransform(binaryImg, distImg, DistanceTypes::DIST_L2, 3, CV_32F);//歸一化,因?yàn)榫嚯x變換后得出來的值都比較小。normalize(distImg, distImg, 0, 1, NORM_MINMAX); namedWindow("distance", CV_WINDOW_AUTOSIZE);imshow("distance", distImg);//這個(gè)二值化的作用是尋找局部最大。threshold(distImg, distImg, 0.4, 1, THRESH_BINARY);namedWindow("distance_binary", CV_WINDOW_AUTOSIZE);imshow("distance_binary", distImg);//生成 markerMat distMaskImg;// distImg 得到的是 0- 1之間的數(shù),轉(zhuǎn)化成8位單通道的。distImg.convertTo(distMaskImg, CV_8U); vector<vector<Point>>contours;//找到 marker 的輪廓findContours(distMaskImg, contours, RETR_EXTERNAL,CHAIN_APPROX_SIMPLE, Point(0, 0));//create marker 填充 markerMat markersImg = Mat::zeros(srcImg.size(), CV_32SC1);for (int i = 0; i < contours.size(); i++){drawContours(markersImg, contours, static_cast<int>(i),Scalar::all(static_cast<int>(i)+1), -1); }circle(markersImg, Point(5, 5), 3, Scalar(255), -1);//形態(tài)學(xué)操作 - 彩色圖像,目的是去掉干擾,讓結(jié)果更好。Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));morphologyEx(srcImg, srcImg, MORPH_ERODE, kernel);//完成分水嶺變換watershed(srcImg, markersImg);Mat mark = Mat::zeros(markersImg.size(), CV_8UC1);markersImg.convertTo(mark, CV_8UC1);bitwise_not(mark, mark, Mat());namedWindow("watershed", CV_WINDOW_AUTOSIZE);imshow("watershed", mark);//下面的步驟可以不做,最好做出來讓結(jié)果顯示更美觀。//生成隨機(jī)顏色vector<Vec3b>colors;for (int 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));}//顏色填充和最終顯示Mat dstImg = Mat::zeros(markersImg.size(), CV_8UC3);int index = 0;for (int i = 0; i < markersImg.rows; i++){for (int j = 0; j < markersImg.cols; j++){index = markersImg.at<int>(i, j);if (index > 0 && index <= contours.size()){dstImg.at<Vec3b>(i, j) = colors[index - 1];}else{dstImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);}}}cout << "number of objects:" << contours.size() << endl;namedWindow("Final Result", CV_WINDOW_AUTOSIZE);imshow("Final Result", dstImg); } int main() {test();waitKey(0);return 0; }效果:
總結(jié):有時(shí)候會導(dǎo)致碎片化,過度分割,因?yàn)槎祷腥绻泻芏嘈〉暮邳c(diǎn)或碎片,在分割的時(shí)候?qū)е潞芏?mask ,即小山頭太多了,這個(gè)時(shí)候我們要考慮怎么去合并它,可以通過聯(lián)通區(qū)域的直方圖,或者像素值均值相似程度等。
例子2:圖像分割
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; //執(zhí)行分水嶺算法函數(shù) Mat watershedCluster(Mat &srcImg, int &numSegments); //結(jié)果顯示函數(shù) void DisplaySegments(Mat &markersImg, int numSegments); void test() {Mat srcImg;srcImg = imread("toux.jpg");if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", srcImg);int numSegments;Mat markers = watershedCluster(srcImg, numSegments);DisplaySegments(markers, numSegments); }Mat watershedCluster(Mat &srcImg, int &numSegments) {//二值化Mat grayImg, binaryImg;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU);//形態(tài)學(xué)和距離變換Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));morphologyEx(binaryImg, binaryImg, MORPH_OPEN, kernel, Point(-1, -1));Mat distImg;distanceTransform(binaryImg, distImg, DistanceTypes::DIST_L2, 3, CV_32F);normalize(distImg, distImg, 0.0, 1.0, NORM_MINMAX);//開始生成標(biāo)記threshold(distImg, distImg, 0.1, 1.0, THRESH_BINARY);normalize(distImg, distImg, 0, 255, NORM_MINMAX);distImg.convertTo(distImg, CV_8UC1); //CV_32F 轉(zhuǎn)成 CV_8UC1//標(biāo)記開始vector<vector<Point>>contours;vector<Vec4i>hireachy;findContours(distImg, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);if (contours.empty()){return Mat();}Mat markersImg(distImg.size(), CV_32S);markersImg = Scalar::all(0);for (int i = 0; i < contours.size(); i++){drawContours(markersImg, contours, i, Scalar(i + 1), -1, 8, hireachy, INT_MAX);}circle(markersImg, Point(5, 5) ,3, Scalar(255), -1);//分水嶺變換watershed(srcImg, markersImg);numSegments = contours.size();return markersImg; }void DisplaySegments(Mat &markersImg, int numSegments) {//生成隨機(jī)顏色vector<Vec3b>colors;for (int i = 0; i < numSegments; 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));}//顏色填充和最終顯示Mat dstImg = Mat::zeros(markersImg.size(), CV_8UC3);int index = 0;for (int i = 0; i < markersImg.rows; i++){for (int j = 0; j < markersImg.cols; j++){index = markersImg.at<int>(i, j);if (index > 0 && index <= numSegments){dstImg.at<Vec3b>(i, j) = colors[index - 1];}else{dstImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);}}}cout << "number of objects:" << numSegments << endl;namedWindow("Final Result", CV_WINDOW_AUTOSIZE);imshow("Final Result", dstImg); } int main() {test();waitKey(0);return 0; }效果圖:
2. GrabCut 算法分割圖像
GrabCut 算法的原理前面有介紹過,這里就不在介紹了,具體可以看下文章末尾往期推薦中閱讀。下面例子實(shí)現(xiàn)圖像中對象的摳圖。
基本步驟:
例子代碼:
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; int numRun = 0; //算法迭代次數(shù) bool init = false; Rect rect; Mat srcImg, MaskImg, bgModel, fgModel;//鼠標(biāo)回調(diào)函數(shù) void onMouse(int event, int x, int y, int flags, void* param); void showImg(); //顯示畫的圖片 void setRoiMask(); //選擇 ROI 的函數(shù) void runGrabCut(); //執(zhí)行算法函數(shù) static void ShowHelpText(); //提示用戶操作函數(shù)void test() {srcImg = imread("toux.jpg");if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", srcImg);//初始化 mask,單通道 8 位MaskImg.create(srcImg.size(), CV_8UC1); //在不知道它是前景還是背景的情況下,把它全部設(shè)為背景。MaskImg.setTo(Scalar::all(GC_BGD)); //結(jié)果不是 0 就是 1 GC_BGD為0setMouseCallback("Original image", onMouse, 0);while (true){char c = (char)waitKey(0);if (c == 'n') // 按下 n 建開始執(zhí)行算法{runGrabCut();numRun++;showImg();cout << "current iteative times:" << numRun << endl;}if (c == 27){break;}} }void onMouse(int event, int x, int y, int flags, void* param) {switch (event){case EVENT_LBUTTONDOWN:rect.x = x;rect.y = y;rect.width = 1;rect.height = 1;break;case EVENT_MOUSEMOVE:if (flags& EVENT_FLAG_LBUTTON){rect = Rect(Point(rect.x, rect.y), Point(x, y));showImg();}break;case EVENT_LBUTTONUP:if (rect.width > 1 && rect.height > 1){showImg();}break;default:break;} }void showImg() {Mat result, binMask;binMask.create(MaskImg.size(), CV_8UC1);binMask = MaskImg & 1;if (init){srcImg.copyTo(result,binMask);}else{srcImg.copyTo(result);}rectangle(result, rect, Scalar(0, 0, 255), 2, 8);namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", result); }void setRoiMask() {//GC_BGD = 0 明確屬于背景的像素//GC_FGD = 1 明確屬于前景的像素//GC_PR_BGD = 2 可能屬于背景的像素//GC_PR_FGD = 3 可能屬于前景的像素MaskImg.setTo(GC_BGD); //為了避免選擇越界rect.x = max(0, rect.x);rect.y = max(0, rect.y);rect.width = min(rect.width, srcImg.cols - rect.x);rect.height = min(rect.height, srcImg.rows - rect.y);//把我們選取的那一塊設(shè)為前景MaskImg(rect).setTo(Scalar(GC_PR_FGD)); }void runGrabCut() {if (rect.width < 2 || rect.height < 2){return;}if (init){grabCut(srcImg, MaskImg, rect, bgModel, fgModel, 1);}else{grabCut(srcImg, MaskImg, rect, bgModel, fgModel, 1, GC_INIT_WITH_RECT);init = true;} }static void ShowHelpText() {cout << "請先用鼠標(biāo)在圖片窗口中標(biāo)記出屬于前景的區(qū)域" << endl;cout << "然后再按按鍵【n】啟動算法" << endl;cout << "按鍵【ESC】- 退出程序" << endl; }int main() {ShowHelpText();test();waitKey(0);return 0; }效果圖:
歡迎關(guān)注我的微信公眾號“OpenCV圖像處理算法”,主要是記錄自己學(xué)習(xí)圖像處理算法的歷程,包括特征提取、目標(biāo)跟蹤、定位、機(jī)器學(xué)習(xí)和深度學(xué)習(xí),每一個(gè)例子都會提供源碼和例子所用的資料,歡迎同行的同學(xué)關(guān)注我和我一起虛度光陰吧!!!
總結(jié)
以上是生活随笔為你收集整理的做形态学方法的团队_图像分割实战-分水岭分割方法和GrabCut 算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Altium Designer20原理图
- 下一篇: 计算机gt的使用方法,旗舰级综合效果器