从Sobel算子中确定图像梯度方向?

6
我正在尝试使用openCV的Sobel方法来确定图像梯度方向。我知道这应该是一个非常简单的任务。我从许多资源和答案中复制了这些方法,但无论我做什么,结果方向始终在0-57度之间(我期望范围为0-360度)。我相信所有深度都是正确的。我已经尝试使用16S数据和8U数据来计算方向。我就是看不出我的错误在哪里?有人能发现我的错误吗?
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.";
            }               
    }
}
3个回答

7
你正在使用整数算术,因此你的弧度和角度计算受到截断的严重影响。
此外,atan2的结果在-PI+PI范围内,因此如果您想要一个0..360范围内的角度值,您需要添加180度的修正。
        double directionRAD = atan2(pixelY[i], pixelX[i]);
        int directionDEG = (int)(180.0 + directionRAD / M_PI * 180.0);

请注意,在directionRAD中使用double而不是int
专业提示:学会使用调试器逐步执行您的代码,检查变量 - 这将使修复诸如此类简单错误比等待在StackOverflow上得到答案更容易。

谢谢建议,保罗。我按照你的建议纠正了截断问题,但是并没有什么太大的改变。我仍然只得到了有限数量的梯度方向0-90度(或者应用了180度校正后的180-270度)。我不再缩放值,而是使用Sobel运算给出的原始16S值。我已经逐步检查了整个图像处理过程,但是找不到哪里出了问题。你有什么想法吗?谢谢。 - CVirtuous
你也删除了 abs 操作吗?如果是这样,我建议你将最新的代码发布为一个新问题。 - Paul R
是的,我已经做了。我现在会发布一个新问题。谢谢。 - CVirtuous

5
您可以使用Sobel算子获取x-导数dx和y-导数dy。然后,您可以使用公式计算梯度的幅度和方向。G = sqrt(dx ^ 2 + dy ^ 2),theta = arctan(dy / dx)。您会发现这就是将笛卡尔坐标系(x,y)转换为极坐标(rho,theta)!在您的代码中有些问题,您对dxdy取绝对值,这使得方向始终在笛卡尔坐标系的第一象限中。而您使用的convertScaleAbs函数将结果转换为8位,这会导致截断误差。我有一个演示来根据您的代码部分计算幅度。
    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


2

您需要对梯度取绝对值,这将所有角度从[-180; 180]映射到[0;90]。此外,您需要使用整数除法。


感谢老UFO。根据Paul的建议,我已经纠正了整数除法问题,并且我只直接使用Sobel操作的16S数据。然而,我仍然从0-90获得了受限制的方向范围。您能看到我的代码还有什么问题吗? - CVirtuous
请更新您的帖子中的代码,这样我们就可以在“当前”版本中找到错误。 - old-ufo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接