Search code examples
opencvelementwise-operations

Element-wise power using OpenCV


I am currently reading this book. The author wrote a code snippet on page 83 in order to (if i understand it correctly) calculate the element-wise power of two matrices. But i think the code doesn't fulfill its purpose because the matrix dst does not contain the element-wise power after the execution.

Here is the original code:

const Mat* arrays[] = { src1, src2, dst, 0 };
float* ptrs[3];

NAryMatIterator it(arrays, (uchar**)ptrs);
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
    for( size_t j = 0; j < it.size; j++ )
    {
        ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
    }
}

Since the parameter of the constructor or cv::NAryMatIterator is const cv::Mat **, i think change of values in the matrix dst is not allowed.

I tried to assign ptrs[2][j] back in dst but failed to get the correct indices of dst. My questions are as follows:

  1. Is there a convenient method for the matrix element-wise power, like A .^ B in Matlab?
  2. Is there a way to use cv::NAryMatIterator to achieve this goal? If no, then what is the most efficient way to implement it?

Solution

  • You can get this working by converting the src1, src2 and dst to float (CV_32F) type matrices. This is because the code treats them that way in float* ptrs[3];.

    An alternative implementation using opencv functions log, multiply and exp is given at the end.

    As an example for your 2nd question,

    Mat src1 = (Mat_<int>(3, 3) <<
        1, 2, 3,
        4, 5, 6,
        7, 8, 9);
    Mat src2 = (Mat_<uchar>(3, 3) <<
        1, 2, 3,
        1, 2, 3,
        1, 2, 3);
    Mat dst = (Mat_<float>(3, 3) <<
        1, 2, 3,
        4, 5, 6,
        7, 8, 9);
    
    src1.convertTo(src1, CV_32F);
    src2.convertTo(src2, CV_32F);
    
    cout << "before\n";
    cout << dst << endl;
    
    const Mat* arrays[] = { &src1, &src2, &dst, 0 };
    float* ptrs[3];
    NAryMatIterator it(arrays, (uchar**)ptrs);
    for( size_t i = 0; i < it.nplanes; i++, ++it )
    {
        for( size_t j = 0; j < it.size; j++ )
        {
            ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
        }
    }
    
    cout << "after\n";
    cout << dst << endl;
    

    outputs

    before
    [1, 2, 3;
      4, 5, 6;
      7, 8, 9]
    after
    [1, 4, 27;
      4, 25, 216;
      7, 64, 729]
    

    If you remove the src1.convertTo(src1, CV_32F); or src2.convertTo(src2, CV_32F);, you won't get the desired result. Try it.

    If this is a separate function, don't place the convertTo within the function, as it modifies the image representation, that could affect later operations. Instead, use convertTo on temporary Mats, like

    Mat src132f, src232f, dst32f;
    src1.convertTo(src132f, CV_32F);
    src2.convertTo(src132f, CV_32F);
    dst.convertTo(dst32f, CV_32F);
    
    pow_mat(&src132f, &src232f, &dst32f); /* or whatever the name */
    

    As for your first question, I'm not aware of such function. But you can try something like

    Mat tmp;
    cv::log(src1, tmp);
    cv::multiply(src2, tmp, dst);
    cv::exp(dst, dst);
    

    using the relation that c = a^b is equivalent to c = e^(b.ln(a)). Here, the matrices should have type 32F or 64F. This produces

    [1, 4, 27.000002;
      4, 25.000002, 216.00002;
      6.9999995, 64, 729.00006]
    

    for the example above.