cv2 interpolate插值-align_corners

大家好,我是知秋君,一个会写博客吟诗的知秋码农。今天说一说cv2 interpolate插值-align_corners,希望能够帮助大家进步!!! torch interpolate torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None,

大家好,我是知秋君,一个会写博客吟诗的知秋码农。今天说一说cv2 interpolate插值-align_corners,希望能够帮助大家进步!!!

torch interpolate

 torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None) 
只听到从知秋君办公室传来知秋君的声音:

山色江声共寂寥,十三陵树晚萧萧。有谁来对上联或下联?

  • input (Tensor):输入数据
  • size (int or Tuple[int] or Tuple[int, int] or Tuple[int, int, int]):输出数据的尺寸
  • scale_factor (float or Tuple[float]):缩放因子
  • mode (str):采样算法
  • align_corners (bool, optional):几何上,我们认为输入和输出的像素是正方形,而不是点。如果设置为True,则输入和输出张量由其角像素的中心点对齐,从而保留角像素处的值。如果设置为False,则输入和输出张量由它们的角像素的角点对齐,插值使用边界外值的边值填充;当scale_factor保持不变时,使该操作独立于输入大小。仅当使用的算法为’linear’, ‘bilinear’, 'bilinear’or 'trilinear’时可以使用。默认设置为False

角像素:缩放后四个角的像素值

注意:

  1. scale_factor与size只能设置一个。
  2. 当设置scale_factor时,会对输出size下取整,比如输入[2, 2], scale_factor=2.1, 则输出size为[4.2, 4.2] = [4, 4]。
  3. 当设置scale_factor时,再设置recompute_scale_factor时,会根据输出的实际大小重新计算一下scale_factor。
  4. 用scale_factor不用size是因为scale_factor可以不写死大小,而size会固定输出大小,在处理多分辨率输入图像的时候会有问题。
此代码由一叶知秋网-知秋君整理
input:输入Tensor。 size:插值后输出Tensor的空间维度的大小,这个spatial size就是去掉Batch,Channel,Depth维度后剩下的值。比如NCHW的spatial size是HW。 scale_factor(float 或者 Tuple[float]):spatial size的乘数,如果是tuple则必须匹配输入数据的大小。 mode(str):上采样的模式,包含'nearest' | 'linear' | 'bilinear' | 'bicubic' | 'trilinear' | 'area'。 默认是 'nearest'。 align_corners(bool):在几何上,我们将输入和输出的像素视为正方形而不是点。 如果设置为True,则输入和输出张量按其角像素的中心点对齐,保留角像素处的值。 如果设置为False,则输入和输出张量按其角像素的角点对齐,插值使用边缘值填充来处理边界外值,当scale_factor保持不变时,此操作与输入大小无关。 这仅在mode为 'linear' | 'bilinear' | 'bicubic' | 'trilinear'时有效。默认值是False。 recompute_scale_factor(bool):重新计算用于插值计算的 scale_factor。 当 scale_factor 作为参数传递时,它用于计算 output_size。 如果 recompute_scale_factor 为 False 或未指定,则传入的 scale_factor 将用于插值计算。 否则,将根据用于插值计算的输出和输入大小计算新的 scale_factor(即,等价于显示传入output_size)。 请注意,当 scale_factor 是浮点数时,由于舍入和精度问题,重新计算的 scale_factor 可能与传入的不同。

ops_version对导出onnx影响:

op9, op10是Unsample,而op11变成了Resize。

不同的ops_version对interpolate的支持程度:

F.interpolate nearest bilinear, align_corners=False bilinear, align_corners=True bicubic
op-9 Y Y N N
op-10 Y Y N N
op-11 Y Y Y Y

align_corner的表现行为:

align_corner

如果设置为True,则输入和输出张量由其角像素的中心点对齐,从而保留角像素处的值。如果设置为False,则输入和输出张量由它们的角像素的角点对齐,插值使用边界外值的边值填充

  • 当**align_corners = True**时,像素被视为网格的格子上的点,拐角处的像素对齐.可知是点之间是等间距
  • 当**align_corners = False**时, 像素被视为网格的交叉线上的点, 拐角处的点依然是原图像的拐角像素,但是差值的点间却按照上图的取法取,导致点与点之间是不等距

opencv, PIL的align_corner为False, mxnet为True,而torch和tensorflow可以设置。


 首先介绍 align_corners=False,它是 pytorch 中 interpolate 的默认选项。这种设定下,我们认定像素值位于像素块的中心,如下图所示:(3*3)

 对它上采样两倍后,得到下图:(6*6)

首先观察绿色框内的像素,我们会发现它们严格遵守了 bilinear 的定义。而对于角上的四个点,其像素值保持了原图的值。边上的点(超出边界的点)则根据角点的值填充。所以,我们从全局来看,内部和边缘处采用了比较不同的规则。 

# align_corners = False # x_ori is the coordinate in original image # x_up is the coordinate in the upsampled image x_ori = (x_up + 0.5) / factor - 0.5

 接下来,我们看看 align_corners=True 情况下,用同样画法对上采样的可视化:(5*5)

这里像素之间毫无对齐的美感,强迫症看到要爆炸。事实上,在 align_corners=True 的世界观下,上图的画法是错误的。在其世界观里,像素值位于网格上,如下图所示: 

那么,把它上采样两倍后,我们会得到如下的结果:


1、align_corners 参数的实验(1*1-1*4)

此代码由一叶知秋网-知秋君整理
import torch import torch.nn as nn import torch.nn.functional as F s= [4.] # 由于函数需要浮点数,所以需要加点 s = torch.tensor(s).reshape(1, 1, 1, 1) # 自定义s的通道数和尺寸大小 print(s) # tensor([[[[4.]]]]) x = F.interpolate(s, size=(1,4), mode='bilinear', align_corners=False) print(x) # tensor([[[[4., 4., 4., 4.]]]]) x = F.interpolate(s, size=(32,32), mode='bilinear', align_corners=False) print(x) #tensor([[[[4., 4., 4., ..., 4., 4., 4.], # [4., 4., 4., ..., 4., 4., 4.], # [4., 4., 4., ..., 4., 4., 4.], # ..., # [4., 4., 4., ..., 4., 4., 4.], # [4., 4., 4., ..., 4., 4., 4.], # [4., 4., 4., ..., 4., 4., 4.]]]])

2、align_corners 参数的实验(2*2-4*4)

import torch import torch.nn as nn import torch.nn.functional as F a = [[1., 2.], [4., 5.]] a = torch.tensor(a).reshape(1, 1, 2, 2) x = F.interpolate(a, scale_factor=2, mode='bilinear', align_corners=True) print(x) #tensor([[[[1.0000, 1.3333, 1.6667, 2.0000], # [2.0000, 2.3333, 2.6667, 3.0000], # [3.0000, 3.3333, 3.6667, 4.0000], # [4.0000, 4.3333, 4.6667, 5.0000]]]]) # 等距 # 像素被视为网格的格子上的点,拐角处的像素对齐.可知是点之间是等间距的 y = F.interpolate(a, scale_factor=2, mode='bilinear', align_corners=False) print(y) #tensor([[[[1.0000, 1.2500, 1.7500, 2.0000], # [1.7500, 2.0000, 2.5000, 2.7500], # [3.2500, 3.5000, 4.0000, 4.2500], # [4.0000, 4.2500, 4.7500, 5.0000]]]]) # 不等距 #

3、align_corners 参数的实验(3*3-6*6) 

import torch import torch.nn as nn import torch.nn.functional as F a = [[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]] a = torch.tensor(a).reshape(1, 1, 3, 3) print(a) #tensor([[[[1., 2., 3.], # [4., 5., 6.], # [7., 8., 9.]]]]) #*************等价的写法**********# r = torch.arange(1,10,dtype=torch.float32).view(1,1,3,3) r #tensor([[[[1., 2., 3.], # [4., 5., 6.], # [7., 8., 9.]]]]) #*********************************# x = F.interpolate(a, scale_factor=2, mode='bilinear', align_corners=True) print(x) #tensor([[[[1.0000, 1.4000, 1.8000, 2.2000, 2.6000, 3.0000], # [2.2000, 2.6000, 3.0000, 3.4000, 3.8000, 4.2000], # [3.4000, 3.8000, 4.2000, 4.6000, 5.0000, 5.4000], # [4.6000, 5.0000, 5.4000, 5.8000, 6.2000, 6.6000], # [5.8000, 6.2000, 6.6000, 7.0000, 7.4000, 7.8000], # [7.0000, 7.4000, 7.8000, 8.2000, 8.6000, 9.0000]]]]) # 等距 y = F.interpolate(a, scale_factor=2, mode='bilinear', align_corners=False) print(y) #tensor([[[[1.0000, 1.2500, 1.7500, 2.2500, 2.7500, 3.0000], # [1.7500, 2.0000, 2.5000, 3.0000, 3.5000, 3.7500], # [3.2500, 3.5000, 4.0000, 4.5000, 5.0000, 5.2500], # [4.7500, 5.0000, 5.5000, 6.0000, 6.5000, 6.7500], # [6.2500, 6.5000, 7.0000, 7.5000, 8.0000, 8.2500], # [7.0000, 7.2500, 7.7500, 8.2500, 8.7500, 9.0000]]]]) # 不等距


 补充说明:

由于图像双线性插值只会用相邻的4个点,因此上述公式双线性插值的分母都是1。opencv中的源码如下,用了一些优化手段,比如用整数计算代替float(下面代码中的*2048就是变11位小数为整数,最后有两个连乘,因此>>22位),以及源图像和目标图像几何中心的对齐
SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5
SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5

这个要重点说一下,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下。如果没有这个中心对齐,根据基本公式去算,就会得到左边这样的结果;而用了对齐,就会得到右边的结果:

 原本的插值公式:

(原本的)srcX=dstX*(srcW/dstW)  eg:srcX=0(5/3)=0 

(中心对齐)srcX=(0+0.5)/(5/3)-0.5=1/3


中心点对齐的缩放在卷积网络结构设计中的注意事项

  • OpenCV缩放图片是基于中心点对齐的,
  • Pytorch中 mode=‘bilinear’, align_corners=False 与OpenCV中的保持一致,
  • Pytorch中 mode=‘bilinear’, align_corners=True 与TensorFlow中的align_corners=True的条件下保持一致。

tensorFlow的resize_bilinear并未中心对齐,坐标计算方式为

align_corners=False:

srcX=dstX* (srcWidth/dstWidth) ,
srcY = dstY * (srcHeight/dstHeight)

align_corners=True:

srcX=dstX* (srcWidth-1/dstWidth-1) ,
srcY = dstY * (srcHeight-1/dstHeight-1)

参考博客:

一文看懂align_corners - 知乎

cv2.reisze, interpolate采样比较 - bairuiworld - 博客园

【上采样问题】双线性插值的几何中心点重合与align_corners_Hali_Botebie的博客-CSDN博客

知秋君
上一篇 2024-07-03 15:31
下一篇 2024-07-03 15:31

相关推荐