Canny边缘检测算法
参考:http://blog.csdn.net/jia20003/article/details/41173767
经典的Canny边缘检测算法通常都是从高斯模糊开始,到基于双阈值实现边缘连接结束。但是在实际工程应用中,考虑到输入图像都是彩色图像,最终边缘连接之后的图像要二值化输出显示,所以完整的Canny边缘检测算法实现步骤如下:
1. 彩色图像转换为灰度图像
2. 对图像进行高斯模糊
3. 计算图像梯度,根据梯度计算图像边缘幅值与角度
4. 非最大信号压制处理(边缘细化)
5. 双阈值边缘连接处理
6. 二值化图像输出结果
1、对图像进行高斯模糊,实现图形的平滑,去除尖锐噪声
定义:高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
高斯滤波的相关解释:http://blog.csdn.net/hhygcy/article/details/43290
void gaussianFilter2 (int width, int height) //高斯滤波器 { int templates[25] = { 1, 4, 7, 4, 1, //高斯滤波窗口 4, 16, 26, 16, 4, 7, 26, 41, 26, 7, 4, 16, 26, 16, 4, 1, 4, 7, 4, 1 }; memcpy(smooth, gray, width*height*sizeof(int) ); //给smooth赋值gray for (int j=2;j<height-2;j++) { for (int i=2;i<width-2;i++) //i,j用于循环gray中每一个像素(除去图像的边缘) { int sum = 0; int index = 0; for ( int m=j-2; m<j+3; m++) { for (int n=i-2; n<i+3; n++) { sum += gray[ m*width + n] * templates[index++] ; } } sum /= 273; //sum等于使用高斯滤波平滑后的像素值 if (sum > 255) sum = 255; smooth[ j*width+i ] = sum; } } }
2、梯度求解函数
void gradient(int width, int height) { for (int row = 0; row < height-1; row++) { for (int col = 0; col < width-1; col++) { int index = row * width + col; // 计算X方向梯度 //float xg = 0.5*(smooth[(row+1)*width+col]- smooth[row*width+col]+ smooth[(row+1)*width+col+1]- smooth[row*width+col]); //此处算法有点错误修改如下 float xg = 0.5*(smooth[(row+1)*width+col]- smooth[row*width+col]+ smooth[(row+1)*width+col+1]- smooth[row*width+col+1]); float yg = 0.5*(smooth[row*width+col]- smooth[row*width+col+1]+ smooth[(row+1)*width+col]- smooth[(row+1)*width+col+1]); // 计算振幅与角度 data[index] =sqrt(xg*xg+yg*yg); if(xg == 0) { if(yg > 0) { magnitudes[index]=90; } if(yg < 0) { magnitudes[index]=-90; } } else if(yg == 0) { magnitudes[index]=0; } else { magnitudes[index] = (float)((atan(yg/xg) * 180)/3.1415926); } // make it 0 ~ 180 magnitudes[index] += 90; } } }
3、非最大信号压制(边缘细化)
信号压制本来是数字信号处理中经常用的,这里的非最大信号压制主要目的是实现边缘细化,通过该步处理边缘像素进一步减少。非最大信号压制主要思想是假设3x3的像素区域,中心像素P(x,y) 根据上一步中计算得到边缘角度值angle,可以将角度分为四个离散值0、45、90、135分类依据如下:
其中黄色区域取值范围为0~22.5 与157.5~180
绿色区域取值范围为22.5 ~ 67.5
蓝色区域取值范围为67.5~112.5
红色区域取值范围为112.5~157.5
分别表示上述四个离散角度的取值范围。得到角度之后,比较中心像素角度上相邻
两个像素,如果中心像素小于其中任意一个,则舍弃该边缘像素点,否则保留。一
个简单的例子如下:
void byxh(int width, int height) // 非最大信号压制 { for (int row = 1; row < height-1; row++) { for (int col = 1; col < width-1; col++) { int index = row * width + col; float angle = magnitudes[index]; float m0 = data[index]; magnitudes[index] = m0; if(angle >=0 && angle < 22.5) // angle 0 { float m1 =data[row*width+col-1]; float m2 = data[row*width+col+1]; if(m0 < m1 || m0 < m2) { magnitudes[index] = 0; } } else if(angle >= 22.5 && angle < 67.5) // angle +45 { float m1 = data[(row-1)*width+col+1]; float m2 = data[(row+1)*width+col-1]; if(m0 < m1 || m0 < m2) { magnitudes[index] = 0; } } else if(angle >= 67.5 && angle < 112.5) // angle 90 { float m1 = data[(row+1)*width+col]; float m2 = data[(row-1)*width+col]; if(m0 < m1 || m0 < m2) { magnitudes[index] = 0; } } else if(angle >=112.5 && angle < 157.5) // angle 135 / -45 { float m1 = data[(row-1)*width+col-1]; float m2 = data[(row+1)*width+col+11]; if(m0 < m1 || m0 < m2) { magnitudes[index] = 0; } } else if(angle >=157.5) // angle 0 { float m1 = data[(row+1)*width+col]; float m2 =data[(row-1)*width+col]; if(m0 < m1 || m0 < m2) { magnitudes[index] = 0; } } } } }
4、 模糊阈值和边缘连接
非最大信号压制以后,输出的幅值如果直接显示结果可能会少量的非边缘像素被包含到结果中,所以要通过选取阈值进行取舍,传统的基于一个阈值的方法如果选择的阈值较小起不到过滤非边缘的作用,如果选择的阈值过大容易丢失真正的图像边
缘,Canny提出基于双阈值(Fuzzy threshold)方法很好的实现了边缘选取,在实际应用中双阈值还有边缘连接的作用。双阈值选择与边缘连接方法通过假设两个阈值其中一个为高阈值TH另外一个为低阈值TL则有:
a. 对于任意边缘像素低于TL的则丢弃
b. 对于任意边缘像素高于TH的则保留
c. 对于任意边缘像素值在TL与TH之间的,如果能通过边缘连接到一个像素大于
TH而且边缘所有像素大于最小阈值TL的则保留,否则丢弃。代码实现如下:
void FuzzyThreshold(int width, int height) { float lowThreshold =1.5; float highThreshold =3.75; //通过改变lowThreshold、highThreshold两个值来修正提取效果 int offset = 0; for(int i=0;i<width*height;i++) data[i]=0; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { if(magnitudes[offset] >= highThreshold && data[offset] == 0) { edgeLink(width, height,col, row, offset, lowThreshold); } offset++; } } } void edgeLink(int width, int height,int x1, int y1, int index, float threshold) { int x0 = (x1 == 0) ? x1 : x1 - 1; int x2 = (x1 == width - 1) ? x1 : x1 + 1; int y0 = y1 == 0 ? y1 : y1 - 1; int y2 = y1 == height -1 ? y1 : y1 + 1; data[index] = magnitudes[index]; for (int x = x0; x <= x2; x++) { for (int y = y0; y <= y2; y++) { int i2 = x + y * width; if ((y != y1 || x != x1) && data[i2] == 0 && magnitudes[i2] >= threshold) { edgeLink(width, height,x, y, i2,threshold); return; } } } }
5、图像二值化
void arrayToImg(int width, int height,int bitcount)//二值化 { DWORD dwLineBytes=GetLineBytes(width,bitcount); int k=0; BYTE s; for(int i=0;i<height;i++) { for(int j=0;j<width*3;j++) { s=data[k]; *(imgData+dwLineBytes*(height-1-i)+j)=data[k]; j++; *(imgData+dwLineBytes*(height-1-i)+j)=data[k];; j++; *(imgData+dwLineBytes*(height-1-i)+j)=data[k];; k++; } } }