您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“OpenCV基于分水嶺算法的圖像分割怎么實現”,內容詳細,步驟清晰,細節處理妥當,希望這篇“OpenCV基于分水嶺算法的圖像分割怎么實現”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
分水嶺分割可以通過使用 cv::watershed
函數實現,函數的輸入是一個 32
位有符號整數標記圖像,其中每個非零像素表示一個標簽。通過標記圖像中已知屬于給定區域的一些像素,利用初始標記,分水嶺算法可以確定其他像素所屬的區域。
(1) 首先,將標記圖像讀取為灰度圖像,然后將其轉換為整數類型:
class WatershedSegmentater { private: cv::Mat markers; public: void setMarkers(const cv::Mat& markerImage) { // 轉換數據類型 markerImage.convertTo(markers, CV_32S); } cv::Mat process(const cv::Mat& image) { // 應用分水嶺算法 cv::watershed(image, markers); return markers; }
有多種獲取標記的方式,例如,使用預處理步驟識別出屬于感興趣對象的某些像素,然后利用分水嶺算法根據初始標記分割完整的對象。在本節中,我們將使用二值圖像來識別相應原始圖像中的動物。因此,從二值圖像中,我們需要識別屬于前景(動物)的像素和屬于背景(主要是雪地)的像素,我們用標簽 255
標記前景像素,用標簽 128
標記背景像素,其他像素則標記為 0
。
(2) 初始二值圖像包含過多屬于圖像各個部分的白色像素,為了只保留屬于重要對象的像素,我們首先需要腐蝕該圖像:
// 消除噪音 cv::Mat fg; cv::erode(binary, fg, cv::Mat(), cv::Point(-1, -1), 4);
結果如下圖所示:
(3) 圖中仍然存在一些屬于背景(雪地)的像素,我們通過對原始二值圖像進行膨脹來選擇幾個屬于背景的像素:
// 標記圖像像素 cv::Mat bg; cv::dilate(binary, bg, cv::Mat(), cv::Point(-1, -1), 4); cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);
結果如下圖所示,黑色像素對應于背景像素:
(4) 將這些圖像組合起來形成標記圖像:
cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0)); markers = fg+bg;
我們使用重載的 +
運算符來組合圖像,得到用作分水嶺算法的輸入:
(5) 在這個輸入圖像中,白色區域屬于前景對象,灰色區域是背景的一部分,黑色區域則屬于未知標簽,得到分割結果如下:
// 創建分水嶺分割對象 WatershedSegmentater segmenter; segmenter.setMarkers(markers); segmenter.process(image);
更新標記圖像,以便為黑色區域中的像素重新分配標簽,而屬于邊界的像素的值為 -1
。結果標簽圖像如下:
圖像中對象邊緣的可視化結果如下圖所示:
我們使用拓撲圖進行類比,為了創建分水嶺分割,我們從級別 0
開始注水,隨著水位逐漸增加,就形成了集水盆地。這些盆地的大小也會逐漸增加,兩個不同盆地的水最終會匯合,發生這種情況時,會創建一個分水嶺,以將兩個盆地分開。一旦水位達到最高水位,這些水域和分水嶺就形成了分水嶺分割。
在注水過程中最初會產生許多小盆地,當這些盆地進行合并時,會創建許多分水嶺線,從而導致圖像被過度分割。為了克服這個問題,已經提出了多種改進算法,在 OpenCV
調用 cv::watershed
函數時,注水過程從一組預定義的標記像素開始,根據分配給初始標記的值對盆地進行標記,當具有相同標簽的兩個盆地合并時,不會創建分水嶺,從而防止過度分割,更新輸入標記圖像以獲得最終的分水嶺分割。用戶可以輸入帶有任意數量的標簽和未知標簽的標記圖像,標記圖像的像素類型為為 32
位有符號整數,以便能夠定義超過 255
個標簽。cv::watershed
函數還允許返回與分水嶺關聯的像素(使用特殊值 -1
進行標記)。
為了便于顯示結果,我們引入兩種特殊的方法。第一個方法 getSegmentation()
通過閾值返回標簽圖像,分水嶺值為 0
:
// 返回結果 cv::Mat getSegmentation() { cv::Mat tmp; markers.convertTo(tmp, CV_8U); return tmp; }
第二種方法 getWatersheds()
返回的圖像中,分水嶺線使用值 0
進行標記,圖像的其余部分像素值為 255
,可以使用 cv::convertTo
方法實現:
// 返回分水嶺 cv::Mat getWatersheds() { cv::Mat tmp; markers.convertTo(tmp,CV_8U,255,255); return tmp; }
在轉換之前應用線性變換,可以將像素值 -1
轉換為 0
( − 1 × 255 + 255 = 0 -1\times 255+255=0 −1×255+255=0)。由于將有符號整數轉換為無符號字符時需應用飽和操作,大于 255
的像素值將轉換為 255
。
我們也可以通過許多不同的方式獲得標記圖像。例如,可以令用戶以交互方式在圖像中標記屬于對象和背景的像素區域;或者,如果我們需要識別位于圖像中心的物體,可以輸入一個中心區域標有特定標簽的圖像,且圖像背景標記帶有另一個標簽,可以按以下方式創建標記圖像:
// 標記背景像素 cv::Mat imageMask(image.size(), CV_8U, cv::Scalar(0)); cv::rectangle(imageMask, cv::Point(5, 5), cv::Point(image.cols-5, image.rows-5), cv::Scalar(255), 3); // 標記前景像素 cv::rectangle(imageMask, cv::Point(image.cols/2-10, image.rows/2-10), cv::Point(image.cols/2+10, image.rows/2+10), cv::Scalar(1), 10);
頭文件 (watershedSegmentation.h
) 完整代碼如下:
#if !defined WATERSHS #define WATERSHS #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> class WatershedSegmentater { private: cv::Mat markers; public: void setMarkers(const cv::Mat& markerImage) { // 轉換數據類型 markerImage.convertTo(markers, CV_32S); } cv::Mat process(const cv::Mat& image) { // 應用分水嶺算法 cv::watershed(image, markers); return markers; } // 返回結果 cv::Mat getSegmentation() { cv::Mat tmp; markers.convertTo(tmp, CV_8U); return tmp; } // 返回分水嶺 cv::Mat getWatersheds() { cv::Mat tmp; markers.convertTo(tmp,CV_8U,255,255); return tmp; } }; #endif
主文件 (segment.cpp
) 完整代碼如下所示:
#include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include "watershedSegmentation.h" int main() { // 讀取輸入圖像 cv::Mat image = cv::imread("1.png"); if (!image.data) return 0; cv::namedWindow("Original Image"); cv::imshow("Original Image",image); // 讀取二值圖像 cv::Mat binary; binary = cv::imread("binary.png", 0); cv::namedWindow("Binary Image"); cv::imshow("Binary Image", binary); // 消除噪音 cv::Mat fg; cv::erode(binary, fg, cv::Mat(), cv::Point(-1, -1), 4); cv::namedWindow("Foreground Image"); cv::imshow("Foreground Image", fg); // 標記圖像像素 cv::Mat bg; cv::dilate(binary, bg, cv::Mat(), cv::Point(-1, -1), 4); cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV); cv::namedWindow("Background Image"); cv::imshow("Background Image", bg); cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0)); markers = fg+bg; cv::namedWindow("Markers"); cv::imshow("Markers", markers); // 創建分水嶺分割對象 WatershedSegmentater segmenter; segmenter.setMarkers(markers); segmenter.process(image); cv::namedWindow("Segmentation"); cv::imshow("Segmentation", segmenter.getSegmentation()); cv::namedWindow("Watersheds"); cv::imshow("Watersheds", segmenter.getWatersheds()); // 打開另一張圖像 image = cv::imread("3.png"); // 標記背景像素 cv::Mat imageMask(image.size(), CV_8U, cv::Scalar(0)); cv::rectangle(imageMask, cv::Point(5, 5), cv::Point(image.cols-5, image.rows-5), cv::Scalar(255), 3); // 標記前景像素 cv::rectangle(imageMask, cv::Point(image.cols/2-10, image.rows/2-10), cv::Point(image.cols/2+10, image.rows/2+10), cv::Scalar(1), 10); segmenter.setMarkers(imageMask); segmenter.process(image); cv::rectangle(image, cv::Point(5, 5), cv::Point(image.cols-5, image.rows-5), cv::Scalar(255, 255, 255), 3); cv::rectangle(image, cv::Point(image.cols/2-10, image.rows/2-10), cv::Point(image.cols/2+10, image.rows/2+10), cv::Scalar(1, 1, 1), 10); cv::namedWindow("Image with marker"); cv::imshow("Image with marker", image); cv::namedWindow("Watershed"); cv::imshow("Watershed", segmenter.getWatersheds()); cv::waitKey(); return 0; }
讀到這里,這篇“OpenCV基于分水嶺算法的圖像分割怎么實現”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。