OpenCV基于距離變換和分水嶺實現(xiàn)圖像分割
一.圖像分割
圖像分割是根據(jù)灰度、顏色、紋理和形狀等特征,把圖像分成若干個特定的、具有獨特性質(zhì)的區(qū)域,這些特征在同一區(qū)域內(nèi)呈現(xiàn)出相似性,而在不同區(qū)域間呈現(xiàn)出明顯的差異性,并提出感興趣目標的技術(shù)和過程。 它是由圖像處理到圖像分析的關(guān)鍵步驟。從數(shù)學角度來看,圖像分割是將數(shù)字圖像劃分成互不相交的區(qū)域的過程。圖像分割的過程也是一個標記過程,即把屬于同一區(qū)域的像索賦予相同的編號。
其目的是將圖像中像素根據(jù)一定的規(guī)則分為若干(N)個聚(cluster)集合,每個集合包含一類像素。將對象在背景提取出來。
二.基于距離變換和分水嶺的圖像分割
分水嶺法(Meyer)是一種基于拓撲理論的數(shù)學形態(tài)學的分割方法,其基本思想是把圖像看作是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的海拔高度,每一個局部極小值及其影響區(qū)域稱為集水盆,而集水盆的邊界則形成分水嶺。該算法的實現(xiàn)可以模擬成洪水淹沒的過程,圖像的最低點首先被淹沒,然后水逐漸淹沒整個山谷。當水位到達一定高度的時候?qū)绯?,這時在水溢出的地方修建堤壩,重復這個過程直到整個圖像上的點全部被淹沒,這時所建立的一系列堤壩就成為分開各個盆地的分水嶺。分水嶺算法對微弱的邊緣有著良好的響應,但圖像中的噪聲會使分水嶺算法產(chǎn)生過分割的現(xiàn)象。
距離變換API函數(shù)接口
距離變換用于計算圖像中每一個非零點像素與其周圍最近的零點像素之間的距離,返回的值保存了每一個非零點與最近零點的距離信息;在圖像上的體現(xiàn)為圖像上越亮的點,代表了離零點的距離越遠。
void distanceTransform( InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP );
參數(shù)說明
(1)src是單通道的8bit的二值圖像(只有0或1)
(2)dst表示的是計算距離的輸出圖像,可以使單通道32bit浮點數(shù)據(jù)
(3)distanceType表示的是選取距離的類型,可以設(shè)置為CV_DIST_L1,CV_DIST_L2,CV_DIST_C等,具體如下:
| DIST_USER | User defined distance |
| DIST_L1=1 | distance = |x1-x2| + |y1-y2 |
| DIST_L2 | the simple euclidean distance |
| DIST_C | distance = max(|x1-x2|,|y1-y2|) |
| DIST_L12 | L1-L2 metric: distance =2(sqrt(1+x*x/2) - 1)) |
| DIST_FAIR | distance = c^2(|x|/c-log(1+|x|/c)),c = 1.3998 |
| DIST_WELSCH | distance = c2/2(1-exp(-(x/c)2)), c= 2.9846 |
| DIST_HUBER | distance = |x|<c ? x^2/2 :c(|x|-c/2), c=1.345 |
(4)maskSize表示的是距離變換的掩膜模板,可以設(shè)置為3,5或CV_DIST_MASK_PRECISE,對 CV_DIST_L1 或CV_DIST_C 的情況,參數(shù)值被強制設(shè)定為 3, 因為3×3 mask 給出5×5 mask 一樣的結(jié)果,而且速度還更快。
| DIST_MASK_3 | mask=3 |
| DIST_MASK_5 | mask=5 |
| DIST_MASK-PRECISE |
(5)labels表示可選輸出2維數(shù)組;
(6)labelType表示的是輸出二維數(shù)組的類型,8位或者32位浮點數(shù),圖像是單一通道,并且大小與輸入圖像一致
watershed 分水嶺函數(shù)API接口
void watershed( InputArray image, InputOutputArray markers );
參數(shù)說明
(1)參數(shù) image,必須是一個8bit3通道彩色圖像矩陣序列。
(2) 輸入或輸出32位單通道的標記,和圖像一樣大小。(輸入高峰輪廓標記);在執(zhí)行分水嶺函數(shù)watershed之前,必須對第二個參數(shù)markers進行處理,它應該包含不同區(qū)域的輪廓,每個輪廓有一個自己唯一的編號,輪廓的定位可以通過Opencv中findContours方法實現(xiàn),這個是執(zhí)行分水嶺之前的要求。
算法會根據(jù)markers傳入的輪廓作為種子(也就是所謂的注水點),對圖像上其他的像素點根據(jù)分水嶺算法規(guī)則進行判斷,并對每個像素點的區(qū)域歸屬進行劃定,直到處理完圖像上所有像素點。而區(qū)域與區(qū)域之間的分界處的值被置為“-1”,以做區(qū)分。
代碼實現(xiàn)
1、將白色背景改為黑色,為后面的變換做準備;
2、使用filter2D與Laplace算子實現(xiàn)圖像對比度的提高;sharpen銳化
3、通過threshold()轉(zhuǎn)換為二值圖像;
4、距離變換;
5、對距離變換的結(jié)果歸一化到0-1之間;
6、使用閾值,再次二值化,得到標記;
7、腐蝕(erode)得到每個Peak;(peak : 山峰,山頂 )
8、發(fā)現(xiàn)輪廓findContours;
9、繪制輪廓;
10、分水嶺變換;
11、對每個分割區(qū)域著色輸出結(jié)果;
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
char input_win[] = "input image";
char watershed_win[] = "watershed segmentation demo";
Mat src = imread("D:/vcprojects/images/cards.png");
// Mat src = imread("D:/kuaidi.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
// 1. change background
for (int row = 0; row < src.rows; row++) {
for (int col = 0; col < src.cols; col++) {
if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
}
namedWindow("black background", CV_WINDOW_AUTOSIZE);
imshow("black background", src);
// sharpen
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
Mat imgLaplance;
Mat sharpenImg = src;
filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
src.convertTo(sharpenImg, CV_32F);
Mat resultImg = sharpenImg - imgLaplance;
resultImg.convertTo(resultImg, CV_8UC3);
imgLaplance.convertTo(imgLaplance, CV_8UC3);
imshow("sharpen image", resultImg);
// src = resultImg; // copy back
// convert to binary
Mat binaryImg;
cvtColor(src, resultImg, CV_BGR2GRAY);
threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary image", binaryImg);
Mat distImg;
distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);
normalize(distImg, distImg, 0, 1, NORM_MINMAX);
imshow("distance result", distImg);
// binary again
threshold(distImg, distImg, .4, 1, THRESH_BINARY);
Mat k1 = Mat::ones(13, 13, CV_8UC1);
erode(distImg, distImg, k1, Point(-1, -1));
imshow("distance binary image", distImg);
// markers
Mat dist_8u;
distImg.convertTo(dist_8u, CV_8U);
vector<vector<Point>> contours;
findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
// create makers
Mat markers = Mat::zeros(src.size(), CV_32SC1);
for (size_t i = 0; i < contours.size(); i++) {
drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
imshow("my markers", markers*1000);
// perform watershed
watershed(src, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed image", mark);
// generate random color
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++) {
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// fill with color and display final result
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
for (int row = 0; row < markers.rows; row++) {
for (int col = 0; col < markers.cols; col++) {
int index = markers.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size())) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("Final Result", dst);
waitKey(0);
return 0;
}圖像處理效果
輸入原圖像和銳化圖像

原圖和黑背景圖(背景應為黑色)

threshold轉(zhuǎn)化的二值化圖片和距離變換結(jié)果圖

距離變換結(jié)果圖和二值化圖像

到此這篇關(guān)于OpenCV基于距離變換和分水嶺實現(xiàn)圖像分割的文章就介紹到這了,更多相關(guān)OpenCV圖像分割內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt實現(xiàn)指針式時鐘 Qt實現(xiàn)動態(tài)時鐘
這篇文章主要為大家詳細介紹了Qt實現(xiàn)指針式時鐘,Qt實現(xiàn)動態(tài)時鐘,兩者相互切換,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-07-07
c語言數(shù)據(jù)結(jié)構(gòu)與算法之順序表的定義實現(xiàn)詳解
這篇文章主要介紹了c語言數(shù)據(jù)結(jié)構(gòu)與算法之順序表的定義實現(xiàn)詳解,用順序存儲的方式實現(xiàn)線性表順序存儲,把邏輯上相鄰的元素存儲在物理位置上也相鄰的存儲單元中,元素之間的關(guān)系由存儲單元的鄰接關(guān)系來體現(xiàn),需要的朋友可以參考下2023-08-08
C++數(shù)據(jù)結(jié)構(gòu)分析多態(tài)的實現(xiàn)與原理及抽象類
繼承就是可以直接使用前輩的屬性和方法。自然界如果沒有繼承,那一切都是處于混沌狀態(tài)。多態(tài)是同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力。多態(tài)就是同一個接口,使用不同的實例而執(zhí)行不同操作2022-02-02
c語言循環(huán)加數(shù)組實現(xiàn)漢諾塔問題
本文主要介紹了c語言循環(huán)加數(shù)組實現(xiàn)漢諾塔問題,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01

