你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】基于STM32H7的图像处理--canny边缘检测(一)

[复制链接]
STMCU小助手 发布时间:2021-12-23 15:00
最近在用STM32H7这块板子做机器视觉的学习,在这里记录下学习心得与过程。有不对的地方欢迎大家指正。(stm32H7开发板,lcd9332显示屏)
(一)边缘检测概念
边缘是图像的基本特征,图像的边缘通常意味着一个区域的结束与另一个区域的开始,对于图像后续处理与检测有重要作用。
在图像处理中,边缘通常被认为是周围的像素灰度发生阶跃变化或者屋顶变化的像素集合。
边缘的特征是沿边缘走向的像素变化平缓,垂直与边缘走向的像素变化剧烈。
边缘检测就是采用某种算法来提取出图像中灰度剧烈变化的区域边界,并且将其来接起来的方法。
(二)Canny边缘检测
Canny算子检测是目前使用较为广泛的边缘检测方法之一,本文也是通过canny算子来实现边缘检测的。
其主要步骤为:高斯滤波–>一阶差分算子计算梯度幅值及其方向–>对梯度幅值进行极大值抑制–>双阈值提取边缘。

1. 基于全局阈值的二值化处理
假设某一副灰度图有如下的直方图,该图像由暗色背景下的较亮物体组成,从背景中提取这一物体时,将阈值T作为分割点,分割后的图像g(x, y)由下述公式给出,称为全局阈值处理

2b5ba45722fb403b99672d03b89b99ce.png


基本全局阈值处理方法

为全局阈值T选择一个初始的估计值

用T分割图像,产生两组像素:G1由大于T的像素组成,G2由小于T的像素组成

对G1和G2的像素分别计算平均灰度值m1和m2

计算新的阈值T = 1/2 * (m1 + m2)

重复步骤2-4,直到连续迭代中的T值差小于一个预定义的参数ΔT
代码如下:

  1. while(1){
  2.                         T = 0.5 * (m1 + m2);
  3.                         m1 = 0.0;
  4.                         m2 = 0.0;
  5.                         m1_num = 0;
  6.                         m2_num = 0;

  7.                         for (i = 0; i < len; i++){
  8.                                 if (GetImageData<i> </i><= T){
  9.                                                 m1 += GetImageData;
  10.                                                 m1_num++;
  11.                                         }
  12.                                 else{
  13.                                                 m2 += GetImageData;
  14.                                                 m2_num++;
  15.                                                 }
  16. //                                GetImageData+=1;
  17. //                                DataNum++;
  18.                         }
  19.                         if (m1_num != 0)
  20.                                         m1 /= m1_num;
  21.                         if (m2_num != 0)
  22.                                         m2 /= m2_num;
  23.                         if(fabs(T-(0.5*(m1 + m2)))<delt_t)
  24.                                 break;
  25.         }
  26.         GetImageData-=DataNum;
  27.         for(j=0;j<len;j++)
  28.         {
  29.                 if(GetImageData[j]<=T)
  30.                         GetImageData[j]=0x00;
  31.                 else
  32.                         GetImageData[j]=0xFF;        
复制代码

原图:

60ab4c4e9f7d45e1b7cbdbff05e940ef.png


二值化之后的图片:

5d94753424c14be1aee9b75d12ebc6cd.png


我在刚开始做二值化的时候直接给定阈值进行处理,每张图片处理效果都不一样,很多时候会出现去不掉阴影的情况,此处通过参考阈值处理后达到所需要的效果。

2. 高斯滤波
进行简单的二值化处理之后开始Canny边缘检测的第一部分:高斯滤波。
滤波的主要目的是降噪,一般的图像处理算法都需要先进行降噪。而高斯滤波主要使图像变得平滑。
高斯函数是一个类似与正态分布的中间大两边小的函数。
对于一个位置(x,y)的像素点,其灰度值(这里只考虑二值图)为f(x,y)。
那么经过高斯滤波后的灰度值将变为:

ba3fa05849c8499db1c76e825717**.png

简单说就是用一个高斯矩阵乘以每一个像素点及其邻域,取其带权重的平均值作为最后的灰度值。

如下图3x3的像素区域为例,此矩阵中,我们选择的原点(0,0)周围的八个点为其领域:

6358a7a8aba7418eb63e20ad63bddf5d.png

我们设σ=1.5,将这个此区域的每个像素点做高斯变换之后就得到了以下的数据:

c94070bb751e40f195c01bbfb156dcb6.png

此矩阵为其权重矩阵,这个区域的权重总和为0.4787147,我们需要让他们的权重和为1,于是每个点的值在除以0.4787147,此时我们重新得到了一个3x3的矩阵:

eb9062d4d27847dbbe3d4f59d534e325.png

有了相应的3x3矩阵的权重值,我们再对原图像素点进行卷积运算,简单来说就是原图中的点附近的3x3大小区域乘以高斯模板区域。
代码如下:

  1. for(i=widthG+1;i<((heightG-1)*widthG)-2;i++)
  2.         {
  3.                         temp=(0.094*ImageNowg[i-widthG-1]+0.118*ImageNowg[i-widthG]+0.094*ImageNowg[i-widthG+1]
  4.                 +0.118*ImageNowg[i-1]+0.147*ImageNowg+0.118*ImageNowg[i+1]+0.094*ImageNowg[i+widthG-1]
  5.                 +0.118*ImageNowg[i+widthG]+0.094*ImageNowg[i+widthG+1]);
  6.                         
  7.                         gerytemp=(u8)temp;
  8.         }
  9.         for(j=widthG+1;j<((heightG-1)*widthG)-2;j++)
  10.         {
  11.                
  12.                         if(gerytemp[j]>150)
  13.                         {
  14.                                         gerytemp[j]=255;        
  15.                         }
  16.                         ImageNowg[j]=gerytemp[j];
  17.                         
  18.         }
复制代码

高斯模糊后的图片(由于显示像素我没有进行色彩转换,高斯模糊的部分不是灰色),其中蓝色的部分为高斯模糊的像素。

30828be0163848deac2f00477e403d7d.png


3.Sobel算子与极大值抑制
Sobel算子是图像处理中的基本算子之一,他是一离散差分算子,用于计算图像亮度函数的梯度近似值。
Sobel算子有两个,一个是水平算子,一个是垂直算子。由于Sobel算子是滤波算子公式,用于提取边缘,可以利用快速卷积函数,简单有效,在Canny边缘检测被作为常用算子之一。
Sobel算子也是一种梯度幅值,该算子包含两组3x3的矩阵,分别为横向与纵向,将之与 图像作平面卷积既可以得出横向及纵向的灰度差分近似值。假设原图像为I,横向边缘检测图像为G(x),纵向边缘检测图像为G(y),模板卷积因子如下:

d97656b50de34ca0859ec7c9358341f3.png


有了模板卷积因子,我们计算出每个像素点的横向纵向梯度近似值之后,需要知道梯度的幅值大小以及梯度放向,梯度幅值由以下公式得出:
G=|G(x)+G(y)|
梯度方向公式:
Θ=artan(G(y)/G(x))
计算出梯度幅值之后,对图像进行门限处理,设定门限值为T,梯度幅值大于T时,规定该像素点的灰度值为0XFF,否则为0,即:

22558e0e6fdc42dda6684757d5245e6e.png

非极大值抑制
在Sobel算子对图像进行处理之后,图像的边缘不能作为最佳边缘,边缘有且应当只有一个准确的响应。非极大值抑制作用是对于梯度不是最大值的点进行抑制,对梯度图像中每个像素进行非极大值抑制的算法是:

1.将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
2.如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。
如下图所示,其中P点为当前像素点,P1,P2为领域像素梯度幅值比较点,在这三点当中,如果P点的幅值是最大值,则保留,如果不是则抑制:

ba517364092844d0a2dff24cd8d19cc4.png


为了方便计算,通常会在俩个相邻像素(G(i+1,j-1),G(i+1,j)),之间采用线性插值的方法计算P1,P2的梯度幅值。
具体计算方式如下:
P2=(1-tanΘ)xG(i+1,j)+tanΘxG(i+1,j-1);

P1=(1-tanΘ)xG(i-1,j)+tanΘxG(i-1,j+1);
代码如下:
  1. for(i=widthS+1;i<((widthS-1)*(heightS-1))-2;i++)
  2.         {
  3.                
  4.                 i00=ImageNow[i-widthS-1];
  5.                 i01=ImageNow[i-widthS];
  6.                 i02=ImageNow[i-widthS+1];
  7.                 i10=ImageNow[i-1];
  8.                 i12=ImageNow[i+1];
  9.                 i20=ImageNow[i+widthS-1];
  10.                 i21=ImageNow[i+widthS];
  11.                 i22=ImageNow[i+widthS+1];
  12.                
  13.                
  14.                 V=-i00-2*i01-i02+i20+2*i21+i22;
  15.                 H=-i00+i02-2*i10+2*i12-i20+i22;
  16.                
  17.                 grey=(u8)abs(H)+(u8)abs(V);
  18.                
  19.                         if(grey==0)
  20.                 {
  21.                         ImageOperation=0x00;
  22.                         continue;
  23.                 }
  24.                 else
  25.                 {
  26.                         if(abs(H)>abs(V))///X///
  27.                         {
  28.                                 weight=abs(V)/abs(H);
  29.                                 if(H*V>0)
  30.                                 {
  31.                                         comp1=grey[i+1]*(1-weight)+grey[i-widthS+1]*weight;
  32.                                         comp2=grey[i-1]*(1-weight)+grey[i+widthS-1]*weight;
  33.                                 }
  34.                                 else
  35.                                 {
  36.                                         comp1=grey[i+1]*(1-weight)+grey[i+widthS+1]*weight;
  37.                                         comp2=grey[i-1]*(1-weight)+grey[i-widthS-1]*weight;
  38.                                 }
  39.                         }
  40.                         else/Y/
  41.                         {
  42.                                 weight=abs(H)/abs(V);
  43.                                 if(H*V>0)
  44.                                 {
  45.                                         comp1=grey[i-widthS]*(1-weight)+grey[i-widthS+1]*weight;
  46.                                         comp2=grey[i+widthS]*(1-weight)+grey[i+widthS-1]*weight;
  47.                                 }
  48.                                 else
  49.                                 {
  50.                                         comp1=grey[i+widthS]*(1-weight)+grey[i+widthS+1]*weight;
  51.                                         comp2=grey[i-widthS]*(1-weight)+grey[i-widthS-1]*weight;
  52.                                 }
  53.                         }

  54.                                 if((grey>comp1)&&(grey>comp2))
  55.                                         ImageOperation=0xff;
  56.                                 
  57.                                 else
  58.                                 ImageOperation=0x00;
  59.                 }
  60.                
  61.         }
  62.         for(j=widthS+1;j<((widthS-1)*(heightS-1))-2;j++)
  63.         {
  64.                         ImageNow[j]=ImageOperation[j];
  65.         }
复制代码

代码中有判断当前像素梯度方向落在哪个象限的代码,具体看代码。
效果图:


f50c570819364f24924ae3bcc2c934cc.png


4.双阈值处理
在施加非极大值抑制之后,剩余的像素可以更准确地表示图像中的实际边缘。然而,仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。
具体实现代码如下:


  1. nhist=(int*)malloc((400)*sizeof(int));
  2.         
  3.         for(i=0;i<400;i++)
  4.                 nhist=0;
  5.         for(i=0;i<widthS*heightS;i++)
  6.                 if(ImageNow==0xff)
  7.                         nhist[grey]++;
  8.         sum=0;
  9.         for(i=0;i<400;i++)
  10.                 sum+=nhist;
  11.         sum2=0;
  12.         for(i=0;i<400;i++)
  13.                 {
  14.                         sum2+=nhist;
  15.                         if(sum2>(0.7*sum+0.5))
  16.                         {
  17.                                 high=i;                                                        //取出高阈值
  18.                                 break;
  19.                         }
  20.                 }
  21.         low=(int)(0.4*((float)high)+0.5);//取出低阈值
  22.         free(nhist);
  23.         
  24.                 for(i=widthS+1;i<widthS*heightS;i++)双阈值检测处理
  25.                 {
  26.                         if((grey>=high)&&(ImageNow==0xff))
  27.                                                         ImageNow=0xff;
  28.                         else if(grey>low&&grey<0xff)
  29.                         {
  30.                                 judge=1;
  31.                                 while(judge)
  32.                                 {
  33.                                         for(j=-widthS;j<widthS+1;j+=widthS)
  34.                                         {
  35.                                                 for(k=-1;k<2;k++)
  36.                                                 {
  37.                                                         if((grey[i+j+k-1]>low)&&(ImageNow==0xff))
  38.                                                         {
  39.                                                                 ImageNow[i+j+k-1]=0xff;
  40.                                                                 i=i+j+k;
  41.                                                                 cnt=0;
  42.                                                                 break;
  43.                                                         }
  44.                                                         else
  45.                                                                 cnt++;
  46.                                                         if(cnt>=8)
  47.                                                         {
  48.                                                                 cnt=0;
  49.                                                                 judge=0;
  50.                                                                 break;
  51.                                                         }
  52.                                                 }
  53.                                                 if(judge==0||cnt==0)
  54.                                                         break;
  55.                                         }
  56.                                         if(j>widthS+1||cnt==0)
  57.                                                 judge=0;
  58.                                 }
  59.                         }
  60.                         else
  61. <i>                                              </i>          ImageNow<span style="font-style: italic;"><span style="font-style: normal;">=0;</span>
  62.                </span> }
复制代码

处理效果图:

c5009952d07c4a6dbe664363d433a4f0.png


由于我的图片噪声较小,与上一步的处理结果看不出太大的差别。

stm32h7的Canny边缘检测算法基本完毕,在32上运行起来和Matlab上的差距很大,我觉得应该是我图片存储方式的问题,我这里是用ImageLcd软件将图片直接转为一维数组进行操作显示的。建议使用SD卡存储图片在读取运算(由于我手头没有SD卡只能用这种方式)。



收藏 评论0 发布时间:2021-12-23 15:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版