无需循环即可高效执行1D线性插值

时间:2015-03-27 17:18:18

标签: arrays matlab linear-interpolation

我正在尝试使用特定的精度在MATLAB中执行线性插值。 我想知道是否有一种有效的方法在MATLAB中编写线性插值函数,这样它就不需要for-loops并且运行速度非常快?

我想将输入数据修改为特定的位宽(使用quantize()函数),然后我还要确保所有的中间操作都是使用另一个位宽完成的。

现在我使用以下等式在给定点x处的值y的特定点xq处执行线性插值。为清晰起见,我没有在下面包含quantize()命令。

for j = 1:length(xq)
  for k = 1:length(x)
    if ((x(k) <= xq(j)) && (xq(j) < x(k+1)))
      yq(j) = y(k) + (y(k+1) - y(k))/(x(k+1)-x(k)) * (xq(j)-x(k));
      break;
    end
  end
end

这是与输入数据的尺寸相匹配的内容。此外,我必须多次进行线性插值(超过200次),因此它需要非常快,并且与matlab中的interp1相当。 xq是一个大小为501x501的矩阵,我们希望插入x个位置。 x和y都是长度为4096的向量.y包含复杂的数据。:

x = linspace(-100.3,100.5,4096);
y = (cos(rand(4096,1))+j*sin(rand(4096,1)))*1/100000;
for i = 1:501
    xq(i,:) = (200).*rand(501,1)-100;
end

4 个答案:

答案 0 :(得分:3)

烨。你可以做的是每个xq值,我们可以找到值适合的区间。我们可以使用两个bsxfun来电并利用广播来实现这一目标。我们来做一个简单的例子吧。我们说我们有xxq值:

x = [1 1.5 1.7 2 2.5 2.6 2.8];
xq = [1.2 1.6 2.2];

进行线性插值时,我们的工作是确定每个y点属于x的区间。具体来说,您希望找到索引i,以使索引xq的值j满足:

xq(j) >= x(i) && xq(j) < x(i+1)

让我们以矢量化的方式分别完成布尔表达式的每个部分。对于第一部分,我将创建一个矩阵,其中每列告诉我x的哪些位置满足xq >= x(i)不等式。你可以通过以下方式做到:

ind = bsxfun(@ge, xq, x(1:end-1).');

我们得到的是:

ind =

   1   1   1
   0   1   1
   0   0   1
   0   0   1
   0   0   0
   0   0   0

第一列是第一个值xq或1.2,下一列是1.6,下一列是2.2。这告诉我们x满足xq(1) > x(i)的值。请记住,由于i+1条件,我们只想检查倒数第二个元素。因此,对于第一列,这意味着xq(1) >= x(1),即xq(1) >= 1,并且非常棒。对于下一列,这意味着xq(2) >= x(1)xq(2) >= x(2)都是>=,而不是1和1.5,依此类推。

现在我们需要做表达式的另一面:

ind2 = bsxfun(@lt, xq, x(2:end).')

ind2 =

   1   0   0
   1   1   0
   1   1   0
   1   1   1
   1   1   1
   1   1   1

这也是有道理的。对于第一列,这意味着对于xq = 1,从第二个元素开始的x的每个值(由于i+1)都比第一个值更大 xq,或等效xq(1) < x(i+1)。现在,您所要做的就是将这些结合在一起:

ind_final = ind & ind2

ind_final = 

   1   0   0
   0   1   0
   0   0   0
   0   0   1
   0   0   0
   0   0   0

因此,对于xq的每个值,行位置告诉我们精确每个值落入哪个区间。因此,对于xq = 1.2,这适用于区间1或[1,1.2]。对于xq = 1.6,这适用于区间2或[1.5,1.7]。对于xq = 2.2,这适用于区间4或[2,2.5]。您现在要做的就是找到每个1的行位置:

[vals,~] = find(ind_final);

vals现在包含您需要访问x进行插值的位置。因此,你终于得到:

 yq = y(vals) + (y(vals+1) - y(vals))./(x(vals+1) - x(vals)).*(xq - x(vals));

请注意./.*元素明细运算符,因为我们正在进行此向量化。


买者

我们没有考虑到的是当您指定超出<{strong>> x点范围的值时会发生什么。我要做的是有几个声明检查这个。首先,检查是否有xq个值小于第一个x点。如果是,请将它们设置为第一个输出点。如果值大于最后一个点并将输出设置为最后一个点,则对另一方执行相同的操作。因此,您必须执行以下操作:

%// Allocate output array first
yq = zeros(1, numel(xq));

%// Any xq values that are less than the first value, set this to the first
yq(xq < x(1)) = y(1);

%// Any xq values that are less than the last value, set this to the last value
yq(xq >= x(end)) = y(end);

%// Get all of the other values that are not within the above ranges
ind_vals = (xq >= x(1)) & (xq < x(end));
xq = xq(ind_vals);

%// Do our work
ind = bsxfun(@ge, xq, x(1:end-1).');
ind2 = bsxfun(@lt, xq, x(2:end).');
ind_final = ind & ind2;
[vals,~] = find(ind_final);

%// Place output values in the right spots, escaping those values outside the ranges
yq(ind_vals) = y(vals) + (y(vals+1) - y(vals))./(x(vals+1) - x(vals)).*(xq - x(vals))

上面的代码应该成功地进行插值并处理边界条件。


要看到这一点,我们要定义一堆xy值,以及我们希望进行插值的xq值:

x = [1 1.5 1.7 2 2.5 2.6 2.8];
y = [2 3 4 5 5.5 6.6 7.7];
xq = [0.9 1 1.1 1.2 1.8 2.2 2.5 2.75 2.8 2.85];

使用上面的内容,并在我添加范围检查后运行上面的代码,我们得到:

yq =

   2.0000   2.0000   2.2000   2.4000   4.3333   5.2000   5.5000   7.4250   7.7000   7.7000

您可以看到任何小于第一个xq值的x值,我们只需将输出设置为第一个y值。任何大于上一个x值的值,我们将输出设置为最后y值。其他所有内容都是线性插值的。

答案 1 :(得分:1)

这应该是一个评论,但我缺乏业力。 在半新版本的Matlab中,For循环并不等于慢。我之前看到过一些情况,人们为了消除任何循环而跳过篮球,最终导致性能下降。

无论如何,你的循环的很大一部分似乎独立于 j 。尝试一下这一行的开始。我怀疑你通过删除循环看到了很多改进的性能,但我希望被证明是错误的:)

C = y(1:end-1) + (y(2:end) - y(1:end-1))./(x(2:end)-x(1:end-1))';
for j = 1:length(xq)
  for k = 1:length(x)-1
    if ((x(k) <= xq(j)) && (xq(j) < x(k+1)))
      yq(j) = C(k) * (xq(j)-x(k));
      break;
    end
  end
end

使用示例数据,运行速度比原始代码快25%。另请注意,仅当x和y具有不同的大小(例如,(1,4096)与(4096,1))时,才需要第一行末尾的&#39; 。还要考虑预先分配像xq和yq这样的数组。

答案 2 :(得分:1)

为什么不使用内置功能的matlabs来做到这一点?

yq = interp1(x, y, xq);

答案 3 :(得分:1)

由于我还不完全确定你想以哪种方式进行插值,这里只是一些关于如何降低算法复杂性的建议。 在目前的形式中,O(m*n)m=length(x)的算法具有复杂度n=length(xq)。可以将其减少到O(m*log(m) + n*log(n))

在低级语言中,您首先会对向量xqx进行排序(并重新排列y的相应值),然后遍历向量xq 。这样,搜索正确的区间索引k可以比从k=1length(x)的搜索更有效。相反,您可以从最后找到的k值开始搜索。

然而,

MATLAB提供了一个方便的函数histc,它可以为您完成此任务。它可以返回值为k的区间的xq - 索引。您仍然需要对xy进行排序。

%%// Find the intervals the values lie in.
[x,I] = sort(x);
y = y(I);
[~, ks] = histc(xq, x);

%%// Do the interpolation.
for j = 1:length(xq)
    k = ks(j);
    yq(j) = y(k) + (y(k+1) - y(k))/(x(k+1)-x(k)) * (xq(j)-x(k));
end

您可以将for - 循环向量化,但由于您没有指定实际执行量化步骤的方式,我会将此留给您。您可能想看一下 rayryeng 的最后一个计算步骤。使用此histc预处理步骤,只需交换vals ks即可。

对于问题中列出的数据大小,这应该可以为您提供~30倍的加速。