-1

I'm trying to create a convolution function but I'm having trouble during the access to the kernel data (cv::Mat).

I create the 3x3 kernel:

  cv::Mat krn(3, 3, CV_32FC1);
  krn.setTo(1);
  krn = krn/9;

And I try to loop over it. Next the image Mat will be the image to which I want to apply the convolution operator and output will be the result of convolution:

     for (int r = 0; r < image.rows - krn.rows; ++r) {

        for (int c = 0; c < image.cols - krn.cols; ++c) {

        int sum = 0;

        for (int rs = 0; rs < krn.rows; ++rs) {
          for (int cs = 0; cs < krn.cols; ++cs) {

            sum += krn.data[rs * krn.cols + cs] * image.data[(r + rs) * image.cols + c + cs];
          }
        }
        output.data[(r+1)*src.cols + c + 1]=sum;  // assuming 3x3 kernel
    }
  }

However the output is not as desired (only randomic black and white pixel). However, if I change my code this way:

for (int r = 0; r < image.rows - krn.rows; ++r) {
    
            for (int c = 0; c < image.cols - krn.cols; ++c) {
    
            int sum = 0;
    
            for (int rs = 0; rs < krn.rows; ++rs) {
              for (int cs = 0; cs < krn.cols; ++cs) {
    
                sum += 0.11 * image.data[(r + rs) * image.cols + c + cs];           // CHANGE HERE
              }
            }
            output.data[(r+1)*src.cols + c + 1]=sum;  // assuming 3x3 kernel
        }
      }

Using 0.11 instead of the kernel values seems to give the correct output. For this reason I think I'm doing something wrong accessing the kernel's data.

P.S: I cannot use krn.at<float>(rs,cs).

Thanks!

l000000l
  • 330
  • 1
  • 3
  • 14

2 Answers2

1

cv::Mat::data is pointer of type uchar.

By data[y * cols + x] you access some byte of stored float values in krn. To get full float values use at method template:

krn.at<float>(rs,cs)

Consider changing type of sum variable to be real. Without this, you may lose partial results when calculating convolution .


So, if you cannot use at, just read 4 bytes from data pointer:

float v = 0.0;
memcpy(&v, krn.data + (rs * krn.step + cs * sizeof(float)), 4);

step - means total bytes occupied by one line in mat.

rafix07
  • 20,001
  • 3
  • 20
  • 33
  • Thank you so much for your answer. However as an exercise, I cannot use `krn.at`. Would you suggest another method? – l000000l Nov 03 '21 at 10:57
  • Thanks again for your help. I'm comparing my results using 5x5 kernel and the filter2D() function. However, my results seems to be wrong. Looking at the code, do you have any idea of what I'm missing? Thanks again! – l000000l Nov 03 '21 at 17:45
1

Instead of needlessly using memcpy, you can just cast the pointer. I'll use a C-style cast because why not.

cv::Mat krn = 1 / (cv::Mat_<float>(3,3) <<
    1, 2, 3,
    4, 5, 6,
    7, 8, 9);

for (int i = 0; i < krn.rows; i += 1)
{
    for (int j = 0; j < krn.cols; j += 1)
    {
        // to see clearly what's happening
        uint8_t *byteptr = krn.data + krn.step[0] * i + krn.step[1] * j;
        float *floatptr = (float*) byteptr;

        // or in one step:
        float *floatptr = (float*) (krn.data + krn.step[0] * i + krn.step[1] * j);

        cout << "krn.at<float>(" << i << "," << j << ") = " << (*floatptr) << endl;
endl;
    }
}
krn.at<float>(0,0) = 1
krn.at<float>(0,1) = 0.5
krn.at<float>(0,2) = 0.333333
krn.at<float>(1,0) = 0.25
krn.at<float>(1,1) = 0.2
krn.at<float>(1,2) = 0.166667
krn.at<float>(2,0) = 0.142857
krn.at<float>(2,1) = 0.125
krn.at<float>(2,2) = 0.111111

Note that pointer arithmetic may not be obvious. if you have a uint8_t*, adding 1 moves it by one uint8_t, and if you have a float*, adding 1 moves it by one float which is four bytes. The step[] contains offsets expressed in bytes.

Consult the documentation for details, which include information on the step[] array that contains the strides/steps to calculate the offset given a tuple of indices into the matrix.

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
  • Thank you. I prefer this method to the memcpy one. – l000000l Nov 04 '21 at 09:53
  • Could I ask you one more thing? I'm comparing my results using 5x5 kernel and the filter2D() function. However, my results seems to be wrong. Looking at the code, do you have any idea of what I'm missing? Thanks – l000000l Nov 04 '21 at 09:53
  • 1
    yes, in your code you are accessing the *float* data from am *uint8_t* (uchar) pointer (`image.data` and `krn.data`)... that will give you garbage. apply what I've shown and the issue will resolve. – Christoph Rackwitz Nov 04 '21 at 14:16
  • you ought to find learning materials for C++. this is basic C++, even basic C. `*floatptr = something` – Christoph Rackwitz Nov 12 '21 at 09:16