基于STM32H7的图像处理--canny边缘检测(一)

**

基于STM32H7的图像处理–canny边缘检测(一)

**

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

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

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

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

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

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

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

while(1){
			T = 0.5 * (m1 + m2);
			m1 = 0.0;
			m2 = 0.0;
			m1_num = 0;
			m2_num = 0;

			for (i = 0; i < len; i++){
				if (GetImageData[i] <= T){
						m1 += GetImageData[i];
						m1_num++;
					}
				else{
						m2 += GetImageData[i];
						m2_num++;
						}
//				GetImageData+=1;
//				DataNum++;
			}
			if (m1_num != 0)
					m1 /= m1_num;
			if (m2_num != 0)
					m2 /= m2_num;
			if(fabs(T-(0.5*(m1 + m2)))<delt_t)
				break;
	}
	GetImageData-=DataNum;
	for(j=0;j<len;j++)
	{
		if(GetImageData[j]<=T)
			GetImageData[j]=0x00;
		else
			GetImageData[j]=0xFF;	

原图:

基于STM32H7的图像处理--canny边缘检测(一)_第2张图片

二值化之后的图片:
基于STM32H7的图像处理--canny边缘检测(一)_第3张图片

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

2. 高斯滤波
(此处参考:简单易懂的高斯滤波 )
进行简单的二值化处理之后开始Canny边缘检测的第一部分:高斯滤波。
滤波的主要目的是降噪,一般的图像处理算法都需要先进行降噪。而高斯滤波主要使图像变得平滑。
高斯函数是一个类似与正态分布的中间大两边小的函数。
对于一个位置(x,y)的像素点,其灰度值(这里只考虑二值图)为f(x,y)。
那么经过高斯滤波后的灰度值将变为:
在这里插入图片描述
简单说就是用一个高斯矩阵乘以每一个像素点及其邻域,取其带权重的平均值作为最后的灰度值。

如下图3x3的像素区域为例,此矩阵中,我们选择的原点(0,0)周围的八个点为其领域:
基于STM32H7的图像处理--canny边缘检测(一)_第4张图片
我们设σ=1.5,将这个此区域的每个像素点做高斯变换之后就得到了以下的数据:
基于STM32H7的图像处理--canny边缘检测(一)_第5张图片
此矩阵为其权重矩阵,这个区域的权重总和为0.4787147,我们需要让他们的权重和为1,于是每个点的值在除以0.4787147,此时我们重新得到了一个3x3的矩阵:
基于STM32H7的图像处理--canny边缘检测(一)_第6张图片
有了相应的3x3矩阵的权重值,我们再对原图像素点进行卷积运算,简单来说就是原图中的点附近的3x3大小区域乘以高斯模板区域。
代码如下:

for(i=widthG+1;i<((heightG-1)*widthG)-2;i++)
	{
			temp=(0.094*ImageNowg[i-widthG-1]+0.118*ImageNowg[i-widthG]+0.094*ImageNowg[i-widthG+1]
		+0.118*ImageNowg[i-1]+0.147*ImageNowg[i]+0.118*ImageNowg[i+1]+0.094*ImageNowg[i+widthG-1]
		+0.118*ImageNowg[i+widthG]+0.094*ImageNowg[i+widthG+1]);
			
			gerytemp[i]=(u8)temp;
	}
	for(j=widthG+1;j<((heightG-1)*widthG)-2;j++)
	{
		
			if(gerytemp[j]>150)
			{
					gerytemp[j]=255;	
			}
			ImageNowg[j]=gerytemp[j];
			
	}

高斯模糊后的图片(由于显示像素我没有进行色彩转换,高斯模糊的部分不是灰色),其中蓝色的部分为高斯模糊的像素。
基于STM32H7的图像处理--canny边缘检测(一)_第7张图片

3.Sobel算子与极大值抑制
Sobel算子是图像处理中的基本算子之一,他是一离散差分算子,用于计算图像亮度函数的梯度近似值。
Sobel算子有两个,一个是水平算子,一个是垂直算子。由于Sobel算子是滤波算子公式,用于提取边缘,可以利用快速卷积函数,简单有效,在Canny边缘检测被作为常用算子之一。
Sobel算子也是一种梯度幅值,该算子包含两组3x3的矩阵,分别为横向与纵向,将之与 图像作平面卷积既可以得出横向及纵向的灰度差分近似值。假设原图像为I,横向边缘检测图像为G(x),纵向边缘检测图像为G(y),模板卷积因子如下:
基于STM32H7的图像处理--canny边缘检测(一)_第8张图片
有了模板卷积因子,我们计算出每个像素点的横向纵向梯度近似值之后,需要知道梯度的幅值大小以及梯度放向,梯度幅值由以下公式得出:
G=|G(x)+G(y)|
梯度方向公式:
Θ=artan(G(y)/G(x))
计算出梯度幅值之后,对图像进行门限处理,设定门限值为T,梯度幅值大于T时,规定该像素点的灰度值为0XFF,否则为0,即:
在这里插入图片描述
非极大值抑制
在Sobel算子对图像进行处理之后,图像的边缘不能作为最佳边缘,边缘有且应当只有一个准确的响应。非极大值抑制作用是对于梯度不是最大值的点进行抑制,对梯度图像中每个像素进行非极大值抑制的算法是:

1.将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
2.如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。
如下图所示,其中P点为当前像素点,P1,P2为领域像素梯度幅值比较点,在这三点当中,如果P点的幅值是最大值,则保留,如果不是则抑制:
基于STM32H7的图像处理--canny边缘检测(一)_第9张图片

为了方便计算,通常会在俩个相邻像素(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);
代码如下:


	for(i=widthS+1;i<((widthS-1)*(heightS-1))-2;i++)
	{
		
		i00=ImageNow[i-widthS-1];
		i01=ImageNow[i-widthS];
		i02=ImageNow[i-widthS+1];
		i10=ImageNow[i-1];
		i12=ImageNow[i+1];
		i20=ImageNow[i+widthS-1];
		i21=ImageNow[i+widthS];
		i22=ImageNow[i+widthS+1];
		
		
		V=-i00-2*i01-i02+i20+2*i21+i22;
		H=-i00+i02-2*i10+2*i12-i20+i22;
		
		grey[i]=(u8)abs(H)+(u8)abs(V);
		
			if(grey[i]==0)
		{
			ImageOperation[i]=0x00;
			continue;
		}
		else
		{
			if(abs(H)>abs(V))///X///
			{
				weight=abs(V)/abs(H);
				if(H*V>0)
				{
					comp1=grey[i+1]*(1-weight)+grey[i-widthS+1]*weight;
					comp2=grey[i-1]*(1-weight)+grey[i+widthS-1]*weight;
				}
				else
				{
					comp1=grey[i+1]*(1-weight)+grey[i+widthS+1]*weight;
					comp2=grey[i-1]*(1-weight)+grey[i-widthS-1]*weight;
				}
			}
			else/Y/
			{
				weight=abs(H)/abs(V);
				if(H*V>0)
				{
					comp1=grey[i-widthS]*(1-weight)+grey[i-widthS+1]*weight;
					comp2=grey[i+widthS]*(1-weight)+grey[i+widthS-1]*weight;
				}
				else
				{
					comp1=grey[i+widthS]*(1-weight)+grey[i+widthS+1]*weight;
					comp2=grey[i-widthS]*(1-weight)+grey[i-widthS-1]*weight;
				}
			}

				if((grey[i]>comp1)&&(grey[i]>comp2))
					ImageOperation[i]=0xff;
				
				else
				ImageOperation[i]=0x00;
		}
		
	}
	for(j=widthS+1;j<((widthS-1)*(heightS-1))-2;j++)
	{
			ImageNow[j]=ImageOperation[j];
	}

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


nhist=(int*)malloc((400)*sizeof(int));
	
	for(i=0;i<400;i++)
		nhist[i]=0;
	for(i=0;i(0.7*sum+0.5))
			{
				high=i;							//取出高阈值
				break;
			}
		}
	low=(int)(0.4*((float)high)+0.5);//取出低阈值
	free(nhist);
	
		for(i=widthS+1;i=high)&&(ImageNow[i]==0xff))
							ImageNow[i]=0xff;
			else if(grey[i]>low&&grey[i]<0xff)
			{
				judge=1;
				while(judge)
				{
					for(j=-widthS;jlow)&&(ImageNow[i]==0xff))
							{
								ImageNow[i+j+k-1]=0xff;
								i=i+j+k;
								cnt=0;
								break;
							}
							else
								cnt++;
							if(cnt>=8)
							{
								cnt=0;
								judge=0;
								break;
							}
						}
						if(judge==0||cnt==0)
							break;
					}
					if(j>widthS+1||cnt==0)
						judge=0;
				}
			}
			else
							ImageNow[i]=0;
		}

处理效果图:
基于STM32H7的图像处理--canny边缘检测(一)_第11张图片

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

stm32h7的Canny边缘检测算法基本完毕,在32上运行起来和Matlab上的差距很大,我觉得应该是我图片存储方式的问题,我这里是用ImageLcd软件将图片直接转为一维数组进行操作显示的。建议使用SD卡存储图片在读取运算(由于我手头没有SD卡只能用这种方式)。
第一次写博客,有不对的地方希望多多指教!(过几天做模板匹配!冲啊)

你可能感兴趣的:(笔记,c语言,canny算法,stm32)