canny算子共分四步:高斯滤波-》求梯度-》非最大抑制-》用双阈值法检测和连接边缘。
canny四部曲将详细分析各个步骤,并且附上每个步骤的源码。
第一个步骤为高斯滤波。高斯函数的傅立叶变换也是高斯函数,高斯函数可看成一个低通滤波器,可以去除高斯噪声。对图像来说,常用二维零均值离散高斯函数作平滑滤波器,函数表达式如下:
式(1)
高斯函数具有可分离性,因此高斯滤波器可以有效实现。通过二维高斯函数的卷积可以分两步来进行,首先将图像与一维高斯函数进行卷积,然后将卷积的结果与方向垂直的相同一维高斯函数进行卷积。因此,二维高斯滤波的计算量随滤波模板宽度成线性增长而不是成平方增长。高斯函数的可分离性很容易表示:
式(2)
式(3)
式(4)
于是在高斯滤波器的设计中,二维高斯函数的卷积运算可以通过使用单个一维高斯模板,对两次卷积之间的图像和最后卷积的结果图像进行转置来完成。
以下是产生一维高斯模板的函数:
// 一维高斯分布函数,用于平滑函数中生成的高斯滤波系数
/*
* @parameter sigma: 高斯函数参数
* @parameter pdKernel: 高斯核函数模板
* @parameter pnWidowSize: 高斯模板大小
*/
void CreatGauss(double sigma, double **pdKernel, int *pnWidowSize)
{
LONG i;
int nCenter;//数组中心点
double dDis;//数组中一点到中心点距离
//中间变量
double dValue;
double dSum;
dSum = 0;
*pnWidowSize = 1+ 2*ceil(3*sigma);// [-3*sigma,3*sigma] 以内数据,会覆盖绝大部分滤波系数
nCenter = (*pnWidowSize)/2;
//生成高斯数据
for(i=0;i<(*pnWidowSize);i++)
{
dDis = (double)(i - nCenter);
dValue = exp(-(1/2)*dDis*dDis/(sigma*sigma))/(sqrt(2*3.1415926)*sigma);
(*pdKernel)[i] = dValue;
dSum+=dValue;
}
//归一化
for(i=0;i<(*pnWidowSize);i++)
{
(*pdKernel)[i]/=dSum;
}
}
以下函数调用一阶函数模板,对原图想分水平垂直两次卷积实现高斯平滑:
//用高斯滤波器平滑原图像
/*
* @parameter sz : 图像尺寸
* @parameter pGray : 图像灰度值
* @parameter pResult: 图像
* @parameter sigma: 高斯函数参数
*/
void GaussianSmooth(SIZE sz, LPBYTE pGray, LPBYTE pResult, double sigma)
{
LONG x, y;
LONG i;
int nWindowSize;//高斯滤波器长度
int nLen;//窗口长度
double *pdKernel;//一维高斯滤波器
double dDotMul;//高斯系数与图像数据的点乘
double dWeightSum;//滤波系数总和
double *pdTemp;
nWindowSize = 1+ 2*ceil(3*sigma);// [-3*sigma,3*sigma] 以内数据,会覆盖绝大部分滤波系数
if ((pdTemp = (double *)malloc(sz.cx*sz.cy*sizeof(double)))==NULL)
{
printf("melloc memory for pdTemp failed!!");
exit(0);
}
if ((pdKernel = (double *)malloc(nWindowSize*sizeof(double)))==NULL)
{
printf("malloc memory for pdKernel,failed!!");
exit(0);
}
//产生一维高斯数据
CreatGauss(sigma, &pdKernel, &nWindowSize);
nLen = nWindowSize/2;
//x方向滤波
for(y=0;y<sz.cy;y++)
{
for(x=0;x<sz.cx;x++)
{
dDotMul = 0;
dWeightSum = 0;
for(i=(-nLen);i<=nLen;i++)
{
//判断是否在图像内部
if((i+x)>=0 && (i+x)<sz.cx)
{
dDotMul+=(double)(pGray[y*sz.cx+(i+x)] * pdKernel[nLen+i]);
dWeightSum += pdKernel[nLen+i];
}
}
pdTemp[y*sz.cx+x] = dDotMul/dWeightSum;
}
}
//y方向滤波
for(x=0; x<sz.cx;x++)
{
for(y=0; y<sz.cy; y++)
{
dDotMul = 0;
dWeightSum = 0;
for(i=(-nLen);i<=nLen;i++)
{
if((i+y)>=0 && (i+y)< sz.cy)
{
dDotMul += (double)pdTemp[(y+i)*sz.cx+x]*pdKernel[nLen+i];
dWeightSum += pdKernel[nLen+i];
}
}
pResult[y*sz.cx+x] = (unsigned char)dDotMul/dWeightSum;
}
}
free(pdTemp);//释放内存
free(pdKernel);
}
以下是用到的一些type:
typedef struct {
int cy;
int cx;
} SIZE;
typedef unsigned char *LPBYTE;
typedef long LONG;
canny边缘检测之二(获取梯度)将很快送到。