3

I am trying to migrate some code from Matlab to Opencv and need an exact replica of the gradient function. I have tried the cv::Sobel function but for some reason the values in the resulting cv::Mat are not the same as the values in the Matlab version. I need the X and Y gradient in separate matrices for further calculations.

Any workaround that could achieve this would be great

Arpan Shah
  • 243
  • 1
  • 3
  • 9
  • `cv::Sobel()` is exactly what you want. If you post the code you used (and what you expect it to output), we can look and see what your problem is. – Aurelius Jul 31 '13 at 19:52

5 Answers5

6

Sobel can only compute the second derivative of the image pixel which is not what we want.

(f(i+1,j) + f(i-1,j) - 2f(i,j)) / 2

What we want is

(f(i+i,j)-f(i-1,j)) / 2

So we need to apply

Mat kernelx = (Mat_<float>(1,3)<<-0.5, 0, 0.5);
Mat kernely = (Mat_<float>(3,1)<<-0.5, 0, 0.5);
filter2D(src, fx, -1, kernelx)
filter2D(src, fy, -1, kernely);

Matlab treats border pixels differently from inner pixels. So the code above is wrong at the border values. One can use BORDER_CONSTANT to extent the border value out with a constant number, unfortunately the constant number is -1 by OpenCV and can not be changed to 0 (which is what we want).

So as to border values, I do not have a very neat answer to it. Just try to compute the first derivative by hand...

Ela782
  • 5,041
  • 5
  • 53
  • 66
Pei Guo
  • 79
  • 1
  • 4
  • 1
    Oh, in fact, you can use copyMakeBorder() to extent the border of a Mat with a specific constant value. – Pei Guo Mar 04 '14 at 07:20
  • 1
    How did this get so many upvotes??? *“Sobel can only compute the second derivative”* **No no no!** Sobel estimates the **first** derivative, not the second. – Cris Luengo Jan 17 '19 at 01:58
0

You have to call Sobel 2 times, with arguments:

xorder = 1, yorder = 0

and

xorder = 0, yorder = 1

You have to select the appropriate kernel size.

See documentation

It might still be that the MatLab implementation was different, ideally you should retrieve which kernel was used there...

Edit:

If you need to specify your own kernel, you can use the more generic filter2D. Your destination depth will be CV_16S (16bit signed).

Antonio
  • 19,451
  • 13
  • 99
  • 197
  • I am still uncertain what borderType to use which would produce similar results. A more detailed explanation of those would really help – Arpan Shah Aug 01 '13 at 17:04
  • I am looking for a matrix exact result after matlab gradient and cv::Sobel. – Arpan Shah Aug 01 '13 at 17:48
  • @ArpanShah Is it clear that border type only affects pixel on the border OF THE IMAGE (so, first row, first column, last row, last column if you have kernel size 3, otherwise first 2 rows, first 2 columnts, etc. if you have kernel size 5, and so on). Something different from default is used only when the region for you are computing Sobel is not the whole image, but something inside it, where information beyond the region limit is still meaningful – Antonio Aug 01 '13 at 19:43
  • I did a simple test on a impulse matrix such as the following: [0 0 0; 0 1 0; 0 1 0] and I got different results when I ran gradient in Matlab and Sobel in opencv – Arpan Shah Aug 01 '13 at 19:48
  • @ArpanShah That's because Sobel has a different kernel, which could be for example [-1 -2 -1; 0 0 0; 1 2 1]. If you want to use a specific kernel, your function is filter2D, I have updated my answer – Antonio Aug 02 '13 at 09:06
  • Hey so I figured out that kernel size of 1 mirrors matlab. However the boundary values are still not matching. Do you have any idea on how to make that match? – Arpan Shah Aug 02 '13 at 17:34
0

Matlab computes the gradient differently for interior rows and border rows (the same is true for the columns of course). At the borders, it is a simple forward difference gradY(1) = row(2) - row(1). The gradient for interior rows is computed by the central difference gradY(2) = (row(3) - row(1)) / 2.

I think you cannot achieve the same result with just running a single convolution filter over the whole matrix in OpenCV. Use cv::Sobel() with ksize = 1, then treat the borders (either manually or by applying a [ 1 -1 ] filter).

Atze Kaputtnik
  • 262
  • 3
  • 10
0

Pei's answer is partly correct. Matlab uses these calculations for the borders:

G(:,1) = A(:,2) - A(:,1); G(:,N) = A(:,N) - A(:,N-1);

so used the following opencv code to complete the gradient:

static cv::Mat kernelx = (cv::Mat_<double>(1, 3) << -0.5, 0, 0.5);
static cv::Mat kernely = (cv::Mat_<double>(3, 1) << -0.5, 0, 0.5);
cv::Mat fx, fy;

cv::filter2D(Image, fx, -1, kernelx, cv::Point(-1, -1), 0, cv::BORDER_REPLICATE);
cv::filter2D(Image, fy, -1, kernely, cv::Point(-1, -1), 0, cv::BORDER_REPLICATE);

fx.col(fx.cols - 1) *= 2;
fx.col(0) *= 2;
fy.row(fy.rows - 1) *= 2;
fy.row(0) *= 2;
Jorrit Smit
  • 81
  • 1
  • 6
0

Jorrit's answer is partly correct. In some cases, the value of the directional derivative may be negative, and MATLAB will retain these negative numbers, but OpenCV Mat will set the negative number to 0.

Harry Lee
  • 1
  • 1
  • Here is the solution.Using OpenCV convertTo function,convert the CV_8U type to CV_32FC1,when we have done for processing,convert the CV_32FC1 type to CV_8U again! – Harry Lee Jan 20 '19 at 06:33