使用OpenCV计算Canny边缘检测中的非定向边缘数量。

3

能否有人帮我解决如何用OpenCV的Canny边缘检测算法计算无方向边缘数量的问题? 我已经通过OpenCV获得了Canny边缘图像,我想基于边缘方向制作直方图以此来统计有向和无向边缘的数量。

1个回答

39
我认为你混淆了边缘检测和梯度检测。Canny基于梯度幅值(通常使用Sobel算子,但也可以使用其他算子)提供边缘图,因为Canny仅返回经过阈值处理的梯度幅值信息,它无法提供方向信息。
编辑:我应该澄清一下,Canny算法确实在非最大抑制步骤中使用梯度方向。然而,OpenCV实现的Canny会隐藏这个方向信息,并且只返回一个边缘幅值图。
获取梯度幅值和方向的基本算法如下:
1. 计算X方向的Sobel(Sx)。 2. 计算Y方向的Sobel(Sy)。 3. 计算梯度幅值sqrt(Sx*Sx + Sy*Sy)。 4. 使用arctan(Sy / Sx)计算梯度方向。
这个算法可以使用以下OpenCV函数实现:Sobelmagnitudephase
下面是一个示例,计算梯度大小和相位,并显示梯度方向的粗略颜色映射:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

Mat mat2gray(const cv::Mat& src)
{
    Mat dst;
    normalize(src, dst, 0.0, 255.0, cv::NORM_MINMAX, CV_8U);

    return dst;
}

Mat orientationMap(const cv::Mat& mag, const cv::Mat& ori, double thresh = 1.0)
{
    Mat oriMap = Mat::zeros(ori.size(), CV_8UC3);
    Vec3b red(0, 0, 255);
    Vec3b cyan(255, 255, 0);
    Vec3b green(0, 255, 0);
    Vec3b yellow(0, 255, 255);
    for(int i = 0; i < mag.rows*mag.cols; i++)
    {
        float* magPixel = reinterpret_cast<float*>(mag.data + i*sizeof(float));
        if(*magPixel > thresh)
        {
            float* oriPixel = reinterpret_cast<float*>(ori.data + i*sizeof(float));
            Vec3b* mapPixel = reinterpret_cast<Vec3b*>(oriMap.data + i*3*sizeof(char));
            if(*oriPixel < 90.0)
                *mapPixel = red;
            else if(*oriPixel >= 90.0 && *oriPixel < 180.0)
                *mapPixel = cyan;
            else if(*oriPixel >= 180.0 && *oriPixel < 270.0)
                *mapPixel = green;
            else if(*oriPixel >= 270.0 && *oriPixel < 360.0)
                *mapPixel = yellow;
        }
    }

    return oriMap;
}

int main(int argc, char* argv[])
{
    Mat image = Mat::zeros(Size(320, 240), CV_8UC1);
    circle(image, Point(160, 120), 80, Scalar(255, 255, 255), -1, CV_AA);

    imshow("original", image);

    Mat Sx;
    Sobel(image, Sx, CV_32F, 1, 0, 3);

    Mat Sy;
    Sobel(image, Sy, CV_32F, 0, 1, 3);

    Mat mag, ori;
    magnitude(Sx, Sy, mag);
    phase(Sx, Sy, ori, true);

    Mat oriMap = orientationMap(mag, ori, 1.0);

    imshow("magnitude", mat2gray(mag));
    imshow("orientation", mat2gray(ori));
    imshow("orientation map", oriMap);
    waitKey();

    return 0;
}

使用圆形图片:
enter image description here 这将产生以下大小和方向图像:
magnitude orientation 最后,这是梯度方向图:
map 更新:Abid在评论中实际上提出了一个很好的问题“这里的方向是什么意思?”,我认为需要进一步讨论。我假设`phase`函数不会从正常的图像处理角度切换坐标系,其中正y轴向下,正x轴向右。在这种假设下,导致以下图像显示圆周周围的梯度方向向量:

enter image description here

这可能会让人感到困难,因为坐标轴与我们在数学课上通常使用的相反... 因此,梯度方向是法向量与梯度面在增加变化方向上形成的角度。
希望您觉得这很有帮助!

1
@AbidRahmanK 嘿,阿比德!在这种情况下,您可以将梯度方向视为垂直于梯度表面的矢量的角度。从方向图像中可以看出,这是因为随着您沿着圆圈移动,它从0度增长到360度。 - mevatron
+1 很好。如果我有一组带有垂直、水平和对角线的图像怎么办? - Mzk
1
@MizukiKai 同样的原则适用。方向是相对于像素网格轴的,如上所示。例如,垂直线应输出0度或180度;取决于最大梯度的方向。作为练习,我建议在只包含您感兴趣的线方向的理想图像上运行上述代码,以了解其工作原理。 - mevatron
非常愿意这么做。谢谢@mevatron。 - Mzk
+1 这是一个非常好的回答,针对一个写得不好的问题(如果可以的话,我会再次点赞)。 - karlphillip

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