上一節我們學習了用如何用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); /<vec3b>/<vec4i>/<vector>/<iostream>/<cstdio>/<opencv2>/<opencv2>/<opencv2>/<opencv2>
// 未找到的用白色填充
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>
5、實驗結果
我是奕雙,現在已經畢業將近兩年了,從大學開始學編程,期間學習了C語言編程,C++語言編程,Win32編程,MFC編程,畢業之後進入一家圖像處理相關領域的公司,掌握了用OpenCV對圖像進行處理,如果大家對相關領域感興趣的話,可以關注我,我這邊會為大家進行解答哦!如果大家需要相關學習資料的話,可以私聊我哦!
閱讀更多 奕雙分享 的文章