我沿着一维线有许多(〜一百万个)不规则间隔的点P。这些标记线段,以便如果点是{0,x_a,x_b,x_c,x_d,...},则这些段从0-> x_a,x_a-> x_b,x_b-> x_c,x_c- > x_d等。对于每个片段,我也都有一个y值,我希望将其解释为颜色的深度。我需要将这条线绘制为图像,但是可能只有(比如说)1000个像素可以代表线的整个长度。当然,这些像素对应于沿着线的规则间隔,例如在0..X1,X1..X2,X2..X3等处,其中X1,X2,X3是规则间隔的。要计算每个像素的颜色,我需要取落在规则间隔的像素边界内的所有y值的平均值,并按该间隔内段的长度加权。可能还有一些像素在P中不包含任何值,它们只是采用了跨整个像素的线段所定义的颜色值。
似乎在图像分析中可能需要做很多事情。那么,该操作有一个名称吗?在numpy中,什么是最快的方法来计算这样一组规则的平均y值?我想这有点像插值法,只是我不想只取两个周围点的平均值,而是取规则间隔内所有点的加权平均值(加上一点重叠)。
[编辑-添加了最小的示例]
假设沿水平线有5个段,它们由[0,1.1,2.2,2.3,2.8,4]分隔(即,该线从0到4)。假设每个线段采用任意阴影值,例如,我们可以使用5个阴影值[0,0.88,0.55,0.11,0.44]-其中0为黑色,而1为白色。然后,如果我想使用4个像素进行绘制,则需要创建4个值,分别是0 ... 1、1 ... 2等,并且希望该计算为每个值返回以下值:
0 ... 1 = 0(第一行覆盖0-> 1.1)
1 ... 2 = 0.1 * 0 + 0.9 * 0.88(1 ... 1.1被第一行覆盖,其余被第二行覆盖)
2 ... 3 = 0.2 * 0.88,0.1 * 0.55 + 0.5 * 0.11 + 0.2 * 0.44(第二到第五条线段覆盖
3 ... 4 = 0.44(这由最后一个线段2.8-> 4覆盖)
如果我想将此数据拟合为2像素长的线,则2像素将具有以下值:
0 ... 2 = 1.1 / 2 * 0 + 0.9 / 2 * 0.88
2 ... 4 = 0.2 / 2 * 0.88 + 0.1 / 2 * 0.55 + 0.5 / 2 * 0.11 + 1.2 * 0.44
这似乎是沿着1d线进行下采样的“正确”方法。我正在寻找一种快速实现(最好是内置的)的功能,当我沿线有(例如)一百万个点,并且只有1000个(或大约)像素可以容纳它们时。
答案 0 :(得分:3)
如您所料,有一个纯粹的numpy解决方案可以解决此问题。诀窍是巧妙地混合np.searchsorted
和np.add.reduceat
来将常规网格放在原始的最近的bin上,以计算这些bin的总和:
import numpy as np
def distribute(x, y, n):
"""
Down-samples/interpolates the y-values of each segment across a
domain with `n` points. `x` represents segment endpoints, so should
have one more element than `y`.
"""
y = np.asanyarray(y)
x = np.asanyarray(x)
new_x = np.linspace(x[0], x[-1], n + 1)
# Find the insertion indices
locs = np.searchsorted(x, new_x)[1:]
# create a matrix of indices
indices = np.zeros(2 * n, dtype=np.int)
# Fill it in
dloc = locs[:-1] - 1
indices[2::2] = dloc
indices[1::2] = locs
# This is the sum of every original segment a new segment touches
weighted = np.append(y * np.diff(x), 0)
sums = np.add.reduceat(weighted, indices)[::2]
# Now subtract the adjusted portions from the right end of the sums
sums[:-1] -= (x[dloc + 1] - new_x[1:-1]) * y[dloc]
# Now do the same for the left of each interval
sums[1:] -= (new_x[1:-1] - x[dloc]) * y[dloc]
return new_x, sums / np.diff(new_x)
seg = [0, 1.1, 2.2, 2.3, 2.8, 4]
color = [0, 0.88, 0.55, 0.11, 0.44]
seg, color = distribute(seg, color, 4)
print(seg, color)
结果是
[0. 1. 2. 3. 4.] [0. 0.792 0.374 0.44 ]
这正是您在手动计算中所期望的。
基准
我运行了以下基准测试,以确保EE_'s solution和我的都同意答案,并检查时间安排。我对其他解决方案进行了少许修改,使其具有与我相同的界面:
from scipy.interpolate import interp1d
def EE_(x, y, n):
I = np.zeros_like(x)
I[1:] = np.cumsum(np.diff(x) * y)
f = interp1d(x, I, bounds_error=False, fill_value=(0, I[-1]))
pix_x = np.linspace(x[0], x[-1], n + 1)
pix_y = (f(pix_x[1:]) - f(pix_x[:-1])) / (pix_x[1:] - pix_x[:-1])
return pix_x, pix_y
这是测试平台(方法MadPhysicist
仅从上面的distribute
函数重命名)。对于x
,输入始终为1001个元素,对于y
,输入始终为1000个元素。输出数字为5、10、100、1000、10000:
np.random.seed(0x1234ABCD)
x = np.cumsum(np.random.gamma(3.0, 0.2, size=1001))
y = np.random.uniform(0.0, 1.0, size=1000)
tests = (
MadPhysicist,
EE_,
)
for n in (5, 10, 100, 1000, 10000):
print(f'N = {n}')
results = {test.__name__: test(x, y, n) for test in tests}
for name, (x_out, y_out) in results.items():
print(f'{name}:\n\tx = {x_out}\n\ty = {y_out}')
allsame = np.array([[np.allclose(x1, x2) and np.allclose(y1, y2)
for x2, y2 in results.values()]
for x1, y1 in results.values()])
print()
print(f'Result Match:\n{allsame}')
from IPython import get_ipython
magic = get_ipython().magic
for test in tests:
print(f'{test.__name__}({n}):\n\t', end='')
magic(f'timeit {test.__name__}(x, y, n)')
我将跳过数据和协议打印输出(结果相同),并显示时间:
N = 5
MadPhysicist: 50.6 µs ± 349 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
EE_: 110 µs ± 568 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
N = 10
MadPhysicist: 50.5 µs ± 732 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
EE_: 111 µs ± 635 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
N = 100
MadPhysicist: 54.5 µs ± 284 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
EE_: 114 µs ± 215 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
N = 1000
MadPhysicist: 107 µs ± 5.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
EE_: 148 µs ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
N = 10000
MadPhysicist: 458 µs ± 2.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
EE_: 301 µs ± 4.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
您可以看到,在较小的输出尺寸下,numpy解决方案要快得多,这可能是因为开销占主导。但是,在更多的断点处,scipy解决方案变得更快。您必须比较不同的输入大小,才能真正了解时序的计算原理,而不仅仅是不同的输出大小。
答案 1 :(得分:2)
您仍然可以使用线性插值来完成此操作。尽管您的函数是分段恒定的,但您希望在很多小的间隔内获得平均值。从 a 到 b 的时间间隔内某些函数 f ( x )的平均值只是其整数范围除以 a 和 b 之间的差异。分段常数函数的积分将是分段线性函数。因此,假设您拥有数据:
x = [0, 1.1, 2.2, 2.3, 2.8, 4]
y = [0, 0.88, 0.55, 0.11, 0.44]
制作一个函数,该函数将以任何 x 值给出其积分。在这里,数组I
将包含您给定的每个 x 值的积分值,函数f
是它的线性内插值,它将在任何一点:
I = numpy.zeros_like(x)
I[1:] = numpy.cumsum(numpy.diff(x) * y)
f = scipy.interpolate.interp1d(x, I)
现在评估每个像素的平均值很容易:
pix_x = numpy.linspace(0, 4, 5)
pix_y = (f(pix_x[1:]) - f(pix_x[:-1])) / (pix_x[1:] - pix_x[:-1])
我们可以检查这些数组中的内容:
>>> pix_x
array([0., 1., 2., 3., 4.])
>>> pix_y
array([0. , 0.792, 0.374, 0.44 ])
像素的阴影值现在位于pix_y
中。这些应该与您在示例中上面给出的值完全匹配。
即使很多点,这也应该相当快:
def test(x, y):
I = numpy.zeros_like(x)
I[1:] = numpy.cumsum(numpy.diff(x) * y)
f = scipy.interpolate.interp1d(x, I,
bounds_error=False, fill_value=(0, I[-1]))
pix_x = numpy.linspace(0, 1, 1001)
pix_y = (f(pix_x[1:]) - f(pix_x[:-1])) / (pix_x[1:] - pix_x[:-1])
return pix_y
timeit
报告:
225 ms ± 37.6 ms per loop
在我的系统上,当x的大小为1000000(y的大小为999999)时。请注意,bounds_error=False
和fill_value=(0, I[-1])
被传递到interp1d
。这样做的效果是假设您的着色函数在 x 值范围之外为零。另外,interp1d
不需要对输入值进行排序;在上面的测试中,我同时给出了 x 和 y 作为0到1之间的统一随机数数组。但是,如果您确定它们是已排序的,则可以通过assume_sorted=True
,您应该会提高速度:
20.2 ms ± 377 µs per loop