OpenCV基于距離變換和分水嶺實(shí)現(xiàn)圖像分割
一.圖像分割
圖像分割是根據(jù)灰度、顏色、紋理和形狀等特征,把圖像分成若干個(gè)特定的、具有獨(dú)特性質(zhì)的區(qū)域,這些特征在同一區(qū)域內(nèi)呈現(xiàn)出相似性,而在不同區(qū)域間呈現(xiàn)出明顯的差異性,并提出感興趣目標(biāo)的技術(shù)和過程。 它是由圖像處理到圖像分析的關(guān)鍵步驟。從數(shù)學(xué)角度來看,圖像分割是將數(shù)字圖像劃分成互不相交的區(qū)域的過程。圖像分割的過程也是一個(gè)標(biāo)記過程,即把屬于同一區(qū)域的像索賦予相同的編號(hào)。
其目的是將圖像中像素根據(jù)一定的規(guī)則分為若干(N)個(gè)聚(cluster)集合,每個(gè)集合包含一類像素。將對(duì)象在背景提取出來。
二.基于距離變換和分水嶺的圖像分割
分水嶺法(Meyer)是一種基于拓?fù)淅碚摰臄?shù)學(xué)形態(tài)學(xué)的分割方法,其基本思想是把圖像看作是測(cè)地學(xué)上的拓?fù)涞孛?,圖像中每一點(diǎn)像素的灰度值表示該點(diǎn)的海拔高度,每一個(gè)局部極小值及其影響區(qū)域稱為集水盆,而集水盆的邊界則形成分水嶺。該算法的實(shí)現(xiàn)可以模擬成洪水淹沒的過程,圖像的最低點(diǎn)首先被淹沒,然后水逐漸淹沒整個(gè)山谷。當(dāng)水位到達(dá)一定高度的時(shí)候?qū)?huì)溢出,這時(shí)在水溢出的地方修建堤壩,重復(fù)這個(gè)過程直到整個(gè)圖像上的點(diǎn)全部被淹沒,這時(shí)所建立的一系列堤壩就成為分開各個(gè)盆地的分水嶺。分水嶺算法對(duì)微弱的邊緣有著良好的響應(yīng),但圖像中的噪聲會(huì)使分水嶺算法產(chǎn)生過分割的現(xiàn)象。
距離變換API函數(shù)接口
距離變換用于計(jì)算圖像中每一個(gè)非零點(diǎn)像素與其周圍最近的零點(diǎn)像素之間的距離,返回的值保存了每一個(gè)非零點(diǎn)與最近零點(diǎn)的距離信息;在圖像上的體現(xiàn)為圖像上越亮的點(diǎn),代表了離零點(diǎn)的距離越遠(yuǎn)。
void distanceTransform( InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP );
參數(shù)說明
(1)src是單通道的8bit的二值圖像(只有0或1)
(2)dst表示的是計(jì)算距離的輸出圖像,可以使單通道32bit浮點(diǎn)數(shù)據(jù)
(3)distanceType表示的是選取距離的類型,可以設(shè)置為CV_DIST_L1,CV_DIST_L2,CV_DIST_C等,具體如下:
DIST_USER | User defined distance |
DIST_L1=1 | distance = |x1-x2| + |y1-y2 |
DIST_L2 | the simple euclidean distance |
DIST_C | distance = max(|x1-x2|,|y1-y2|) |
DIST_L12 | L1-L2 metric: distance =2(sqrt(1+x*x/2) - 1)) |
DIST_FAIR | distance = c^2(|x|/c-log(1+|x|/c)),c = 1.3998 |
DIST_WELSCH | distance = c2/2(1-exp(-(x/c)2)), c= 2.9846 |
DIST_HUBER | distance = |x|<c ? x^2/2 :c(|x|-c/2), c=1.345 |
(4)maskSize表示的是距離變換的掩膜模板,可以設(shè)置為3,5或CV_DIST_MASK_PRECISE,對(duì) CV_DIST_L1 或CV_DIST_C 的情況,參數(shù)值被強(qiáng)制設(shè)定為 3, 因?yàn)?×3 mask 給出5×5 mask 一樣的結(jié)果,而且速度還更快。
DIST_MASK_3 | mask=3 |
DIST_MASK_5 | mask=5 |
DIST_MASK-PRECISE |
(5)labels表示可選輸出2維數(shù)組;
(6)labelType表示的是輸出二維數(shù)組的類型,8位或者32位浮點(diǎn)數(shù),圖像是單一通道,并且大小與輸入圖像一致
watershed 分水嶺函數(shù)API接口
void watershed( InputArray image, InputOutputArray markers );
參數(shù)說明
(1)參數(shù) image,必須是一個(gè)8bit3通道彩色圖像矩陣序列。
(2) 輸入或輸出32位單通道的標(biāo)記,和圖像一樣大小。(輸入高峰輪廓標(biāo)記);在執(zhí)行分水嶺函數(shù)watershed之前,必須對(duì)第二個(gè)參數(shù)markers進(jìn)行處理,它應(yīng)該包含不同區(qū)域的輪廓,每個(gè)輪廓有一個(gè)自己唯一的編號(hào),輪廓的定位可以通過Opencv中findContours方法實(shí)現(xiàn),這個(gè)是執(zhí)行分水嶺之前的要求。
算法會(huì)根據(jù)markers傳入的輪廓作為種子(也就是所謂的注水點(diǎn)),對(duì)圖像上其他的像素點(diǎn)根據(jù)分水嶺算法規(guī)則進(jìn)行判斷,并對(duì)每個(gè)像素點(diǎn)的區(qū)域歸屬進(jìn)行劃定,直到處理完圖像上所有像素點(diǎn)。而區(qū)域與區(qū)域之間的分界處的值被置為“-1”,以做區(qū)分。
代碼實(shí)現(xiàn)
1、將白色背景改為黑色,為后面的變換做準(zhǔn)備;
2、使用filter2D與Laplace算子實(shí)現(xiàn)圖像對(duì)比度的提高;sharpen銳化
3、通過threshold()轉(zhuǎn)換為二值圖像;
4、距離變換;
5、對(duì)距離變換的結(jié)果歸一化到0-1之間;
6、使用閾值,再次二值化,得到標(biāo)記;
7、腐蝕(erode)得到每個(gè)Peak;(peak : 山峰,山頂 )
8、發(fā)現(xiàn)輪廓findContours;
9、繪制輪廓;
10、分水嶺變換;
11、對(duì)每個(gè)分割區(qū)域著色輸出結(jié)果;
#include <opencv2/opencv.hpp> #include <iostream> #include <math.h> using namespace std; using namespace cv; int main(int argc, char** argv) { char input_win[] = "input image"; char watershed_win[] = "watershed segmentation demo"; Mat src = imread("D:/vcprojects/images/cards.png"); // Mat src = imread("D:/kuaidi.jpg"); if (src.empty()) { printf("could not load image...\n"); return -1; } namedWindow(input_win, CV_WINDOW_AUTOSIZE); imshow(input_win, src); // 1. change background for (int row = 0; row < src.rows; row++) { for (int col = 0; col < src.cols; col++) { if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) { src.at<Vec3b>(row, col)[0] = 0; src.at<Vec3b>(row, col)[1] = 0; src.at<Vec3b>(row, col)[2] = 0; } } } namedWindow("black background", CV_WINDOW_AUTOSIZE); imshow("black background", src); // sharpen Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1); Mat imgLaplance; Mat sharpenImg = src; filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT); src.convertTo(sharpenImg, CV_32F); Mat resultImg = sharpenImg - imgLaplance; resultImg.convertTo(resultImg, CV_8UC3); imgLaplance.convertTo(imgLaplance, CV_8UC3); imshow("sharpen image", resultImg); // src = resultImg; // copy back // convert to binary Mat binaryImg; cvtColor(src, resultImg, CV_BGR2GRAY); threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU); imshow("binary image", binaryImg); Mat distImg; distanceTransform(binaryImg, distImg, DIST_L1, 3, 5); normalize(distImg, distImg, 0, 1, NORM_MINMAX); imshow("distance result", distImg); // binary again threshold(distImg, distImg, .4, 1, THRESH_BINARY); Mat k1 = Mat::ones(13, 13, CV_8UC1); erode(distImg, distImg, k1, Point(-1, -1)); imshow("distance binary image", distImg); // markers Mat dist_8u; distImg.convertTo(dist_8u, CV_8U); vector<vector<Point>> contours; findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); // create makers Mat markers = Mat::zeros(src.size(), CV_32SC1); for (size_t i = 0; i < contours.size(); i++) { drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1); } circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1); imshow("my markers", markers*1000); // perform watershed watershed(src, markers); Mat mark = Mat::zeros(markers.size(), CV_8UC1); markers.convertTo(mark, CV_8UC1); bitwise_not(mark, mark, Mat()); imshow("watershed image", mark); // generate random color vector<Vec3b> colors; for (size_t 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)); } // fill with color and display final result Mat dst = Mat::zeros(markers.size(), CV_8UC3); for (int row = 0; row < markers.rows; row++) { for (int col = 0; col < markers.cols; col++) { int index = markers.at<int>(row, col); if (index > 0 && index <= static_cast<int>(contours.size())) { dst.at<Vec3b>(row, col) = colors[index - 1]; } else { dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0); } } } imshow("Final Result", dst); waitKey(0); return 0; }
圖像處理效果
輸入原圖像和銳化圖像
原圖和黑背景圖(背景應(yīng)為黑色)
threshold轉(zhuǎn)化的二值化圖片和距離變換結(jié)果圖
距離變換結(jié)果圖和二值化圖像
到此這篇關(guān)于OpenCV基于距離變換和分水嶺實(shí)現(xiàn)圖像分割的文章就介紹到這了,更多相關(guān)OpenCV圖像分割內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++如何獲取系統(tǒng)信息 C++獲取IP地址、硬件信息等
這篇文章主要為大家詳細(xì)介紹了C++如何獲取系統(tǒng)信,C++獲取IP地址、硬件信息等,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04OpenCV基于背景減除實(shí)現(xiàn)行人計(jì)數(shù)
本文主要介紹了如何使用OpenCV C++對(duì)視頻中的人流量進(jìn)行統(tǒng)計(jì)。文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)OpenCV有一定的幫助,需要的可以了解一下2022-01-01VScode+ESP32簡(jiǎn)單環(huán)境搭建
本文章向大家介紹ESP32-C3搭建環(huán)境教程,主要包括ESP32-C3搭建環(huán)境教程使用實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06C++ BloomFilter布隆過濾器應(yīng)用及概念詳解
布隆過濾器是由布?。˙urton Howard Bloom)在1970年提出的 一種緊湊型的、比較巧妙的概率型數(shù)據(jù)結(jié)構(gòu),特點(diǎn)是高效地插入和查詢,可以用來告訴你 “某樣?xùn)|西一定不存在或者可能存在”,它是用多個(gè)哈希函數(shù),將一個(gè)數(shù)據(jù)映射到位圖結(jié)構(gòu)中2023-03-03