我正在研究老式的“图像扭曲”过滤器。基本上,我有一个2D像素阵列(忽略了它们是彩色,灰度,浮点,RGBA等的问题)和另一个2D矢量数组(带有浮点组件),图像是< em>至少与矢量数组一样大。在伪代码中,我想这样做:
FOR EACH PIXEL (x,y)
vec = vectors[x,y] // Get vector
val = get(img, x + vec.x, y + vec.y) // Get input at <x,y> + vec
output[x,y] = val // Write to output
问题是get()
需要对输入图像进行双线性采样,因为矢量可以引用子像素坐标。但不像双线性采样,比如纹理映射,我们可以将插值数学运算到循环中,所以它只是添加,这里的读取来自随机位置。所以get()
的定义看起来像这样:
FUNCTION get(in,x,y)
ix = floor(x); iy = floor(y) // Integer upper-left coordinates
xf = x - ix; yf = y - iy // Fractional parts
a = in[ix,iy]; b = in[iy+1,iy] // Four bordering pixel values
b = in[ix,iy+1]; d = in[ix+1,iy+1]
ab = lerp(a,b,xf) // Interpolate
cd = lerp(c,d,xf)
RETURN lerp(ab,cd,yf)
和lerp()
只是
FUNCTION lerp(a,b,x)
RETURN (1-x)*a + x*b
假设输入图像和矢量数组都不是事先已知的,那么可以进行哪种高级优化? (注意:“使用GPU”是作弊。)我能想到的一件事是重新排列get()
中的插值数学,以便我们可以缓存给定(ix,iy)的像素读取和中间计算。这样,如果连续访问是相同的子像素四边形,我们可以避免一些工作。如果矢量数组是预先知道的,那么我们可以重新排列它,以便传递给get()
的坐标更倾向于局部。这可能也有助于缓存局部性,但代价是output
的写入遍布整个地方。但是,我们不能做一些奇特的事情,比如在飞行中缩放矢量,或者甚至将扭曲效果从其原始预先计算的位置移开。
唯一的另一种可能性是使用定点矢量分量,可能使用非常有限小数部分。例如,如果矢量仅具有2位分数分量,则仅可访问16个子像素区域。我们可以预先计算这些权重,并完全避免大量的插值数学,但是质量会受到影响。
还有其他想法吗?我想在实现它们之前积累一些不同的方法,看看哪个是最好的。如果有人能指出快速实现的源代码,那就太好了。
答案 0 :(得分:2)
有趣的问题。
您的问题定义基本上强制对[x,y]进行不可预测的访问 - 因为可能会提供任何向量。假设矢量图像倾向于引用局部像素,那么第一个优化就是确保以合适的顺序遍历内存以充分利用缓存局部性。这可能意味着在“for each pixel”循环中扫描32 * 32块,以便[x,y]在短时间内尽可能频繁地击中相同的像素。
您的算法的性能最有可能受到两件事的约束
vectors[x,y]
和in[x,y]
的速度SSE指令可以一次将多个元素相乘,然后将它们相加(乘法和累加)。你应该做的是计算
af = (1 - xf) * ( 1 - yf )
bf = ( xf) * ( 1 - yf )
cf = (1 - xf) * ( yf )
df = ( xf) * ( yf )
然后计算
a *= af
b *= bf
c *= cf
d *= cf
return (a + b + c + d)
很有可能这两个步骤都可以用极少量的SSE指令完成(取决于你的像素表示)。
我认为缓存中间值不太可能有用 - 似乎极不可能> 1%的矢量请求将指向完全相同的位置,而缓存内容将花费你更多的内存带宽比它会保存。
如果在处理in[vectors[x+1, y]]
时使用cpu上的预取指令来预取vectors[x,y]
,那么可能会提高内存性能,否则CPU无法预测随机遍历内存
提高算法性能的最后一种方法是同时处理输入像素的块,即x[0..4], x[5..8]
- 这使您可以展开内部数学循环。但是,你很可能受到记忆限制,这无济于事。