Search code examples
pythonperformancecomputer-visionhistogramopticalflow

Most efficient way to generate Histograms of Oriented Optical Flow (HOOF) in python


I have a 498-frames-long image sequence for which I calculated optical flow using cv2.calcOpticalFlowFarneback. Therefore now I have 497 vector maps representing my motion vectors, and these vector are described by magnitude and direction.

What I need to do is to generate a histogram where on the x-axis I have angle ranges in degrees. More specifically, I have 12 bins where the first bin contains all the vectors with direction 0 < angle < 30, the second one 30 < angle < 60 and so on. On the y-axis, instead, I need to have the sum of the modulus of those vectors contained in each bin.

The problem here is that doing all of this using simple for loops and if statements takes ages:

#magnitude and angle are two np.array of shape (497, 506, 1378)

bins = [1,2,3,4,5,6,7,8,9,10,11,12]
sum = np.zeros_like(bins)

for idx in range(np.array(magnitude).shape[0]): # for each flow map, i.e. for each image pair
    for mag, ang in zip(magnitude[idx].reshape(-1), angle[idx].reshape(-1)): 
        if ang >= 0 and ang <= 30:
            sum[0] += mag
        elif ang > 30 and ang <= 60:
            sum[1] += mag
        elif ang > 60 and ang <= 90:
            sum[2] += mag
        elif ang > 90 and ang <= 120:
            sum[3] += mag
        elif ang > 120 and ang <= 150:
            sum[4] += mag
        elif ang > 150 and ang <= 180:
            sum[5] += mag
        elif ang > 180 and ang <= 210:
            sum[6] += mag
        elif ang > 210 and ang <= 240:
            sum[7] += mag
        elif ang > 240 and ang <= 270:
            sum[8] += mag
        elif ang > 270 and ang <= 300:
            sum[9] += mag
        elif ang > 300 and ang <= 330:
            sum[10] += mag
        elif ang > 330 and ang <= 360:
            sum[11] += mag

Which took ~3 hrs to compute. Can somebody suggest a better, more efficient way to perform this calculations?

Thanks in advance.


Edit

Got rid of the conditionals and used Numba to speed it up even further. The following code takes less than 10s to compute:

import numpy as np
from numba import jit

@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def hoof(magnitude, angle):
    sum = np.zeros(13)

    for idx in range(magnitude.shape[0]): # for each flow map, i.e. for each image pair
        for mag, ang in zip(magnitude[idx].reshape(-1), angle[idx].reshape(-1)): 
            sum[int((ang)//30)] += mag
    
    sum[11] += sum[12]

    return sum[0:12]

Solution

  • Conditionals are slow. You should avoid them as much as possible. Also Numpy vectorization and Numba JIT help to speed up such a code by a large margin. Here is an untested example:

    import numba as nb
    
    @nb.jit
    def compute(magnitude, angle):
        s = np.zeros(12)
        for idx in range(magnitude.shape[0]):
            for mag, ang in zip(magnitude[idx].reshape(-1), angle[idx].reshape(-1)):
                if ang == 0:
                    s[0] += mag
                elif ang > 0 and ang <= 360: # The condition can be removed if always true
                    s[(ang-1)//30] += mag
        return s
    
    # Assume both are Numpy array and angle is of type int.
    # Note that the first call will be slower unless you precise the types.
    compute(magnitude, angle)