我希望使用3x3滤镜过滤每像素1位图像:对于每个输入像素,如果围绕它的像素的加权和(由滤镜确定的权重),则相应的输出像素设置为1超过某个门槛。
我希望这比转换为8 bpp然后过滤它更有效,但我想不出一个好方法。一个简单的方法是跟踪九个指向字节的指针(三个连续的行以及指向每一行当前字节任一侧的指针,用于计算这些字节中第一个和最后一个位的输出)和每个输入像素计算
sum = filter[0] * (lastRowPtr & aMask > 0) + filter[1] * (lastRowPtr & bMask > 0) + ... + filter[8] * (nextRowPtr & hMask > 0)
,
在字节边缘使用额外的faff作为位。然而,这很慢,看起来真的很难看。你并没有从每个字节中有8个像素的事实中获得任何并行性,而是需要做大量的额外工作来掩盖事物。
有没有什么好的资料来说明如何最好地做这类事情?这个特定问题的解决方案将是惊人的,但我很高兴指出在C / C ++中对1bpp图像进行有效图像处理的任何示例。我希望将来用1 bpp算法替换更多8 bpp的东西,以避免图像转换和复制,所以任何一般的资源都会受到赞赏。
答案 0 :(得分:2)
查看可分离的过滤器。除此之外,它们在它们工作的情况下允许大规模的并行性。
例如,在您的3x3样本重量和过滤器情况下:
这种方法在数学上的优势在于它将样本操作的数量从n^2
减少到2n
,尽管它需要一个与源相同大小的缓冲区(如果你已经执行副本,可以用作缓冲区;您无法修改步骤2)的原始源。为了在2n
保持内存使用,你可以一起执行第2步和第3步(这有点棘手,并不完全令人愉快);如果内存不是问题,你可以在两个缓冲区(source,hblur,vblur)上花费3n
。
由于每个操作都与不可变源完全隔离,因此如果有足够的内核,则可以同时对每个像素执行过滤。或者,在更现实的情况下,您可以利用分页和缓存来加载和处理单个列或行。这在使用奇数步幅,在行尾填充等时很方便。第二轮样本(垂直)可能会与您的缓存混在一起,但在最糟糕的情况下,一轮将缓存友好并且您已经切割处理从指数到线性。
现在,我还没有涉及具体存储数据的情况。这确实使事情稍微复杂一点,但并非如此。假设您可以使用滚动窗口,例如:
d = s[x-1] + s[x] + s[x+1]
的工作原理。有趣的是,如果你在步骤1的输出期间将图像旋转90度(平凡,在阅读时从(y,x)
采样),你可以为任何样本加载最多两个水平相邻的字节,并且只有一个单字节就像75%的时间。在读取过程中,这对缓存有一点不太友好,但大大简化了算法(足以让它重新获得损失)。
的伪代码:
buffer source, dest, vbuf, hbuf;
for_each (y, x) // Loop over each row, then each column. Generally works better wrt paging
{
hbuf(x, y) = (source(y, x-1) + source(y, x) + source(y, x+1)) / 3 // swap x and y to spin 90 degrees
}
for_each (y, x)
{
vbuf(x, 1-y) = (hbuf(y, x-1) + hbuf(y, x) + hbuf(y, x+1)) / 3 // 1-y to reverse the 90 degree spin
}
for_each (y, x)
{
dest(x, y) = threshold(hbuf(x, y))
}
访问字节内的位(source(x, y)
表示访问/样本)是相对简单的,但在这里写出来有点痛苦,因此留待读者阅读。原理,特别是以这种方式实现(旋转90度),每次只需要2次n
个样本,并且始终从紧邻的位/字节采样(从不要求你计算位的位置)下一行)。总而言之,它比任何替代方案都更快更简单。
答案 1 :(得分:2)
我发现很多年前将这些位解包为字节,进行过滤,然后将字节打包回位比直接使用位更快。这似乎是违反直觉的,因为它是3个循环而不是1循环,但每个循环的简单性超过了它。
我无法保证它仍然是最快的;编译器,尤其是处理器很容易发生变化。然而,简化每个循环不仅使其更容易优化,而且使其更易于阅读。这是值得的。
拆包到单独的缓冲区的另一个好处是它可以让您灵活地处理边缘处的操作。通过使缓冲区比输入大2个字节,从字节1开始解压缩,然后将字节0和n
设置为您喜欢的任何内容,并且过滤循环根本不必担心边界条件。
答案 2 :(得分:1)
不是将整个图像扩展为1位/字节(或者基本上是8bpp,如您所述),您可以简单地展开当前窗口 - 读取第一行的第一个字节,移位和掩码,然后读出你需要三个位;对其他两行做同样的事情。然后,对于下一个窗口,您只需丢弃左列并从每一行中再取一位。执行此操作的逻辑和代码并不像简单地扩展整个图像那样简单,但它会占用更少的内存。
作为中间立场,您可以扩展您当前正在处理的三行。可能更容易以这种方式编码。