欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

OpenCV實戰(zhàn)記錄之基于分水嶺算法的圖像分割

 更新時間:2023年02月22日 10:47:21   作者:盼小輝丶  
在機器視覺中,有時需要對產(chǎn)品進行檢測和計數(shù),其難點無非是對于產(chǎn)品的圖像分割,這篇文章主要給大家介紹了關(guān)于OpenCV實戰(zhàn)記錄之基于分水嶺算法的圖像分割的相關(guān)資料,需要的朋友可以參考下

0. 前言

分水嶺變換是一種流行的圖像處理算法,用于快速將圖像分割成同質(zhì)區(qū)域。分水嶺變換主要基于以下思想:當圖像被視為拓撲浮雕時,均質(zhì)區(qū)域?qū)?yīng)于相對平坦且由陡峭的邊緣界定的盆地。算法的原始版本傾向于過度分割圖像,從而產(chǎn)生多個小區(qū)域,因此 OpenCV 中實現(xiàn)了該算法的改進版本,通過使用一組預定義的標記來指導圖像分割區(qū)域的定義。

1. 分水嶺算法

分水嶺分割可以通過使用 cv::watershed 函數(shù)實現(xiàn),函數(shù)的輸入是一個 32 位有符號整數(shù)標記圖像,其中每個非零像素表示一個標簽。
通過標記圖像中已知屬于給定區(qū)域的一些像素,利用初始標記,分水嶺算法可以確定其他像素所屬的區(qū)域。

(1) 首先,將標記圖像讀取為灰度圖像,然后將其轉(zhuǎn)換為整數(shù)類型:

class WatershedSegmentater {
    private:
        cv::Mat markers;
    public:
        void setMarkers(const cv::Mat& markerImage) {
            // 轉(zhuǎn)換數(shù)據(jù)類型
            markerImage.convertTo(markers, CV_32S);
        }
        cv::Mat process(const cv::Mat& image) {
            // 應(yīng)用分水嶺算法
            cv::watershed(image, markers);
            return markers;
        }

有多種獲取標記的方式,例如,使用預處理步驟識別出屬于感興趣對象的某些像素,然后利用分水嶺算法根據(jù)初始標記分割完整的對象。在本節(jié)中,我們將使用二值圖像來識別相應(yīng)原始圖像中的動物。因此,從二值圖像中,我們需要識別屬于前景(動物)的像素和屬于背景(主要是雪地)的像素,我們用標簽 255 標記前景像素,用標簽 128 標記背景像素,其他像素則標記為 0。

(2) 初始二值圖像包含過多屬于圖像各個部分的白色像素,為了只保留屬于重要對象的像素,我們首先需要腐蝕該圖像:

// 消除噪音
cv::Mat fg;
cv::erode(binary, fg, cv::Mat(), cv::Point(-1, -1), 4);

結(jié)果如下圖所示:

腐蝕圖像

(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);

結(jié)果如下圖所示,黑色像素對應(yīng)于背景像素:

圖像膨脹

(4) 將這些圖像組合起來形成標記圖像:

cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0));
markers = fg+bg;

我們使用重載的 + 運算符來組合圖像,得到用作分水嶺算法的輸入:

標記圖像

(5) 在這個輸入圖像中,白色區(qū)域?qū)儆谇熬皩ο?,灰色區(qū)域是背景的一部分,黑色區(qū)域則屬于未知標簽,得到分割結(jié)果如下:

// 創(chuàng)建分水嶺分割對象
WatershedSegmentater segmenter;
segmenter.setMarkers(markers);
segmenter.process(image);

更新標記圖像,以便為黑色區(qū)域中的像素重新分配標簽,而屬于邊界的像素的值為 -1。結(jié)果標簽圖像如下:

結(jié)果標簽圖像

圖像中對象邊緣的可視化結(jié)果如下圖所示:

圖像邊緣

2. 分水嶺算法直觀理解

我們使用拓撲圖進行類比,為了創(chuàng)建分水嶺分割,我們從級別 0 開始注水,隨著水位逐漸增加,就形成了集水盆地。這些盆地的大小也會逐漸增加,兩個不同盆地的水最終會匯合,發(fā)生這種情況時,會創(chuàng)建一個分水嶺,以將兩個盆地分開。一旦水位達到最高水位,這些水域和分水嶺就形成了分水嶺分割。

在注水過程中最初會產(chǎn)生許多小盆地,當這些盆地進行合并時,會創(chuàng)建許多分水嶺線,從而導致圖像被過度分割。為了克服這個問題,已經(jīng)提出了多種改進算法,在 OpenCV 調(diào)用 cv::watershed 函數(shù)時,注水過程從一組預定義的標記像素開始,根據(jù)分配給初始標記的值對盆地進行標記,當具有相同標簽的兩個盆地合并時,不會創(chuàng)建分水嶺,從而防止過度分割,更新輸入標記圖像以獲得最終的分水嶺分割。用戶可以輸入帶有任意數(shù)量的標簽和未知標簽的標記圖像,標記圖像的像素類型為為 32 位有符號整數(shù),以便能夠定義超過 255 個標簽。cv::watershed 函數(shù)還允許返回與分水嶺關(guān)聯(lián)的像素(使用特殊值 -1 進行標記)。

為了便于顯示結(jié)果,我們引入兩種特殊的方法。第一個方法 getSegmentation() 通過閾值返回標簽圖像,分水嶺值為 0

// 返回結(jié)果
cv::Mat getSegmentation() {
    cv::Mat tmp;
    markers.convertTo(tmp, CV_8U);
    return tmp;
}

第二種方法 getWatersheds() 返回的圖像中,分水嶺線使用值 0 進行標記,圖像的其余部分像素值為 255,可以使用 cv::convertTo 方法實現(xiàn):

// 返回分水嶺
cv::Mat getWatersheds() {
    cv::Mat tmp;
    markers.convertTo(tmp,CV_8U,255,255);
    return tmp;
}

在轉(zhuǎn)換之前應(yīng)用線性變換,可以將像素值 -1 轉(zhuǎn)換為 0 ( − 1 × 255 + 255 = 0 -1\times 255+255=0 −1×255+255=0)。由于將有符號整數(shù)轉(zhuǎn)換為無符號字符時需應(yīng)用飽和操作,大于 255 的像素值將轉(zhuǎn)換為 255

我們也可以通過許多不同的方式獲得標記圖像。例如,可以令用戶以交互方式在圖像中標記屬于對象和背景的像素區(qū)域;或者,如果我們需要識別位于圖像中心的物體,可以輸入一個中心區(qū)域標有特定標簽的圖像,且圖像背景標記帶有另一個標簽,可以按以下方式創(chuàng)建標記圖像:

// 標記背景像素
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);

如果我們將此標記圖像疊加在測試圖像上,可以得到以下圖像:

生成的分水嶺圖像如下圖所示:

分水嶺圖像

3. 完整代碼

頭文件 (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) {
            // 轉(zhuǎn)換數(shù)據(jù)類型
            markerImage.convertTo(markers, CV_32S);
        }
        cv::Mat process(const cv::Mat& image) {
            // 應(yīng)用分水嶺算法
            cv::watershed(image, markers);
            return markers;
        }
        // 返回結(jié)果
        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);
    // 創(chuàng)建分水嶺分割對象
    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;
}

總結(jié)

到此這篇關(guān)于OpenCV實戰(zhàn)記錄之基于分水嶺算法的圖像分割的文章就介紹到這了,更多相關(guān)OpenCV分水嶺算法的圖像分割內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python3實現(xiàn)網(wǎng)頁版raspberry pi(樹莓派)小車控制

    python3實現(xiàn)網(wǎng)頁版raspberry pi(樹莓派)小車控制

    這篇文章主要為大家詳細介紹了python3實現(xiàn)網(wǎng)頁版raspberry pi(樹莓派)小車控制,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • python中使用zip函數(shù)出現(xiàn)<zip object at 0x02A9E418>錯誤的原因

    python中使用zip函數(shù)出現(xiàn)<zip object at 0x02A9E418>錯誤的原因

    這篇文章主要介紹了python中使用zip函數(shù)出現(xiàn)<zip object at 0x02A9E418>錯誤的原因分析及解決方法,需要的朋友可以參考下
    2018-09-09
  • Python try except else使用詳解

    Python try except else使用詳解

    這篇文章主要介紹了Python try except else使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • Python彈出輸入框并獲取輸入值的實例

    Python彈出輸入框并獲取輸入值的實例

    今天小編就為大家分享一篇Python彈出輸入框并獲取輸入值的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-06-06
  • 用Python代碼自動生成文獻的IEEE引用格式的實現(xiàn)

    用Python代碼自動生成文獻的IEEE引用格式的實現(xiàn)

    這篇文章主要介紹了用Python代碼自動生成文獻的IEEE引用格式的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • Python包裝異常處理方法

    Python包裝異常處理方法

    這篇文章主要介紹了Python包裝異常處理方法,相比java,python的異常和java中不同,python主要是防止程序異常被中止。一旦被catch后它還行往下執(zhí)行,本文就分享python相關(guān)的異常處理方法,需要的小伙伴可以參考一下
    2022-06-06
  • Python實現(xiàn)提取語句中的人名

    Python實現(xiàn)提取語句中的人名

    這篇文章主要為大家介紹一個小工具:可以將語句中的人名提取出來。文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起學習一下
    2022-01-01
  • 利用pandas按日期做分組運算的操作

    利用pandas按日期做分組運算的操作

    這篇文章主要介紹了利用pandas按日期做分組運算的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • Pandas Series如何轉(zhuǎn)換為DataFrame

    Pandas Series如何轉(zhuǎn)換為DataFrame

    這篇文章主要介紹了Pandas Series如何轉(zhuǎn)換為DataFrame問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Python 實現(xiàn)遙感影像波段組合的示例代碼

    Python 實現(xiàn)遙感影像波段組合的示例代碼

    這篇文章主要介紹了Python 實現(xiàn)遙感影像波段組合的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-08-08

最新評論