I am trying to write OpenCV FFI in Racket and arrived at a point where arrays need to be manipulated efficiently. However, all my attempts to access arrays by using Racket FFI resulted in very inefficient code. Is there a way for fast access of C arrays using FFI?
In Racket, this type of manipulation is reasonably fast, i.e.:
(define a-vector (make-vector (* 640 480 3)))
(time (let loop ([i (- (* 640 480 3) 1)])
(when (>= i 0)
;; invert each pixel channel-wise
(vector-set! a-vector i (- 255 (vector-ref a-vector i)))
(loop (- i 1)))))
-> cpu time: 14 real time: 14 gc time: 0
Now, in OpenCV, there is a struct called IplImage
that looks like this:
typedef struct _IplImage
{
int imageSize; /* sizeof(IplImage) */
...
char *imageData; /* Pointer to aligned image data.*/
}IplImage;
The struct is defined in Racket as follows:
(define-cstruct _IplImage
([imageSize _int]
...
[imageData _pointer]))
Now we load an image using cvLoadImage
function as follows:
(define img
(ptr-ref
(cvLoadImage "images/test-image.png" CV_LOAD_IMAGE_COLOR)
_IplImage))
The pointer imageData
can be accessed by: (define data (IplImage-imageData img)))
Now, we want to manipulate data
, and the most efficient way I could come up with was by using pointers:
(time (let loop ([i (- (* width height channels) 1)]) ;; same 640 480 3
(when (>= i 0)
;; invert each pixel channel-wise
(ptr-set! data _ubyte i (- 255 (ptr-ref data _ubyte i)))
(loop (- i 1)))))
-> cpu time: 114 real time: 113 gc time: 0
This is very slow, compared to the speed of native Racket vectors.
I also tried other ways, such as _array
, _cvector
that don't even come close to the speed of using pointers, except for writing a first-class function in C that gets a function for running over the whole array. This C function is compiled to a library and bound in Racket using FFI. Then, Racket procedures can be passed to it and applied to all elements of the array. The speed was the same as with pointers, but still not sufficient to continue porting OpenCV library to Racket.
Is there a better way to do this?
I tried the approach suggested by Eli and it worked out! The idea is to use a bytestring. Since in this case the size of the array is known, (make-sized-byte-string cptr length)
can be used:
(define data (make-sized-byte-string (IplImage-imageData img)
(* width height channels)))
This results in run times close to Racket's native vectors:
(time (let loop ([i (- (* 640 480 3) 1)])
(when (>= i 0)
;; invert each pixel channel-wise
(bytes-set! data i (- 255 (bytes-ref data i)))
(loop (- i 1)))))
-> cpu time: 18 real time: 18 gc time: 0
Thank you, Eli.
It would probably be better to set the whole thing using a bytestring (via _bytes
), but that's a very rough guess. It would be much better to ask this question on the mailing list...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With