Home > Software design >  Speed problems when extracting data from arrays with for loops
Speed problems when extracting data from arrays with for loops

Time:12-05

I am using simple code to extract the raw rgb data from an opencv image array (x,y,3):

import numpy as np
import opencv2 as cv
image = cv.imread(image)
rgb = True
l = image.shape
lx, ly = l[0], l[1]
frame = []
for py in range (0, ly):
    for px in range (0, lx):
        p = _color16(image[py,px])
        frame.extend((int(p[0]), int(p[1])))

print (len(frame))

def _color16(acolor):
    aR, aG, aB = acolor
    if rgb is True:
        rgb_word = ((aR >> 3) << 11)   ((aG >> 2) << 5)   (aB >> 3)
        rgb_byte1 = rgb_word >> 8
        rgb_byte2 = rgb_word & 255
        return rgb_byte1, rgb_byte2
    else:
        bgr_word = ((aB >> 3) << 11)   ((aG >> 2) << 5)   (aR >> 3)
        bgr_byte1 = bgr_word >> 8
        bgr_byte2 = bgr_word & 255
        return bgr_byte1, bgr_byte2

This runs dead slow, it seems. To extract 115.2 Kbytes of data (240x240x3 8-bit values), it takes around 4 seconds on a raspberry Pi 4B running at 1.3 GHz. The _color16 function returns processed rgb data for 16 bit rgb/bgr transmission over SPI. I don't think this part is the source, but I am not sure so I have added the code for completeness.

Can anyone give any clues to why this code runs so slow?

I've been trying to convert the frame buffer data from a (x,y,3) ndarray to a list with (r,g,b) tuples with numpy but so far have failed to identify the correct function for that.

Converting with numpy.ndarray.flatten works perfectly for 18/24 bit rgb data as it is 1:1, and gives me frame rates of 40 with the SPI controller running at 62.5 MHz.

CodePudding user response:

You're using python-level loops, calling a function, creating a tuple of the result and then calling list.extend for each iteration. The python-level loops by themselves are way too slow. Instead, take advantage of numpy's speed to do your operations elementwise.

if not self._rgb:
    image = image[...,::-1]
# Assuming your image has dtype `np.uint8`, you need to upcast it
# so the bitwise operations will not go out of range.
image = image.astype(np.uint16)
rgb_word = ((image[...,0] >> 3) << 11)   ((image[...,1] >> 2) << 5)   (image[...,2] >> 3)
rgb_word = rgb_word.flatten()
# Use `frame.tolist()` if you really want a list
frame = np.column_stack([rgb_word >> 8, rgb_word & 255]).flatten()

This runs about 600x faster on my machine for a (240, 240, 3) array.


By the way, you have an error in your looping but it's invisible because both the width and height of the image are the same.

l = image.shape
lx, ly = l[0], l[1]
frame = []
for py in range (0, ly):       # this should be `range(lx)`
    for px in range (0, lx):   # and this should be `range(ly)`

CodePudding user response:

So I went to work and the following is now my converter function:

def imageConvert(self, image):
    if not self._rgb:
        image = image [...,::-1]
    if self._bpp == 18:
        return image.flatten()
    elif self._bpp == 16:
        image = image.astype(np.uint16)
        awords = ((image[...,0] >> 3) << 11) | ((image[...,1] >> 2) << 5) | (image[...,2] >> 3)
        words = awords.flatten()
        frame = np.column_stack([words >> 8, words & 255]).flatten().astype(np.uint8)
        return frame
    else:
        image = image.astype(np.uint16)
        words = (((image[...,0] >> 4) << 8) | ((image[...,1] >> 4) << 4) | (image[...,2] >> 4)).flatten()
        dwords = np.column_stack([words[1::2] | words[::2] & 255]).flatten()
        byte1 = dwords >> 16
        byte2 = (dwords >> 8) & 255
        byte3 = dwords & 255
        frame = np.column_stack([byte1,byte2,byte3]).flatten().astype(np.uint8)
        return frame

This works like a charm. Thanks for your help, again.

  • Related