1.用sober算子,对图像进行卷积,算出图像在x,y方向的梯度。
由此可以算出,每一个像素点都会对应一个x方向的梯度和y方向的梯度。合一起就可以算出在该点处的梯度G和梯度方向θ。这里也对应着之前我们为什么要先对图像进行平滑处理,平滑处理有助于消除噪声点对我们梯度的影响。这样我们算出来有梯度大地方就是图像的边缘。
2.非极大值抑制
这里考虑到如果按梯度的大小来判断边界的话,在边界上的多个像素点都会被识别为边界,最后就会产生很粗的黑线。所以通过非极大值抑制的方法选出梯度里面的那个极大值作为我们的边界点。
这里如何判断梯度值是否为极大值呢?
我们前面已经算出θ,即梯度的方向,我们只需要判断梯度方向上相邻的两个点的大小与该点进行比较,即可判断出是否为极大值了。因为梯度方向不一定会经过相邻的8个点中的两个点,所以这里还会涉及到插值,这里不做叙述。可以参考入如下:
图片来自:opencv目标检测之canny算法
从边界的起始到边界的结束,有几个极值点,那么就会产生对应个数的边界点。这样就实现了对边缘进行细化的效果。
3.双阈值检测和边缘连接
我们得到边界以后,我们需要一个最低梯度的阈值Gmin,如果小于Gmin,我们就不识别为边界。
还需要一个Gmax,如果大于Gmax,我们就是识别为边界。
那在Gmin~Gmax之间的梯度点呢?这里我们是为了尽量避免某些点,他属于边界点,但是被限制在了Gmax之下而没有显示。这里如果点在Gmin到Gmax之间,而且他的旁边有边间点的话,我们就把他当做为边界点。
# 双边高斯滤波:
void cv::bilateralFilter ( InputArray src,
OutputArray dst,
int d,
double sigmaColor,
double sigmaSpace,
int borderType = BORDER_DEFAULT
)
Python:
dst = cv.bilateralFilter( src, d, sigmaColor, sigmaSpace[, dst[, borderType]] )
# 高斯滤波
void cv::GaussianBlur ( InputArray src,
OutputArray dst,
Size ksize,
double sigmaX,
double sigmaY = 0,
int borderType = BORDER_DEFAULT
)
Python:
dst = cv.GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] )
# canny边缘检测
void cv::Canny ( InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false
)
Python:
edges = cv.Canny( image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]] )
edges = cv.Canny( dx, dy, threshold1, threshold2[, edges[, L2gradient]] )
通过如上的步骤,我们就提取出了边界,我们来看一下程序吧。
void Demo::edge_demo(Mat src)
{
Mat dst1, dst2;
cvtColor(src, src, COLOR_BGR2GRAY);
imshow("原图", src);
src.copyTo(dst1);
src.copyTo(dst2);
bilateralFilter(src, dst1, 0, 10, 10);//高斯滤波
GaussianBlur(src, dst2, Size(5, 5), 10);//双边高斯滤波
Canny(dst1, dst1, 40, 45, 3, false);
Canny(dst2, dst2, 25, 30, 3, false);
imshow("双边高斯", dst1);
imshow("单边高斯", dst2);
waitKey(0);
}
如下是通过高斯滤波和双边高斯滤波参产生的灰度图,可以明显看到双百边高斯滤波对边缘细节保留更完整。
如下是canny边缘检测后结果,可以看出来在两张图清晰度大致一致的时候,双边滤波后进行canny检测,保留的细节更多。