
I'm trying to experiment with image processing in haskell (with which
I haven't much experience). I've written some FFI code to talk to
the ImageMagick library which provokes a few questions (environment is
ghc 6.2.1 on debian):
1. Speed: I'm reading in a 2000x1500 pixel image, and have defined a
Pixel type like this:
data Pixel a = Pixel !a !a !a deriving Show
I use ImageMagick to load the image, then build an Array of Pixel
Floats. Building the array takes 45 seconds on a 2.5Ghz P4 with
code compiled -O2, which seems slow to me - are my expectations
unrealistic? I've tried various UNPACK things which didn't make
much difference.
2. How do I convert a CFloat into a Float?
3. I get the wrong answer ;-) I expect the C and haskell code below
to produce the same pixel data, but they don't (the C code is right).
C code:
#include

John Kozak
data Pixel a = Pixel !a !a !a deriving Show
I use ImageMagick to load the image, then build an Array of Pixel Floats. Building the array takes 45 seconds on a 2.5Ghz P4 with code compiled -O2, which seems slow to me - are my expectations unrealistic? I've tried various UNPACK things which didn't make much difference.
Are you using UArrays? If you're going to read the whole image anyway, they will probably be faster than the normal arrays. Also, I read somewhere that operations are often specialized for Double but not Float - since you talk about image loading rather than processing, it may be worth it to use Floats to save space, even if Doubles could be faster. -kzm -- If I haven't seen further, it is by standing in the footprints of giants

1. Speed: I'm reading in a 2000x1500 pixel image, and have defined a Pixel type like this:
data Pixel a = Pixel !a !a !a deriving Show
[with a==Float being a typical instantiation]
For images of this size, I'd recommend using a different datatype because: 1) Each Pixel Float will take at least 56 bytes to store instead of the 12 you expect in C. An array of pixels will require 4 bytes to point to each pixel bringing the total cost to 60 bytes or 180M instead of 36M per image. (I'm adding 2 words of GC-related header which I think is accurate for GHC.) 2) Access will be inefficient: 2 levels of indirection to access each field. GHC may also check that a pixel and a field are evaluated on each dereference so expect 4 memory reads and 2 branches per field access. 3) Memory locality is a major factor when processing images of this size. A conventional C-style array gives good locality for either columns or rows while a Haskell array of Pixel will give terrible locality because the garbage collector will keep moving things around without any thoughts about locality. 4) Most image processing operations can be coded up using 1 and 2-dimensional array variants of the familiar list operations: map, fold{l,r}, scan{l,r}. This lets you hide details of the representation from most code manipulating images and to match the pattern of access to the memory layout of the image to improve cache locality. Overall, I'd probably use an unboxed Haskell array. This would let you get a memory layout (and memory consumption) close to the normal C layout. I'd use access functions to hide the boxing/unboxing and I'd write some map/fold/scan-like functions to operate on the arrays. In some cases, I'd leave the object in the C world and access it using FFI functions and using various convolution operators written in C. This works great if you already have a good image processing library, you're not interested in writing too many new image mangling functions of your own, and costs of copying the image from C to Haskell (and back again, no doubt), are excessive. (I did this some years ago in a real time visual tracking system that had to deal with 640x512 images coming in at the rate of 30 frames per second.) Hope this helps, -- Alastair Reid

Alastair Reid writes:
[...] Overall, I'd probably use an unboxed Haskell array. This would let you get a memory layout (and memory consumption) close to the normal C layout. I'd use access functions to hide the boxing/unboxing and I'd write some map/fold/scan-like functions to operate on the arrays.
Thanks (and thanks to the other respondents, too). Using unboxed arrays has got my load time down to 15s (from 45), which is OK.
In some cases, I'd leave the object in the C world and access it using FFI functions and using various convolution operators written in C. This works great if you already have a good image processing library, you're not interested in writing too many new image mangling functions of your own, and costs of copying the image from C to Haskell (and back again, no doubt), are excessive. (I did this some years ago in a real time visual tracking system that had to deal with 640x512 images coming in at the rate of 30 frames per second.)
I found your FVision paper a couple of days ago - nice! John
participants (4)
-
Alastair Reid
-
John Kozak
-
John Kozak
-
Ketil Malde