Canny检测步骤:
1.平滑:可以采用诸如高斯滤波等来对图像进行滤波。
2.对图像进行梯度的幅值计算和梯度的角度计算。
3.对上一步计算出来的梯度幅值进行非极大值抑制。
4.对非极大值抑制处理后的梯度幅值图像进行双阈值检测。
5.对双阈值处理的图像进行双阈值之间的像素点进行边缘连接处理
------------------------------------------图像平滑-----------------------------------------
以高斯滤波为例。
进行滤波之前首先要获得高斯滤波内核,因为图像是二维的,所以我们获取的是二维的高斯滤波内核。
二维高斯内核计算公式: (
为标准差)
获取高斯内核代码如下:
// 高斯内核获取
//内核存储数组(二维) GaussianKernel
//内核阶数 order
//标准差 sigma
void Gaussian_Kernel_init(float (*GaussianKernel)[order], float sigma){
uint8 i,j;
uint8 center=order/2;
float buff;
//高斯内核获取
for(i=0;i<order;i++)
for(j=0;j<order;j++){
GaussianKernel[i][j]=expf(-(powf((float)(i-center),2)+powf((float)(i-center),2))/(2.0*sigma*sigma))/(2.0*3.1415*sigma*sigma);
buff+=GaussianKernel[i][j];
}
//高斯内核归一化
for(i=0;i<order;i++)
for(j=0;j<order;j++)
GaussianKernel[i][j]=GaussianKernel[i][j]/buff;
}
然后进行平滑处理,就是拿高斯内核对图像进行卷积,需要注意的是需要对图像外围进行插值处理或者越界处理,代码如下
for(i=0;i<imageHeight;i++){
for(j=0;j<imageWidth;j++){
for(m=0;m<order;m++){
for(n=0;n<order;n++){
//越界数据默认是0
if((i-order/2+m)>=0 && (i-order/2+m)<imageHeight && (j-order/2+n)>=0 && (j-order/2+n)<imageWidth)
buff += GaussianKernel[m][n]*grayImage[i-order/2+m][j-order/2+n];
}
}
GaussianImage[i+1][j+1]=buff;
buff=0;
}
}
至此,第一步的高斯平滑便完成了.
-------------------------------------------------------梯度计算--------------------------------------------------------
梯度计算有很多方法,例如可以采用Roberts,Prewitt,Sobel等算子来计算,本文以Sobel为例。
Sobel算子如下(需要注意的是需要对图像进行插值):
我们需要用这两个分别对整个图像进行卷积,用Sx卷积出来的便是图像X轴的梯度幅值,用Sy卷积出来的便是图像Y轴的梯度幅值,勾股定理便可以计算出图像的梯度幅值。而图像的梯度方向则可以采用反三角函数来获得。
dy=Sx*图像,dy=Sy*图像
图像梯度幅值=,梯度方向=artan(dy/dx)
代码如下:
for(i=1;i<imageHeight+1;i++){
for(j=1;j<imageWidth+1;j++){
buff_dx = GaussianImage[i-1][j-1] - GaussianImage[i-1][j+1] //1,0,-1
+ 2*GaussianImage[i][j-1] - 2*GaussianImage[i][j+1] //2,0,-2
+ GaussianImage[i+1][j-1] - GaussianImage[i+1][j+1]; //1,0,-1
buff_dy = GaussianImage[i-1][j-1] + 2*GaussianImage[i-1][j] + GaussianImage[i-1][j+1] // 1, 2, 1
- GaussianImage[i+1][j-1] - 2*GaussianImage[i+1][j] - GaussianImage[i+1][j+1]; //-1,-2,-1
//计算幅度和角度
gradient_Amplitude[i-1][j-1] = (uint8)sqrtf(powf((float)buff_dx,2)+powf((float)buff_dy,2));
gradient_angle[i-1][j-1] = atanf((float)buff_dy/(float)buff_dx);
}
}
-----------------------------------非极大值移植------------------------------------------
梯度方向共有四个方向,分别是0度方向线,90度方向线,45度方向线,-45度方向线。
我们需要对图像每一个像素点遍历判断其属于一个方向,判断方法就是距离那一条方向线距离近就属于那一条,然后在该像素点的8领域上判断方向线上的两个像素点的梯度幅值是否小于该像素点的梯度幅值,若小于则该点不做处理,若大于则将该点的幅值赋0.
代码如下:
for(i=0;i<imageHeight;i++){
for(j=0;j<imageWidth;j++){
num=(gradient_angle[i][j]/22.5);
if(fabs(num)<1){ //梯度方向为0度水平方向
if(j==0){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i][j+1])
gradient_Amplitude[i][j]=0;
}else if(j==(imageWidth)){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i][j-1])
gradient_Amplitude[i][j]=0;
}else{
if(gradient_Amplitude[i][j]<gradient_Amplitude[i][j+1] || gradient_Amplitude[i][j]<gradient_Amplitude[i][j-1])
gradient_Amplitude[i][j]=0;
}
}else if(num>1 && num<3){ //梯度方向为45度方向
if((i==0 && j==0) || (i==imageHeight && j==imageWidth)){ //比较点均越界,因此不处理
}else if(j==0 || i==imageHeight){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i+1][j+1])
gradient_Amplitude[i][j]=0;
}else if(i==0 || j==imageWidth){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i-1][j-1])
gradient_Amplitude[i][j]=0;
}else{
if(gradient_Amplitude[i][j]<gradient_Amplitude[i-1][j+1] || gradient_Amplitude[i][j]<gradient_Amplitude[i+1][j-1])
gradient_Amplitude[i][j]=0;
}
}else if(num>-3 && num<-1){ //梯度方向为-45度方向
if((i==0 && j==imageWidth) || (i==imageHeight && j==0)){ //比较点均越界,因此不处理
}else if(j==imageWidth || i==imageHeight){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i-1][j-1])
gradient_Amplitude[i][j]=0;
}else if(i==0 || j==0){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i+1][j+1])
gradient_Amplitude[i][j]=0;
}else{
if(gradient_Amplitude[i][j]<gradient_Amplitude[i+1][j+1] || gradient_Amplitude[i][j]<gradient_Amplitude[i-1][j-1])
gradient_Amplitude[i][j]=0;
}
}else if(fabs(num)>3){ //梯度方向为90度方向
if(i==0){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i+1][j])
gradient_Amplitude[i][j]=0;
}else if(i==imageHeight){ //单独处理
if(gradient_Amplitude[i][j]<gradient_Amplitude[i-1][j])
gradient_Amplitude[i][j]=0;
}else{
if(gradient_Amplitude[i][j]<gradient_Amplitude[i+1][j] || gradient_Amplitude[i][j]<gradient_Amplitude[i-1][j])
gradient_Amplitude[i][j]=0;
}
}
}
}
--------------------------------------------双阈值处理--------------------------------------
很好理解就是设置两个阈值,一个高阈值,一个低阈值,其大致关系为3:1或者2:1
该阈值可以手动赋值,也可以采取其他动态阈值算法计算出来。
代码如下:
Thshold_TH=GetOtsuThrehold(gradient_Amplitude);//mt9v03x_image
Thshold_LO=Thshold_TH/3;
for(i=0;i<imageHeight;i++){
for(j=0;j<imageWidth;j++){
if(gradient_Amplitude[i][j]>Thshold_TH)
gradient_Amplitude[i][j]=255;
else if(gradient_Amplitude[i][j]<Thshold_LO)
gradient_Amplitude[i][j]=0;
}
}
--------------------------------------------边缘连接-----------------------------------
最后再用八邻域边缘检测对位于双阈值之间的进行边缘链接处理,其原理是该点的八邻域如果有边缘那么该点幅值赋值255,否则幅值0。