本來沒想研究這個,但Nvidia NPP的《NVIDIA 2D Image And Signal Performance Primitives》這個模塊的NPP Image Processing部分的Filtering Functions中的computer vision部分有連通域標記以及分水嶺分割現成的函數。但是當我google這個函數的內容時,卻發現根本沒普通人使用,只彈出nvidia官網對這個函數的介紹:
如果有很多人使用過NPP的這個函數,怎麼可能就2條結果。我內心有點忐忑是不是這部分內容並不好用。
其中官網介紹這個分水嶺分割是基於論文《Effiffifficient 2D and 3D Watershed on Graphics Processing Unit: Block-Asynchronous Approches Based on Cellular automata》然後我就查關於CA的內容,然後看到了有CA的區域生長,對於粘連目標,選每個目標的中心爲種子點,分別區域生長是不是就把這個粘連目標分割開了。想這樣做。
代碼:https://github.com/KiriteeGak/region-growing-by-cellular-automata 其參考的論文是Vezhnevets, Vladimir, and Vadim Konouchine. "GrowCut: Interactive multi-label ND image segmentation by cellular automata." proc. of Graphicon. Vol. 1. 2005. 我的理解如下:
class RegionGrowing(object):
def region_growing(self, image_array, file_path, cutoff_threshold, iterations):
size = np.shape(image_array)
#圖像矩陣的數組
image_array_map = {(r, c): pixel for r, each_row in enumerate(image_array) for c, pixel in
enumerate(each_row)}
#種子點狀態初始化爲1,其他設置爲0??
seeds_map = {(r, c): 1 for (r, c) in self._seed_points(file_path)}
#默認要經過50次迭代
for it in range(0, iterations):
#先複製上一次的種子點的狀態
_update_seeds_map = seeds_map.copy()
for pixel_coord, strength in seeds_map.iteritems():
#更新每個點的鄰域的值
_temp_weights_neighbors_pixel = self._neighborhood_weighting(pixel_coord, image_array_map,
seeds_map, cutoff_threshold, size)
#更新種子點的座標和值
_update_seeds_map = self._update_weights(_temp_weights_neighbors_pixel, _update_seeds_map)
#將更新後的種子點作爲下一次迭代開始的種子點
seeds_map = _update_seeds_map
self._save_image(self._make_binary_image(np.shape(image_array), seeds_map))
@staticmethod
def _seed_points(file_path):
fid = open(file_path, 'rb')
return [list(map(lambda x: int(x.replace(' ', '')), line.strip().split(','))) for line in fid]
@staticmethod
#更新每個點的鄰域的值後,更新種子點的統計(包括種子點座標和值)
def _update_weights(_temp_weights_neighbors_pixel, _update_seeds_map):
for pixel_address, strength in _temp_weights_neighbors_pixel.iteritems():
#如果原來的種子點統計內沒有這個點,那要把這個點新加進去。
if pixel_address not in _update_seeds_map:
_update_seeds_map[pixel_address] = strength
#如果原來的種子點內有這個點,已經是種子點,但原來這個種子點的值小於現在的值,那麼這個將種子點統計內這個點的值進行更新
elif pixel_address in _update_seeds_map and _update_seeds_map[pixel_address] < strength:
_update_seeds_map[pixel_address] = strength
else:
pass
return _update_seeds_map
#更新每個像素點的鄰域點的值
def _neighborhood_weighting(self, coord, image_map, seeds_map, threshold, canvas_size):
#所有像素點還是種子點???應該是所有像素點的具體座標[r,c],圖像尺寸大小[max_r,max_c]
[[r, c], [max_r, max_c]] = [coord, canvas_size]
_temp_weights = {}
#計算所有像素點3X3鄰域
for i in range(-1, 2, 1):
for j in range(-1, 2, 1):
pixel_key = (r, c)
#中心點不用計算,判斷越界
if [i, j] != [0, 0] and 0 <= r + i < max_r and 0 <= c + j < max_c:
#如果這個中心點是種子點
if pixel_key in seeds_map:
#計算這個中心點與鄰域的strength
trans_strength = self._calculate_strength(image_map[pixel_key],
image_map[(r + i, c + j)],
seeds_map[pixel_key], threshold)
else:
##計算這個中心點與鄰域的strength
trans_strength = self._calculate_strength(image_map[pixel_key],
image_map[(r + i, c + j)], 0,
threshold)
if trans_strength != 0:
#對這個點的鄰域進行更新
_temp_weights[(r + i, c + j)] = trans_strength
return _temp_weights
@staticmethod
#中心點,待計算的鄰域點,種子點,閾值默認是0.5
def _calculate_strength(dat1, dat2, strength, threshold):
if dat1 != 0 or dat2 != 0:
strength_trans = strength * (1 - (abs(dat1 - dat2) / max(dat1, dat2)))
if strength_trans >= threshold:
return strength_trans
return 0
然後準備按照這個理解實現C++版本,我的註釋部分即每個像素點都要計算3X3鄰域然後更新鄰域的值,那豈不是一個鄰域會被相鄰的幾個中心點更新很多次?這樣不是很浪費時間嗎??另外一點,我按照這個註釋運行完C++結果,這是以[300,300]爲種子點迭代50、100、200、300、400、450、480次的結果:
可以看到需要迭代很多次輪廓纔是完全的。是不是因此網上給出的python代碼才選了不止一個種子點,而是5個然後迭代幾十次就夠了!!!因爲單一種子點耗時太長是嗎。
論文中提到對於多個目標的分割也是可以應用的,但我看論文中多個目標時選的種子點更多,我覺得這個算法應用有點狹窄,最起碼現在我有點不想使用了。
但是我還是要試試對於粘連輪廓分割怎樣,是否可以達到分水嶺的效果
這是我的待分割原圖,其實分水嶺的效果已經分割得可以。然後以下分別是迭代10、15、20次的結果
可以看到不適合我的應用,因爲我的目標大小不一。
看來還是要去理解關於CA的分水嶺算法那篇論文。
另外感覺這個博客平臺越來越功利了,現在只爲了賺錢而存在,不想在這裏記錄了,想轉簡書或博客園去。