OpenCV(26)图像分割 -- 距离变换与分水岭算法(硬币检测、扑克牌检测、车道检测)
目錄
一、基礎理論
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) -> NonedistanceType :所用的求解距離的類型。
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 #未知區域標記為0Show_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 #未知區域標記為0Show_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 #未知區域標記為0Show_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 #未知區域標記為0Show_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/
總結
以上是生活随笔為你收集整理的OpenCV(26)图像分割 -- 距离变换与分水岭算法(硬币检测、扑克牌检测、车道检测)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV(25)轮廓检测(轮廓提取、
- 下一篇: OpenCV(项目)二维码识别(二维码、