设: \(f(x), g(x)\) 是 \(\mathbb{R}1\) 上的两个可积函数, \(f(x)\) 和 \(g(x)\) 的卷积定义如下:
\( (f * g)(t) \, \stackrel{\mathrm{def}}{=}\ \int_{-\infty}^\infty f(\tau)\, g(t-\tau) \, d\tau = \int_{-\infty}^\infty f(t-\tau) \, g(\tau)\, d\tau \)
按公式来,卷积计算的等效C++代码如下
/*
* m - a 的长度
* n - b 的长度
*/
void convolution(double *a, double *b, double *output, int m, int n)
{
double *x = new double[m+n-1];
// do convolution
for (int i = 0; i < m+n-1; i++)
{
x[i] = 0.0;
for (int j = 0; j < m; j++)
{
if (i-j > 0 && i-j < n)
x[i] += a[j] * b[i-j];
}
}
// set value to the output array
for (int i = 0; i < m; i++)
output[i] = x[i + (n-1) / 2];
delete[] x;
}
R 中有现成的关于向量的卷积计算函数 convolve()。使用 R 中的 convolve() 时,默认 使用的是 type=”circular”,这时要求输入参数 a、b 长度相同。长度不同,则只能使用 type=”open” 或者 “filter”。需要注意的是,在 R 中使用 convolve() 只能做一维向量 的计算,并且计算的结果与其它的库得到的有些差异,关键差异还是在边界处。 下面是在 R 中使用 convolve() 计算向量的卷积的例子:
> a <- 1:9
> convolve(a, c(1,2,1,rep(0,6))) # circular type convolution
[1] 8 12 16 20 24 28 32 27 13
> convolve(a, c(1,2,1), type="open") # open type convolution
[1] 1 4 8 12 16 20 24 28 32 26 9
> convolve(a,c(1,2,1), type="filter") # filter type convolution
[1] 8 12 16 20 24 28 32
为了对比,我们也来看一下例如 python 中的 mahotas 的 convolve 的计算结果。 默认情况下, mahotas 中的 convolve 使用的 mode 为 reflect。mahotas 中的 mode 是处理边界处的方式,有: reflect, nearest, wrap, mirror, constant, ignore。
>>> import numpy as np
>>> import mahotas as mh
>>> a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b = np.array([1, 2, 1])
>>> mh.convolve(a, b)
array([ 5, 8, 12, 16, 20, 24, 28, 32, 35])
>>> mh.convolve(a, b, mode='nearest')
array([ 5, 8, 12, 16, 20, 24, 28, 32, 35])
>>> mh.convolve(a, b, mode='wrap')
array([13, 8, 12, 16, 20, 24, 28, 32, 27])
>>> mh.convolve(a, b, mode='mirror')
array([ 6, 8, 12, 16, 20, 24, 28, 32, 34])
>>> mh.convolve(a, b, mode='constant')
array([ 4, 8, 12, 16, 20, 24, 28, 32, 26])
>>> mh.convolve(a, b, mode='ignore')
array([ 4, 8, 12, 16, 20, 24, 28, 32, 26])
一般地,为了加快计算,一般不大会使用上面提到的直接展开卷积定义式进行循环计算的 方法,在工程上我们会使用傅立叶的卷积定理来计算卷积:
\( (f*g)(t) = \mathcal{F}^{-1}( \mathcal{F}(f) \times \mathcal{F}(g) )\)
其中 \(\mathcal{F}(f)\) 为 \(f(t)\) 的傅立叶变换。
R 中的 convolve 的实现使用的正是傅立叶变换,并用卷积定理来求解的,其代码如下:
convolve <- function (x, y, conj = TRUE, type = c("circular", "open", "filter"))
{
type <- match.arg(type)
n <- length(x)
ny <- length(y)
Real <- is.numeric(x) && is.numeric(y)
if (type == "circular") {
if (ny != n)
stop("length mismatch in convolution")
}
else {
n1 <- ny - 1
x <- c(rep.int(0, n1), x)
n <- length(y <- c(y, rep.int(0, n - 1)))
}
x <- fft(fft(x) * (if (conj)
Conj(fft(y))
else fft(y)), inverse = TRUE)
if (type == "filter")
(if (Real)
Re(x)
else x)[-c(1L:n1, (n - n1 + 1L):n)]/n
else (if (Real)
Re(x)
else x)/n
}
为了在 R 中提供可用于图像的2维卷积计算,可以对这个实现进行改进。这段代码已经够 短了,而如果我们只考虑 open 边界条件,还可以进一步简化为:
convolve.open <- function (x, y)
{
m <- length(x)
n <- length(y)
x <- c(rep.int(0, n - 1), x) # x前面补 n-1 个 0
y <- c(y, rep.int(0, m - 1)) # y后面补 m-1 个 0
c <- fft(fft(x) * Conj(fft(y)), inverse = TRUE)
Re(c)/length(y)
}
这时,结构就非常清晰了,可以进一步把它改造成2维的卷积,我的代码如下:
conv2d <- function (x, y)
{
if (is.vector(x) | is.vector(y))
convolve(x, y)
else {
m <- dim(x)
n <- dim(y)
# x前面补 n-1 个 0
a <- matrix(0, m[1]+n[1]-1, m[2]+n[2]-1)
a[n[1]:(m[1]+n[1]-1), n[2]:(m[2]+n[2]-1)] <- x
# y后面补 m-1 个 0
b <- matrix(0, m[1]+n[1]-1, m[2]+n[2]-1)
b[1:n[1], 1:n[2]] <- y
c <- fft(fft(a) * Conj(fft(b)), inverse = TRUE)
c <- Re(c)/((m[1]+n[1]-1)*(m[2]+n[2]-1))
# 裁成 m 的尺寸并输出
c[ceiling(n[1]/2):(ceiling(n[1]/2)-1+m[1]),
ceiling(n[2]/2):(ceiling(n[2]/2)-1+m[2])]
}
}
下面,可以来试一下这个卷积的图像处理效果了。先做两个差分模板:
ptn.a <- matrix(0, 4, 4)
ptn.a[,1:2] <- -1
ptn.a[,3:4] <- 1
ptn.a
# [,1] [,2] [,3] [,4]
# [1,] -1 -1 1 1
# [2,] -1 -1 1 1
# [3,] -1 -1 1 1
# [4,] -1 -1 1 1
ptn.b <- t(ptn.a)
ptn.b
# [,1] [,2] [,3] [,4]
# [1,] -1 -1 -1 -1
# [2,] -1 -1 -1 -1
# [3,] 1 1 1 1
# [4,] 1 1 1 1
再打开昨天的那张图像来试试,参见R操作图像数据,并且会用到这里的一些 函数,不再在这里重复。
原图
使用 ptn.a 做一次卷积,并显示一下效果:
pic <- read.yuv('horses-640x360.jpg') # 读取图像
a <- conv2d(pic$y, ptn.a) # 使用模板做卷积
b <- conv2d(pic$y, ptn.b)
# 有必要做一下正规化操作
y.r <- range(pic$y)
a.r <- range(a)
b.r <- range(b)
a.y <- (a - a.r[1]) * (y.r[2] - y.r[1]) / (a.r[2] - a.r[1])
b.y <- (b - b.r[1]) * (y.r[2] - y.r[1]) / (b.r[2] - b.r[1])
a.yuv <- list(y=a.y, u=pic$u, v=pic$v) # 做成一幅 yuv 图像
b.yuv <- list(y=b.y, u=pic$u, v=pic$v)
show.yuv(a.yuv) # 显示效果
show.yuv(b.yuv)
与ptn.a卷积的效果
与ptn.b卷积的效果