Свертка изображений с четным ядром
Я хочу выполнить простую свертку 2D-изображения, но мое ядро имеет четные размеры. Какие индексы я должен выбрать для своего центра ядра? Я попытался поискать в поиске ответа и искать существующие коды. Люди обычно центрируют свое ядро, поэтому перед новым 0 будет еще один образец. Итак, если у нас есть ядро 4x4, центрированные индексы должны быть -2 -1 0 +1
, Это верно? И если это так, то почему это так? Может кто-нибудь объяснить, почему -2 -1 0 +1
в то время как правильно -1 0 +1 +2
не является? Имейте в виду, что я хочу выполнить свертку без использования БПФ.
2 ответа
Если я правильно понимаю ваш вопрос, то для ядер четного размера вы правы в том, что принято центрировать ядро так, чтобы перед новым нулем оставался еще один образец.
Так, для ядра ширины 4 центрированные индексы будут -2 -1 0 +1
как вы говорите выше.
Однако на самом деле это просто соглашение - асимметричная свертка очень редко используется в любом случае, и точный характер асимметрии (слева / справа и т. Д.) Не имеет отношения к "правильному" результату. Я полагаю, что причина того, что большинство реализаций ведут себя таким образом, заключается в том, что они могут давать сопоставимые результаты при одинаковых входных данных.
При выполнении свертки в частотной области ядро дополняется, чтобы в любом случае соответствовать размеру изображения, и вы уже заявили, что выполняете свертку в пространственной области.
Я гораздо больше заинтригован тем, почему вам нужно использовать ядро четного размера.
Правильный ответ - вернуть пиксель результатов в верхнем левом углу независимо от того, имеет ли матрица одинаковый размер или нет. Тогда вы можете просто выполнить операцию в простой строке сканирования, и они не требуют памяти.
private static void applyBlur(int[] pixels, int stride) {
int v0, v1, v2, r, g, b;
int pos;
pos = 0;
try {
while (true) {
v0 = pixels[pos];
v1 = pixels[pos+1];
v2 = pixels[pos+2];
r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
g = ((v0 >> 8 ) & 0xFF) + ((v1 >> 8) & 0xFF) + ((v2 >> 8) & 0xFF);
b = ((v0 ) & 0xFF) + ((v1 ) & 0xFF) + ((v2 ) & 0xFF);
r/=3;
g/=3;
b/=3;
pixels[pos++] = r << 16 | g << 8 | b;
}
}
catch (ArrayIndexOutOfBoundsException e) { }
pos = 0;
try {
while (true) {
v0 = pixels[pos];
v1 = pixels[pos+stride];
v2 = pixels[pos+stride+stride];
r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
g = ((v0 >> 8 ) & 0xFF) + ((v1 >> 8) & 0xFF) + ((v2 >> 8) & 0xFF);
b = ((v0 ) & 0xFF) + ((v1 ) & 0xFF) + ((v2 ) & 0xFF);
r/=3;
g/=3;
b/=3;
pixels[pos++] = r << 16 | g << 8 | b;
}
}
catch (ArrayIndexOutOfBoundsException e) { }
}
Поразмыслив над сверткой равного размера и ее применением в Temporal Convolutional Networks, я решил, что следующий эксперимент даст ответ для центрирования свертки равного размера в tensorflow/keras:
import keras
import numpy as np
import tensorflow as tf
import keras.backend as K
import keras.layers as layers
from keras.layers import Conv2D, Input
from keras.initializers import Constant
if __name__ == '__main__':
inputs = Input(shape=(None,1,1))
even_conv = Conv2D(1,(4,1),padding="same",
kernel_initializer=Constant(value=1.),use_bias=False)(inputs)
f = K.function(inputs=[inputs],outputs=[even_conv])
test_input = np.arange(10)[np.newaxis,...,np.newaxis,np.newaxis].astype(np.float)
result = f(inputs=[test_input])[0]
print(np.squeeze(test_input))
# [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
print(np.squeeze(result))
# [ 3. 6. 10. 14. 18. 22. 26. 30. 24. 17.]
Как вы можете видеть, для "того же" входной массив заполнения был дополнен 1 нулем в начале и 2 нулями в конце: [0. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 0. 0.]
. Таким образом, для тензорного потока центрирование ядра даже по размеру будет следующим для 4-ядер:-1 0 +1 +2
и для 2*n
размер ядра: -(n-1), -(n-2),... -1, 0, +1,... +(n-1), +n,