边缘检测的方法有主要有两种:
一种是基于模板匹配的,简单来说即用多个方向的检测模板与图像进行卷积,然后取邻域像素点的最大值。这种方法简单方便,容易理解,容易得到平滑的边缘,但是由于采用非常有限方向的模板(一般是8个方向,但是由于对称性可以减少为4个),因此不能检测出所有的边缘方向。
一种是基于求图像梯度的方法,一阶二阶的梯度算子,如sobel、prewitt、Robert等一阶算子和拉普拉斯算子。检测的灵敏度高,精度定位准确,但同时容易受到噪声点的影响。
kirsch算子属于模板匹配算子,采用八个模板来处理图像的检测图像的边缘,运算量比较大。
8个3x3模板如下:
通过矩阵变换发现经过kirsch算子得到的像素值直接的关系,事实上需要直接由邻域像素点计算得到的只有p0,,因此可以大大减少计算量。
//kirsch算子滤波
void kirsch(const IplImage* src, CvMat* dst)
{
if (src->nChannels != 1 && dst->type != 1)
{
cvError(CV_BadNumChannels, "kirsch", "The channels of image must be 1" , __FILE__, __LINE__);
return;
}
if (src->width != dst->width || src->height != dst->height)
{
cvError(CV_StsUnmatchedSizes, "kirsch", "The sizes of input/output must be same" , __FILE__, __LINE__);
return;
}
//保存当前像素的八个模板滤波的结果
vector<double > r(8);
vector<double > p(8);
CvMat* temp = cvCreateMat(src->height + 2, src->width + 2, CV_64FC1);
CvMat* src_copy = cvCreateMat(src->height, src->width, CV_64FC1);
cvConvertScale(src, src_copy);
cvCopyMakeBorder(src_copy, temp, cvPoint(1, 1), IPL_BORDER_REPLICATE);
for (int y = 1; y != temp->rows - 1; ++y)
{
for (int x = 1; x != temp->cols - 1; ++x)
{
//8个邻域像素值
p[0] = CV_MAT_ELEM(*temp, double , y - 1, x - 1);
p[1] = CV_MAT_ELEM(*temp, double , y - 1, x);
p[2] = CV_MAT_ELEM(*temp, double , y - 1, x + 1);
p[3] = CV_MAT_ELEM(*temp, double , y, x + 1);
p[4] = CV_MAT_ELEM(*temp, double , y + 1, x + 1);
p[5] = CV_MAT_ELEM(*temp, double , y + 1, x);
p[6] = CV_MAT_ELEM(*temp, double , y + 1, x - 1);
p[7] = CV_MAT_ELEM(*temp, double , y, x - 1);
r[0] = 0.625*(p[0] + p[1] + p[2]) - 0.375*(p[3] + p[4] + p[5] + p[6] + p[7]);
r[1] = r[0] + p[7] - p[2];
r[2] = r[1] + p[6] - p[1];
r[3] = r[2] + p[5] - p[0];
r[4] = r[3] + p[4] - p[7];
r[5] = r[4] + p[3] - p[6];
r[6] = r[5] + p[2] - p[5];
r[7] = r[6] + p[1] - p[4];
//排序
std::sort(r.begin(), r.end());
//if (r[1] != 0)
//{
// cout<<"r1 != 0"<<endl;
//}
*(( double*)CV_MAT_ELEM_PTR(*dst, y - 1, x - 1)) = 8*r[7];
}
}
cvReleaseMat(&src_copy);
cvReleaseMat(&src_copy);
}
由lena得到的边缘图像: