【圖像處理】OpenCV系列二十二 --- 分水嶺算法(watershed)詳解

上一節我們學習了用如何用cvtColor函數對一幅圖像進行顏色空間的轉換,相信大家學習之後,已理解如何使用顏色空間轉換的用法,本節呢,我們在學習watershed函數的用法與原理,即分水嶺算法如何使用。

1、函數原型

void watershed(InputArray image, 

InputOutputArray markers);

2、函數功能

使用分水嶺算法執行基於標記的圖像分割;該函數實現了分水嶺非參數標記分割算法的一個變;

在將圖像傳遞給函數之前,您必須大致勾勒出圖像標記中包含正索引的所需區域;

因此,每個區域被表示為具有像素值1、2、3等的一個或多個連通組件;

這樣的標記可以從二進制掩碼中檢索到,可以使用findContours和drawContours對輪廓進行查找以及繪製出輪廓;

標記的區域是未來圖像的“種子”;標記中的所有其他像素,其與所勾畫的區域之間的關係是不知道的,需要通過算法來計算,一般開始設置為0;

在函數輸出中,標記中的每個像素設置為“種子”組件的值或區域之間邊界處的-1;

Note:

任何兩個相鄰的連接部件都不一定由分水嶺邊界(-1的像素)分隔;

例如,它們可以在傳遞給函數的初始標記圖像中互相接觸。

3、參數詳解

  • 第一個參數,InputArray image,輸入圖像,一個8位三通道的圖像;
  • 第二個參數,InputOutputArray markers,輸入/輸出32位單通道標記圖像,與原圖像具有同樣的尺寸;

4、實驗案例

#include <opencv2>
#include <opencv2>
#include <opencv2>
#include <opencv2>
#include <cstdio>
#include <iostream>

using namespace cv;
using namespace std;

Mat markerMask, img;
Point prevPt(-1, -1);

// 鼠標移動事件

static void onMouse(int event, int x, int y, int flags, void*)
{
// 越界判斷
if (x < 0 || x >= img.cols || y < 0 || y >= img.rows)
return;

// 左鍵鬆開消息
if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON))
// 清理鼠標按下的座標
prevPt = Point(-1, -1);

// 左鍵按下消息
else if (event == EVENT_LBUTTONDOWN)
// 記錄鼠標按下的座標
prevPt = Point(x, y);

//鼠標移動消息
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
// 鼠標當前的位置
Point pt(x, y);

// 判斷是鼠標是否越界
if (prevPt.x < 0)
prevPt = pt;

// 在標記圖像上繪製線條
line(markerMask, prevPt, pt, Scalar::all(255), 5, 8, 0);
line(img, prevPt, pt, Scalar::all(255), 5, 8, 0);
prevPt = pt;

// 實時刷新圖像
imshow("image", img);
}
}

int main(int argc, char** argv)
{
// 打開圖像
Mat img0 = imread("lena.png", 1), imgGray;

// 判斷圖像是否為空

if (img0.empty())
{
cout << "image error !\n";
return 0;
}

// 創建顯示圖像的窗口
namedWindow("image", 1);

// 備份圖像
img0.copyTo(img);

// 彩色圖像轉換為灰度圖像
cvtColor(img, markerMask, COLOR_BGR2GRAY);

// 將單通道的灰度圖像,轉換為三通道
cvtColor(markerMask, imgGray, COLOR_GRAY2BGR);

// 將標記圖像全部像素置為0
markerMask = Scalar::all(0);

// 顯示原圖
imshow("image", img);
//imshow("imgGray", imgGray);

// 調用鼠標事件
setMouseCallback("image", onMouse, 0);
for (;;)
{
// 接收鍵盤的輸入
char c = (char)waitKey(0);

// esc鍵退出
if (c == 27)
break;

// 重置圖像為初始狀態
if (c == 'r')
{
markerMask = Scalar::all(0);
img0.copyTo(img);
imshow("image", img);

}

// 按w或者空格對圖像進行分水嶺算法處理
if (c == 'w' || c == ' ')
{
int i, j, compCount = 0;
vector<vector> > contours;
vector<vec4i> hierarchy;

// 查找圖像的輪廓
findContours(markerMask, contours,
hierarchy, RETR_CCOMP,
CHAIN_APPROX_SIMPLE);

// 如果圖像的輪廓為空,不處理
if (contours.empty())
continue;

// 標記
Mat markers(markerMask.size(), CV_32S);
markers = Scalar::all(0);
int idx = 0;

// 繪製輪廓
for (; idx >= 0; idx = hierarchy[idx][0], compCount++)
drawContours(markers, contours, idx,
Scalar::all(compCount + 1),
-1, 8, hierarchy, INT_MAX);

if (compCount == 0)
continue;
vector<vec3b> colorTab;

// 隨機生成顏色
for (i = 0; i < compCount; i++)
{
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}

double t = (double)getTickCount();

// 分水嶺算法處理

watershed(img0, markers);
t = (double)getTickCount() - t;

// 一次分水嶺算法所耗費時間
printf("execution time = %gms\n",
t*1000. / getTickFrequency());

Mat wshed(markers.size(), CV_8UC3);

// 繪製經過分水嶺處理之後的圖像
for (i = 0; i < markers.rows; i++)
for (j = 0; j < markers.cols; j++)
{
int index = markers.at(i, j);

// 未找到的用白色填充
if (index == -1)
wshed.at<vec3b>(i, j) = Vec3b(255, 255, 255);

// 不再處理範圍的用黑色填充
else if (index <= 0 || index > compCount)
wshed.at<vec3b>(i, j) = Vec3b(0, 0, 0);
else
// 每一塊用不同的顏色繪製
wshed.at<vec3b>(i, j) = colorTab[index - 1];
}

// 顯示分水嶺算法處理後的圖像,分水嶺佔%50,灰度圖像佔%50
wshed = wshed*0.5 + imgGray*0.5;
imshow("watershed transform", wshed);
}
}
return 0;
}
/<vec3b>/<vec3b>/<vec3b>
/<vec3b>/<vec4i>/<vector>/<iostream>/<cstdio>/<opencv2>/<opencv2>/<opencv2>/<opencv2>

5、實驗結果

【圖像處理】OpenCV系列二十二 --- 分水嶺算法(watershed)詳解

原圖(左)與效果圖(右)


我是奕雙,現在已經畢業將近兩年了,從大學開始學編程,期間學習了C語言編程,C++語言編程,Win32編程,MFC編程,畢業之後進入一家圖像處理相關領域的公司,掌握了用OpenCV對圖像進行處理,如果大家對相關領域感興趣的話,可以關注我,我這邊會為大家進行解答哦!如果大家需要相關學習資料的話,可以私聊我哦!


分享到:


相關文章: