セマンティックセグメンテーションモデルでは、通常、バックボーンを介して異なる解像度の特徴マップを取得し、特徴マップを融合して予測結果を生成します。このプロセスでは、低解像度の特徴マップをアップサンプリングして解像度を向上させる必要があります。論文一般的に使用されているアップサンプリング手法を数え、アップサンプリングアルゴリズムの一部のnumpy実装コードを示し、opencvとの比較によりコードの正しさをチェックします。コードの一部は、pytorchの使用例を示しています。
コンテンツ
1.補間
補間は、ピクセル間の関係を使用して挿入されたピクセル値を計算します。最も単純で最も一般的に使用されるのは、最近隣補間と双一次補間(numpy実装のコードが提供されています)であり、他の補間方法もあります。この記事ではあまり紹介しません。 。
1.最近隣内挿
最近隣内挿法が最も簡単な内挿法です。次の例に示すように、ターゲット点に最も近い点を新しい挿入点として選択します。
Numpyの実装とopencvの比較:
import cv2
from math import floor
import numpy as np
def interpolate_nearest(image, size):
new_img = np.zeros(shape=size[::-1] + (image.shape[-1], )).astype('uint8')
scale_h = image.shape[0] / size[1]
scale_w = image.shape[1] / size[0]
for i in range(size[1]):
for j in range(size[0]):
new_img[i, j] = image[int(floor(i * scale_h)), int(floor(j * scale_w))]
return new_img
image = cv2.imread('512.png')
size = (256, 256) # w, h
my_resized_image = interpolate_nearest(image, size)
cv_resized_image = cv2.resize(image, size, image, 0, 0, cv2.INTER_NEAREST)
assert np.allclose(my_resized_image, cv_resized_image), "Image not equal between your implemented and opencv."
cv2.imshow("opencv", cv_resized_image)
cv2.imshow('my_op', my_resized_image)
cv2.waitKey(0)
効果:
2.双一次内挿
バイリニア補間は、補間位置と周囲のピクセルとの間の距離に基づいて現在のピクセル値を計算します。下の図に示すように、Pは補間されるポイント、Q11、Q12、Q21、およびQ22は元のピクセルの座標です。 f(Q22)は、Q22の位置のピクセル値を表します。(ディープラーニングの多くのモデルはこれを使用します)
Pピクセル値を計算するには、最初に水平方向に補間し、R1、R2のピクセル値を計算します。
次に、垂直方向に補間し、前の手順で取得したR1およびR2ピクセル値に従ってPのピクセル値を取得します:
numpyの実装:
import cv2
from math import floor
import numpy as np
def interpolate_linear(image, size):
h, w = image.shape[0:2]
w_new, h_new = size
h_scale = h / h_new
w_scale = w / w_new
h_index = np.linspace(0, h_new - 1, h_new)
w_index = np.linspace(0, w_new - 1, w_new)
wv, hv = np.meshgrid(w_index, h_index)
hv = (hv + 0.5) * h_scale - 0.5
wv = (wv + 0.5) * w_scale - 0.5
# hv = hv * h_scale
# wv = wv * w_scale
hv[hv < 0] = 0
wv[wv < 0] = 0
h_down = hv.astype('int')
w_down = wv.astype('int')
h_up = h_down + 1
w_up = w_down + 1
h_up[h_up > (h - 1)] = h - 1
w_up[w_up > (w - 1)] = w - 1
pos_00 = image[h_down, w_down].astype('int') # 左上
pos_01 = image[h_up, w_down].astype('int') # 左下
pos_11 = image[h_up, w_up].astype('int') # 右下
pos_10 = image[h_down, w_up].astype('int') # 右上
m, n = np.modf(hv)[0], np.modf(wv)[0]
m = np.expand_dims(m, axis=-1)
n = np.expand_dims(n, axis=-1)
a = pos_10 - pos_00
b = pos_01 - pos_00
c = pos_11 + pos_00 - pos_10 - pos_01
image = np.round(a * n + b * m + c * n * m + pos_00).astype('uint8')
return image
image = cv2.imread('512.png')
size = (256, 256) # w, h
my_resized_image = interpolate_linear(image, size)
cv_resized_image = cv2.resize(image, size, image, 0, 0, cv2.INTER_LINEAR)
print(np.mean(np.abs(my_resized_image.astype('int') - cv_resized_image.astype('int')))) # 线性插值四舍五入数值计算像素值可能差1
assert np.allclose(my_resized_image, cv_resized_image, atol=1), "Image not equal between your implemented and opencv."
cv2.imshow("opencv", cv_resized_image)
cv2.imshow('my_op', my_resized_image)
cv2.waitKey(0)
効果:
3.その他の補間方法
ここにリストされていない多くの補間方法があります。opencvのドキュメントを参照できます。
二、PixelShuffle
次元[N、C、H、W]の特徴マップの場合、次元[N、C /(R ^ 2)、H * R、W * R]の特徴マップを取得するには、R回アップサンプリングする必要があります。 。実装は非常に簡単で、形を変える必要があります。コードは次のとおりです(torch.nn.PixelShuffleに合わせて):
import torch
import numpy as np
def pixel_shuffle_np(x, up_factor):
n, c, h, w = x.shape
new_shape = (n, c // (up_factor * up_factor), up_factor, up_factor, h, w)
npresult = np.reshape(x, new_shape)
npresult = npresult.transpose(0, 1, 4, 2, 5, 3)
oshape = [n, c // (up_factor * up_factor), h * up_factor, w * up_factor]
npreslut = np.reshape(npresult, oshape)
return npreslut
np.random.seed(10001)
image = np.random.rand(2, 16, 224, 224)
scale = 4
np_image = pixel_shuffle_np(image, scale)
torch_pixel_shuffle = torch.nn.PixelShuffle(scale)
torch_image = torch_pixel_shuffle(torch.from_numpy(image))
assert np.allclose(np_image, torch_image.numpy()), "Implemented PixelShuffle is not the same with torch.nn.PixelShuffle."
3.アンプール
次の図に、プール解除プロセスを示します。入力された特徴マップがプールされると、元の特徴マップの最大値のインデックスが保存されます。プール解除されると、特徴値は対応するインデックスに入れられ、他の位置は0で埋められます。
トーチコード:
import torch
import numpy as np
inputs = np.array([1, 2, 6, 3, 3, 5, 2, 1, 1, 2, 2, 1, 7, 3, 4, 8], dtype='float').reshape([1, 1, 4, 4])
inputs = torch.from_numpy(inputs)
pool = torch.nn.MaxPool2d(2, stride=2, return_indices=True)
unpool = torch.nn.MaxUnpool2d(2, stride=2)
output, indices = pool(inputs)
output = unpool(output, indices)
print(output)
結果:
第四に、転置畳み込み(デコンボリューション)
上記の方法はすべてパラメータなしの方法ですが、タスクごとに異なるパラメータを学習できる方法はありますか?明らかに、つまり、転置された畳み込みがあります。
次の図に示すように、簡単な例から始めます。入力は2x2、カーネルサイズは2x2、各入力番号にカーネルを掛けてから累積して3x3の出力を取得すると、特徴マップに大きくなります(3x3入力2x2畳み込みの後、2x2の出力が得られ、転置された畳み込みは畳み込みの逆演算です)。
上記の転置畳み込みは、2x2の入力特徴マップを3x3の出力特徴マップに変換しますが、出力特徴マップを大きくすることはできますか?明らかに、下の図を参照してください。ステップサイズが導入された後(下の図のステップサイズは、出力特徴マップのステップサイズとして理解できます)、入力特徴マップは2x2、カーネルは2x2、出力は2x2です。特徴マップは4x4です。
同様に、パディングや膨張などのパラメータを導入できます。入力次元が、であり、出力次元がであると仮定すると、転置畳み込みの次元計算式は次のようになります。
コードプラクティス(入力特徴マップ:[1、3、50、50]、出力特徴マップ:[1、3、98、98]):
import torch
x = torch.rand(1, 3, 50, 50)
transpose_conv = torch.nn.ConvTranspose2d(3, 3, kernel_size=3, stride=2, padding=2, output_padding=1)
y = transpose_conv(x)
print(x.shape, y.shape)
# 输出:
# torch.Size([1, 3, 50, 50]) torch.Size([1, 3, 98, 98])