圖像處理之基于標記的分水嶺算法(C++)
文章目錄
- 圖像處理之基于標記的分水嶺算法(C++)
- 前言
- 一、基于標記點的分水嶺算法應用
- 1.實現步驟:
- 2.代碼實現
- 總結
前言
傳統分水嶺算法存在過分割的不足,OpenCV提供了一種改進的分水嶺算法,使用一系列預定義標記來引導圖像分割的定義方式。使用OpenCV的分水嶺算法cv::wathershed,需要輸入一個標記圖像,圖像的像素值為32位有符號正數(CV_32S類型),每個非零像素代表一個標簽。**它的原理是對圖像中部分像素做標記,表明它的所屬區域是已知的。分水嶺算法可以根據這個初始標簽確定其他像素所屬的區域。**傳統的基于梯度的分水嶺算法和改進后基于標記的分水嶺算法示意圖如下圖所示。
從上圖可以看出,傳統基于梯度的分水嶺算法由于局部最小值過多造成分割后的分水嶺較多。而基于標記的分水嶺算法,水淹過程從預先定義好的標記圖像(像素)開始,較好的克服了過度分割的不足。本質上講,基于標記點的改進算法是利用先驗知識來幫助分割的一種方法。因此,改進算法的關鍵在于如何獲得準確的標記圖像,即如何將前景物體與背景準確的標記出來。
一、基于標記點的分水嶺算法應用
1.實現步驟:
- 封裝分水嶺算法
- 獲取標記圖像(在標記圖中前景設置為255,背景像素設置為128,未知像素設置為0)
- 將原圖和標記圖輸入分水嶺算法
- 顯示結果
2.代碼實現
#include <iostream>
#include <opencv.hpp>class WatershedSegmenter {private:cv::Mat markers; // 標記圖public:/** @param const cv::Mat& markerImage 傳入的標記圖* @brief 將傳入的標記圖轉換成CV_32S的標記圖*/void setMarkers(const cv::Mat& markerImage) {// Convert to image of intsmarkerImage.convertTo(markers, CV_32S);}/** @param const cv::Mat& image 原圖* @brief 對原圖和標記圖執行基于標記的分水嶺分割*/cv::Mat process(const cv::Mat& image) {// Apply watershedcv::watershed(image, markers);return markers;}// Return result in the form of an image/** @brief 得到分割結果的標簽*/cv::Mat getSegmentation() {cv::Mat tmp;// all segment with label higher than 255// will be assigned value 255markers.convertTo(tmp, CV_8U);return tmp;}// Return watershed in the form of an image以圖像的形式返回分水嶺cv::Mat getWatersheds() {cv::Mat tmp;//在變換前,把每個像素p轉換為255p+255(在conertTo中實現)markers.convertTo(tmp, CV_8U, 255, 255);return tmp;}
};int main()
{// 讀取圖片std::string filepath = "F://work_study//algorithm_demo//watershedSeg.jpg";cv::Mat src = cv::imread(filepath);if (src.empty()){return -1;}imshow("src", src);// 高斯濾波平滑圖像cv::Mat gaussImg;// 彩色圖轉換為灰度圖cv::cvtColor(src, gaussImg, cv::COLOR_BGR2GRAY);cv::GaussianBlur(gaussImg, gaussImg,cv::Size(7,7),1.0);imshow("gaussImg", gaussImg);// 形態學梯度cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));cv::morphologyEx(gaussImg, gaussImg, cv::MORPH_GRADIENT,kernel);imshow("morphologyEx", gaussImg);// OTSU大津法閾值cv::threshold(gaussImg, gaussImg, 0, 255, cv::THRESH_OTSU | cv::THRESH_BINARY);imshow("threshold", gaussImg);// 形態學操作,生成確定的背景區域cv::Mat kernel_bg = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7)); 開運算(先腐蝕后膨脹)消除噪聲點//cv::morphologyEx(gaussImg, gaussImg, cv::MORPH_OPEN, kernel1,cv::Point(-1,-1),2);// 生成確定的背景區域cv::Mat backgroundImg;cv::dilate(gaussImg, backgroundImg, kernel_bg);imshow("backgroundImg", backgroundImg);// 距離變換,生成確定的前景區域cv::Mat distanceImg,foregroundImg;cv::distanceTransform(gaussImg, distanceImg, cv::DIST_L1,5);double min, max;cv::minMaxLoc(distanceImg, &min, &max);cv::threshold(distanceImg, distanceImg, 0.1 * max, 255, cv::THRESH_BINARY);cv::normalize(distanceImg, foregroundImg, 0, 255, cv::NORM_MINMAX, CV_8U);imshow("foregroundImg", foregroundImg);// 去除連通域中的背景部分cv::Mat unknownImg;cv::subtract(backgroundImg, foregroundImg, unknownImg); //待定區域,減去前景與背景的重合區域imshow("unknownImg", unknownImg);cv::Mat makers(gaussImg.size(),CV_8U,cv::Scalar(128));for (int i = 0; i < makers.rows; i++){for (int j = 0; j < makers.cols; j++){if (foregroundImg.at<uchar>(i, j) == 255){makers.at<uchar>(i, j) = 255;}else if (unknownImg.at<uchar>(i, j) == 255){makers.at<uchar>(i, j) = 0;}}}imshow("makers", makers);// 分水嶺算法分割圖像WatershedSegmenter seg; // 實例化一個分水嶺分割對象seg.setMarkers(makers); // 設置算法的標記圖像,使得水淹過程從這組預定義好的標記像素開始seg.process(src); // 傳入待分割的原圖(要求為CV_8UC3)cv::Mat resultImg;resultImg = seg.getSegmentation(); // 將修改后的標記圖makers轉換為可顯示的8位灰度圖并返回分割結果(白色為前景,灰色為背景,0為邊緣)cv::threshold(resultImg, resultImg, 250, 1, cv::THRESH_BINARY);cv::cvtColor(resultImg, resultImg, cv::COLOR_GRAY2BGR);resultImg = src.mul(resultImg);cv::cvtColor(resultImg, resultImg, cv::COLOR_BGR2GRAY);std::cout << resultImg.type() << std::endl;for(int i=0;i<resultImg.rows;i++)for (int j = 0; j < resultImg.cols; j++){if (resultImg.at<uchar>(i, j) == 0){resultImg.at<uchar>(i, j) = 255;}}imshow("resultImg", resultImg);cv::waitKey(0);return 0;}
總結
本文主要介紹了一種基于標記的分水嶺算法的應用,關鍵在于如何巧妙地設計“標記圖”,歡迎討論交流。