我自己用的图像检测的方法,最主要的操作只有两个:canny边缘检测和hough直线检测。这些都是前人早就实现的算法,十分成熟,但是考虑到以后希望将算法在不同的硬件上实现,一方面用C自己编写有利于深入了解算法原理(当然直接matlab转dsp、FPGA也是一种能力,这是后话),另一方面将来硬件实现需要考虑存储的分配,自己编写的话肯定对存储的使用情况了如指掌。
canny边缘检测的实现大致有4步:1.高斯滤波对图像去噪。2.由原始灰度图求出纵横2个梯度图,以及综合梯度图(求梯度的算子很多)。3.结合3个梯度图来进行非极大抑制(此步一过,检测的图像边缘已经很细了)4.进行边缘连接(个人感觉这一步虽叫连接,算法也确实体现了连接的行为,但那是强边缘到弱边缘的连接,所有可能连接的点都出不了非极大抑制后的范围,所以这一步更准确的目的应该是在非极大抑制的结果中去除假边缘保留真边缘,同时又能让保留的边缘尽量连贯真实)5.对边缘进行细化(这步在matlab的canny算法中有,但是考虑到通过以上4步尤其是第3步得到的边缘已经很细,一般没有再对边缘进行细化的必要,因此,此步可以省略)
上述4步中,前两步比较简单,实现的关键在于后两步。这里对第二步也概括的说一下,求纵横梯度的算子是很多的,像3×3的sobel算子,其卷积核是[1,0,-1;1,0,-1;1,0,-1]和[1,1,1;0,0,0;-1,-1,-1];2×2的robert算子,其卷积核是[1,-1;1,-1]和[1,1;-1,-1]。而我们学习matlab中canny检测的算法,在第一步滤波和第二步求纵横梯度时用的都是高斯模版。注意,滤波的模版系数之和为1,求梯度的模版系数之和为0,这是卷积模版的一个规律。我们滤波和求梯度的模版都是二维的高斯模版,但是用纵横2次两个一维的高斯卷积模版进行卷积可以达到和一个二维模版一样的效果,同时减小了总计算量,所以,在第一步滤波的时候我们用两个一维高斯模版代替一个二维高斯模版,但是第二步的高斯模版牵扯到梯度方向,所以还是用一个二维的高斯模版来直接卷积。
第3步的非极大抑制要用到第2步得到的3个梯度图,其实第2步的“综合梯度图”已经可以看出图像边缘的轮廓了,只是边缘较粗且我们还没有对此梯度图上的边缘强度进行二值化(因为后面还有双阈值),这里的边缘含有大量的“假边缘”,它们的灰度值可能不如真边缘大,但是一个渐近的变化过程,非极大抑制的目的就是结合“梯度方向”和“梯度强度”去除这些“假边缘”。
第4步的边缘连接,其实是强边缘到弱边缘的连接,我们经过第3步,得到一个二值化的“非极大抑制后的边缘图”,它和第二步得到的“综合梯度图”不同,一是去掉了大量伪边缘,二是它已经二值化了边缘灰度是统一的。这时,为什么我们还需要第4步?我在上面开始时的总结处说是为了进一步去除假边缘,这是有道理的,只不过这里的“假边缘”和第三步要去的“假边缘”性质不同,第3步处的假边缘可以理解成是真正依附在真边缘周围的假边缘(这样的边缘在第3步后就剩下很细的真边缘),甚至是依附在假边缘周围的假边缘(这样的边缘全是假边缘,经过第3步还会剩下一部分假边缘),两者中的后者正是第4步要处理的,第4步最重要的参数是两个自适应的阈值,所以说第4步对假边缘的鉴定是根据其灰度是否太弱来确定的(小于强阈值),而不是像第3步那样,鉴定的依据是其灰度值与周围灰度值的比较。在经由强阈值确定了真边缘后,再利用弱阈值对真边缘进行连接处理,以保证边缘的顺滑真实。注意,第4步的“连接方法”是自定义一个“染色的嵌套函数”来对一个点的8邻域点进行染色操作,这种编程方法对图像中具有相同性质(灰度值等)的联通区域进行染色操作是很有用的。
附录:(一些关键代码)
第一步的滤波去噪,用两个一维的模版,实现一个二维模版的作用
for(i=0;i<5;i++){
conv[i]=exp(-(i-2)*(i-2)/(2*sigma*sigma))/(sqrt(2*3.1415926)*sigma);
sum+=conv[i];
}
for(i=0;i<5;i++)
conv[i]=conv[i]/sum;
for(i=0;i<height;i++)
for(j=0;j<width;j++){
v=0;
for(m=0;m<5;m++)
if(i-m+2>=0&&i-m+2<height)
v+=conv[m]*((float)indata[i-m+2][j]);
outdata[i][j]=(int)v;
}
for(i=0;i<height;i++)
for(j=0;j<width;j++){
v=0;
for(n=0;n<5;n++)
if(j-n+2>=0&&j-n+2<width)
v+=conv[n]*((float)outdata[i][j-n+2]);
indata[i][j]=(int)v;
}
第二步的求纵横梯度图,matlab中就用了二维高斯求梯度模版
for(i=0;i<5;i++)
for(j=0;j<5;j++){
convv[i][j]=(2-j)*exp(-((i-2)*(i-2)+(j-2)*(j-2))/(2*sigma*sigma))/(sigma*sigma*3.1415926);
} //j和x
for(i=0;i<height;i++)
for(j=0;j<width;j++){
v=0;
for(m=0;m<5;m++)
for(n=0;n<5;n++)
if(i-m+2>=0&&i-m+2<height&&j-n+2>=0&&j-n+2<width)
v+=convv[m][n]*((float)indata[i-m+2][j-n+2]);
outdata[i][j]=(int)v;
}
第三步是进行非极大抑制,我们的思路和matlab也是完全相同,利用了“梯度方向”和相关方向上的“梯度强度”,八邻域分区如下:
% The X marks the pixel in question, and each
% 3 2 of the quadrants for the gradient vector
% O----0----0 fall into two cases, divided by the 45
% 4 | | 1 degree line. In one case the gradient
% | | vector is more horizontal, and in the other
% O X O it is more vertical. There are eight
% | | divisions, but for the non-maximum suppression
% (1) | |(4) we are only worried about 4 of them since we
% O----O----O use symmetric points about the center pixel.
% (2) (3)
第4步是边缘连接,用到强弱阈值,牵涉到直方图自适应;另外就是前面说到的“一定条件域内染色的嵌套函数”
自适应阈值
convvv = (int *) malloc((k+1)*sizeof(int));
for(i=1;i<=k;i++)
convvv[i]=0;
for(i=0;i<height;i++)
for(j=0;j<width;j++)
if(indata[i][j]==254)
convvv[fdata[i][j]]++;
sum=0;
for(i=1;i<=k;i++)
sum+=convvv[i];
v=0;
for(i=1;i<=k;i++){
v+=convvv[i];
if(v>=0.85*sum){
high=i;
break;
}
}
low=(int)(0.4*((float)high));
free(convvv);
“一定条件域内染色的嵌套函数”
trace(int low,int x,int y,unsigned char ** indata,int **fdata,long width,long height)
{
int i,j;
for(i=-1;i<2;i++)
for(j=-1;j<2;j++)//这里没考虑图像4边界情况,因为边界灰度都事先设为0。不可能是254
if(indata[x+i][y+j]==254&&fdata[x+i][y+j]>low){
indata[x+i][y+j]=255;
trace(low,x+i,y+j,indata,fdata,width,height);
}
}