Python實現(xiàn)基于標記的分水嶺分割算法
分水嶺技術(shù)是一種眾所周知的分割算法,特別適用于提取圖片中的相鄰或重疊對象。使用分水嶺方法時,我們必須從用戶定義的標記開始。這些標記可以使用點擊手動定義,也可以使用閾值或形態(tài)學處理方法定義。

分水嶺技術(shù)將輸入圖像中的像素視為基于這些標記的局部極小值(稱為地形)——該方法從標記向外“淹沒”山谷,直到各種標記的山谷相遇。為了產(chǎn)生準確的分水嶺分割,必須準確地設(shè)置標記。
我們使用一種基于OpenCV標記的分水嶺技術(shù)來指定哪些谷點應(yīng)該合并,哪些不應(yīng)該合并。它是一種交互式圖像分割,而不是自動圖像分割。
1. 原理
任何灰度圖像都可以看作是一個地形表面,高峰代表高強度,山谷代表低強度。首先,用各種顏色的水(標簽)填充孤立的山谷(局部極小值)。來自不同山谷的河流,顏色明顯不同,隨著水位上升,根據(jù)相鄰的山峰(梯度)開始融合。為了避免這種情況,在水與水相遇的地方建造了屏障。你不斷注水,設(shè)置障礙,直到所有的山峰都被淹沒,分割結(jié)果由創(chuàng)建的障礙決定。

然而,由于圖像中存在噪聲或其他異常,該方法會產(chǎn)生過分割的結(jié)果。因此,OpenCV創(chuàng)建了一個基于標記的分水嶺方法,允許您選擇哪些谷點應(yīng)該合并,哪些不應(yīng)該合并。它是一種交互式圖像分割方法。我們所做的就是給每一個前景物體區(qū)域貼上不同的標簽,我們不確定的區(qū)域是標簽記為0。然后,使用分水嶺算法。獲得的結(jié)果中,對象的邊界值將為-1。
2.代碼實現(xiàn)
2.1 利用OpenCV和c++實現(xiàn)分水嶺算法
#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <vector>
void showImg(const std::string& windowName,const cv::Mat& img){
cv::imshow(windowName,img);
}
void getBackground(const cv::Mat& source,cv::Mat& dst) {
cv::dilate(source,dst,cv::Mat::ones(3,3,CV_8U)); //Kernel 3x3
}
void getForeground(const cv::Mat& source,cv::Mat& dst) {
cv::distanceTransform(source,dst,cv::DIST_L2,3,CV_32F);
cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
}
void findMarker(const cv::Mat& sureBg,cv::Mat& markers,
std::vector<std::vector<cv::Point>>& contours)
{
cv::findContours(sureBg,contours,cv::RETR_EXTERNAL,
cv::CHAIN_APPROX_SIMPLE);
// Draw the foreground markers
for (size_t i = 0,size = contours.size(); i < size; i++)
drawContours(markers, contours, static_cast<int>(i),
cv::Scalar(static_cast<int>(i)+1), -1);
}
void getRandomColor(std::vector<cv::Vec3b>& colors,size_t size)
{
for (int i = 0; i < size ; ++i)
{
int b = cv::theRNG().uniform(0, 256);
int g = cv::theRNG().uniform(0, 256);
int r = cv::theRNG().uniform(0, 256);
colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
}
}
int main (int argc,char** argv) {
// 讀取圖片
if(argc < 2)
{
std::cerr << "Error\n";
std::cerr << "Provide Input Image:\n<program> <inputimage>\n";
return -1;
}
cv::Mat original_img = cv::imread(argv[1]);
if(original_img.empty())
{
std::cerr << "Error\n";
std::cerr << "Cannot Read Image\n";
return -1;
}
// 去除圖像中的噪聲, 均值偏移模糊(MeanShift)是一種圖像邊緣保留濾波算法,常用于圖像分水嶺分割前的去噪,可顯著提高分水嶺分割效果。
cv::Mat shifted;
cv::pyrMeanShiftFiltering(original_img,shifted,21,51);
showImg("Mean Shifted",shifted);
// 將原始圖像轉(zhuǎn)換為灰度和二值圖像
cv::Mat gray_img;
cv::cvtColor(original_img,gray_img,cv::COLOR_BGR2GRAY);
showImg("GrayIMg",gray_img);
cv::Mat bin_img;
cv::threshold(gray_img,bin_img,0,255,cv::THRESH_BINARY | cv::THRESH_OTSU);
showImg("thres img",bin_img);
// 尋找確定的背景圖像, 在這一步中,我們找到圖像中的背景區(qū)域。
cv::Mat sure_bg;
getBackground(bin_img,sure_bg);
showImg("Sure Background",sure_bg);
// 找到確定前景的圖像, 對于圖像的前景,我們采用距離變換算法
cv::Mat sure_fg;
getForeground(bin_img,sure_fg);
showImg("Sure ForeGround",sure_fg);
// 找到標記,在應(yīng)用分水嶺算法之前,我們需要標記。為此,我們將使用opencv中提供的findContour()函數(shù)來查找圖像中的標記。
cv::Mat markers = cv::Mat::zeros(sure_bg.size(),CV_32S);
std::vector<std::vector<cv::Point>> contours;
findMarker(sure_bg,markers,contours);
cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); //Drawing Circle around the marker
// 應(yīng)用分水嶺算法
cv::watershed(original_img,markers);
cv::Mat mark;
markers.convertTo(mark, CV_8U);
cv::bitwise_not(mark, mark); //黑變白,白變黑
showImg("MARKER",mark);
//高亮顯示圖像中的標記
std::vector<cv::Vec3b> colors;
getRandomColor(colors,contours.size());
//構(gòu)建結(jié)果圖像
cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
// 用隨機的顏色填充已標記的物體
for (int i = 0; i < markers.rows; i++)
{
for (int j = 0; j < markers.cols; j++)
{
int index = markers.at<int>(i,j);
if (index > 0 && index <= static_cast<int>(contours.size()))
dst.at<cv::Vec3b>(i,j) = colors[index-1];
}
}
showImg("Final Result",dst);
cv::waitKey(0);
return 0;
}結(jié)果展示:



2.2 Python實現(xiàn)分水嶺分割(1)
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\
Sample code showing how to segment overlapping objects using Laplacian filtering, \
in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='HFOUG.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image:', args.input)
exit(0)
# Show source image
cv.imshow('Source Image', src)
cv.waitKey()
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# noise removal
kernel = np.ones((5, 5), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
# 獲取背景圖
sure_bg = opening.copy() # 背景
# Show output image
cv.imshow('Black Background Image', sure_bg) # 黑色是背景
cv.waitKey()
# 獲取前景圖
dist = cv.distanceTransform(opening, cv.DIST_L2, 3)
# Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)
_, dist = cv.threshold(dist, 0.2, 1.0, cv.THRESH_BINARY)
# Dilate a bit the dist image
kernel1 = np.ones((3, 3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)
cv.imshow('Peaks', dist)
# 構(gòu)建初始markers
dist_8u = dist.astype('uint8')
# Find total markers
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 創(chuàng)建即將應(yīng)用分水嶺算法的標記圖像
markers = np.zeros(dist.shape, dtype=np.int32)
# 標記前景
for i in range(len(contours)):
cv.drawContours(markers, contours, i, (i + 1), -1) # 輪廓標記從1開始
# 標記背景
cv.circle(markers, (5, 5), 3, 255, -1) # 此處背景標記為255
print("before watershed: ", np.unique(markers)) # 0表示不確定標記區(qū)域
# 可視化markers
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)
cv.waitKey()
# 應(yīng)用分水嶺分割算法
markers = cv.watershed(src, markers)
print("after watershed: ", np.unique(markers)) # -1表示邊界
# mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
mark = cv.bitwise_not(mark)
# uncomment this if you want to see how the mark
# image looks like at that point
# cv.imshow('Markers_v2', mark)
# Generate random colors
colors = []
for contour in contours:
colors.append((rng.randint(0, 256), rng.randint(0, 256), rng.randint(0, 256)))
# Create the result image
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# Fill labeled objects with random colors
for i in range(markers.shape[0]):
for j in range(markers.shape[1]):
index = markers[i, j]
if index > 0 and index <= len(contours): # -1表示邊界, 255表示背景
dst[i, j, :] = colors[index - 1]
# Visualize the final image
cv.imshow('Final Result', dst)
cv.waitKey()
結(jié)果展示:

2.3 Python實現(xiàn)分水嶺分割(2)
import cv2 as cv
import numpy as np
import argparse
import random as rng
def process_img2(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gray = cv2.GaussianBlur(img_gray, (5, 5), 0.1)
img_gray = cv2.medianBlur(img_gray, 5)
_, image_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
kernel = np.ones((7, 7), np.uint8)
# sure_bg = cv.morphologyEx(image_binary, cv.MORPH_CLOSE, kernel, iterations=3)
sure_bg = cv.dilate(image_binary, kernel, iterations=2)
sure_bg = cv.bitwise_not(sure_bg)
element = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
image_binary = cv2.morphologyEx(image_binary, cv2.MORPH_OPEN, element)
imageSC = cv2.distanceTransform(image_binary, cv2.DIST_L2, 5)
imageSC = imageSC.astype(np.uint8)
imageSC = cv2.normalize(imageSC, 0, 255, cv2.NORM_MINMAX)
_, imageSC = cv2.threshold(imageSC, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
return imageSC, sure_bg
rng.seed(12345)
imgPath = "HFOUG.jpg"
src = cv.imread(imgPath)
shifted = cv2.pyrMeanShiftFiltering(src, 7, 15)
if src is None:
print('Could not open or find the image:')
# print('Could not open or find the image:', args.input)
exit(0)
# Show source image
cv.imshow('Source Image', src)
cv.waitKey()
opening, sure_bg = process_img2(shifted)
# Show output image
cv.imshow('Background Image', sure_bg) # 背景
cv.waitKey()
# 獲取前景圖
dist = cv.distanceTransform(opening, cv.DIST_L2, 3)
# Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)
_, dist = cv.threshold(dist, 0.3, 1.0, cv.THRESH_BINARY)
# Dilate a bit the dist image
kernel1 = np.ones((3, 3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)
cv.imshow('Peaks', dist)
# 構(gòu)建初始markers
dist_8u = dist.astype('uint8')
# Find total markers
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 創(chuàng)建即將應(yīng)用分水嶺算法的標記圖像
# markers = np.zeros(dist.shape, dtype=np.int32)
markers = sure_bg.copy().astype(np.int32)
# 標記前景
for i in range(len(contours)):
cv.drawContours(markers, contours, i, (i + 1), -1) # 輪廓標記從1開始
# 標記背景
# cv.circle(markers, (5, 5), 3, 255, -1) # 此處背景標記為255
# 可視化markers
print("before watershed: ", np.unique(markers)) # 0表示不確定標記區(qū)域
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)
# 應(yīng)用分水嶺分割算法
markers = cv.watershed(src, markers)
print("after watershed: ", np.unique(markers)) # -1表示邊界
# mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
mark = cv.bitwise_not(mark)
cv.imshow('Markers_v2', mark)
# Generate random colors
colors = []
for contour in contours:
colors.append((rng.randint(0, 256), rng.randint(0, 256), rng.randint(0, 256)))
# Create the result image
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# Fill labeled objects with random colors
for i in range(markers.shape[0]):
for j in range(markers.shape[1]):
index = markers[i, j]
if index > 0 and index <= len(contours): # -1表示邊界, 255表示背景
dst[i, j, :] = colors[index - 1]
# Visualize the final image
cv.imshow('Final Result', dst)
cv.waitKey(0)
cv2.destroyAllWindows()
結(jié)果展示:

到此這篇關(guān)于Python實現(xiàn)基于標記的分水嶺分割算法的文章就介紹到這了,更多相關(guān)Python分水嶺分割算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解使用python的logging模塊在stdout輸出的兩種方法
這篇文章主要介紹了詳解使用python的logging模塊在stdout輸出的相關(guān)資料,需要的朋友可以參考下2017-05-05
Python更新數(shù)據(jù)庫腳本兩種方法及對比介紹
這篇文章給大家介紹了Python更新數(shù)據(jù)庫腳本兩種方法及數(shù)據(jù)庫查詢?nèi)N方式,然后在文章下面給大家介紹了兩種方式對比介紹,非常不錯,感興趣的朋友參考下吧2017-07-07
python實現(xiàn)從pdf文件中提取文本,并自動翻譯的方法
今天小編就為大家分享一篇python實現(xiàn)從pdf文件中提取文本,并自動翻譯的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11
淺談Python 命令行參數(shù)argparse寫入圖片路徑操作
這篇文章主要介紹了淺談Python 命令行參數(shù)argparse寫入圖片路徑操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
python數(shù)據(jù)處理實戰(zhàn)(必看篇)
下面小編就為大家?guī)硪黄猵ython數(shù)據(jù)處理實戰(zhàn)(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06
Python中使用OpenCV庫來進行簡單的氣象學遙感影像計算
這篇文章主要介紹了Python中使用OpenCV庫來進行簡單的氣象學圖像計算的例子,文中是用來進行光譜輻射定標、大氣校正和計算反射率,需要的朋友可以參考下2016-02-02

