##兩種圖像分割方法比較
此次研究兩種圖像分割法,分別是基于形態學的分水嶺算法和基于圖割理論的GrabCut算法。OpenCV均提供了兩張算法或其變種。鑒于研究所需,記錄一些知識點,開發平臺為OpenCV2.4.9+Qt5.3.2。
一、使用分水嶺算法進行圖像分割
分水嶺變換是一種常用的圖像處理算法,在網上很容易搜到詳細的原理分析。簡單來說,這是一種基于拓撲理論的數學形態學的圖像分割方法,其基本思想是把圖像看作是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的海拔高度,每一個局部極小值及其影響區域稱為集水盆,而集水盆的邊界則形成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明。在每一個局部極小值表面,刺穿一個小孔,然后把整個模型慢慢浸入水中,隨著浸入的加深,每一個局部極小值的影響域慢慢向外擴展,在兩個集水盆匯合處構筑大壩,即形成分水嶺。
分水嶺算法簡單,因此存在一些缺陷,如容易導致圖像的過度分割。分水嶺算法對微弱邊緣具有良好的響應,圖像中的噪聲、物體表面細微的灰度變化,都會產生過度分割的現象。
為消除分水嶺算法產生的過度分割,有兩種常規的處理方法,一是利用先驗知識去除無關邊緣信息。二是修改梯度函數使得集水盆只響應想要探測的目標。
OpenCV提供了該算法的改進版本,使用預定義的一組標記來引導對圖像的分割,該算法是通過cv::watershed函數來實現的。
要實現分水嶺算法,首先新建一個類WaterShedSegmentation,在watershedsegmentation.h中添加:
~~~
#ifndef WATERSHEDSEGMENTATION_H
#define WATERSHEDSEGMENTATION_H
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class WaterShedSegmentation
{
public:
void setMarkers(const cv::Mat &markerImage); // 將原圖像轉換為整數圖像
cv::Mat process(const cv::Mat &image); // // 分水嶺算法實現
// 以下是兩種簡化結果的特殊方法
cv::Mat getSegmentation();
cv::Mat getWatersheds();
private:
cv::Mat markers; // 用于非零像素點的標記
};
#endif // WATERSHEDSEGMENTATION_H
~~~
接著,在watershedsegmentation.cpp中添加:
~~~
#include "watershedsegmentation.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
void WaterShedSegmentation::setMarkers(const cv::Mat &markerImage) // 該函數將原圖像轉換為整數圖像
{
markerImage.convertTo(markers,CV_32S);
}
cv::Mat WaterShedSegmentation::process(const cv::Mat &image)
{
// 使用分水嶺算法
cv::watershed(image,markers);
return markers;
}
// 以下是兩種簡化結果的特殊方法
// 以圖像的形式返回分水嶺結果
cv::Mat WaterShedSegmentation::getSegmentation()
{
cv::Mat tmp;
// 所有像素值高于255的標簽分割均賦值為255
markers.convertTo(tmp,CV_8U);
return tmp;
}
cv::Mat WaterShedSegmentation::getWatersheds()
{
cv::Mat tmp;
markers.convertTo(tmp,CV_8U,255,255);
return tmp;
}
~~~
main函數修改如下:
~~~
#include <QCoreApplication>
#include "watershedsegmentation.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 輸入待處理的圖像
cv::Mat image= cv::imread("c:/Fig4.41(a).jpg");
if (!image.data)
return 0;
cv::namedWindow("Original Image");
cv::imshow("Original Image",image);
// 輸入圖像,將其轉化為二值圖像
cv::Mat binary;
binary= cv::imread("c:/Fig4.41(a).jpg",0);
// 顯示二值圖像
cv::namedWindow("Binary Image");
cv::imshow("Binary Image",binary);
// 移除噪點
cv::Mat fg;
cv::erode(binary,fg,cv::Mat(),cv::Point(-1,-1),6);
// 顯示前景圖像
cv::namedWindow("Foreground Image");
cv::imshow("Foreground Image", fg);
// 識別背景圖像,生成的黑色像素對應背景像素
cv::Mat bg;
cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),6);
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);
// 以下進行分水嶺算法
WaterShedSegmentation segmenter;
segmenter.setMarkers(markers);
segmenter.process(image);
// 以下是兩種處理結果,顯示分割結果
cv::namedWindow("Segmentation");
cv::imshow("Segmentation", segmenter.getSegmentation());
cv::namedWindow("Watersheds");
cv::imshow("Watersheds",segmenter.getWatersheds());
return a.exec();
}
~~~
效果1:算法識別出屬于前景和背景的像素(有誤差)。

效果2:組合前景和背景圖,形成標記圖形,這是分水嶺的輸入參數。

效果3:分割結果中,標記圖像得到更新。

效果4:顯示邊界圖像。

可以看出,分水嶺算法對微弱邊緣具有良好的響應,是得到封閉連續邊緣的保證的。但對于不同質量的圖像其分割效果不盡相同,但總的來說效果仍需要改進。
二、使用GrabCut算法分割圖像
GrabCut是另一種同樣較為流行的圖像分割算法。GrabCut是在GraphCut基礎上改進的一種圖像分割算法,它并非基于圖像形態學,而是基于圖割理論(參考:[http://www.cnblogs.com/tornadomeet/archive/2012/11/06/2757585.html](http://www.cnblogs.com/tornadomeet/archive/2012/11/06/2757585.html))。在使用GrabCut時,需要人工給定一定區域的目標或者背景,然后算法根據設定的參數來進行分割。GrabCut在計算時比分水嶺算法更加復雜,尤其適合從靜態圖像中提取前景照片的應用。?
OpenCV中提供了cv::grabcut函數,因此只需提供圖像并標記背景像素和前景像素,基于局部的標記,算法即可將圖像中的像素進行分割。在這里使用的局部標記方法是定義一個矩形。cv::grabcut的函數定義如下:
~~~
cv::grabCut(image, // 輸入圖像
result, // 分割輸出結果
rectangle,// 包含前景物體的矩形
bgModel,fgModel, // 模型
1, // 迭代次數
cv::GC_INIT_WITH_RECT); // 使用矩形進行初始化
~~~
在main函數添加:
~~~
// GrabCut算法
cv::Mat image= cv::imread("c:/Fig8.04(a).jpg");
// 設定矩形
cv::Rect rectangle(50,70,image.cols-150,image.rows-180);
cv::Mat result; // 分割結果 (4種可能取值)
cv::Mat bgModel,fgModel; // 模型(內部使用)
// 進行GrabCut分割
cv::grabCut(image, result, rectangle, bgModel, fgModel, 1, cv::GC_INIT_WITH_RECT);
// 得到可能為前景的像素
cv::compare(result, cv::GC_PR_FGD, result,cv::CMP_EQ);
// 生成輸出圖像
cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255,255,255));
image.copyTo(foreground, result); // 不復制背景數據
// 包含矩形的原始圖像
cv::rectangle(image, rectangle, cv::Scalar(255,255,255),1);
cv::namedWindow("Orginal Image");
cv::imshow("Orginal Image", image);
// 輸出前景圖像結果
cv::namedWindow("Foreground Of Segmented Image");
cv::imshow("Foreground Of Segmented Image", foreground);
~~~
效果:?

在函數cv::grabCut中,最后一個參數表示我們使用的是包圍盒模式,而該算法支持的輸入/輸出分割圖像可以有四種數值,如函數cv::compare函數中的參數:?
cv::GC_BGD:確定屬于背景的像素;?
cv::GC_FGD:確定屬于前景的元素;?
cv::GC_PR_BGD:可能屬于背景的元素;?
cv::GC_PR_FGD:可能屬于前景的元素。
在上圖中,GrabCut算法通過指定方框區域來提取前景物體。同時,也可將數值cv::GC_BGD和cv::GC_FGD賦予分割圖像的某些特定像素,并且把這類分割圖像作為cv::grabcut函數的第二個參數(此時需要指定GC_INIT_WITH_MASK作為輸入模式)。
基于這些信息,GrabCut通過以下主要步驟創建分割:
1. 前景標簽(cv::GC_PR_FGD)被臨時賦予所有為標記的像素。基于當前的分類,算法將像素歸類為顏色或灰度值相似的聚類。
2. 通過引入背景與前景像素的邊界進行分割。這個優化的過程嘗試將標簽相似的像素相連接,這里利用了在強度相對已知的區域之間對邊界像素的(懲罰?)。這個最優化問題通過GraphCut算法得到高效解決。
3. 對獲取的分割結果產生新的像素標簽,重復聚類過程,找到新的最優解。根據場景的復雜度,得到最佳結果,對于簡單的場景,有時只需要一次迭代。
關于GrabCut算法,還需要進一步研究GraphCut才能深刻理解。
- 前言
- Win8.1下OpenCV2.4.9+Qt5.3.2開發環境搭建
- OpenCV2學習筆記(一)
- OpenCV2學習筆記(二)
- OpenCV2學習筆記(三)
- OpenCV2學習筆記(四)
- OpenCV2學習筆記(五)
- OpenCV2學習筆記(六)
- OpenCV2學習筆記(七)
- OpenCV2學習筆記(八)
- OpenCV2學習筆記(九)
- OpenCV2學習筆記(十)
- OpenCV2學習筆記(十一)
- OpenCV2學習筆記(十二)
- OpenCV2學習筆記(十三)
- OpenCV2學習筆記(十四)
- OpenCV2學習筆記(十五)
- OpenCV2學習筆記(十六)
- OpenCV2學習筆記(十七)
- OpenCV2學習筆記(十八)
- OpenCV2學習筆記(十九)
- OpenCV2學習筆記(二十)
- OpenCV2學習筆記(二十一)
- OpenCV2學習筆記(二十二)