1、前言
边缘检测:
图像边缘检测是指在图像中寻找灰度、颜色、纹理等变化比较剧烈的区域,它们可能代表着物体之间的边界或物体内部的特征。边缘检测是图像处理中的一项基本操作,可以用于人脸识别、物体识别、图像分割等多个领域。
边缘检测实质上是计算当前点和周围点灰度的差别。
图像边缘检测流程主要分为以下几个步骤:
(1)读取待处理图像;
(1)图像滤波,例如使用高斯滤波器,平滑图像,去除噪声;
(2)计算图像中每个像素点的梯度强度和方向;
(3)应用非极大值抑制(Non-Maximum Suooression),保留梯度方向上的局部最大值,抑制非边缘点,消除边缘检测带来的杂散响应;
(4)应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘,即将梯度幅值映射到两个阈值,根据梯度值高于高阈值或在高低阈值之间的情况,将像素标记为强边缘、弱边缘或非边缘。
(5)边缘连接,通过连接相邻的强边缘像素和与之相连的弱边缘像素,形成最终的边缘图像;
(6)显示结果。
在介绍各种边缘检测算子之前先简单阐述下怎么寻找边缘。
下面左图是一张黑白相间的图,右图是左图的每个像素的灰度值
我们设定一个卷积核如下(关于卷积的介绍请看之前的文章):
原图在通过卷积核进行卷积计算后得到的图像如下:
可以看到原图在卷积运算后黑色向白色突变的边缘被很好的保留了下来,因此可以通过这个卷积核找到图像中垂直的边缘。
同理,如果我们用下面的卷积核也可以找到图像中水平的边缘。
卷积运算后:
2、Prewitt算子边缘检测
如果我们把上面两个卷积核组合起来再对图像进行卷积便可以同时找到图像中水平和垂直的边缘,这种卷积核就是prewitt算子。
标准的 Prewitt 边缘检测算子由以下两个卷积核组成。
下面是用prewitt算子进行边缘检测的案例:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main() {
// 读取图像
Mat image = imread("your_image.jpg", IMREAD_GRAYSCALE);
// 定义Prewitt算子
Mat prewitt_x = (Mat_<float>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
Mat prewitt_y = (Mat_<float>(3, 3) << -1, -1, -1, 0, 0, 0, 1, 1, 1);
// 对图像应用Prewitt算子
Mat edges_x, edges_y;
filter2D(image, edges_x, CV_64F, prewitt_x);
filter2D(image, edges_y, CV_64F, prewitt_y);
// 计算梯度幅值和方向
Mat gradient_magnitude, gradient_direction;
magnitude(edges_x, edges_y, gradient_magnitude);
phase(edges_x, edges_y, gradient_direction, true);
// 归一化梯度幅值
cv::normalize(gradient_magnitude, gradient_magnitude, 0, 1, cv::NORM_MINMAX);
// 显示结果
imshow("Original Image", image); //原灰度图
imshow("Gradient Magnitude", gradient_magnitude); //prewitt算子边缘检测图
waitKey(0);
destroyAllWindows();
return 0;
}
代码解读:
(1)在代码中我们先分别定义一个水平方向和垂直方向的prewitt算子edges_x和edges_y
(2)filter2D是对图像进行卷积操作,即获取prewitt算子与原图像卷积后的图像edges_x和edges_y
(3)magnitude 函数的主要用途是计算两个输入数组的逐元素平方和的平方根。在图像处理中,常常用于计算图像中每个像素点的梯度幅值。相位(Phase)在图像处理中通常指的是梯度的方向(边缘方向)。在梯度计算中,梯度向量的方向表示图像在该点上灰度变化最快的方向。在梯度计算中,通常使用 magnitude 函数计算梯度的幅值,使用 phase 函数计算梯度的方向。这两个信息一起构成了梯度向量,提供了有关图像局部变化的重要信息。
(4)最后归一化梯度幅值图像,因为64位图像显示范围为0-1。
最后效果如下(左边是原灰度图,右边是边缘检测出的图像):
3、Roberts算子
常用来处理具有陡峭的低噪声图像,当图像边缘接近于正45度或负45度时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。
下图左边为水平方向Roberts算子,也称正对角算子。右边为垂直方向Roberts算子,也称斜对角算子。
下面是Roberts算子的使用案例:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main() {
// 生成一个简单的图像
cv::Mat image = cv::Mat::zeros(100, 100, CV_8U);
cv::rectangle(image, cv::Rect(20, 20, 60, 60), cv::Scalar(255), cv::FILLED);
// 定义Sobel算子
cv::Mat sobel_x = (cv::Mat_<float>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
cv::Mat sobel_y = (cv::Mat_<float>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
// 应用Sobel算子
cv::Mat edges_x, edges_y;
cv::filter2D(image, edges_x, CV_64F, sobel_x);
cv::filter2D(image, edges_y, CV_64F, sobel_y);
// 计算梯度幅值和方向
cv::Mat gradient_magnitude, gradient_direction;
cv::magnitude(edges_x, edges_y, gradient_magnitude);
cv::phase(edges_x, edges_y, gradient_direction, true); // true 表示计算角度的弧度值
// 归一化梯度方向到[0, 1]范围
cv::normalize(gradient_direction, gradient_direction, 0, 1, cv::NORM_MINMAX);
// 显示结果
cv::imshow("Original Image", image);
cv::imshow("Gradient Magnitude", gradient_magnitude);
cv::imshow("Gradient Direction", gradient_direction);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
最后效果如下(左边是原灰度图,右边是边缘检测出的图像):
4、Sobel算子边缘检测
Sobel算子在Prewitt算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。但Sobel算子并不是基于图像灰度进行处理的,因为Sobel算子并没有严格地模拟人的视觉生理特性,因此图像轮廓的提取有时并不能让人满意。当对精度要求不是很高时,Sobel算子是一种较为常用的边缘检测方法。
它的水平和垂直方向的卷积核如下:
接口说明:
void cv::Sobel(
InputArray src,
OutputArray dst,
int ddepth,
int dx,
int dy,
int ksize = 3,
double scale = 1,
double delta = 0,
int borderType = cv::BORDER_DEFAULT
);
src: 输入图像。可以是单通道(灰度图)或多通道图像。
dst: 输出图像,梯度的计算结果将存储在这里。
ddepth: 输出图像的深度,通常使用 CV_64F 或 CV_32F 表示。
dx: x方向上的导数阶数,设为1表示在水平方向上进行操作。
dy: y方向上的导数阶数,设为1表示在垂直方向上进行操作。
ksize: Sobel核的大小。默认为 3,表示一个 3x3 的核。通常使用奇数值。
scale: 可选的比例因子,用于调整梯度的幅值,也表示对比度。
delta: 可选的偏移值,用于调整输出图像的亮度。
borderType: 边界处理类型,可以使用 cv::BORDER_DEFAULT 或其他合适的边界处理标志。
Sobel算子边缘检测案例:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main() {
// 读取图像
cv::Mat image = cv::imread("your_image.jpg", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cerr << "Error: Could not read the image." << std::endl;
return -1;
}
// 应用Sobel算子
cv::Mat edges_x, edges_y;
cv::Sobel(image, edges_x, CV_64F, 1, 0, 3); // 1表示在水平方向上进行操作
cv::Sobel(image, edges_y, CV_64F, 0, 1, 3); // 1表示在垂直方向上进行操作
// 计算梯度幅值
cv::Mat gradient_magnitude;
cv::magnitude(edges_x, edges_y, gradient_magnitude);
// 归一化梯度方向到[0, 1]范围
cv::normalize(gradient_magnitude, gradient_magnitude, 0, 1, cv::NORM_MINMAX);
// 显示结果
cv::imshow("Original Image", image);
cv::imshow("Sobel Edges X", edges_x);
cv::imshow("Sobel Edges Y", edges_y);
cv::imshow("Gradient Magnitude", gradient_magnitude);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}