图像分割相关概念汇总
一、上采样
在做图像分割的时候,需要对图像进行像素级别的分类,因此在卷积提取到抽象特征后需要通过上采样将feature map还原到原图大小。
常见的上采样方法有双线性插值、反卷积(转置卷积)、上采样(unsampling)和上池化(unpooling)。其中前两种方法较为常见,后两种用得较少。下面对其进行简单介绍:
1.双线性插值
1.1 关于插值
插值,是根据已知的数据序列(可以理解为你坐标中一系列离散的点),找到其中的规律,然后根据找到的这个规律,来对其中尚未有数据记录的点
进行数值估计。
应用有:
1)对数据中的缺失进行合理补偿
2)对数据进行放大或缩小
3)其他 (围笑(* ̄︶ ̄))
1.2 线性插值
线性插值是针对一维数据的插值方法。它根据一维数据序列中需要插值的点的左右临近两个数据来进行数值估计。当然了它不是求这两个点数据大小的平均值(在中心点的时候就等于平均值)。而是根据到这两个点的距离来分配比重的。
维基百科上图:
已知点(x0,y0)、(x1,y1),求取插值点x处的y。推导过程如下:
我在大牛的代码里发现他是这样写的,很有美感:
由于 ( y-y0)/(x-x0)=(y1-y0)/(x1-x0)
所以变换一下:(x-x0)/(x1-x0)=(y-y0)/(y1-y0)=k
那么: y=(1-k)y0+ky1
比较方便记忆
线性插值举例:
问: 假如一天中,我测得了其中7个时间点的温度。1点、3点、8点、12点、15点、20点、24点摄氏度分别是8、9、16、23、22、18、10。请问用线性插值的方法得到这一天中1点到24点之间其他时间点的温度该怎么做?
注:线性插值是属于内插法,要去求这一天1点到24点以外的数据就不行了
方法一:matlab有插值函数,interp1()
time = [1 3 8 12 15 20 24];
tem = [8 9 16 23 22 18 10];
time_i = 1:0.01:24;
tem_i = interp1(time,tem,time_i,'linear');
plot(time,tem,'o',time_i,tem_i);
运行结果:
其中,如果你想知道其中的几个点的温度,修改变量time_i就可以了。对应于time_i的温度值存放着在tem_i变量中。
方法二:根据插值原理自己来写函数
%自己写一个interp1类似功能的接口
%在这个接口中参数x需要从大到小排列,y随意
function [yi]=self_interp1(x,y,xi,method)
% 初始化yi,给它xi对应的列
col_xi = size(xi,2);
yi = zeros(1,col_xi);
% 检测使用的插值方法 这里期望的是'linear'
if strcmp(method,'linear')
% 找到每个xi在x序列中的位置
col_x = size(x,2);
for i = 1:col_xi,
for j = 1:col_x-1,
% 假如需要计算插值公式
if x(j+1) > xi(i),
yi(i) = y(j)+(y(j+1)-y(j))/(x(j+1)-x(j))*(xi(i)-x(j));
break;
end
% 假如插值处的数据已经测得了,就直接把值给它,节约计算资源
if x(j) == xi(i),
yi(i) = y(j);
break;
end
end
% 以上没有把最后一个数据点考虑进去,需要加上
yi(col_xi) = y(col_x);
end
else
error('插值方法请选择(linear)\n');
end
end
然后进行调用:
time = [1 3 8 12 15 20 24];
tem = [8 9 16 23 22 18 10];
time_i = 1:0.01:24;
tem_i = self_interp1(time,tem,time_i,'linear');
plot(time,tem,'o',time_i,tem_i);
结果跟上图一样。
1.3 双线性插值
双线性插值,又称为双线性内插。在数学上,双线性插值是对线性插值在二维直角网格上的扩展,用于对双变量函数(例如 x 和 y)进行插值。其核心思想是在两个方向分别进行一次线性插值。
假设我们想得到未知函数 f 在点 P = (x, y) 的值,假设我们已知函数 f 在 Q11 = (x1, y1)、Q12 = (x1, y2), Q21= (x2, y1) 以及 Q22 = (x2, y2) 四个点的值。
首先在 x 方向进行线性插值,得到:
然后在 y 方向进行线性插值,得到 f(x, y):
在FCN中上采样用的就是双线性插值。
2.反卷积(转置卷积)
在上面的双线性插值方法中不需要学习任何参数。而转置卷积就像卷积一样需要学习参数,关于转置卷积的具体计算可以参见一文搞懂反卷积,转置卷积。
转置卷积,下面的为输入feature map
2.1 反卷积的数学推导
2.1.1 正向卷积的实现过程
假设输入图像 input 尺寸为 4 x 4 ,元素矩阵为:
卷积核 kernel 尺寸为 3 x 3 ,元素矩阵为:
步长 strides = 1 ,填充 padding = 0 ,即 i = 4 ,k = 3 ,p = 0 ,则按照卷积计算公式 o = ( i + 2p - k ) / s + 1 ,输出图像 output 的尺寸为 2 x 2 。
2.1.2 用矩阵乘法描述卷积
把 input 的元素矩阵展开成一个列向量 X:
把输出图像 output 的元素矩阵展开成一个列向量 Y :
对于输入的元素矩阵 X 和输出的元素矩阵 Y ,用矩阵运算描述这个过程:
Y = CX
通过推导,我们可以得到稀疏矩阵 C :
反卷积的操作就是要对这个矩阵运算过程进行逆运算,即通过 C 和 Y 得到 X ,根据各个矩阵的尺寸大小,我们能很轻易的得到计算的过程,即为反卷积的操作:
X = CTY
但是,如果你代入数字计算会发现,反卷积的操作只是恢复了矩阵 X 的尺寸大小,并不能恢复 X 的每个元素值,本文最后会在 tensorflow 平台进行这个实验。
在此之前,我们先给出反卷积图像尺寸变化的公式。
2.1.3 反卷积的输入输出尺寸关系
在进行反卷积时,简单来说,大体上可分为以下两种情况:
o = size of output
i = size of input
p = padding
s = strides
Relationship 1:( o + 2p - k ) % s = 0 (可以整除)
此时反卷积的输入输出尺寸关系为:
o = s (i - 1) - 2p + k
如上图所示,我们选择一个输入 input 尺寸为 3 x 3 ,卷积核 kernel 尺寸为 3 x 3 ,步长 strides = 2 ,填充 padding = 1 ,即 i = 3,k = 3,s = 2,p = 1,则输出 output 的尺寸为 o = 2 x (3 - 1) + 3 = 5 。
Relationship 2: (o + 2p - k) % s ≠ 0 (不可整除)
此时反卷积的输入输出尺寸关系为:
o = s(i - 1) - 2p + k + (o + 2p - k)%s
如上图所示,我们选择一个输入 input 的尺寸为 3 x 3 ,卷积核 kernel 的尺寸为 3 x 3 ,步长 strides = 2 ,填充 padding =1 ,即 i = 3,k = 3,s = 2,p = 1 ,则输出 output 的尺寸为 o = 2 x (3 - 1) + 3 - 2 x 1 + 1 = 6。
2.1.4 反卷积在 FCN 中的应用
在图像语义分割网络 FCN-32s 中,上采样反卷积操作的输入每张图像的尺寸是 7 x 7,我们希望进行一次上采样后能恢复成原始图像的尺寸 224 x 224 ,代入公式:
o = s (i - 1) - 2p + k
o = 224,i = 7
根据上式,我们可以得到一个关于 s,k,p 三者之间关系的等式:
6s + k - 2p = 224
通过实验,最终找出了最合适的一组数据:
s = 16,k = 64,p = 32
2.2 tensorflow 中反卷积的实现
2.2.1 strides=[1,1,1,1],padding="VALID"情况
反卷积是卷积的逆过程,其参数代表了正向卷积的过程,如:注意此时的参数 padding=“VALID” 反映的是正向卷积过程不进行填充,但是在上采样反卷积过程中,是一直存在填充0的。
直接上代码,简单易懂。
import tensorflow as tf
test1 = tf.constant(1.0, shape=[1,2,2,1])
w = tf.constant(1.0, shape=[3,3,1,1])
result = tf.nn.conv2d_transpose(test1, w, output_shape=[1,4,4,1], strides=[1,1,1,1],padding="VALID")
with tf.Session() as sess:
print(sess.run(result))
我们定义了一个shape为 [1,2,2,1] 的张量 test1,即其实可以直接理解为2×2的矩阵,因为其他维度(batch size和channel)均为1。
反卷积核为3×3,输出通道和输入通道不变均为1。
此时output_shape只能为[1,4,4,1]了,尺寸扩大了一倍。
现在注意到了,因为其实该 tf.nn.conv2d_transpose反卷积过程是卷积的逆过程,只有[1,4,4,1]的张量在strides=[1,1,1,1],padding="VALID"情况下卷积才能得到[1,2,2,1]的结果,所以发现里面的参数都是相互联系的,互相已经进行了确认。
[[[[1.]
[2.]
[2.]
[1.]]
[[2.]
[4.]
[4.]
[2.]]
[[2.]
[4.]
[4.]
[2.]]
[[1.]
[2.]
[2.]
[1.]]]]
2.2.2 strides=[1,1,1,1],padding="SAME"情况
import tensorflow as tf
test1 = tf.constant(1.0, shape=[1,2,2,1])
w = tf.constant(1.0, shape=[3,3,1,1])
result = tf.nn.conv2d_transpose(test1, w, output_shape=[1,2,2,1], strides=[1,1,1,1],padding="SAME")
with tf.Session() as sess:
print(sess.run(result))
若strides=[1,1,1,1],padding="SAME"情况,同上一样,这时候的output_shape只能是[1,2,2,1],只能是2×2的张量大小经过3×3卷积,并且参数strides=[1,1,1,1],padding=“SAME”,才能得到2×2的张量。
[[[[4.]
[4.]]
[[4.]
[4.]]]]
2.2.3 strides=[1,2,2,1],padding="VALID"情况
当strides步长为2的时候,在进行0填充的时候有一点不同,它在进行填充的时候进行了一些改变,在下图中进行了表示,同时strides步长为2同样表示的是正向卷积过程的参数(正如上面第一点讨论的padding="VALID"但是也进行了0填充),而反卷积过程中,其卷积核移动仍然是一步一步移动的。
话不多说,还是直接上代码。
import tensorflow as tf
test1 = tf.constant(1.0, shape=[1,2,2,1])
w = tf.constant(1.0, shape=[3,3,1,1])
result = tf.nn.conv2d_transpose(test1, w, output_shape=[1,5,5,1], strides=[1,2,2,1],padding="VALID")
with tf.Session() as sess:
print(sess.run(result))
此时发现output_shape只能是[1,5,5,1]了,不难理解,在strides=[1,2,2,1],padding="VALID"的情况下,也只有5×5的张量能够得到2×2的张量。
[[[[1.]
[1.]
[2.]
[1.]
[1.]]
[[1.]
[1.]
[2.]
[1.]
[1.]]
[[2.]
[2.]
[4.]
[2.]
[2.]]
[[1.]
[1.]
[2.]
[1.]
[1.]]
[[1.]
[1.]
[2.]
[1.]
[1.]]]]
2.2.4 strides=[1,2,2,1],padding="SAME"情况
import tensorflow as tf
test1 = tf.constant(1.0, shape=[1,2,2,1])
w = tf.constant(1.0, shape=[3,3,1,1])
result = tf.nn.conv2d_transpose(test1, w, output_shape=[1,3,3,1], strides=[1,2,2,1],padding="SAME")
with tf.Session() as sess:
print(sess.run(result))
[[[[1.]
[2.]
[1.]]
[[2.]
[4.]
[2.]]
[[1.]
[2.]
[1.]]]]
彩蛋:可能大家发现了其实此时输出的张量也可以是[1,4,4,1](上面Relationship 1\2 是否可整除的两张情况),这也正是印证了为什么当strides,padding等等这些参数都确定了还需要指定out_shape,只有这样才是最后完善的!
3. unsampling和unpooling
unsampling和unpooling可以通过一个图来简单理解:
其中右侧为unsampling,可以看出unsampling就是将输入feature map中的某个值映射填充到输出上采样的feature map的某片对应区域中,而且是全部填充的一样的值。
unpooling的操作与unsampling类似,区别是unpooling记录了原来pooling是取样的位置,在unpooling的时候将输入feature map中的值填充到原来记录的位置上,而其他位置则以0来进行填充。
转:
https://www.zhihu.com/question/48279880
https://blog.csdn.net/qq_42192910/article/details/89791539
https://www.jianshu.com/p/587c3a45df67
https://www.cnblogs.com/sggggr/p/11853243.html