本次的内容主要讲解NCCNormalized cross-correlation 归一化互相关。python
两张图片是不是同一个内容,如今深度学习的方案天然是用神经网络,比方说:孪生网络的架构作人面识别等等;微信
在传统的非参数方法中,常见的也有相关系数等。我在上一片文章voxelmorph的模型的学习中发现,在医学图像配准任务(不限于医学),衡量两个图片类似的度量有一种叫作NCC的网络
而这个NCC就是Normalized Cross-Correlation归一化互相关系数。架构
若是你知道互相关系数,那么你就能很好的理解归一化互相关系数。机器学习
相关系数的计算公式以下:ide
公式中的X,Y分别表示两个图片,\(Cov(X,Y)\)表示两个图片的协方差,\(Var(X)\)表示X自身的方差;函数
若是把一张图片,按照必定的像素,比方说9x9的一个框滑动,那么就能够把图片分红不少的9x9的小图片,那么NCC就是X,Y两张大图片中的对应的小图片的互相关系数的平均值。学习
这里看一下协方差的计算方式:
\(Cov(X,Y) = E[(X-E(X))(Y-E(Y))]\)spa
方差的计算为:
\(Var(X) = E[(X-E(X))^2]\)code
其实NCC不难理解,可是如何用代码计算呢?固然咱们能够一行一行遍历求解,可是这样时间复杂度太高,因此咱们作好仍是选择矩阵运算。
class NCC: """ Local (over window) normalized cross correlation loss. """ def __init__(self, win=None): self.win = win def loss(self, y_true, y_pred): I = y_true J = y_pred # get dimension of volume # assumes I, J are sized [batch_size, *vol_shape, nb_feats] ndims = len(list(I.size())) - 2 assert ndims in [1, 2, 3], "volumes should be 1 to 3 dimensions. found: %d" % ndims # set window size win = [9] * ndims if self.win is None else self.win # compute filters sum_filt = torch.ones([1, 1, *win]).to("cuda") pad_no = math.floor(win[0]/2) if ndims == 1: stride = (1) padding = (pad_no) elif ndims == 2: stride = (1,1) padding = (pad_no, pad_no) else: stride = (1,1,1) padding = (pad_no, pad_no, pad_no) # get convolution function conv_fn = getattr(F, 'conv%dd' % ndims) # compute CC squares I2 = I * I J2 = J * J IJ = I * J I_sum = conv_fn(I, sum_filt, stride=stride, padding=padding) J_sum = conv_fn(J, sum_filt, stride=stride, padding=padding) I2_sum = conv_fn(I2, sum_filt, stride=stride, padding=padding) J2_sum = conv_fn(J2, sum_filt, stride=stride, padding=padding) IJ_sum = conv_fn(IJ, sum_filt, stride=stride, padding=padding) win_size = np.prod(win) u_I = I_sum / win_size u_J = J_sum / win_size cross = IJ_sum - u_J * I_sum - u_I * J_sum + u_I * u_J * win_size I_var = I2_sum - 2 * u_I * I_sum + u_I * u_I * win_size J_var = J2_sum - 2 * u_J * J_sum + u_J * u_J * win_size cc = cross * cross / (I_var * J_var + 1e-5) return -torch.mean(cc)
这段代码其实不是很好看懂,我思考了好久才明白。其中的关键就在于如何理解:
# compute CC squares I2 = I * I J2 = J * J IJ = I * J I_sum = conv_fn(I, sum_filt, stride=stride, padding=padding) J_sum = conv_fn(J, sum_filt, stride=stride, padding=padding) I2_sum = conv_fn(I2, sum_filt, stride=stride, padding=padding) J2_sum = conv_fn(J2, sum_filt, stride=stride, padding=padding) IJ_sum = conv_fn(IJ, sum_filt, stride=stride, padding=padding) win_size = np.prod(win) u_I = I_sum / win_size u_J = J_sum / win_size cross = IJ_sum - u_J * I_sum - u_I * J_sum + u_I * u_J * win_size I_var = I2_sum - 2 * u_I * I_sum + u_I * u_I * win_size J_var = J2_sum - 2 * u_J * J_sum + u_J * u_J * win_size
咱们能够才到,这个cross应该是协方差部分,I_var和J_var是方差部分。
咱们对协方差公式进行推导:\(Cov(X,Y) = E[(X-E(X))(Y-E(Y))]\)
\(=E[XY-XE(Y)-YE(X)+E(X)E(Y)]\)
这样恰好和cross对应上。
对方差公式进行推导:\(Var(X) = E[(X-E(X))^2]=E[X^2-2XE(X)+E(X)^2]\)