图像特征提取:边缘检测

1. 边缘检测的概念

边缘检测是图像处理与计算机视觉中极为重要的一种分析图像的方法,至少在我做图像分析与识别时,边缘是我最喜欢的图像特征。边缘检测的目的就是找到图像中亮度变化剧烈的像素点构成的集合,表现出来往往是轮廓。在对现实世界的图像采集中,有下面4种情况会表现在图像中时形成一个边缘。

  1. 深度的不连续(物体处在不同的物平面上);
  2. 表面方向的不连续(如正方体的不同的两个面);
  3. 物体材料不同(这样会导致光的反射系数不同);
  4. 场景中光照不同(如被树萌投向的地面);

上面的图像是图像中水平方向7个像素点的灰度值显示效果,我们很容易地判断在第4和第5个像素之间有一个边缘,因为它俩之间发生了强烈的灰度跳变。在实际的边缘检测中,边缘远没有上图这样简单明显,我们需要取对应的阈值来区分出它们。

原图 Roberts边缘 Prewitt边缘
Sobel边缘 Log边缘 Canny边缘

2. 边缘检测的基本方法

2.1 一阶微分边缘算子

一阶微分边缘算子也称为梯度边缘算子,它是利用图像在边缘处的阶跃性,即图像梯度在边缘取得极大值的特性进行边缘检测。梯度是

一个矢量,它具有方向$\theta$和模$|\Delta I|$:

$$\Delta I = \begin{pmatrix} \frac{\partial I}{\partial x} \\ \frac{\partial I}{\partial y} \end{pmatrix}$$

$$|\Delta I| = \sqrt{(\frac{\partial I}{\partial x} )^2+(\frac{\partial I}{\partial y} )^2} = \sqrt{I_x^2+I_y^2} $$

$$ \theta = arctan(I_y / I_x)$$

梯度的方向提供了边缘的趋势信息,因为梯度方向始终是垂直于边缘方向,梯度的模值大小提供了边缘的强度信息。

在实际使用中,通常利用有限差分进行梯度近似。对于上面的公式,我们有如下的近似:

$$\frac{\partial I}{\partial x} = \lim_{h\to 0}\frac{I(x+\Delta x,y)-I(x,y)}{\Delta x} \approx I(x+1,y)-I(x,y),(\Delta x = 1)$$

$$\frac{\partial I}{\partial y} = \lim_{h\to 0}\frac{I(x,y+\Delta xy)-I(x,y)}{\Delta y} \approx I(x,y+1)-I(x,y),(\Delta y = 1)$$

2.2 Roberts边缘检测算子

1963年,Roberts提出了这种寻找边缘的算子。Roberts边缘算子是一个2x2的模板,采用的是对角方向相邻的两个像素之差。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。在Roberts检测算子中:

$$\frac{\partial I}{\partial x} = I(i,j)-I(i+1,j+1)$$

$$\frac{\partial I}{\partial y} = I(i+1,j)-I(i,j+1)$$

可以导出Roberts在点$(i+1/2,j+1/2)$处的水平与竖直边缘检测卷积核为:

$$m_x = \begin{bmatrix}1&0 \\ 0&-1\end{bmatrix}, m_y=\begin{bmatrix}0&-1 \\ 1&0\end{bmatrix}$$

2.3 Prewitt边缘检测算子

Prewitt利用周围邻域8个点的灰度值来估计中心的梯度,它的梯度计算公式如下:

$$\frac{\partial I}{\partial x} = I(i-1,j+1)+I(i,j+1)+I(i+1,j+1) – I(i-1, j-1)-I(i,j-1)-I(i+1,j-1)$$

$$\frac{\partial I}{\partial y} =-I(i+1,j-1)+I(i+1,j)+I(i+1,j+1) -  I(i-1,j-1)+I(i-1,j)+I(i-1,j+1) $$

所以,Prewitt的卷积核为:

$$m_x = \begin{bmatrix}-1&0&+1 \\ -1&0&+1 \\ –1&0&+1\end{bmatrix}, m_y=\begin{bmatrix}-1&-1&-1 \\ 0&0&0 \\ +1&+1&+1\end{bmatrix}$$

2.4 Sobel边缘检测算子

比起Prewitt算子,Sobel也是用周围8个像素来估计中心像素的梯度,但是Sobel算子认为靠近中心像素的点应该给予更高的权重,所以Sobel算子把与中心像素4邻接的像素的权重设置为2或-2。

Sobel边缘检测算子的卷积核为:

$$m_x = \begin{bmatrix}-1&0&+1 \\ -2&0&+2\\ –1&0&+1\end{bmatrix}, m_y=\begin{bmatrix}-1&-2&-1 \\ 0&0&0 \\ +1&+2&+1\end{bmatrix}$$

Sobel进行边缘检测的实现可以参考我原来写的一篇博文:图像特征检测:sobel边缘检测,重要的是梯度图像计算后的阈值的确定与边缘的非极大值抑制算法,Roberts与Prewitt原理与sobel一致。

3. 二阶微分算子

学过微积分我们都知道,边缘即是图像的一阶导数局部最大值的地方,那么也意味着该点的二阶导数为零。二阶微分边缘检测算子就是利用图像在边缘处的阶跃性导致图像二阶微分在边缘处出现零值这一特性进行边缘检测的。

对于图像的二阶微分可以用拉普拉斯算子来表示:

$$\nabla^2I = \frac{\partial^2I}{\partial x^2}+\frac{\partial^2I}{\partial y^2}$$

我们在像素点$(i,j)$的$3\times 3$的邻域内,可以有如下的近似:

$$\frac{\partial^2I}{\partial x^2} = I(i,j+1)-2I(i,j)+I(i,j-1)$$

$$\frac{\partial^2I}{\partial y^2}=I(i+1,j)-2I(i,j)+I(i-1,j)$$

$$\nabla^2I = –4I(i,j)+I(i,j+1)+I(i,j-1)+I(i+1,j)+I(i-1,j)$$

对应的二阶微分卷积核为:

$$\boldsymbol{m} = \begin{bmatrix}0&1&0 \\ 1&4&1\\ 0&1&0\end{bmatrix}$$

所以二阶微分检测边缘的方法就分两步:1)用上面的Laplace核与图像进行卷积;2)对卷积后的图像,取得那些卷积结果为0的点。

虽然上述使用二阶微分检测边缘的方法简单,但它的缺点是对噪声十分敏感,同时也没有能够提供边缘的方向信息。为了实现对噪声的抑制,Marr等提出了LOG的方法。

为了减少噪声对边缘的影响,首先图像要进行低通滤波,LOG采用了高斯函数作为低通滤波器。高斯函数为:

$$G(x,y)=\frac{1}{2\pi\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}$$

上面的公式中$\sigma$决定了对图像的平滑程度。高斯函数生成的滤波模板尺寸一般设定为$6\sigma +1$(加1是会了使滤波器的尺寸为奇数)。使用高斯函数对图像进行滤波并对图像滤波结果进行二阶微分运算的过程,可以转换为先对高斯函数进行二阶微分,再利用高斯函数的二阶微分结果对图像进行卷积运算:

$$\nabla^2[G(x,y)*f(x,y)] = \nabla^2[G(x,y)]*f(x,y)$$

$$\nabla^2G(x,y) = -\frac{1}{2\pi\sigma^4}[1-\frac{x^2+y^2}{\sigma^2}]\cdot exp(-\frac{x^2+y^2}{2\sigma^2})$$

关于LOG算子的计算在上一篇文章:图像特征提取:斑点检测中有实现的代码。

4. Canny边缘检测

canny边缘检测实际上是一种一阶微分算子检测算法,但为什么这里拿出来说呢,因为它几乎是边缘检测算子中最为常用的一种,也是个人认为现在最优秀的边缘检测算子。Canny提出了边缘检测算子优劣评判的三条标准:

  • 高的检测率。边缘检测算子应该只对边缘进行响应,检测算子不漏检任何边缘,也不应该将非边缘标记为边缘。
  • 精确定位。检测到的边缘与实际边缘之间的距离要尽可能的小。
  • 明确的响应。对每一条边缘只有一次响应,只得到一个点。

Canny边缘检测之所以优秀是因为它在一阶微分算子的基础上,增加了非最大值抑制和双阈值两项改进。利用非极大值抑制不仅可以有效地抑制多响应边缘,而且还可以提高边缘的定位精度;利用双阈值可以有效减少边缘的漏检率。

Canny边缘检测主要分四步进行:

  1. 去噪声;
  2. 计算梯度与方向角;
  3. 非最大值抑制;
  4. 滞后阈值化;

其中前两步很简单,先用一个高斯滤波器对图像进行滤波,然后用Sobel水平和竖直检测子与图像卷积,来计算梯度和方向角。

非极大值抑制

图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。

根据上图可知,要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。这就是非极大值抑制的工作原理。

在理解的过程中需要注意以下两点:

1)中非最大抑制是回答这样一个问题:“当前的梯度值在梯度方向上是一个局部最大值吗?” 所以,要把当前位置的梯度值与梯度方向上两侧的梯度值进行比较;

2)梯度方向垂直于边缘方向。

但实际上,我们只能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在其中,要得到这两个值就需要对该两个点两端的已知灰度进行线性插值,也即根据图中的g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值,这要用到其梯度方向。

滞后阈值化

由于噪声的影响,经常会在本应该连续的边缘出现断裂的问题。滞后阈值化设定两个阈值:一个为高阈值$T_h$,一个为低阈值$T_l$。如果任何像素边缘算子的影响超过高阈值,将这些像素标记为边缘;响应超过低阈值(高低阈值之间)的像素,如果与已经标记为边缘的像素4-邻接或8-邻接,则将这些像素也标记为边缘。所以不整个过程描述如下:

  1. 如果该像素的梯度值小于$T_l$,则该像素为非边缘像素;
  2. 如果该像素的梯度值大于$T_h$,则该像素为边缘像素;
  3. 如果该像素的梯度值介于$T_l$与$T_h$之间,需要进一步检测该像素的$3\times 3$邻域内的8个点,如果这8个点内有一个或以上的点梯度超过了$T_h$,则该像素为边缘像素,否则不是边缘像素。

Canny边缘检测的实现

#include <iostream>

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace std;
using namespace cv;

void getCannyEdge(const Mat& imgSrc, Mat& imgDst, double lowThresh = -1, double highThresh = -1, double sigma = 1);
double getGaussianThresh(const Mat& inputArray, double percentage);

int main(int argc, char** argv)
{
    Mat image = imread(argv[1]);
    Mat imgCanny;
    getCannyEdge(image,imgCanny);
    t = ((double)getTickCount() - t)/getTickFrequency();
    imwrite("imgCanny.png", imgCanny);

    return 0;
}

void getCannyEdge(const Mat& imgSrc, Mat& imgDst, double lowThresh, double highThresh, double sigma)
{
    Mat gray;
    if (imgSrc.channels() == 3)
    {
        cvtColor(imgSrc, gray, CV_BGR2GRAY);
    }
    else
    {
        gray = imgSrc.clone();
    }
    gray.convertTo(gray, CV_64F);
    gray = gray / 255;

    double gaussianDieOff = .0001;
    double percentOfPixelsNotEdges = .7; // Used for selecting thresholds
    double thresholdRatio = .4;   // Low thresh is this fraction of the high

    int possibleWidth = 30;
    double ssq = sigma * sigma;
    for (int i = 1; i <= possibleWidth; i++)
    {
        if (exp(-(i * i) / (2* ssq)) < gaussianDieOff)
        {
            possibleWidth = i - 1;
            break;
        }
    }

    if (possibleWidth == 30)
    {
        possibleWidth = 1; // the user entered a reallly small sigma
    }

    // get the 1D gaussian filter
    int winSz = 2 * possibleWidth + 1;
    Mat gaussKernel1D(1, winSz, CV_64F);
    double* kernelPtr = gaussKernel1D.ptr<double>(0);
    for (int i = 0; i < gaussKernel1D.cols; i++)
    {
        kernelPtr[i] = exp(-(i - possibleWidth) * (i - possibleWidth) / (2 * ssq)) / (2 * CV_PI * ssq);
    }

    // get the derectional derivatives of gaussian kernel
    Mat dGaussKernel(winSz, winSz, CV_64F);
    for (int i = 0; i < dGaussKernel.rows; i++)
    {
        double* linePtr = dGaussKernel.ptr<double>(i);
        for (int j = 0; j< dGaussKernel.cols; j++)
        {
            linePtr[j] = - (j - possibleWidth) * exp(-((i - possibleWidth) * (i - possibleWidth) + (j - possibleWidth) * (j - possibleWidth)) / (2 * ssq)) / (CV_PI * ssq);
        }
    }

    /* smooth the image out*/
    Mat imgSmooth;
    filter2D(gray, imgSmooth, -1, gaussKernel1D);
    filter2D(imgSmooth, imgSmooth, -1, gaussKernel1D.t());
    /*apply directional derivatives*/

    Mat imgX, imgY;
    filter2D(imgSmooth, imgX, -1, dGaussKernel);
    filter2D(imgSmooth, imgY, -1, dGaussKernel.t());

    Mat imgMag;
    sqrt(imgX.mul(imgX) + imgY.mul(imgY), imgMag);
    double magMax;
    minMaxLoc(imgMag, NULL, &magMax, NULL, NULL);

    if (magMax > 0 )
    {
        imgMag = imgMag / magMax;
    }

    if (lowThresh == -1 || highThresh == -1)
    {
        highThresh = getGaussianThresh(imgMag, percentOfPixelsNotEdges);
        lowThresh = thresholdRatio * highThresh;
    }

    Mat imgStrong = Mat::zeros(imgMag.size(), CV_8U);
    Mat imgWeak = Mat::zeros(imgMag.size(), CV_8U);

    for (int dir = 1; dir <= 4; dir++)
    {
        Mat gradMag1(imgMag.size(), imgMag.type());
        Mat gradMag2(imgMag.size(), imgMag.type());
        Mat idx = Mat::zeros(imgMag.size(), CV_8U);
        if (dir == 1)
        {
            Mat dCof = abs(imgY / imgX);
            idx = (imgY <= 0 & imgX > -imgY) | (imgY >= 0 & imgX < -imgY);
            idx.row(0).setTo(Scalar(0));
            idx.row(idx.rows - 1).setTo(Scalar(0));
            idx.col(0).setTo(Scalar(0));
            idx.col(idx.cols - 1).setTo(Scalar(0));
            for (int i = 1; i < imgMag.rows - 1; i++)
            {
                for (int j = 1; j < imgMag.cols - 1; j++)
                {
                    gradMag1.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i,j + 1) + dCof.at<double>(i,j) * imgMag.at<double>(i - 1,j + 1);
                    gradMag2.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i,j - 1) + dCof.at<double>(i,j) * imgMag.at<double>(i + 1,j - 1);
                }
            }
        }
        else if(dir == 2)
        {
            Mat dCof = abs(imgX / imgY);
            idx = (imgX > 0 & -imgY >= imgX) | (imgX < 0 & -imgY <= imgX);
            for (int i = 1; i < imgMag.rows - 1; i++)
            {
                for (int j = 1; j < imgMag.cols - 1; j++)
                {
                    gradMag1.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i - 1,j) + dCof.at<double>(i,j) * imgMag.at<double>(i - 1,j + 1);
                    gradMag2.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i + 1,j) + dCof.at<double>(i,j) * imgMag.at<double>(i + 1,j - 1);
                }
            }
        }
        else if(dir == 3)
        {
            Mat dCof = abs(imgX / imgY);
            idx = (imgX <= 0 & imgX > imgY) | (imgX >= 0 & imgX < imgY);
            for (int i = 1; i < imgMag.rows - 1; i++)
            {
                for (int j = 1; j < imgMag.cols - 1; j++)
                {
                    gradMag1.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i - 1,j) + dCof.at<double>(i,j) * imgMag.at<double>(i - 1,j - 1);
                    gradMag2.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i + 1,j) + dCof.at<double>(i,j) * imgMag.at<double>(i + 1,j + 1);
                }
            }

        }
        else
        {
            Mat dCof = abs(imgY / imgX);
            idx = (imgY <0 & imgX <= imgY) | (imgY > 0 & imgX >= imgY);
            for (int i = 1; i < imgMag.rows - 1; i++)
            {
                for (int j = 1; j < imgMag.cols - 1; j++)
                {
                    gradMag1.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i,j - 1) + dCof.at<double>(i,j) * imgMag.at<double>(i - 1,j - 1);
                    gradMag2.at<double>(i,j) = (1 - dCof.at<double>(i,j)) * imgMag.at<double>(i,j + 1) + dCof.at<double>(i,j) * imgMag.at<double>(i + 1,j + 1);
                }
            }
        }

        Mat idxLocalMax = idx & ((imgMag >= gradMag1) & (imgMag >= gradMag2));

        imgWeak = imgWeak | ((imgMag > lowThresh) & idxLocalMax);
        imgStrong= imgStrong| ((imgMag > highThresh) & imgWeak);

    }

    imgDst = Mat::zeros(imgWeak.size(),imgWeak.type());
    for (int i = 1; i < imgWeak.rows - 1; i++)
    {
        uchar* pWeak = imgWeak.ptr<uchar>(i);
        uchar* pDst = imgDst.ptr<uchar>(i);
        uchar* pStrPre = imgStrong.ptr<uchar>(i - 1);
        uchar* pStrMid = imgStrong.ptr<uchar>(i);
        uchar* pStrAft = imgStrong.ptr<uchar>(i + 1);
        for (int j = 1; j < imgWeak.cols - 1; j++)
        {
            if (!pWeak[j])
            {
                continue;
            }
            if (pStrMid[j])
            {
                pDst[j] = 255;
            }
            if (pStrMid[j-1] || pStrMid[j+1] || pStrPre[j-1] || pStrPre[j] || pStrPre[j+1] || pStrAft[j-1] || pStrAft[j] ||pStrAft[j+1])
            {
                pDst[j] = 255;
            }
        }
    }
}

double getGaussianThresh(const Mat& inputArray, double percentage)
{
    double thresh = -1.0;
    // compute the 64-hist of inputArray
    int nBins = 64;
    double minValue, maxValue;
    minMaxLoc(inputArray, &minValue, &maxValue, NULL, NULL);
    double step = (maxValue - minValue) / nBins;

    vector<unsigned> histBin(nBins,0);
    for (int i = 0; i < inputArray.rows; i++)
    {
        const double* pData = inputArray.ptr<double>(i);
        for(int j = 0; j < inputArray.cols; j++)
        {

            int index = (pData[j] - minValue) / step;
            histBin[index]++;
        }
    }
    unsigned cumSum = 0;
    for (int i = 0; i < nBins; i++)
    {
        cumSum += histBin[i];

        if (cumSum > percentage * inputArray.rows * inputArray.cols)
        {
            thresh = (i + 1) / 64.0;
            break;
        }
    }
    return thresh;

}

5. 参考资料

1. 《图像局部不变特征与描述》王永明,王贵锦。

2. Canny边缘检测算法原理及其VC实现详解(一)

时间: 2024-08-25 19:18:39

图像特征提取:边缘检测的相关文章

图像特征提取:Sobel边缘检测

图像特征提取:Sobel边缘检测 前言 点和线是做图像分析时两个最重要的特征,而线条往往反映了物体的轮廓,对图像中边缘线的检测是图像分割与特征提取的基础.文章主要讨论两个实际工程中常用的边缘检测算法:Sobel边缘检测和Canny边缘检测,Canny边缘检测由于算法复杂将在另一篇文章中单独介绍,文章不涉及太多原理,因为大部分的图像处理书籍都有相关内容介绍,文章主要通过Matlab代码,一步一步具体实现两种经典的边缘检测算法. Sobel边缘检测 Soble边缘检测算法比较简,实际应用中效率比ca

医学CT图像特征提取算法--肺结节CT图像特征提取算法

摘自本人毕业论文<肺结节CT影像特征提取算法研究> 医学图像特征提取可以认为是基于图像内容提取必要特征,医学图像中需要什么特征基于研究需要,提取合适的特征.相对来说,医学图像特征提取要求更加高,因为对医生的辅助诊断起着至关重要的作用,所以需要严谨可靠的特征.肺结节CT影像特征提取也是属于医学图像特征提取领域的一个部分,有着医学图像特征提取的基本要求.既有其他医学图像特征提取的方法,也有针对肺结节的特定特征提取方法.本小节主要对一些常用的肺结节CT影像医学图像特征提取方法进行介绍,主要可以分为灰

目标检测的图像特征提取之(一)HOG特征(转载)

目标检测的图像特征提取之(一)HOG特征 [email protected] http://blog.csdn.net/zouxy09 1.HOG特征: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和统计图像局部区域的梯度方向直方图来构成特征.Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功.需要提醒的是,HOG+SVM进行行人检测的方法是法

OpenCV图像Canny边缘检测

Canny边缘检测 图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘函数原型:     void cvCanny(       const CvArr* image,              //第一个参数表示输入图像,必须为单通道灰度图      CvArr* edges,                      //第二个参数表示输出的边缘图像,为单通道黑白图      double threshold1,    

图像特征提取:SIFT定位算法关键步骤的说明

1. SIFT算法中一些符号的说明 $I(x,y)$表示原图像. $G(x,y,\sigma)$表示高斯滤波器,其中$G(x,y,\sigma) = \frac{1}{2\pi\sigma^2}exp(-(x^2+y^2)/2\sigma^2)$. $L(x,y,\sigma)$表示由一个高斯滤波器与原图像卷积而生成的图像,即$L(x,y,\sigma) = G(x,y,\sigma)\otimes I(x,y)$.一系列的$\sigma_i$,则可以生成一系列的$L(x,y,\sigma_i)

OpenCV笔记(十五)——使用Laplace算子进行图像的边缘检测

在笔记十四中,我们使用了Sobel算子对图像进行边缘检测,理论依据是像素变化最快的地方最有可能是边缘处,所以使用sobel算子对图像做微分,得到的结果图像当中灰度较大的区域,即为边缘处. 在这里,我们使用Laplace算子来做边缘检测,也是类似的道理,只不过换成了二阶微分,在一阶微分的极大值的地方,二阶微分为零.我们以二阶微分为零的区域作为边缘.当然了一阶微分极小值的地方,即图像变化很慢很慢的地方,二阶微分也可能为零.所以我们还需要进行额外的滤波. 在理论上是这样,但是在实现上,OpenCV使用

Opencv对图像做边缘检测——canny算子

图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘. Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法.Canny 边缘检测的数学原理和算法实现这里就不再了,有兴趣的读者可以查阅专业书籍,本文主要介绍如何在OpenCV中对图像进行Canny 边缘检测,下面就来看看这个函数的原型. 一. 主要函数 1.1 cvCanny 函数功能:采用Canny方法对图像进行边缘检测 函数原型:

[OpenCV-Python] OpenCV 中图像特征提取与描述 部分 V (一)

部分 V图像特征提取与描述 29 理解图像特征 目标本节我会试着帮你理解什么是图像特征,为什么图像特征很重要,为什么角点很重要等.29.1 解释 我相信你们大多数人都玩过拼图游戏吧.首先你们拿到一张图片的一堆碎片,要做的就是把这些碎片以正确的方式排列起来从而重建这幅图像.问题是,你怎样做到的呢?如果把你做游戏的原理写成计算机程序,那计算机就也会玩拼图游戏了.如果计算机可以玩拼图,我们就可以给计算机一大堆自然图片,然后就可以让计算机把它拼成一张大图了.如果计算机可以自动拼接自然图片,那我们是不是可

[OpenCV-Python] OpenCV 中图像特征提取与描述 部分 V (二)

部分 V图像特征提取与描述 34 角点检测的 FAST 算法 目标 ? 理解 FAST 算法的基础 ? 使用 OpenCV 中的 FAST 算法相关函数进行角点检测原理 我们前面学习了几个特征检测器,它们大多数效果都很好.但是从实时处理的角度来看,这些算法都不够快.一个最好例子就是 SLAM(同步定位与地图构建),移动机器人,它们的计算资源非常有限.为了解决这个问题,Edward_Rosten 和 Tom_Drummond 在 2006 年提出里 FAST 算法.我们下面将会对此算法进行一个简单