• 使用VC++设计程序,实现基于拉普拉斯算子、Canny的边缘检测功能、实现Otsu分割方法


    边缘检测

    实验内容

    A部分:
    (3)使用VC++设计程序:对一幅256级灰度图像,实现基于拉普拉斯算子的边缘检测功能
    (4)使用VC++设计程序:对一幅256级灰度图像,实现Canny边缘检测方法。
    B部分:
    (1)包括A部分全部要求。
    (2)使用VC++设计程序:对一幅256级灰度图像,实现Otsu分割方法。

    一、 拉普拉斯算子的边缘检测功能

    边缘检测是图像处理中一种常见的操作,其目的是识别图像中物体或场景中的边缘部分。图像中的边缘通常是灰度、颜色或强度发生显著变化的地方,边缘检测旨在捕捉这些变化,并用于图像分析、计算机视觉和图像识别等领域。

    边缘检测的概念和目的包括:

    1. 检测图像中物体的轮廓: 边缘通常表示不同区域之间的边界,因此边缘检测有助于定位图像中物体的轮廓。这对于对象识别和目标跟踪等任务至关重要。

    2. 提取图像中的特征: 边缘是图像中的重要特征之一。通过检测边缘,可以提取出物体的形状和结构信息,有助于进行更高层次的图像分析。

    3. 减少图像数据量: 边缘检测可以将图像中的详细信息转化为简化的边缘表示,从而降低图像的数据量。这对于图像压缩和存储是有益的。

    4. 改善图像分割: 边缘检测有助于图像分割,即将图像划分为不同的区域或对象。通过找到区域之间的边缘,可以更好地区分不同的物体。

    5. 增强图像特征: 在某些情况下,边缘检测可用于增强图像的局部特征。通过突出物体的轮廓,有助于人眼更容易理解和识别图像内容。

    常见的边缘检测方法包括Sobel算子、Prewitt算子、Canny边缘检测等。这些方法通常基于图像梯度、灰度变化或滤波等技术,以找到图像中灰度或颜色变化较大的地方。

    1. 拉普拉斯算子的边缘检测的原理

    拉普拉斯算子(Laplacian operator)是一种常用于边缘检测的图像处理算子,它通过计算图像中像素值的二阶导数来寻找图像中的边缘。

    拉普拉斯算子的原理基于以下思想:

    1. 灰度变化的二阶导数: 边缘通常对应于图像中像素值变化较大的区域。拉普拉斯算子对灰度变化的二阶导数进行检测,因为边缘处的像素值变化会导致灰度的急剧变化。

    2. 图像中的边缘位置: 在图像中,边缘位置对应于灰度函数的极值点。通过计算灰度函数的二阶导数,我们可以找到灰度函数的极值点,这些极值点通常表示边缘的位置。

    数学上,拉普拉斯算子通常使用以下的离散形式表示:

    ∇ 2 I ( x , y ) = I ( x − 1 , y ) + I ( x + 1 , y ) + I ( x , y − 1 ) + I ( x , y + 1 ) − 4 I ( x , y ) \nabla^2 I(x, y) = I(x-1, y) + I(x+1, y) + I(x, y-1) + I(x, y+1) - 4I(x, y) 2I(x,y)=I(x1,y)+I(x+1,y)+I(x,y1)+I(x,y+1)4I(x,y)

    其中, ∇ 2 I ( x , y ) \nabla^2 I(x, y) 2I(x,y) 表示图像 I I I 在位置 ( x , y ) (x, y) (x,y) 处的拉普拉斯值。

    在实际应用中,为了增强边缘的检测效果,通常会在计算完拉普拉斯值后,应用阈值处理或者进一步的图像增强操作。

    值得注意的是,由于噪声和图像中其他变化可能影响到灰度的二阶导数,因此在应用拉普拉斯算子时,可能需要进行平滑或者使用其他预处理技术,以提高边缘检测的准确性。

    2. 拉普拉斯算子的边缘检测功能的实验代码

    /*************************************************************************
     *
     * \函数名称:
     *   LaplacianOperator()
     *
     * \输入参数:
     *   CDib * pDib  - 指向CDib类的指针,含有原始图象信息
     *   double * pdGrad - 指向梯度数据的指针,含有图像的梯度信息
     *
     * \返回值:
     *   无
     *
     * \说明:
     *   LaplacianOperator算子,是二阶算子,不想Roberts算子那样需要两个模板计算
     *   梯度,LaplacianOperator算子只要一个算子就可以计算梯度。但是因为利用了
     *   二阶信息,对噪声比较敏感
     *
     *************************************************************************
     */
    void LaplacianOperator(CDib * pDib, double * pdGrad)
    {
      // 遍历图象的纵坐标
     int y;
    
     // 遍历图象的横坐标
     int x;
    
     // 图象的长宽大小
     CSize sizeImage  = pDib->GetDimensions();
     int nWidth   = sizeImage.cx  ;
     int nHeight   = sizeImage.cy  ;
    
     // 图像在计算机在存储中的实际大小
     CSize sizeImageSave = pDib->GetDibSaveDim();
    
     // 图像在内存中每一行象素占用的实际空间
     int nSaveWidth = sizeImageSave.cx;
    
     // 图像数据的指针
     LPBYTE  lpImage = pDib->m_lpImage;
    
     // 初始化
     for(y=0; y<nHeight ; y++ )
      for(x=0 ; x<nWidth ; x++ )
      {
       *(pdGrad+y*nWidth+x)=0;
      }
    
     // 设置模板系数
     static int nWeight[3][3] ;
     nWeight[0][0] = -1 ;   
     nWeight[0][1] = -1 ;   
     nWeight[0][2] = -1 ;   
     nWeight[1][0] = -1 ;   
     nWeight[1][1] =  8 ;   
     nWeight[1][2] = -1 ;   
     nWeight[2][0] = -1 ;   
     nWeight[2][1] = -1 ;   
     nWeight[2][2] = -1 ;   
    
    
    
     //这个变量用来表示Laplacian算子象素值
     int nTmp[3][3];
     
     // 临时变量
     double dGrad;
    
     // 模板循环控制变量
     int yy ;
     int xx ;
    
     
     // 下面开始利用Laplacian算子进行计算,为了保证计算所需要的
     // 的数据位于图像数据的内部,下面的两重循环的条件是
     // y
     // 而不是x
     for(y=1; y<nHeight-2 ; y++ )
      for(x=1 ; x<nWidth-2 ; x++ )
      {
       dGrad = 0 ; 
       // Laplacian算子需要的各点象素值
    
       // 模板第一行
       nTmp[0][0] = lpImage[(y-1)*nSaveWidth + x - 1 ] ; 
       nTmp[0][1] = lpImage[(y-1)*nSaveWidth + x     ] ; 
       nTmp[0][2] = lpImage[(y-1)*nSaveWidth + x + 1 ] ; 
    
       // 模板第二行
       nTmp[1][0] = lpImage[y*nSaveWidth + x - 1 ] ; 
       nTmp[1][1] = lpImage[y*nSaveWidth + x     ] ; 
       nTmp[1][2] = lpImage[y*nSaveWidth + x + 1 ] ; 
    
       // 模板第三行
       nTmp[2][0] = lpImage[(y+1)*nSaveWidth + x - 1 ] ; 
       nTmp[2][1] = lpImage[(y+1)*nSaveWidth + x     ] ; 
       nTmp[2][2] = lpImage[(y+1)*nSaveWidth + x + 1 ] ; 
       
       // 计算梯度
       for(yy=0; yy<3; yy++)
        for(xx=0; xx<3; xx++)
        {
         dGrad += nTmp[yy][xx] * nWeight[yy][xx] ;
        }
       
       // 梯度值写入内存
       *(pdGrad+y*nWidth+x)=dGrad;
      }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    3. 拉普拉斯算子的边缘检测功能的实验现象

    在这里插入图片描述

    二、 Canny边缘检测

    1. Canny边缘检测原理

    Canny 边缘检测是一种经典的边缘检测算法,由John F. Canny 在 1986 年提出。它的设计旨在满足三个主要要求:

    1. 低错误率: 算法应该能够准确地找到图像中的真实边缘,尽量减少误检和漏检。
    2. 高定位性: 算法检测到的边缘应该尽量靠近实际边缘。
    3. 最小响应: 单个边缘点应该只被检测一次。

    Canny 边缘检测的步骤包括:

    1. 噪声抑制: 使用高斯滤波器对图像进行平滑,以减少噪声对边缘检测的影响。

    2. 计算梯度: 使用 Sobel 等算子计算图像的梯度,得到每个像素点的梯度强度和方向。

    3. 非极大值抑制: 对梯度图进行非极大值抑制,以保留梯度方向上的局部极大值。

    4. 双阈值检测: 设置两个阈值,高阈值 T high T_{\text{high}} Thigh 和低阈值 T low T_{\text{low}} Tlow。将梯度图分为强边缘、弱边缘和非边缘三类。强边缘是梯度值大于 T high T_{\text{high}} Thigh 的点,非边缘是梯度值小于 T low T_{\text{low}} Tlow 的点,弱边缘是梯度值介于两者之间的点。

    5. 边缘跟踪: 通过连接强边缘像素,形成完整的边缘。通常使用连接弱边缘像素的方式,如果弱边缘像素与某个强边缘像素相邻,则被认为属于同一边缘。

    这样,Canny 边缘检测能够在图像中找到细且明显的边缘,同时能够抑制噪声。

    2. Canny边缘检测的实验代码

    /*************************************************************************
     *
     * \函数名称:
     *   Canny()
     *
     * \输入参数:
     *   unsigned char *pUnchImage- 图象数据
     *  int nWidth               - 图象数据宽度
     *  int nHeight              - 图象数据高度
     *   double sigma             - 高斯滤波的标准方差
     *  double dRatioLow         - 低阈值和高阈值之间的比例
     *  double dRatioHigh        - 高阈值占图象象素总数的比例
     *   unsigned char *pUnchEdge - canny算子计算后的分割图
     *
     * \返回值:
     *   无
     *
     * \说明:
     *   canny分割算子,计算的结果保存在pUnchEdge中,逻辑1(255)表示该点为
     *   边界点,逻辑0(0)表示该点为非边界点。该函数的参数sigma,dRatioLow
     *   dRatioHigh,是需要指定的。这些参数会影响分割后边界点数目的多少
     *************************************************************************
     */
    void Canny(unsigned char *pUnchImage, int nWidth, int nHeight, double sigma,
          double dRatioLow, double dRatioHigh, unsigned char *pUnchEdge)
    {
     // 经过高斯滤波后的图象数据
     unsigned char * pUnchSmooth ;
      
     // 指向x方向导数的指针
     int * pnGradX ; 
    
     // 指向y方向导数的指针
     int * pnGradY ;
    
     // 梯度的幅度
     int * pnGradMag ;
    
     pUnchSmooth  = new unsigned char[nWidth*nHeight] ;
     pnGradX      = new int [nWidth*nHeight]          ;
     pnGradY      = new int [nWidth*nHeight]          ;
     pnGradMag    = new int [nWidth*nHeight]          ;
    
     // 对原图象进行滤波
     GaussianSmooth(pUnchImage, nWidth, nHeight, sigma, pUnchSmooth) ;
    
     // 计算方向导数
     DirGrad(pUnchSmooth, nWidth, nHeight, pnGradX, pnGradY) ;
    
     // 计算梯度的幅度
     GradMagnitude(pnGradX, pnGradY, nWidth, nHeight, pnGradMag) ;
    
     // 应用non-maximum 抑制
     NonmaxSuppress(pnGradMag, pnGradX, pnGradY, nWidth, nHeight, pUnchEdge) ;
    
     // 应用Hysteresis,找到所有的边界
     Hysteresis(pnGradMag, nWidth, nHeight, dRatioLow, dRatioHigh, pUnchEdge);
    
    
     // 释放内存
     delete pnGradX      ;
     pnGradX      = NULL ;
     delete pnGradY      ;
     pnGradY      = NULL ;
     delete pnGradMag    ;
     pnGradMag    = NULL ;
     delete pUnchSmooth ;
     pUnchSmooth  = NULL ;
    }
         
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    3. Canny边缘检测的实验现象

    在这里插入图片描述

    三、 Otsu分割方法

    1. Otsu分割方法原理

    Otsu 分割方法是一种自适应阈值分割的算法,它由日本学者大津秀一在1979年提出,主要用于图像二值化。Otsu 方法的目标是找到一个全局阈值,将图像分为两个类别,使得两个类别之间的类内方差最小,即最大化两个类别之间的类间方差。

    算法步骤如下:

    1. 直方图计算: 对图像进行灰度直方图统计,得到每个灰度级别的像素数目。

    2. 归一化直方图: 将直方图归一化,得到每个灰度级别的概率。

    3. 计算类间方差: 对于每个可能的阈值 t t t,将直方图分为两个部分:背景(小于等于阈值)和前景(大于阈值)。计算两个类别的类间方差:

      σ 2 ( t ) = w 1 ( t ) ⋅ w 2 ( t ) ⋅ [ μ 1 ( t ) − μ 2 ( t ) ] 2 \sigma^2(t) = w_1(t) \cdot w_2(t) \cdot [ \mu_1(t) - \mu_2(t) ]^2 σ2(t)=w1(t)w2(t)[μ1(t)μ2(t)]2

      其中:

      • w 1 ( t ) w_1(t) w1(t) w 2 ( t ) w_2(t) w2(t) 是分别位于两个类别的概率;
      • μ 1 ( t ) \mu_1(t) μ1(t) μ 2 ( t ) \mu_2(t) μ2(t) 是分别位于两个类别的平均灰度。
    4. 找到最大类间方差: 找到最大的类间方差对应的阈值 $$,即 t Otsu t_{\text{Otsu}} tOtsu

    5. 图像二值化: 使用找到的阈值 t Otsu t_{\text{Otsu}} tOtsu 对图像进行二值化。

    通过这种方法,Otsu 分割能够自适应地找到一个能够将图像背景和前景分离的阈值,适用于各种图像类型。

    2. Otsu分割方法实验代码

    //获取图高
     int height = pDoc->m_pDibInit->GetHeight();
     //获取图宽
     int width = pDoc->m_pDibInit->GetWidth();
     int m,i;
     //大津阈值分割
     float avl = 0; //灰度平均值
     int graysum = 0; //灰度值之和
     int num[256] = { 0 }; //每个灰度级的频数
     double p[256] = { 0 }; //~频率
    
     for( i=0;i<height;i++)
      for (int j = 0; j < width; j++)
      {
       int gray = pDoc->m_pDibInit->GetPixelGray(i, j);
       num[gray]++;
       graysum += gray;
      }
    
     avl = graysum / (width * height);
    
     for ( i = 0; i < 256; i++)
      p[i] = (num[i]+0.0) / (width * height);
    
     double max = 0; //最大类间方差
     int max_k=0; //记录令取得最大类间方差的K
     for (int k = 0; k < 256; k++) //分别算以k分割的均值和类间方差
     {
      double p_sum1 = 0, p_sum2 = 0; //sum1为小于等于k的灰度值概率和
      double avl1 = 0, avl2 = 0; //均值
      double sqare = 0; //类间方差
    
      for ( m = 0; m <= k; m++)
       p_sum1 += p[m];
    
      for ( m = k; m < 256; m++)
       p_sum2 += p[m];
    
      for ( m = 0; m < 256; m++)
      {
       if (m <= k)
        avl1 += (m * p[m] + 0.0) / p_sum1;
       else
        avl2 += (m * p[m] + 0.0) / p_sum2;
      }
      
      sqare = p_sum1 * p_sum2 * (avl1 - avl2) * (avl1 - avl2);
    
      if (sqare >= max)
      {
       max = sqare;
       max_k = k;
      }
     }
     CString str1;
     str1.Format("阈值:%d", max_k);
     MessageBox(str1);
    
     //二值化
     for( i=0;i<width;i++)
      for (int j = 0; j < height; j++)
      {
       if (pDoc->m_pDibInit->GetPixelGray(i, j) <= max_k)
        pDoc->m_pDibInit->SetPixelGray(i, j, 0);
       else
        pDoc->m_pDibInit->SetPixelGray(i, j, 255);
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    3. Otsu分割方法实验现象

    在这里插入图片描述

  • 相关阅读:
    【无标题】
    【Transformer系列】关于Transformer的若干问题FAQ解析
    使用python读写xlsx格式中的数据【xlrd、pywin32】
    【LeetCode】升级打怪之路 Day 06:哈希表的应用
    Python机器学习实战-特征重要性分析方法(9):卡方检验(附源码和实现效果)
    ArrayList源码分析
    动态代理IP常见超时原因及解决方法
    关于libxml的使用
    【ARM 嵌入式 编译系列 11.1 -- GCC __attribute__((aligned(x)))详细介绍】
    JAVA【Maven中的核心概念】
  • 原文地址:https://blog.csdn.net/qq_63831368/article/details/134458454