CVirtuous
CVirtuous

Reputation: 213

Determining image gradient direction from sobel?

I am attempting to determine the image gradient direction using the results from openCV's Sobel method.

I understand this should be a very simple task. I have copied the methods from a number of resources and answers from here but whatever I do the resultant directions are always between 0 - 57 degrees (I would expect the range to be from 0-360).

I believe all the depths are correct. I have tried calculating the direction using the 16S data as well as 8U data.

I just can't see where I'm going wrong? Can anyone spot my mistake?

void getGradients(IplImage* original, cv::Mat* gradArray)
{
    cv::Mat original_Mat(original, true);

    // Convert it to gray
    cv::cvtColor( original_Mat, original_Mat, CV_RGB2GRAY );
    //cv::blur(original_Mat, original_Mat, cv::Size(7,7));

    /// Generate grad_x and grad_y
    cv::Mat grad_x = cv::Mat::zeros(original->height, original->width, CV_16S); 
    cv::Mat grad_y = cv::Mat::zeros(original->height, original->width, CV_16S);

    cv::Mat abs_grad_x = cv::Mat::zeros(original->height, original->width, CV_8U);
    cv::Mat abs_grad_y = cv::Mat::zeros(original->height, original->width, CV_8U);;

    /// Gradient X
    cv::Sobel(original_Mat, grad_x, CV_16S, 1, 0, 3);
    cv::convertScaleAbs( grad_x, abs_grad_x );

    /// Gradient Y
    cv::Sobel(original_Mat, grad_y, CV_16S, 0, 1, 3);
    cv::convertScaleAbs( grad_y, abs_grad_y );

    uchar* pixelX = abs_grad_x.data;
    uchar* pixelY = abs_grad_y.data;
    uchar* grad1 = gradArray[0].data;
    uchar* grad2 = gradArray[1].data;
    uchar* grad3 = gradArray[2].data;
    uchar* grad4 = gradArray[3].data;
    uchar* grad5 = gradArray[4].data;
    uchar* grad6 = gradArray[5].data;
    uchar* grad7 = gradArray[6].data;
    uchar* grad8 = gradArray[7].data;
    int count = 0;
    int min = 999999;
    int max = 0;

    for(int i = 0; i < grad_x.rows * grad_x.cols; i++) 
    {
            int directionRAD = atan2(pixelY[i], pixelX[i]);
            int directionDEG = directionRAD / PI * 180;

            if(directionDEG < min){min = directionDEG;}
            if(directionDEG > max){max = directionDEG;}

            if(directionDEG >= 0 && directionDEG <= 45)         { grad1[i] = 255; count++;}         
            if(directionDEG >= 45 && directionDEG <= 90)        { grad2[i] = 255; count++;}         
            if(directionDEG >= 90 && directionDEG <= 135)       { grad3[i] = 255; count++;}         
            if(directionDEG >= 135 && directionDEG <= 190)      { grad4[i] = 255; count++;}         
            if(directionDEG >= 190 && directionDEG <= 225)      { grad5[i] = 255; count++;}         
            if(directionDEG >= 225 && directionDEG <= 270)      { grad6[i] = 255; count++;}     
            if(directionDEG >= 270 && directionDEG <= 315)      { grad7[i] = 255; count++;}
            if(directionDEG >= 315 && directionDEG <= 360)      { grad8[i] = 255; count++;}

            if(directionDEG < 0 || directionDEG > 360)
            {
                cout<<"Weird gradient direction given in method: getGradients.";
            }               
    }
}

Upvotes: 6

Views: 12221

Answers (3)

xmfbit
xmfbit

Reputation: 161

You can get the x-derivative dx and y-derivative dy using Sobel operator. Then you can use the formula to calculate the magnitude and direction of the gradient. G=sqrt(dx^2+dy^2), theta=arctan(dy/dx). You can find this is just convert descartes coordinate system(x,y) to polar coordinates(rho, theta)!

There is something wrong in your code that you make absolute value of dx and dy, which makes the direction always in the first quadrant of the Cartesian coordinate system. And the function you used convertScaleAbs converts the result to 8-bit, which results in the truncation error.

I have a demo to calculate the magnitude partly based on your code.

    const string imgname = "F:/OpenCV/square.jpg";
    Mat img = imread(imgname, CV_LOAD_IMAGE_COLOR);

    // 1. convert it to gray value
    Mat gray;
    cvtColor(img, gray, CV_BGR2GRAY);
    // 2. blur the image
    blur(gray, gray, Size(7, 7));
    // 3. sobel
    Mat grad_x, grad_y;
    Scharr(gray, grad_x, CV_32FC1, 1, 0);
    Scharr(gray, grad_y, CV_32FC1, 0, 1);
    // 4. calculate gradient magnitude and direction
    Mat magnitude, direction;
    bool useDegree = true;    // use degree or rad
    // the range of the direction is [0,2pi) or [0, 360)
    cartToPolar(grad_x, grad_y, magnitude, direction, useDegree);

    // test, the histogram of the directions
    vector<int> cnt(8, 0);   // 0-45, 45-90, ..., 315-360

    for(auto iter = direction.begin<float>(); iter != direction.end<float>(); ++iter)
    {
        int idx = static_cast<int>(*iter) / 45;
        ++cnt[idx];
    }

    Mat scaled;
    convertScaleAbs(magnitude, scaled);
    imshow("magnitude", scaled);
    for(auto v : cnt)
        cout << v << " ";

A test picture magnitude visulization result

Upvotes: 5

old-ufo
old-ufo

Reputation: 2850

You take and absolute value of the gradients, which maps all angles from [-180; 180] to [0;90]. Also you use integer division.

Upvotes: 2

Paul R
Paul R

Reputation: 212979

You're using integer arithmetic so your calculations for radians and degrees are suffering badly from truncation.

Also atan2 gives a result in the range -PI to +PI, so if you want a value in degrees in the range 0..360 you'll need to add a 180 degree correction:

        double directionRAD = atan2(pixelY[i], pixelX[i]);
        int directionDEG = (int)(180.0 + directionRAD / M_PI * 180.0);

Note the use of double rather than int for directionRAD.

Pro tip: learn to use a debugger to step through you code, inspecting variables as you go - that will make fixing simple bugs like this a lot easier than waiting for responses on StackOverflow.

Upvotes: 7

Related Questions