1
\$\begingroup\$

I am trying to implement a motion detection algorithm using state of the art on an esp32camera. This board has 512kB RAM and I don't want to use too much CPU. So I wanted to get a review on what I implemented if there is piece of code which could be optimized. I tried to comment as much as possible and put link on existing code I took and reshaped.

Context:

I try to implement Lucas-Kanade optical flow.

My code is composed of :

  • conv : full convolution 1D.

  • transpose : rescale the input vector to 0..255 and transpose the equivalent array into a buffer.

  • LK_optical_flow : The main code that perform a 2D convolution with Sobel filters and input images. Then compute optical flow magnitude.

Code:

/** Rescale vector to 0..255 and transpose
 * @param[in] src vector from convolution unscaled 
 * @param[out] dst pointer of buffer image*/
template<typename T>
void transpose(std::vector<T> src, uint8_t *dst, const int w, const int h) {    
    auto max = *std::max_element(src.begin(), src.end());
    auto min = *std::min_element(src.begin(), src.end());
    for(int n = 0; n< w * h; n++) {
        const int i = n / h;
        const int j = n % h;
        dst[n] = (uint8_t)(src[w * j + i] - min) * 255.0 / max;
    }
}

/** convolution 1D between flattened image and strel
 * from : https://stackoverflow.com/questions/24518989/how-to-perform-1-dimensional-valid-convolution
 * @param f pointer of flattened image buffer
 * @param g structurant element (strel)
 * @return convolved image as vector*/
template<typename T>
std::vector<T> conv(uint8_t *f, const std::vector<T> &g, const int nf) {
    int const ng = g.size();
    int const n  = nf + ng - 1;
    std::vector<T> out(n, T()); 
    for(auto i(0); i < n; ++i) {
        int const jmn = (i >= ng - 1)? i - (ng - 1) : 0;
        int const jmx = (i <  nf - 1)? i            : nf - 1;
        for(auto j(jmn); j <= jmx; ++j) 
            out[i] += (f[j] * g[i - j]);
    }
    out.erase(out.begin(), out.begin() + ng / 2 + 1);  // remove edge due to full convolution
    return out;
}

/// Optical flow Lucas-Kanade
/** Implement LK optical flow source from wiki:
 * https://en.wikipedia.org/wiki/Lucas%E2%80%93Kanade_method
 * @param src1 pointer to grayscale buffer image instant t
 * @param src2 pointer to grayscale buffer image diff Image between t and t+1
 * @param output Magnitude output image in RGB */
void LK_optical_flow(uint8_t *src1, uint8_t *src2, uint8_t *output, int w, int h)
{
    //Allocate 1D strel
    std::vector<int> Kernel_Dy = {1, 2, 1};
    std::vector<int> Kernel_Dx = {-1, 0, 1};
    std::vector<int> Kernel_Dt = {1, 1, 1};

    //Allocate fy only. Too much memory on the heap.
    std::vector<int> tmp; 
    uint8_t *fx = src1;
    uint8_t *fy = new uint8_t[w * h];
    uint8_t *ft = src2;

    memset(output, 0, w * h * sizeof(uint8_t));
    memcpy(fy, fx, w * h * sizeof(uint8_t));

    // Compute equivalent of 2D convolution decompose of two 1D convolution.
    // Sobel Dx
    tmp = conv(fx, Kernel_Dx, w*h);
    transpose(tmp, fx, w, h);
    tmp = conv(fx, Kernel_Dy, w*h);
    transpose(tmp, fx, w, h);   
    // Sobel Dy
    tmp = conv(fy, Kernel_Dy, w*h);
    transpose(tmp, fy, w, h);
    tmp = conv(fy, Kernel_Dx, w*h);  
    transpose(tmp, fy, w, h);   
    // Dt
    tmp = conv(ft, Kernel_Dt, w*h);
    transpose(tmp, ft, w, h);
    tmp = conv(ft, Kernel_Dt, w*h);  
    transpose(tmp, ft, w, h);   

    std::vector<int>().swap(tmp); // deallocate tmp 

    //TODO: Create a function for all above : Mag = opticalflow(fx, fy, ft, window=3)
    const int window = 3; //half window size
    float AtA[2][2];
    float Atb[2];
    std::vector<unsigned> Mag(w*h);

    // Lucas Kanade optical flow algorithm
    for(int i=window; i<=w-window;++i){
        for(int j=window; j<h-window;++j){
            memset(Atb, 0, sizeof(float) * 2);
            memset(AtA, 0, sizeof(float) * 4);
            for(int m=-window; m<window;++m){
                const unsigned index = (j + m) * w + (i + m);
                const float Ix = (float) fx[index];
                const float Iy = (float) fy[index];
                const float It = (float) ft[index];
                AtA[0][0] += Ix * Ix;
                AtA[1][1] += Iy * Iy;
                AtA[0][1] += Ix * Iy;
                AtA[1][0] = AtA[0][1];
                Atb[0] += - Ix * It;
                Atb[1] += - Iy * It;
            }
            //Compute inverse of 2x2 array AtA: 1/(ad-bc)[[d -b][-c a]]
            const float det = AtA[0][0] * AtA[1][1] - AtA[0][1] * AtA[1][0];
            const float iAtA[2][2] = {
                {AtA[1][1] / det, - AtA[0][1] / det},
                {iAtA[0][1]     , AtA[0][0] / det}
                };
            //Compute optical flow : [Vx Vy] = inv[AtA] . Atb
            const float Vx = iAtA[0][0] * Atb[0] + iAtA[0][1] * Atb[1];
            const float Vy = iAtA[1][0] * Atb[0] + iAtA[1][1] * Atb[1]; 

            Mag[i + j * w] = hypotf(Vx, Vy); //sqrt(Vx²+Vy²)
        }
    }
    delete [] fy;   
    int max = *std::max_element(Mag.begin(), Mag.end());
    if(max == 0) 
        return;
    ESP_LOGI(TAG, "maxMag = %i \n", max);

    //compute output which is Mag rescaled nothing interesting here.
}

My next step would be to complete the TODO comments.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.