目錄
一、基礎理論
1、思想
2、原理
二、分水嶺實戰:硬幣
步驟歸納
1、把原影像轉二值圖
2、開運算去噪
3、確定背景區域(膨脹)(得到背景/最大連通域)
4、確定前景區域(距離變換) (分離)(得到種子/前景)
5、找到未知區域(未知區域=背景-前景)
6、根據種子標記最大連通域
7、使用分水嶺演算法:合并種子和不確定區域、標記邊界為-1
8、涂色并顯示
總代碼及效果(硬幣)
三、分水嶺實戰:撲克牌
步驟歸納
1、銳化
2、開運算去噪
3、獲取背景(全連通域)
3、確定前景區域(距離變換)(種子)
4、找未知區域(未知區域 = 確定的背景-確定的前景)
5、根據種子標記最大連通域
6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1
7、涂色并顯示
總代碼及效果(撲克牌)
四、分水嶺實戰:路面檢測
1、 銳化
2、銳化之后再二值化
3、膨脹獲取背景
4、距離變換獲取前景
5、獲取不確定區域(背景-前景)
6、顯示標記情況
7、成功檢測出路面的分水嶺
總代碼
參考資料
一、基礎理論
1、思想
防止同化,
任何灰度影像都可以看作是一個地形表面,其中高強度表示山峰,低強度表示山谷,你開始用不同顏色的水(標簽)填充每個孤立的山谷(區域最小值),隨著水位的上升,根據附近的山峰(坡度),來自不同山谷的水明顯會開始合并,顏色也不同,為了避免這種情況,你要在水融合的地方建造屏障,你繼續填滿水,建造障礙,直到所有的山峰都在水下,然后你創建的屏障將回傳你的分割結果,這就是Watershed背后的“思想”,你可以訪問Watershed的CMM網頁,了解它與一些影片的幫助,
但是這種方法會由于影像中的噪聲或其他不規則性而產生過度分割的結果,因此OpenCV實作了一個基于標記的分水嶺演算法,你可以指定哪些是要合并的山谷點,哪些不是,這是一個互動式的影像分割,
2、原理
我們所做的是:給我們知道的物件賦予不同的標簽,用一種顏色(或強度)標記我們確定為前景或物件的區域,用另一種顏色標記我們確定為背景或非物件的區域,最后用
0
標記我們不確定的區域,這是我們的標記,然后應用分水嶺演算法,然后我們的標記將使用我們給出的標簽進行更新,物件的邊界值將為-1
,靠近物件中心的區域是前景,而離物件中心很遠的區域是背景,剩下的區域是不確定區域,
二、分水嶺實戰:硬幣
首先看看原圖:
步驟歸納
1、影像二值化
2、開運算去噪
3、確定背景區域(膨脹)(得到背景/最大連通域)
4、確定前景區域(距離變換) (分離)(得到種子/前景)
5、找到未知區域(未知區域=背景-前景)
6、根據種子標記最大連通域
7、使用分水嶺演算法:合并種子和不確定區域、標記邊界為-1
8、涂色并顯示
1、把原影像轉二值圖
# 轉二值影像
def ToBinary():
global gray, binary
# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('binary', binary)
(為了方便后面的呼叫,這里把灰度圖和二值圖都設定為了global全域變數)
2、開運算去噪
開運算,去除噪聲
# 1、開運算去噪
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
cv.imshow('opening', opening)
3、確定背景區域(膨脹)(得到背景/最大連通域)
膨脹之后,物件擴大,背景減小,此時物件>原物件,此時背景 < 原背景,那么此時的背景自然可以確定為原背景的一部分,(離物件中心很遠的是背景)
# 2、膨脹確定背景區域
sure_bg = cv.dilate(opening, (3,3), iterations=3)
cv.imshow('sure_bg', sure_bg)
4、確定前景區域(距離變換) (分離)(得到種子/前景)
原理:距離變換,在二值圖中把物件縮小,得到的就是原圖的一部分,可以確定為前景,類似于分離,(不分離的話,可以不用距離變換,只用腐蝕就夠了)
為什么不能直接縮小?
直接縮小會讓圖片也跟著變化,我們要的只是把物件縮小,圖片不變的,
distanceTransform函式:計算非零像素到最近零像素點的最短距離,一般用于求解影像的骨骼(二值圖)
def distanceTransform(src: Any, distanceType: Any, maskSize: Any, dst: Any = None, dstType: Any = None) -> None
distanceType :所用的求解距離的型別,
mask_size :距離變換掩模的大小,可以是 3 或 5, 對 CV_DIST_L1 或 CV_DIST_C 的情況,引數值被強制設定為 3, 因為 3×3 mask 給出 5×5 mask 一樣的結果,而且速度還更快,
# 3、確定前景區域(距離變換)
# 3-1、求物件最大寬度/長度(直徑)
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
# 3-2、最長直徑縮小一定程度,確定前景
ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, cv.THRESH_BINARY)
# 前景 閾值函式 閾值 最大值 二值化方式
sure_fg = np.uint8(sure_fg)
cv.imshow('sure_fg', sure_fg)
sure_fg是種子 ,(后面的標記需要根據種子獲取標記)
5、找到未知區域(未知區域=背景-前景)
未知區域 = 確定的背景 - 確定的前景
# 4、找未知區域(未知區域 = 確定的背景-確定的前景)
unknown = cv.subtract(sure_bg, sure_fg)
cv.imshow('unknown', unknown)
6、根據種子標記最大連通域
根據種子標記最大連通域(大于1為內部區域,標記1為背景區域,0為未知區域)
# 5、根據種子標記最大連通域(大于1為內部區域,標記1為背景區域,0為未知區域)
ret, markers = cv.connectedComponents(sure_fg) #標記最大連通域
markers = markers+1 #最大連通域標記為1(背景)
markers[unknown == 255] = 0 #未知區域標記為0
顯示:連通域(背景)、不確定區域、種子(前景)
#顯示各區域(連通域/背景、不確定區域、種子/前景)
def Show_Markers():
mark = img.copy()
mark[markers == 1] = (255, 0, 0) #連通域/背景(藍)
mark[markers == 0] = (0, 255, 0) #不確定區域(綠)
mark[markers > 1] = (0, 0, 255) #前景/種子(紅)
mark[markers == -1] = (0, 255, 0) #邊界(綠)
cv.imshow('Markers', mark)
7、使用分水嶺演算法:合并種子和不確定區域、標記邊界為-1
# 6、使用分水嶺演算法,邊界修改為-1,邊界涂紅(-1)(分界:連通域背景 -- 未知區域+種子)
markers = cv.watershed(img, markers) # 分水嶺演算法(修改邊界為-1)
8、涂色并顯示
# 7、涂色并顯示(邊界(markers==-1)涂色)
dst = img.copy()
dst[markers == -1] = [0, 0, 255] #邊界(-1)涂色
cv.imshow('dst', dst)
總代碼及效果(硬幣)
# 距離變換與分水嶺演算法
import numpy as np
import cv2 as cv
# 轉二值影像
def ToBinary():
global gray, binary
# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('binary', binary)
#顯示各區域(連通域/背景、不確定區域、種子/前景)
def Show_Markers():
mark = img.copy()
mark[markers == 1] = (255, 0, 0) #連通域/背景(藍)
mark[markers == 0] = (0, 255, 0) #不確定區域(綠)
mark[markers > 1] = (0, 0, 255) #前景/種子(紅)
mark[markers == -1] = (0, 255, 0) #邊界(綠)
cv.imshow('Markers', mark)
# 分水嶺找邊界
def Watershed():
global markers
# 1、開運算去噪
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
cv.imshow('opening', opening)
# 2、確定背景區域(膨脹)
sure_bg = cv.dilate(opening, (3,3), iterations=3)
cv.imshow('sure_bg', sure_bg)
# 3、確定前景區域(距離變換)(種子)
# 3-1、求物件最大寬度/長度(直徑)
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
# 3-2、最長直徑按比例縮小,確定前景
ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, cv.THRESH_BINARY)
# 前景 閾值函式 閾值 最大值 二值化方式
sure_fg = np.uint8(sure_fg)
cv.imshow('sure_fg', sure_fg)
# 4、找未知區域(未知區域 = 確定的背景-確定的前景)
unknown = cv.subtract(sure_bg, sure_fg)
cv.imshow('unknown', unknown)
# 5、根據種子標記最大連通域(大于1為內部區域,標記1為背景區域,0為未知區域)
ret, markers = cv.connectedComponents(sure_fg) #標記最大連通域
markers = markers+1 #背景標記為1(此為最大連通域)
markers[unknown == 255] = 0 #未知區域標記為0
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
# 6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1,(分界:連通域背景 -- 未知區域+種子)
markers = cv.watershed(img, markers) # 分水嶺演算法(修改邊界為-1)
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
# 7、涂色并顯示(邊界(markers==-1)涂色)
dst = img.copy()
dst[markers == -1] = [0, 0, 255] #邊界(-1)涂色
cv.imshow('dst', dst)
if __name__ == '__main__':
img = cv.imread('Resource/test19.jpg')
cv.imshow('img', img)
ToBinary() #轉二值圖
Watershed() #分水嶺找邊界
cv.waitKey(0)
三、分水嶺實戰:撲克牌
(這個是自實作,不那么規范)
原圖:
步驟歸納
1、銳化
2、開運算去噪
3、獲取背景(全連通域)
3、確定前景區域(距離變換)(種子)
4、找未知區域(未知區域 = 確定的背景-確定的前景)
5、根據種子標記最大連通域
6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1
7、涂色并顯示
1、銳化
如果只是用硬幣的代碼,會出現這樣的效果(只能分出外部的區域):
這里我們有必要在二值化前進行銳化,
一開始使用的是卷積核:
kernel = np.array([
[1, 1, 1],
[1, -8, 1],
[1, 1, 1]
])
發現邊緣還是不夠明顯,繼續升級卷積核:
kernel = np.array([
[2, 2, 2],
[2, -16, 2],
[2, 2, 2]
])
現在可以看到,二值影像邊緣已經比較清晰銳利了,
2、開運算去噪
# 1、開運算去噪
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
cv.imshow('opening', opening)
3、獲取背景(全連通域)
這里的撲克牌只是空架子,就不膨脹處理了,
# 2、確定背景區域(這里只是空架子,就不膨脹了)
sure_bg = opening.copy()
cv.imshow('sure_bg', sure_bg)
3、確定前景區域(距離變換)(種子)
# 3、確定前景區域(距離變換)(種子)
# 3-1、求物件最大寬度/長度(直徑)
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 3, 5)
# 3-2、最長直徑按比例縮小,確定前景
ret, sure_fg = cv.threshold(dist_transform, 0.01 * dist_transform.max(), 255, cv.THRESH_BINARY)
# 前景 閾值函式 閾值 最大值 二值化方式
sure_fg = np.uint8(sure_fg)
cv.imshow('sure_fg', sure_fg)
4、找未知區域(未知區域 = 確定的背景-確定的前景)
# 4、找未知區域(未知區域 = 確定的背景-確定的前景)
unknown = cv.subtract(sure_bg, sure_fg)
cv.imshow('unknown', unknown)
5、根據種子標記最大連通域
# 5、根據種子標記最大連通域(大于1為內部區域,標記1為背景區域,0為未知區域)
ret, markers = cv.connectedComponents(sure_fg) #標記最大連通域
markers = markers+1 #背景標記為1(此為最大連通域)
markers[unknown == 255] = 0 #未知區域標記為0
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1
# 6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1(分界:連通域背景 -- 未知區域+種子)
markers = cv.watershed(img, markers) # 分水嶺演算法(修改邊界為-1)
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
7、涂色并顯示
# 7、涂色并顯示(邊界(markers==-1)涂色)
dst = img.copy()
dst[markers == -1] = [0, 255, 0] #邊界(-1)涂色
cv.imshow('dst', dst)
總代碼及效果(撲克牌)
# 距離變換與分水嶺演算法
import numpy as np
import cv2 as cv
# 轉二值影像
def ToBinary():
global gray, binary
# 1、銳化
kernel = np.array([
[2, 2, 2],
[2, -16, 2],
[2, 2, 2]
])
sharp = cv.filter2D(img, -1, kernel)
cv.imshow('sharp', sharp)
# 灰度化
gray = cv.cvtColor(sharp, cv.COLOR_BGR2GRAY)
cv.imshow('gray', gray)
# 二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('binary', binary)
#顯示各區域(連通域/背景、不確定區域、種子/前景)
def Show_Markers():
mark = img.copy()
mark[markers == 1] = (255, 0, 0) #連通域/背景(藍)
mark[markers == 0] = (0, 255, 0) #不確定區域(綠)
mark[markers > 1] = (0, 0, 255) #前景/種子(紅)
mark[markers == -1] = (0, 255, 0) #邊界(綠)
cv.imshow('Markers', mark)
# 分水嶺找邊界
def Watershed():
global markers
# 1、開運算去噪
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
cv.imshow('opening', opening)
# 2、確定背景區域(這里只是空架子,就不膨脹了)
sure_bg = opening.copy()
cv.imshow('sure_bg', sure_bg)
# 3、確定前景區域(距離變換)(種子)
# 3-1、求物件最大寬度/長度(直徑)
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 3, 5)
# 3-2、最長直徑按比例縮小,確定前景
ret, sure_fg = cv.threshold(dist_transform, 0.01 * dist_transform.max(), 255, cv.THRESH_BINARY)
# 前景 閾值函式 閾值 最大值 二值化方式
sure_fg = np.uint8(sure_fg)
cv.imshow('sure_fg', sure_fg)
# 4、找未知區域(未知區域 = 確定的背景-確定的前景)
unknown = cv.subtract(sure_bg, sure_fg)
cv.imshow('unknown', unknown)
# 5、根據種子標記最大連通域(大于1為內部區域,標記1為背景區域,0為未知區域)
ret, markers = cv.connectedComponents(sure_fg) #標記最大連通域
markers = markers+1 #背景標記為1(此為最大連通域)
markers[unknown == 255] = 0 #未知區域標記為0
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
# 6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1(分界:連通域背景 -- 未知區域+種子)
markers = cv.watershed(img, markers) # 分水嶺演算法(修改邊界為-1)
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
# 7、涂色并顯示(邊界(markers==-1)涂色)
dst = img.copy()
dst[markers == -1] = [0, 255, 0] #邊界(-1)涂色
cv.imshow('dst', dst)
if __name__ == '__main__':
img = cv.imread('Resource/test20.jpg')
cv.imshow('img', img)
ToBinary() #轉二值圖
Watershed() #分水嶺找邊界
cv.waitKey(0)
四、分水嶺實戰:路面檢測
先銳化再進行處理,會適用于很多種情況,
這里和撲克牌的類似,都是邊界不夠明顯,需要銳化處理的,前面的撲克牌只有一個框架,沒法膨脹,路面可以膨脹處理獲取背景,
原圖:
1、 銳化
2、銳化之后再二值化
3、膨脹獲取背景
和上面撲克牌不同的就是這里,這里的路面不是空架子,可以進行膨脹,
4、距離變換獲取前景
5、獲取不確定區域(背景-前景)
6、顯示標記情況
7、成功檢測出路面的分水嶺
總代碼
# 距離變換與分水嶺演算法(路面檢測)
import numpy as np
import cv2 as cv
# 轉二值影像
def ToBinary():
global gray, binary
# 1、銳化
kernel = np.array([
[2, 2, 2],
[2, -16, 2],
[2, 2, 2]
])
sharp = cv.filter2D(img, -1, kernel)
cv.imshow('sharp', sharp)
# 灰度化
gray = cv.cvtColor(sharp, cv.COLOR_BGR2GRAY)
cv.imshow('gray', gray)
# 二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('binary', binary)
#顯示各區域(連通域/背景、不確定區域、種子/前景)
def Show_Markers():
mark = img.copy()
mark[markers == 1] = (255, 0, 0) #連通域/背景(藍)
mark[markers == 0] = (0, 255, 0) #不確定區域(綠)
mark[markers > 1] = (0, 0, 255) #前景/種子(紅)
mark[markers == -1] = (0, 255, 0) #邊界(綠)
cv.imshow('Markers', mark)
# 分水嶺找邊界
def Watershed():
global markers
# 1、開運算去噪
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=3)
cv.imshow('opening', opening)
# 2、確定背景區域(膨脹)
sure_bg = cv.dilate(opening, (3,3), iterations=2)
cv.imshow('sure_bg', sure_bg)
# 3、確定前景區域(距離變換)(種子)
# 3-1、求物件最大寬度/長度(直徑)
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 3, 5)
# 3-2、最長直徑按比例縮小,確定前景
ret, sure_fg = cv.threshold(dist_transform, 0.01 * dist_transform.max(), 255, cv.THRESH_BINARY)
# 前景 閾值函式 閾值 最大值 二值化方式
sure_fg = np.uint8(sure_fg)
cv.imshow('sure_fg', sure_fg)
# 4、找未知區域(未知區域 = 確定的背景-確定的前景)
unknown = cv.subtract(sure_bg, sure_fg)
cv.imshow('unknown', unknown)
# 5、根據種子標記最大連通域(大于1為內部區域,標記1為背景區域,0為未知區域)
ret, markers = cv.connectedComponents(sure_fg) #標記最大連通域
markers = markers+1 #背景標記為1(此為最大連通域)
markers[unknown == 255] = 0 #未知區域標記為0
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
# 6、使用分水嶺演算法,合并不確定區域和種子,邊界修改為-1(分界:連通域背景 -- 未知區域+種子)
markers = cv.watershed(img, markers) # 分水嶺演算法(修改邊界為-1)
Show_Markers() #顯示各區域(連通域/背景、不確定區域、種子/前景)
# 7、涂色并顯示(邊界(markers==-1)涂色)
dst = img.copy()
dst[markers == -1] = [0, 255, 0] #邊界(-1)涂色
cv.imshow('dst', dst)
if __name__ == '__main__':
img = cv.imread('Resource/road.jpg')
cv.imshow('img', img)
ToBinary() #轉二值圖
Watershed() #分水嶺找邊界
cv.waitKey(0)
參考資料
http://woshicver.com/FifthSection/4_15_%E5%9B%BE%E5%83%8F%E5%88%86%E5%89%B2%E4%B8%8E%E5%88%86%E6%B0%B4%E5%B2%AD%E7%AE%97%E6%B3%95/
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/295756.html
標籤:其他
上一篇:vue+科大訊飛語音聽寫功能(解決針對vue new Worker報錯問題)
下一篇:ResNet