Поведение аффинного преобразования на трехмерном изображении с неоднородным разрешением с помощью Scipy - PullRequest
1 голос
/ 21 января 2020

Я собираюсь применить аффинное преобразование , определенное в однородных координатах на изображениях с разным разрешением, но я сталкиваюсь с проблемой, когда один топор имеет разное разрешение других.

Обычно, поскольку только аффинная часть перевода зависит от разрешения, я нормализую часть перевода по разрешению и применяю соответствующую аффинность к изображению, используя scipy.ndimage.affine_transform.

Если разрешение изображения одинаково для всех осей, оно работает отлично, вы можете увидеть под изображениями того же преобразования (масштаб, сдвиг или поворот + сдвиг, см. Код ниже) к изображению, его уменьшенная версия (имеется в виду в более низком разрешении). Изображения совпадают (почти) идеально, различия в значениях вокселей в основном вызваны, насколько я знаю, ошибками интерполяции.

Но вы можете видеть, что фигуры перекрываются между преобразованным изображением с пониженной дискретизацией и преобразованным (для сравнения уменьшены) image

Аффинное преобразование масштаба, примененное к одному и тому же изображению, при двух разных (одинаковых) разрешениях

Аффинное преобразование поворота, примененное к одному и тому же изображению при двух разных (единообразные) разрешения

К сожалению, если одна из осей изображения имеет разрешение, отличное от другой (см. код ниже), она хорошо работает с аффинным преобразованием с нулевыми недиагональными терминами (например, перевод, или масштабирование), но результат преобразования дает совершенно неверный результат.

Аффинное преобразование поворота, примененное к одному и тому же изображению, в двух разных (неоднородных) разрешениях

Здесь вы можете увидеть минимальный рабочий пример кода:


import numpy as np
import nibabel as nib
from scipy.ndimage import zoom
from scipy.ndimage import affine_transform
import matplotlib.pyplot as plt

################################
#### LOAD ANY 3D IMAGE HERE ####
################################
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TO BE DEFINED BY USER
orig_img = np.zeros((100, 100, 100))
orig_img[25:75, 25:75, 25:75] = 1

ndim = orig_img.ndim

################################
##### DEFINE RESOLUTIONS #######

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TO BE DEFINED BY USER
# Comment/uncomment to choose the resolutions (in mm) of the images
# ORIG_RESOLUTION = [1., 1., 1.]
# TARGET_RESOLUTION = [2., 2., 2.]

ORIG_RESOLUTION = [1., 0.5, 1.]
TARGET_RESOLUTION = [2., 2., 2.]

#####################################
##### DEFINE AFFINE TRANSFORM #######

affine_scale_translation = np.array([[1.5, 0.0, 0.0, -25.],
                                     [0.0, 0.8, 0.0, 0. ],
                                     [0.0, 0.0, 1.0, 0.  ],
                                     [0.0, 0.0, 0.0, 1.0]])
a = np.sqrt(2)/2.
affine_rotation_translation = np.array([[a  , a  , 0.0, -25.],
                                     [-a , a  , 0.0, 50. ],
                                     [0.0, 0.0, 1.0, 0.0 ],
                                     [0.0, 0.0, 0.0, 1.0]])

# #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TO BE DEFINED BY USER
# Comment/uncomment to choose the transformation to be applied 

# affine_tf, name_affine = affine_scale_translation, "Tf scale"
affine_tf, name_affine = affine_rotation_translation, "Tf rotation"

######################################################
######## DOWNSAMPLE IMAGE TO LOWER RESOLUTION ########
######################################################

downsample_img = zoom(orig_img,
                      zoom=np.array(ORIG_RESOLUTION)/np.array(TARGET_RESOLUTION),
                      prefilter=False, order=1)

##############################################################################
######## APPLY AFFINE TRANSFORMATION TO ORIGINAL AND DOWNSAMPLE IMAGE ########
##############################################################################

affine_st_full_res, affine_st_low_res = affine_tf.copy(), affine_tf.copy()

# Inverse transform as affine_transform apply the tf from the target space to the original space
affine_st_full_res, affine_st_low_res = np.linalg.inv(affine_st_full_res), np.linalg.inv(affine_st_low_res)

# Normalize translation part (normally expressed in millimeters) for the resolution
affine_st_full_res[:ndim, ndim] = affine_st_full_res[:ndim, ndim] / ORIG_RESOLUTION
affine_st_low_res[:ndim, ndim] = affine_st_low_res[:ndim, ndim] / TARGET_RESOLUTION


# Apply transforms on images of different resolutions

orig_tf_img = affine_transform(orig_img, affine_st_full_res, prefilter=False, order=1)
downsample_tf_img = affine_transform(downsample_img, affine_st_low_res, prefilter=False, order=1)


# Downsample result at full resolution to be compared to result on downsample image

downsample_orig_tf_img = zoom(orig_tf_img, zoom=np.array(
                              ORIG_RESOLUTION)/np.array(TARGET_RESOLUTION),
                              prefilter=False, order=1)

# print(orig_img.shape)
# print(downsample_img.shape)
# print(orig_tf_img.shape)
# print(downsample_orig_tf_img.shape)

###############################
######## VISUALISATION ########
###############################
# We'll visualize in 2D the slice at the middle of the z (third) axis of the image, in both resolution
mid_z_slice_full, mid_z_slice_low = orig_img.shape[2]//2, downsample_img.shape[2]//2

fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows=2, ncols=3)

ax1.imshow(orig_img[:, :, mid_z_slice_full], cmap='gray')
ax1.axis('off')
ax1.set_title('1/ Origin image, at full res: {}'.format(ORIG_RESOLUTION))

ax2.imshow(downsample_img[:, :, mid_z_slice_low], cmap='gray')
ax2.axis('off')
ax2.set_title('2/ Downsampled image, at low res: {}'.format(TARGET_RESOLUTION))

ax3.imshow(downsample_tf_img[:, :, mid_z_slice_low], cmap='gray')
ax3.axis('off')
ax3.set_title('3/ Transformed downsampled image')

ax4.imshow(orig_tf_img[:, :, mid_z_slice_full], cmap='gray')
ax4.axis('off')
ax4.set_title('4/ Transformed original image')

ax5.imshow(downsample_orig_tf_img[:, :, mid_z_slice_low], cmap='gray')
ax5.axis('off')
ax5.set_title('5/ Downsampled transformed image')


error = ax6.imshow(np.abs(downsample_tf_img[:, :, mid_z_slice_low] -\
                          downsample_orig_tf_img[:, :, mid_z_slice_low]), cmap='hot')
ax6.axis('off')
ax6.set_title('Error map between 3/ and 5/')

fig.colorbar(error)

plt.suptitle('Result for {} applied on {} and {} resolution'.format(name_affine, ORIG_RESOLUTION, TARGET_RESOLUTION))
plt.tight_layout()

plt.show()


1 Ответ

0 голосов
/ 23 января 2020

Понижение частоты в основном меняет базовые векторы изображения. Это делает понижающую выборку похожей на перевод.

Но ротация и перевод не являются коммутативными (не взаимозаменяемыми).

https://gamedev.stackexchange.com/questions/16719/what-is-the-correct-order-to-multiply-scale-rotation-and-translation-matrices-f

Так что после При понижении частоты вы должны перевести содержимое изображения обратно в исходное положение. Что можно сделать, составив матрицу аффинного преобразования с использованием ORIG_RESOLUTION и применив ее перед фактическим преобразованием. Или используйте матричное умножение (например, с np.dot) для изменения фактического преобразования.

...