对于具有数百万像素的 2D 未装箱像素阵列,建议使用哪种 Haskell 表示?

Posted

技术标签:

【中文标题】对于具有数百万像素的 2D 未装箱像素阵列,建议使用哪种 Haskell 表示?【英文标题】:What Haskell representation is recommended for 2D, unboxed pixel arrays with millions of pixels? 【发布时间】:2011-08-25 18:22:38 【问题描述】:

我想解决 Haskell 中的一些图像处理问题。我正在使用具有数百万像素的双色调(位图)和彩色图像。我有几个问题:

    Vector.UnboxedUArray 之间选择的依据是什么?它们都是未装箱的数组,但Vector 抽象似乎被大量宣传,特别是在循环融合方面。 Vector 总是更好吗?如果不是,我应该什么时候使用哪种表示?

    对于彩色图像,我希望存储 16 位整数的三元组或单精度浮点数的三元组。为此,VectorUArray 是否更易于使用?性能更高?

    对于双色调图像,我只需要每个像素存储 1 位。是否有预定义的数据类型可以通过将多个像素打包成一个单词来帮助我,还是我一个人?

    最后,我的数组是二维的。我想我可以处理由表示为“数组数组”(或向量向量)所施加的额外间接性,但我更喜欢具有索引映射支持的抽象。任何人都可以从标准库或 Hackage 中推荐任何东西吗?

我是一个函数式程序员,不需要突变 :-)

【问题讨论】:

我认为只有 Repa 符合 4 号,请参阅cse.unsw.edu.au/~chak/papers/repa.pdf。 @stephen:标准的Array接口支持多维数组。您可以简单地使用元组作为索引。 这个问题受到高度评价和喜爱(包括我)这一事实似乎表明 Haskell 对数组的处理没有很好的记录。 @Alexandre C.:基本日常数组的处理有据可查;处理包含可变数据的大块内存就像在 C 中一样简单;尽可能高效地处理大型不可变多维数组不太明显。这是关于性能调优的场景,在这种场景中,任何语言都可能存在微妙的、记录较少的细节问题。 @Alexandre C.:对于大多数应用程序来说,它是无缝的。真正有问题的不是 Haskell 本身,而是库和编译器。一个由Ints 元组索引的普通UArray 使用起来很简单,而且通常足够好,但即使是GHC 的深层魔法也无法将使用其最小API 的代码优化为与为快速并行化而调整的库竞争的东西批量数据处理。 【参考方案1】:

对于多维数组,我认为目前 Haskell 中的最佳选择是 repa

Repa 提供高性能、规则、多维、形状多态的并行数组。所有数字数据均未装箱存储。如果您在运行程序时在命令行上提供 +RTS -Nwhatever,使用 Repa 组合器编写的函数会自动并行。

最近被用于一些图像处理问题:

Real time edge detection Efficient Parallel Stencil Convolution in Haskell

我已经开始编写 a tutorial on the use of repa,如果您已经了解 Haskell 数组或向量库,这是一个很好的起点。关键的垫脚石是使用形状类型而不是简单的索引类型来处理多维索引(甚至是模板)。

repa-io 包支持读取和写入 .bmp 图像文件,但需要支持更多格式。

解决您的具体问题,这是一个带有讨论的图形:



我应该在 Vector.Unboxed 和 UArray 之间选择什么?

它们具有大致相同的底层表示,但主要区别在于用于处理向量的 API 的广度:它们几乎具有您通常与列表相关联的所有操作(使用融合驱动的优化框架),而UArray 几乎没有 API。

对于彩色图像,我希望存储 16 位整数的三元组或单精度浮点数的三元组。

UArray 对多维数据有更好的支持,因为它可以使用任意数据类型进行索引。虽然这在Vector 中是可能的(通过为您的元素类型编写UA 的实例),但这不是Vector 的主要目标——相反,这是Repa 介入的地方,使它非常由于 shape 索引,易于使用以高效方式存储的自定义数据类型。

Repa 中,您的三重短裤将具有以下类型:

Array DIM3 Word16

即 Word16 的 3D 数组。

对于双色调图像,我只需要每个像素存储 1 位。

UArrays 将 Bool 打包为位,Vector 使用 Bool 的实例进行位打包,而不是使用基于 Word8 的表示。但是,很容易从(过时的)uvector 库中为向量编写一个位打包实现——here is one。在后台,Repa 使用 Vectors,所以我认为它继承了库表示选择。

是否有预定义的数据类型可以帮助我将多个像素打包成一个单词

您可以将现有实例用于任何库,用于不同的单词类型,但您可能需要使用 Data.Bits 编写一些帮助程序来滚动和展开打包数据。

最后,我的数组是二维的

UArray 和 Repa 支持高效的多维数组。 Repa 也有一个丰富的界面来做这件事。向量本身不会。


值得注意的提及:

hmatrix,一种自定义数组类型,广泛绑定到线性代数包。应该绑定使用vectorrepa 类型。 ix-shapeable,从常规数组中获得更灵活的索引 chalkboard,Andy Gill 的用于处理 2D 图像的库 codec-image-devil,读写各种图片格式到UArray

【讨论】:

另外,感谢repa-devil,您现在可以进行多种格式的 3D repa 数组的图像 IO。 能否请您解释一下 Repa 如何与 C 代码互操作?我没有找到 Data.Array.Repa 的可存储实例... Copying to pointers 可能是存储数据的最简单途径,但显然不是长期解决方案。为此,我们需要底层的可存储向量。 做image desaturation with repa and repa-devil的例子【参考方案2】:

虽然,这并不能完全回答您的问题,甚至不是真正的 haskell,但我建议您查看 hackage 的 CV 或 CV-combinators 库。它们绑定了来自 opencv 库的许多相当有用的图像处理和视觉运算符,使处理机器视觉问题的速度更快。

如果有人弄清楚 repa 或一些这样的数组库如何直接与 opencv 一起使用,那就太好了。

【讨论】:

【参考方案3】:

一旦我查看了对我很重要的 Haskell 数组库的特性,并编译了 a comparison table(仅电子表格:direct link)。所以我会试着回答。

我应该在 Vector.Unboxed 和 UArray 之间进行选择的依据是什么?它们都是未装箱的数组,但 Vector 抽象似乎被大力宣传,特别是在循环融合方面。 Vector总是更好吗?如果不是,我应该什么时候使用哪种表示?

如果需要二维或多维数组,UArray 可能优于 Vector。但是 Vector 有更好的 API 来操作向量。一般来说,Vector 不太适合模拟多维数组。

Vector.Unboxed 不能与并行策略一起使用。我怀疑 UArray 两者都不能使用,但至少从 UArray 切换到盒装数组很容易,看看并行化的好处是否超过了盒装成本。

对于彩色图像,我希望存储 16 位整数的三元组或单精度浮点数的三元组。为此,Vector 或 UArray 是否更易于使用?性能更高?

我尝试使用数组来表示图像(尽管我只需要灰度图像)。对于彩色图像,我使用 Codec-Image-DevIL 库来读取/写入图像(绑定到 DevIL 库),对于灰度图像,我使用 pgm 库(纯 Haskell)。

我对 Array 的主要问题是它只提供随机访问存储,但它没有提供许多构建 Array 算法的方法,也没有提供现成的数组例程库(不与线性代数接口libs,不允许表达卷积、fft 和其他变换)。

几乎每次必须从现有数组构建一个新数组时,都必须构建一个中间值列表(就像在温和介绍中的matrix multiplication 中一样)。数组构造的成本通常超过了更快随机访问的好处,以至于在我的某些用例中,基于列表的表示更快。

STUArray 本来可以帮助我,但我不喜欢与神秘的类型错误作斗争以及编写 polymorphic code with STUArray 所需的努力。

因此,数组的问题在于它们不太适合数值计算。 Hmatrix 的 Data.Packed.Vector 和 Data.Packed.Matrix 在这方面更好,因为它们带有一个实体矩阵库(注意:GPL 许可证)。性能方面,在矩阵乘法上,hmatrix 足够快 (only slightly slower than Octave),但非常占用内存(比 Python/SciPy 多几倍)。

还有矩阵的 blas 库,但它不是基于 GHC7 构建的。

我还没有太多使用 Repa 的经验,也不太了解 repa 代码。从我所见,在其上编写的即用型矩阵和数组算法的范围非常有限,但至少可以通过库来表达重要的算法。例如,repa-algorithms 中已经有matrix multiplication and for convolution 的例程。不幸的是,卷积现在似乎是limited to 7×7 kernels(这对我来说还不够,但对于许多用途来说应该足够了)。

我没有尝试 Haskell OpenCV 绑定。它们应该很快,因为 OpenCV 真的很快,但我不确定绑定是否完整且足够好以便可用。此外,OpenCV 本质上是非常必要的,充满了破坏性的更新。我想很难在它之上设计一个漂亮而高效的功能界面。如果一个人走 OpenCV 的方式,他很可能到处使用 OpenCV 图像表示,并使用 OpenCV 例程来操作它们。

对于双色调图像,我只需要为每个像素存储 1 位。是否有预定义的数据类型可以通过将多个像素打包成一个单词来帮助我,还是我一个人?

据我所知,Unboxed arrays of Bools 负责打包和解包位向量。我记得在其他库中查看了布尔数组的实​​现,但在其他地方没有看到。

最后,我的数组是二维的。我想我可以处理由表示为“数组数组”(或向量的向量)所施加的额外间接性,但我更喜欢具有索引映射支持的抽象。任何人都可以从标准库或 Hackage 中推荐任何东西吗?

除了 Vector(和简单列表)之外,所有其他数组库都能够表示二维数组或矩阵。我想他们避免了不必要的间接。

【讨论】:

下面提到的opencv绑定是不完整的。这么庞大的图书馆,真的不可能一个人来创建和维护一套完整的。但是,即使您必须为自己需要的函数构建一个包装器,使用 opencv 仍然具有成本效益,因为它确实实现了一些非常复杂的东西。 @aleator 是的,我知道这对一个人来说确实是一项巨大的工作。顺便说一句,如果您是维护者,能否请您在某处发布黑线鳕文档,以便无需在本地安装就可以评估库和绑定的覆盖范围? (由于构建错误,文档在 Hackage 上不可用;由于 M_PI 未声明,它不适用于 GHC 6.12.1 和 GHC 7.0.2 为我构建)。 @jextee 嘿,谢谢你的提示!我已经上传了一个可以解决这两个问题的新版本。 @aleator 谢谢,现在它构建得很干净。【参考方案4】:

这是一个新的Haskell Image Processing library,它可以处理所有相关任务等等。目前它使用Repa 和Vector 包作为底层表示,因此继承了融合、并行计算、变异和这些库附带的大多数其他好处。它提供了一个易于使用的界面,非常适合图像处理:

具有任意精度的二维索引和未装箱像素(DoubleFloatWord16 等) 所有基本功能,如mapfoldzipWithtraverse ... 支持各种色彩空间:RGB、HSI、灰度、双色调、复杂等。 常用图像处理功能: 二元形态 卷积 插值 傅里叶变换 直方图绘制 等 能够将像素和图像视为常规数字。 通过JuicyPixels库读写常用图片格式

最重要的是,它是一个纯 Haskell 库,因此不依赖任何外部程序。它还具有高度可扩展性,可以引入新的色彩空间和图像表示。

它没有做的一件事是将多个二进制像素打包在一个Word 中,而是使用每个二进制像素一个Word,也许在将来......

【讨论】:

以上是关于对于具有数百万像素的 2D 未装箱像素阵列,建议使用哪种 Haskell 表示?的主要内容,如果未能解决你的问题,请参考以下文章

基于像素的标签将2D像素阵列更改为图像

如何在 C 中用新的/更新的像素阵列更新/替换 SDL Surface 像素阵列?

love2d game.exe 在 Windows 中不清晰。如何使游戏清晰(像素游戏)?

OpenGL 中的 2D 绘图:在原始尺寸下具有像素精度的线性过滤

UIImage 循环通过像素效率非常低?

pytorch(numpy)计算最接近点的像素