2

与 MATLAB 实现相比,尝试使用 Accelerate 中的 vDSP_conv() 进行卷积时,我得到的结果不一致。在使用此函数计算卷积时,有几篇关于奇怪结果的 StackOverflow 帖子,但据我所知,我正确使用了该框架,并结合了其他 Stack Overflow 帖子的建议。这是我的代码:

public func conv(x: [Float], k: [Float]) -> [Float] {  
    let resultSize = x.count + k.count - 1
    var result = [Float](count: resultSize, repeatedValue: 0)
    let kEnd = UnsafePointer<Float>(k).advancedBy(k.count - 1)
    let xPad: [Float] = [Float](count: (2*k.count)+1, repeatedValue: 0.0)
    let xPadded = x + xPad
    vDSP_conv(xPadded, 1, kEnd, -1, &result, 1, vDSP_Length(resultSize), vDSP_Length(k.count))
}

据我所知,我正在按照此处的 Accelerate 框架文档中的说明进行正确的零填充

我定义了两个测试数组A: [Float] = [0, 0, 1, 0, 0]B: [float] = [1, 0, 0].

在 MATLAB 中,当我运行 时conv(A, B),我得到[0, 0, 1, 0, 0, 0, 0].

但是,当我运行上面的 vDSP 时,conv()我得到了[1, 0, 0, 0, 0, 0, 0].

我的实施有什么问题?我已经检查了很多次,并查看了我能找到的所有 SO 帖子,但仍然无法解释这种不一致。

除此之外,还有比我这里更有效的方法来对数组进行零填充吗?为了保持x不可变,我创建了新xPadded数组,但毫无疑问有一种更有效的方法来执行此填充。

** 编辑 ** 正如 Martin R 所建议的,我k.count -1在数组的开头和结尾处平均填充,如下所示。

public func conv(x: [Float], k: [Float]) -> [Float] {
    let resultSize = x.count + k.count - 1
    var result = [Float](count: resultSize, repeatedValue: 0)
    let kEnd = UnsafePointer<Float>(k).advancedBy(k.count - 1)
    let xPad: [Float] = [Float](count: k.count-1, repeatedValue: 0.0)
    let xPadded = xPad + x + xPad
    vDSP_conv(xPadded, 1, kEnd, -1, &result, 1, vDSP_Length(resultSize), vDSP_Length(k.count))

    return result
}

使用此代码,conv(A, B)仍然返回 [1, 0, 0, 0, 0, 0, 0]。

我正在调用该函数,如下所示:

let A: [Float] = [0, 0, 1, 0, 0]
let B: [Float] = [1, 0, 0]
let C: [Float] = conv(A, k: B)
4

3 回答 3

3

对于两个长度为A和的数组,来自 Accelerate 框架的函数计算一个长度为 的新数组。BmnvDSP_conv()m - n + 1

这对应于conv()将 shape 参数设置为“有效”的 MATLAB 函数的结果:

只有那些在没有零填充边缘的情况下计算的卷积部分。...

要获得与来自 MATLAB 的“完整”卷积相同的结果,您必须在A数组n-1的开头和结尾使用元素进行零填充,这会给出一个长度为的结果数组m + n - 1

应用于您的功能:

let xPad = Repeat(count: k.count - 1, repeatedValue: Float(0.0))
let xPadded = xPad + x + xPad 

使用Repeat()可能会稍微提高性能,因为它创建的是序列而不是数组。但最终,必须创建一个新数组作为vDSP_conv()函数的参数,因此没有太大的改进空间。

于 2016-02-05T21:11:41.380 回答
2

为下一个偶然发现此问题的可怜人做一些澄清:Apple 提供了一些有关如何使用 vDSP_conv 的示例代码,但它毫无用处。事实上,这让我感到困惑,因为该代码中的注释说需要填充输入缓冲区,而没有指定实际输入样本的放置位置:

下面定义的 SignalLength 用于分配空间,它是四舍五入到四元素的倍数并添加到结果长度的过滤器长度。

SignalLength = (FilterLength+3 & -4u) + ResultLength;

xPad + x + xPad因此,上面的公式为您提供了与xPad 是 k.count - 1不同的长度(更大) 。

重要的是在填充缓冲区中复制输入(信号)样本的位置:它需要位于k.count - 1.

因此,上述公认的解决方案有效。但是,如果您相信 Apple 示例中的评论(顺便说一句,官方文档中没有显示该评论),那么您可以做出妥协:使用他们的公式(上面的 SignalLength)来计算和分配填充缓冲区(它会有点更大)并使用k.count - 1(即滤波器长度 - 1)作为信号的起始偏移量(在本例中为 x)。我这样做了,结果现在匹配 ippsConvolve_32f 和 Matlab。

(对不起,这应该是一个评论,但我没有足够的声誉)。

于 2016-09-09T02:07:16.097 回答
1

@MartinR 我弄清楚了为什么我的代码不适用于数组。我在一个使用 Surge 作为链接框架的项目中编写此代码。Surge 重载了+[Float] 和 [Double] 数组的运算符,使其成为数组元素的元素加法。因此,当我这样做时,x + xPad它并没有像预期的那样扩展数组的大小,它只是简单地返回x,因为xPad它只包含零。但是,Surge 并没有+为序列重载运算符,因此使用Repeat()成功扩展了数组。感谢您的帮助 - 从没想过要尝试序列!

于 2016-02-06T20:30:01.773 回答