我有两个数组A、B,它们的形状都是(42、28、4),其中:

42 : y_dim
28 : x_dim
4  : RGBA
## I'm on MacBook Air M1 2020 16Gb btw

我想通过一个类似的过程将它们结合在一起:

def add(A, B):
    X = A.shape[1]
    Y = A.shape[0]
    alpha = A[..., 3] / 255

    B[..., :3] = blend(B[..., :3], A[..., :3], alpha.reshape(Y, X, 1))    

    return B

def blend(c1, c2, alpha):
    return np.asarray((c1 + np.multiply(c2, alpha))/(np.ones(alpha.shape) + alpha), dtype='uint8')

但目前这个速度有点太慢了(大约20毫秒,250幅图像叠加在一个基本数组[1]上),如果你有任何方法来改善这一点(最好是支持8位阿尔法),我很乐意知道.

[1]:

start = time.time()
for obj in l: # len(l) == 250
    _slice = np.index_exp[obj.y * 42:(obj.y+1) * 42, obj.x * 28 : (obj.x+1) * 28, :]
    self.pixels[_slice] = add(obj.array, self.pixels[_slice])

stop = time.time()
>>> stop - start # ~20ms 

我试过以下方法:

# cv2.addWeighted() in add()
## doesn't work because it has one alpha for the whole image,
## but I want to have indiviual alpha control for each pixel

B = cv.addWeighted(A, 0.5, B, 0.5, 0)
# np.vectorize blend() and use in add()
## way too slow because as the docs mention it's basically just a for-loop

B[..., :3] = np.vectorize(blend)(A[..., :3], B[..., :3], A[..., 3] / 255)

# changed blend() to the following
def blend(a, b, alpha):
    if alpha == 0:
        return b
    elif alpha == 1:
        return a
    
    return (b + a * alpha) / (1 + alpha)
# moved the blend()-stuff to add()
## doesn't combine properly; too dark with alpha

np.multiply(A, alpha.reshape(Y, X, 1)) + np.multiply(B, 1 - alpha.reshape(Y, X, 1))

我也试过一些比特的东西,但我的猴脑不能正确理解它.我使用的是M1 Mac,所以如果你有任何metalcompute和Python的经验,请包括任何关于这方面的 idea !

任何输入是欢迎的,提前感谢!

Answer:克里斯托夫·拉克维茨发布了一个非常详细和 struct 良好的答案,所以如果你对类似的事情感兴趣,请查看下面接受的推荐.

为了补充这一点,我在我的M1计算机上运行Christoph的代码来显示结果.

2500 calls (numpy)       = 0.0807
2500 calls (other)       = 0.0833
2500 calls (Christoph´s) = 0.0037

推荐答案

首先,你的混合方程式看起来是错误的.即使alpha等于255,你也只能得到50:50的混合.您可能想要B = B * (1-alpha) + A * alpha或重新排列的B += (A-B) * alpha,但该表达式有齿(整数减法将有上溢/下溢).


您似乎是在游戏显示屏上的网格中绘制"精灵".只需使用2D图形库,甚至3D(OpenGL?).GPU非常擅长绘制具有透明度的纹理四边形.即使没有GPU的参与,正确的库也将包含优化的原语,并且您不必自己编写任何原语.

假设精灵不改变外观,上传纹理(到GPU内存)的成本是一次性成本.如果它们每一帧都改变,这可能是显而易见的.


Since I originally proposed to use and previous answers have only gotten a factor of 2 and then 10 out of it, I'll show a few more points to be aware.

前面的答案在其内部循环中提供了一个函数:

B[i, j, :3] = (B[i, j, :3] + A[i, j, :3] * alpha[i, j]) / (1 + alpha[i, j])

这似乎是合理的,因为它一次处理but100中的整个像素,速度相对较慢,因为它被写入为通用的(各种数据类型和形状).Numba没有这样的要求.它会很高兴地为这种特定的情况(uint8,固定的维数,固定的内循环迭代)生成特定的机器代码.

如果您再展开一次,从内部循环中删除NumPy调用,您将得到100:

for k in range(3):
    B[i, j, k] = (B[i, j, k] + A[i, j, k] * alpha[i, j]) / (1 + alpha[i, j])

计时结果(相对速度很重要,我的电脑太旧了):

2500 calls (numpy)    = 0.3845
2500 calls (other)    = 0.5039
2500 calls (mine)     = 0.0901

您可以继续操作,从每个循环中提取常量,以防LLVM(Numba使用的)没有注意到优化.

缓存的局部性也起着重要的作用.而不是计算一次整个alpha数组,只需计算第二个内部循环中的每个像素的alpha:

你应该看看alpha = .../255100,也就是102.使用Float32而不是Float64,因为那样通常会更快.

alpha = A[i, j, 3] * np.float32(1/255)
for k in range(3):
    B[i, j, k] = (B[i, j, k] + A[i, j, k] * alpha) / (1 + alpha)

现在让我们做整数运算,它比浮点运算更快,并且具有正确的混合:

alphai = np.int32(A[i, j, 3]) # uint8 needs to be widened to avoid overflow/underflow
for k in range(3):
    a = A[i, j, k]
    b = B[i, j, k]
    c = (a * alphai + b * (255 - alphai) + 128) >> 8 # fixed point arithmetic, may be off by 1
    B[i, j, k] = c

最后:

2500 calls (numpy)    = 0.3904
2500 calls (other)    = 0.5211
2500 calls (mine)     = 0.0118

因此,这是33%的加速.这是在我的旧电脑上,没有任何最新的矢量指令集.

而不是调用这样一个函数250次,你可以调用它one次所有的数据.may开启了parallelism的可能性.农巴让你这么做,但这不是小事...

由于您的游戏显示是一个网格,您可以收集每个网格单元的所有精灵(当然是有序的).然后,可以并行渲染每个单元格.

Python相关问答推荐

Pandas 有条件轮班操作

Python库:可选地支持numpy类型,而不依赖于numpy

两个pandas的平均值按元素的结果串接元素.为什么?

导入...从...混乱

不允许访问非IPM文件夹

字符串合并语法在哪里记录

SQLAlchemy bindparam在mssql上失败(但在mysql上工作)

将pandas导出到CSV数据,但在此之前,将日期按最小到最大排序

在Admin中显示从ManyToMany通过模型的筛选结果

为什么我的sundaram筛这么低效

在Docker容器(Alpine)上运行的Python应用程序中读取. accdb数据库

在用于Python的Bokeh包中设置按钮的样式

使用SeleniumBase保存和加载Cookie时出现问题

如何使用Azure Function将xlsb转换为xlsx?

合并相似列表

极点替换值大于组内另一个极点数据帧的最大值

Python:从目录内的文件导入目录

在pandas中,如何在由两列加上一个值列组成的枢轴期间或之后可靠地设置多级列的索引顺序,

PYTHON中的pd.wide_to_long比较慢

是否将Pandas 数据帧标题/标题以纯文本格式转换为字符串输出?