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

边缘检测

获取源工程可访问gitee可在此工程的基础上进行学习。

实验内容

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<nHeight-2 而不是y<nHeight,相应的x方向也是x<nWidth-2
 // 而不是x<nWidth
 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;
  }

}

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 ;
}
     

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);
  }

3. Otsu分割方法实验现象

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写的什么石山代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值