Qt QImage pixel access optimization

915 views
Skip to first unread message

Rafael Vasco

unread,
Dec 16, 2013, 7:59:55 PM12/16/13
to cython...@googlegroups.com
Hi, i'm just getting started with Cython. My situation is that i'm trying to get more speed of a scanline floodfill function that i made , it starts like this:

floodfill.pyx :

def floodFill(imageData, int x, int y, int w, int h, long long fillColor):

cdef int width4 = w * 4

cdef int length = len(imageData)

cdef int colorIndex = (x + y * w)*4

                # gets pixel color given a index
cdef long long hitColor = struct.unpack('I', imageData[colorIndex:colorIndex+4])[0]

               ( .... )

               # modifies pixel color on index "left"
               imageData[left:left+4] = struct.pack('I', fillColor)
               
               ( .... )


Remarks: 'imageData' params comes from image.bits() (QImage.bits() from PyQt) which returns a sip.voidptr.

(struct.unpack comes from Python module 'struct')

In the worst case i already get ~2x more performance with this Cython code compiled to a PYD to flood fill an empty 800x600 image (example) . Python version takes more than 2 seconds to fill the image, Cython version takes ~1.20 seconds. I think it can be optimized much more. My code is already very optimized in Python so the problem appears to be the struct.unpack and pack calls. Is there any way to manipulate those pixels directly in Cython without using struct.unpack ? Has anyone had a similar problem ?

Thanks!

Robert Bradshaw

unread,
Dec 17, 2013, 2:38:33 AM12/17/13
to cython...@googlegroups.com
Does Qimage.bits() return a numpy array? If so, you could do much
better with buffer support
(seehttp://docs.cython.org/src/tutorial/numpy.html or
http://docs.cython.org/src/userguide/memoryviews.html). You might have
to deal with endianness, but I perhaps you could get away with casting
it to a uint32*. I wouldn't be surprised with a 100x or speedup if you
do it right.
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "cython-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to cython-users...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.

Sturla Molden

unread,
Dec 17, 2013, 5:05:52 AM12/17/13
to cython...@googlegroups.com


Den 17. des. 2013 kl. 08:38 skrev Robert Bradshaw <robe...@gmail.com>:

Does Qimage.bits() return a numpy array? If so, you could do much
better with buffer support

QImage.bits() returns a buffer. SIP will always use the old buffer protocol when Qt expects char* or uchar*.

Something like this should work:

import numpy as np 
from PySide.QtGui import QImage 
w,h = 1024,762
image = QImage(w, h, QImage.Format_RGB32) # format: 0xffRRGGBB
record = [('b','u1'), ('g','u1'), ('r','u1'), ('unused','u1')] # reverse if big-endian
array = np.ndarray(shape=(h,w), dtype=record, buffer=image.bits())

Then the next step in Cython would be to view the array using a typed memoryview.

Sturla

Sturla Molden

unread,
Dec 17, 2013, 6:32:07 AM12/17/13
to cython...@googlegroups.com

> Den 17. des. 2013 kl. 01:59 skrev Rafael Vasco <rafael...@gmail.com>:
>
> Hi, i'm just getting started with Cython. My situation is that i'm trying to get more speed of a scanline floodfill function that i made ,

By the way:

If I were your boss, I'd fire you right there. Well, almost.

Do not reinwent the wheel!

For image processing in Python, learn to use NumPy and tools like:

scikit-image
scipy.ndimage
OpenCV
VTK and mayavi2 mlab scripting
PyOpenGL
matplotlib
PIL
Intel IPP (e.g. with Cython)

In this case, you probably want cv2.floodFill from OpenCV or ImageDraw.floodfill from PIL.

(There is an object called ImageQt for interfacing PIL with PyQt/PySide QImage. OpenCV can be interfaced via NumPy.)

Trying to outsmart toolkits like these is brain dead. Intel engineers have e.g. spent years on optimising and quality checking the OpenCV library. You cannot do it better yourself, or if you could, it would still be a complete waste of time.

Check the existing toolkits first. If they don't fit, then write your own code.

Regards,
Sturla



Rafael Vasco

unread,
Dec 17, 2013, 12:37:37 PM12/17/13
to cython...@googlegroups.com
Hey thanks for the help ! And sorry about reinventing the wheel it's just that i stopped to read about flood fill algorithms , found one pseudo-code that i liked and couldn't resist implementing it :D In my day job i would never take that course of action but in my personal projects i've always reinvented tons of wheels i guess.. if i didn't i would know nothing by now :( There's the right time for everything i think. Again thanks for the help ! I'm going to look at the libs you mentioned. 
Btw: One thing motivated me to do it myself  : I couldn't find up to date concrete examples of what i wanted in any of those libs with Qt. 

Rafael Vasco

unread,
Dec 17, 2013, 8:11:32 PM12/17/13
to cython...@googlegroups.com
Got it working. Took some eight hours damn. Got from ~1.2 seconds to ~0.3 seconds with my described test (Going to try to lower that). Many thanks to Mr. Sturla.


Em segunda-feira, 16 de dezembro de 2013 21h59min55s UTC-3, Rafael Vasco escreveu:

Robert Bradshaw

unread,
Dec 17, 2013, 8:18:10 PM12/17/13
to cython...@googlegroups.com
Seems like you could get way more speedup than that. Have you tried cython -a?

Rafael Vasco

unread,
Dec 18, 2013, 1:17:55 PM12/18/13
to cython...@googlegroups.com
Declaring my array as cdef np.uint8_t[::1] byteArray = np.frombuffer(imageData, np.uint8).reshape(length)

where length = w * h * 4 got me to 0.08 seconds for my test case. Looking good. Guess it's good enough now .. Uf i'm tired. 

Em segunda-feira, 16 de dezembro de 2013 21h59min55s UTC-3, Rafael Vasco escreveu:
Reply all
Reply to author
Forward
0 new messages