SML中的邊界抑制以及高斯平滑
邊界平滑抑制類
class BoundarySuppressionWithSmoothing(nn.Module):
"""
Apply boundary suppression and dilated smoothing 邊界抑制,擴張平滑
"""
初始化
def __init__(self, boundary_suppression=True, boundary_width=4, boundary_iteration=4,
dilated_smoothing=True, kernel_size=7, dilation=6):
定義一些引數
super(BoundarySuppressionWithSmoothing, self).__init__()
self.kernel_size = kernel_size # 卷積核大小
self.dilation = dilation # 擴張
self.boundary_suppression = boundary_suppression # 邊界抑制
self.boundary_width = boundary_width # 邊界寬度
self.boundary_iteration = boundary_iteration # 邊界迭代
創建高斯核
sigma = 1.0
size = 7
# function為二維高斯分布的概率密度函式
gaussian_kernel = np.fromfunction(lambda x, y:
(1/(2*math.pi*sigma**2)) * math.e ** ((-1*((x-(size-1)/2)**2+(y-(size-1)/2)**2))/(2*sigma**2)),
(size, size)) # 構造高斯核 (7,7) 3 * sigma + 1
gaussian_kernel /= np.sum(gaussian_kernel) # 除以高斯核中所有元素之和(加權平均,避免影像像素溢位)
gaussian_kernel = torch.Tensor(gaussian_kernel).unsqueeze(0).unsqueeze(0)
self.dilated_smoothing = dilated_smoothing # 擴張平滑
\[lambda(x,y) = \frac{1}{2 * \pi * \sigma^2} exp(-\frac{(x - \frac{size-1}{2})^2 + (y - \frac{size-1}{2})^2}{2 * \sigma^2}) \]numpy庫中的
fromfunction:通過自定義的函式fun,形狀shape,資料格式dtype -> 根據陣列下標(x,y)生成每個位置的值,構成一個陣列
函式引數
np.fromfunction(function, shape, dtype)
function:根據坐標變換成一個具體的值的函式
def function(x,y): 函式內部 (x,y) 分別是以左上角為原點的坐標,x為行坐標,y為列坐標,表示第x行y列,shape(a,b):表示陣列array的大小,a行b列,
dtype: 表示陣列的數型別
定義兩層卷積 (in_channel, out_channel, k, s)
- (1, 1, 3, 1) 權重矩陣是全1矩陣
- (1, 1, 7, 1) 權重矩陣是高斯核
self.first_conv = nn.Conv2d(1, 1, kernel_size=3, stride=1, bias=False)
self.first_conv.weight = torch.nn.Parameter(torch.ones_like((self.first_conv.weight)))
self.second_conv = nn.Conv2d(
1, 1, kernel_size=self.kernel_size, stride=1, dilation=self.dilation, bias=False)
self.second_conv.weight = torch.nn.Parameter(gaussian_kernel)
前向傳播
def forward(self, x, prediction=None):
if len(x.shape) == 3:
x = x.unsqueeze(1) # 如果是3維,擴充1維
x_size = x.size()
# B x 1 x H x W
assert len(x.shape) == 4
out = x
分支1:需要邊界抑制
if self.boundary_suppression:
# obtain the boundary map of width 2 by default 默認獲取寬度為2的邊界圖
# this can be calculated by the difference of dilation and erosion 這可以通過膨脹和腐蝕的差異來計算
boundaries = find_boundaries(prediction.unsqueeze(1)) # 尋找邊界
expanded_boundaries = None
if self.boundary_iteration != 0:
assert self.boundary_width % self.boundary_iteration == 0 # 邊界寬度要被迭代次數整除
diff = self.boundary_width // self.boundary_iteration # 每次增加的寬度
邊界抑制主要程序
for iteration in range(self.boundary_iteration):
if len(out.shape) != 4:
out = out.unsqueeze(1)
prev_out = out
-
得到邊界
# if it is the last iteration or boundary width is zero 最后一次迭代或者邊界寬度為0后,停止擴展寬度 if self.boundary_width == 0 or iteration == self.boundary_iteration - 1: expansion_width = 0 # reduce the expansion width for each iteration 否則就在每個迭代不斷貨站寬度 else: expansion_width = self.boundary_width - diff * iteration - 1 # expand the boundary obtained from the prediction (width of 2) by expansion rate expanded_boundaries = expand_boundaries(boundaries, r=expansion_width) # 根據擴展寬度擴展邊界,具體方法在后面的函式詳細解釋中 -
反轉邊界 -> 獲得非邊界掩碼
# invert it so that we can obtain non-boundary mask non_boundary_mask = 1. * (expanded_boundaries == 0) # 反轉邊界,得到非邊界掩碼,非邊界為1,邊界為0 -
使得邊界區域 to 0
f_size = 1 num_pad = f_size # making boundary regions to 0 x_masked = out * non_boundary_mask # 輸入影像 * 非邊界掩碼 -> 得到非邊界區域(1) x_padded = nn.ReplicationPad2d(num_pad)(x_masked) non_boundary_mask_padded = nn.ReplicationPad2d(num_pad)(non_boundary_mask)class torch.nn.ReplicationPad2d(padding)
padding(int ,tuple)填充的大小,如果為 int ,則在所有邊界中使用相同的填充,
如果是4 tuple ,則使用(padding_left, padding_right, padding_top, padding_bottom) -
求和感受野中的值
# sum up the values in the receptive field y = self.first_conv(x_padded) # count non-boundary elements in the receptive field num_calced_elements = self.first_conv(non_boundary_mask_padded) num_calced_elements = num_calced_elements.long() -
求平均
# take an average by dividing y by count # if there is no non-boundary element in the receptive field, # keep the original value avg_y = torch.where((num_calced_elements == 0), prev_out, y / num_calced_elements) out = avg_y -
更新邊界
# update boundaries only out = torch.where((non_boundary_mask == 0), out, prev_out) del expanded_boundaries, non_boundary_mask
第二步驟:擴張平滑
# second stage; apply dilated smoothing
if self.dilated_smoothing == True:
out = nn.ReplicationPad2d(self.dilation * 3)(out)
out = self.second_conv(out)
return out.squeeze(1)
分支1:不需要邊界抑制
else:
if self.dilated_smoothing == True: # 擴張平滑
out = nn.ReplicationPad2d(self.dilation * 3)(out)
out = self.second_conv(out)
else:
out = x
return out.squeeze(1)
find_boundaries
def find_boundaries(label):
"""
Calculate boundary mask by getting diff of dilated and eroded prediction maps
"""
assert len(label.shape) == 4
boundaries = (dilation(label.float(), selem_dilation) != erosion(label.float(), selem)).float()
### save_image(boundaries, f'boundaries_{boundaries.float().mean():.2f}.png', normalize=True)
return boundaries
selem = torch.ones((3, 3)).cuda() # 是一個(3,3)大小的全1的張量,腐蝕卷集核
selem_dilation = torch.FloatTensor(ndi.generate_binary_structure(2, 1)).cuda() # 膨脹卷積核
腐蝕:
膨脹:
膨脹(dilation) & 腐蝕(erosion)
這是兩種基本的形態學運算,主要用來尋找影像中的極大區域和極小區域,
- 膨脹類似與 '領域擴張' ,將影像的高亮區域或白色部分進行擴張,其運行結果圖比原圖的高亮區域更大,
- 腐蝕類似 '領域被蠶食' ,將影像中的高亮區域或白色部分進行縮減細化,其運行結果圖比原圖的高亮區域更小,
具體程序:定義一個卷積核,對圖片進行卷積,膨脹做“或”操作,擴大1的范圍;腐蝕做“與”操作,減少1的數量
dilation(image, kernel) # 影像,卷積核
erosion(image, kernel)
對標簽圖分別做膨脹腐蝕后,不一樣的位置,就是邊界,用1表示
expand_boundaries
def expand_boundaries(boundaries, r=0):
"""
Expand boundary maps with the rate of r
"""
if r == 0:
return boundaries
expanded_boundaries = dilation(boundaries, d_ks[r]) # 做膨脹操作
### save_image(expanded_boundaries, f'expanded_boundaries_{r}_{boundaries.float().mean():.2f}.png', normalize=True)
return expanded_boundaries
關于d_ks[]:
d_k1 = torch.zeros((1, 1, 2 * 1 + 1, 2 * 1 + 1)).cuda()
d_k2 = torch.zeros((1, 1, 2 * 2 + 1, 2 * 2 + 1)).cuda()
d_k3 = torch.zeros((1, 1, 2 * 3 + 1, 2 * 3 + 1)).cuda()
d_k4 = torch.zeros((1, 1, 2 * 4 + 1, 2 * 4 + 1)).cuda()
d_k5 = torch.zeros((1, 1, 2 * 5 + 1, 2 * 5 + 1)).cuda()
d_k6 = torch.zeros((1, 1, 2 * 6 + 1, 2 * 6 + 1)).cuda()
d_k7 = torch.zeros((1, 1, 2 * 7 + 1, 2 * 7 + 1)).cuda()
d_k8 = torch.zeros((1, 1, 2 * 8 + 1, 2 * 8 + 1)).cuda()
d_k9 = torch.zeros((1, 1, 2 * 9 + 1, 2 * 9 + 1)).cuda()
d_ks = {1: d_k1, 2: d_k2, 3: d_k3, 4: d_k4,
5: d_k5, 6: d_k6, 7: d_k7, 8: d_k8, 9: d_k9}
for k, v in d_ks.items():
v[:, :, k, k] = 1
for i in range(k):
v = dilation(v, selem_dilation)
d_ks[k] = v.squeeze(0).squeeze(0)
print(f'dilation kernel at {k}:\n\n{d_ks[k]}')
這些卷積核的樣子大致如下,以此類推
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/500776.html
標籤:其他
