本博客内容来源于网络以及其他书籍,结合自己学习的心得进行重编辑,因为看了很多文章不便一一标注引用,如图片文字等侵权,请告知删除。
学习笔记目录----->传送门 <-----
写在前面的废话
这是我开始写博客的第6篇了,这段时间以写博客为动力,确确实实的把一些知识又夯实了一遍。虽然写博客有的时候占用了一些工作时间,但是也相对磨炼了自己做事情的耐心,工作上也更有条理了一些。既然上篇我们描述了形态学处理,我们接着形态学处理来学习一下边缘检测吧,边缘检测也属于形态学处理的一部分。
边缘检测简介
边缘检测当然就是检测图像的上的边缘,但是什么是边缘,边缘又有何特点,接下来一一道来。
边缘就是图像上灰度或者颜色变化很大的一系列连续的点。或者说是图像上不同的区域之间的交界处。
图像中边缘的特点就要从这两方向去分析:方向和幅度。在沿着边缘走向的像素值变化比较平缓;而沿着垂直于边缘的走向,像素值则变化得比较大。
边缘检测常见方法
我们要研究边缘检测的方法,就要从它的特点入手。是否是边缘可以说是来判断一个点在某个方向上的变化是否剧烈。那么我们怎么用数学的方式去描述这种变化特点呢。在数学上,我们一般使用导数或者微分。
- 导数: 这个很简单,就是连续函数上某点斜率,导数越大表示变化率越大,变化率越大的地方就越是“边缘”。但是,但斜率接近90度的时候,他的斜率就无限大了,在计算机计算的时候就很麻烦了,首先占用空间大,然后就是当斜率过大的时候便无法用常用的数据类型表示了。所以我们一般不用导数来表示
- 微分: 这个概念需要在大学的时候结束,但也很简单。连续函数上x变化了dx,导致y变化了dy,dy/dx 越大,就可以表示变化率很大了。dx趋向于无限小,dy/dx 就是x在该函数上的导数,所以dy/dx就可以来近似导数,我们成这种方式叫做微分。那这种方式有什么优势呢?当我们固定dx,比较不同点的变化率时只用比较dy就好了,所以计算整幅图像的微分,dy的大小就是边缘的强弱了,我们也称之为梯度。所以我们一般会采用微分的方式。
现在我们决定是用微分的方式来判断图像上点的变化率了。那么在实际计算的时候我们算呢,我们是不是还是发现其实还是在计算一个点和周围点的关系来得出一个数值,这跟我们形态学里面说的滤波是不是一回事,对就是一回事。那么我们就可以使用滤波来完成变化率的计算了。接下里的问题就是我们采用什么样的核来进行滤波了,该怎么滤波了。下面我们来罗列一下,然后在下一节中详细解释其中一些。
算子是一个函数空间到另一个函数空间上的映射,表示的是算法中的具体计算方法或者映射方法。习惯在滤波算法使用不同的滤波核,称该算法为某某算子。
类型 | 具体算子 |
---|---|
一阶边缘检测 | Roberts 算子、Prewitt算子、Sobel算子 、canny算子 |
二阶边缘检测 | Laplacian算子、Log算子/Marr算子 |
其他类型 | Spacelk算子、Petrou算子、Susan算子、基于机器学习方法 |
边缘检测算法详解
上面我们罗列了一些常见的边缘检测算法,下面我们详细介绍其中用的比较多的几个Roberts 算子,Prewitt算子, Sobel算子,Canny算子,Laplacian算子。
Roberts 算子
Roberts 算子是第一个边缘检测算子,由Lawrence Roberts in 1963提出,所以也比较简单,我们直接看其滤波核:通过该滤波核,可以计算对角线上而不是坐标轴上的两个像素的微分,从而筛选边缘,选择对角线主要是因为使用标准的一阶差分,会丢失一些角点,具体分析大家可以自己举例分析一下。
Roberts算子是一个非常简单的算子,对具有低噪声的图片效果较好,但是提出的边缘比较粗,所以定位不是很准确。(效果在实现展示章节)
Prewitt算子
Prewitt算子利用像素点上下、左右的临近点的像素差来近似微分,我们直接看滤波核:Prewitt算子有一定的抗噪能力,但这种抗噪能力使用过平均周围像素来实现的,所以对图像有一定的模糊,但是定位精度比Roberts高。
Sobel算子
和Prewitt一样,Sobel算子也是用周围8个像素来估计中心像素的梯度,但是Sobel算子认为靠近中心像素的点应该给予更高的权重,所以Sobel算子把与中心像素4邻接的像素的权重设置为2或-2。如下图:Sobel算子相比较于Roberts算子和Prewitt算子稍好,但是并不能将图像的主体和背景区分开,所以还是有比较高的错误率。当要求不是很高的时候,这也是一个比较好的方法。(效果在实现展示章节)
Canny算子
Canny算子更是一个多步计算的方法,不像上面几种算子一次就能算出值来,我们来看一下它的计算流程。
- 使用高斯滤波器,以平滑图像,滤除噪声。
- 边缘检测过程中需要检测的图片边缘属于变化比较大的信息,也称作高频信息。而图片中噪声部分也属于高频信息,因此我们需要对图像进行去噪处理,滤掉这些噪点。
- 计算图像中每个像素点的梯度强度和方向。
- 计算像素梯度的幅值以及方向,常用的算子有Roberts,Sobel,计算水平及垂直方向的差分。找出梯度较大的区域,这部分区域属于图像增强的区域,此时得到的边缘信息比较粗大。
- 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
- 非极大值抑制主要目的是边缘细化,在梯度值比较大的地方沿着梯度方向,找到像素点的局部最大值,并将非最大值抑制(抑制就是将其弱化吧)。
- 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
- 所谓双阀值方法,设置一个最大阈值,以及最小阈值,梯度大于最大阈值则为强边缘,梯度值介于最大阈值与最小阈值则为弱边缘点,小于最小阈值为抑制点。这样做的目的主要是再下一步保留更多正确的边缘,删除更多错误的边缘。
- 通过抑制孤立的弱边缘最终完成边缘检测。
- 这一步主要处理弱边缘点。由于边缘是连续的,因此可以认为弱边缘如果为真实边缘,就会和强边缘联通。因此通过判断是否与强边缘是否联通,是则保留为边缘,否则,认为不是边缘点。
Canny 算子是一种既能抗噪又能排除弱边缘的算子,而能能比较好的保持边缘的细节,是目前比较的好提取方法。Canny 算子提取的边缘轮廓清晰,而且封闭性好,不易受误差影响。缺点嘛,也是其他都共有的,一个就是,当边缘与背景颜色相近的时候效果不好,还有就是需要调到比较合适的参数,效果才会很好。
Laplacian算子
拉普拉斯算子是用二阶差分计算边缘的,和一阶的分析以上,就是对一阶差分的结果又做了一个一阶差分,看连续函数的情况下
- 在一阶微分图中极大值或极小值处,认为是边缘。
-
在二阶微分图中极大值和极小值之间的过 0 点,被认为是边缘。
写成公式为:
那么:
Laplacian算子对噪声也很敏感,会容易丢失一部分边缘的方向信息,造成边缘不连续。
我们先简单介绍这么多算子,其他的我们有机会再整理成文。看了这么多,我们来看一下检测的效果。
OpenCV 边缘检测效果展示[代码]
#include <opencv2/opencv.hpp>
#include <iostream>
cv::Mat roberts(cv::Mat srcImage)
{
cv::Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows - 1; i++){
for (int j = 0; j < nCols - 1; j++){
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1))*
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1));
int t2 = (srcImage.at<uchar>(i+1, j) -
srcImage.at<uchar>(i , j + 1))*
(srcImage.at<uchar>(i+1, j) -
srcImage.at<uchar>(i , j + 1));
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
}
}
return dstImage;
}
int main(int argc, char *argv[])
{
{
cv::Mat orignal_image = cv::imread (argv[1]);
cv::Mat binary_image,gray_image, roberts_edge_image,sobel_edge_image,canny_edge_image,laplacian_edge_image;
cv::cvtColor(orignal_image, gray_image, cv::COLOR_BGR2GRAY);
//canny
{
blur(gray_image, canny_edge_image, cv::Size(3, 3));
cv::Canny(canny_edge_image, canny_edge_image, 50, 100, 3);
}
//sobel
{
cv::Mat grad_x,grad_y,abs_grad_x,abs_grad_y;
Sobel(orignal_image, grad_x, CV_16S, 1, 0, 3, 1, 1, cv::BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
Sobel(orignal_image, grad_y, CV_16S, 0, 1, 3, 1, 1, cv::BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, sobel_edge_image);
}
//laplacian
{
GaussianBlur(gray_image, gray_image, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);
Laplacian(gray_image, laplacian_edge_image, CV_16S, 3, 1, 0, cv::BORDER_DEFAULT);
convertScaleAbs(laplacian_edge_image, laplacian_edge_image);
}
//roberts
{
roberts_edge_image = roberts(gray_image);
}
cv::imwrite("roberts_edge_image.png",roberts_edge_image);
cv::imwrite("sobel_edge_image.png",sobel_edge_image);
cv::imwrite("canny_edge_image.png",canny_edge_image);
cv::imwrite("laplacian_edge_image.png",laplacian_edge_image);
}
return 0;
}
代码解释不动了,手都写累了。。。。。。直接上一个效果吧,先贴上萌萌哒的原图roberts | sobel |
---|---|
canny | laplacian |
本片先写这么多,如果有什么不清楚的,请您评论,小生定当全力修改添加更多的配图。
重要的事情说三遍:
如果您看到我的文章对您有所帮助,那就点个赞呗 ( * ^ __ ^ * )
如果您看到我的文章对您有所帮助,那就点个赞呗( * ^ __ ^ * )
如果您看到我的文章对您有所帮助,那就点个赞呗( * ^ __ ^ * )
任何人或团体、机构全部转载或者部分转载、摘录,请保留本博客链接或标注来源。博客地址:开飞机的乔巴
作者简介:开飞机的乔巴(WeChat:zhangzheng-thu),现主要从事机器人抓取视觉系统以及三维重建等3D视觉相关方面,另外对slam以及深度学习技术也颇感兴趣,欢迎加我微信或留言交流相关工作。