当前位置: 首页 > news >正文

OpenCV (C/C++) 中使用 Sobel 算子进行边缘检测

在 OpenCV (C++) 中使用 Sobel 算子进行边缘检测 🔪

边缘检测是图像处理和计算机视觉中的一项基础技术。它旨在识别图像中亮度发生剧烈变化或更正式地说是存在不连续性的点。这些剧烈变化通常对应于图像中物体的边界。Sobel 算子是一种广泛使用的一阶离散微分算子,用于计算图像强度函数梯度的近似值。本文将指导您如何在 C++ 中使用 OpenCV 的 Sobel 算子进行边缘检测。


理解 Sobel 算子

Sobel 算子使用两个 3 × 3 3 \times 3 3×3 的卷积核来近似计算图像在 x x x 方向(水平变化)和 y y y 方向(垂直变化)的偏导数。这两个核分别是:

Sobel G x G_x Gx (用于检测垂直边缘):

G x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] G_x = \begin{bmatrix} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \end{bmatrix} Gx= 121000+1+2+1

Sobel G y G_y Gy (用于检测水平边缘):

G y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \end{bmatrix} Gy= 10+120+210+1

当这些核与图像进行卷积时,会产生两个梯度图像: G x ( I ) G_x(I) Gx(I) 代表水平梯度(对垂直边缘敏感), G y ( I ) G_y(I) Gy(I) 代表垂直梯度(对水平边缘敏感)。

每个像素点 ( x , y ) (x, y) (x,y) 的梯度幅值可以通过以下方式近似计算:

∣ G ( x , y ) ∣ = G x ( I ) 2 + G y ( I ) 2 |G(x, y)| = \sqrt{G_x(I)^2 + G_y(I)^2} G(x,y)=Gx(I)2+Gy(I)2

或者,为了计算效率,有时也使用绝对值之和:

∣ G ( x , y ) ∣ = ∣ G x ( I ) ∣ + ∣ G y ( I ) ∣ |G(x, y)| = |G_x(I)| + |G_y(I)| G(x,y)=Gx(I)+Gy(I)

梯度的方向(边缘的朝向)可以估算为:

Θ ( x , y ) = arctan ⁡ ( G y ( I ) G x ( I ) ) \Theta(x, y) = \arctan\left(\frac{G_y(I)}{G_x(I)}\right) Θ(x,y)=arctan(Gx(I)Gy(I))

高梯度幅值表示强烈的强度变化,暗示存在边缘。


在 OpenCV (C++) 中实现 Sobel 边缘检测

OpenCV 提供了 cv::Sobel() 函数,可以方便地将 Sobel 算子应用于图像。

步骤:

  1. 包含头文件:包含必要的 OpenCV 头文件:opencv2/imgproc.hpp (用于图像处理函数如 Sobel) 和 opencv2/highgui.hpp (用于图像显示)。
  2. 加载图像:读取输入图像。通常将图像转换为灰度图是个好主意,因为边缘检测通常基于强度变化。
  3. 高斯模糊 (可选但推荐):在应用 Sobel 算子之前,通常会先对图像进行高斯模糊处理,以减少噪声对边缘检测结果的干扰。可以使用 cv::GaussianBlur()
  4. 应用 Sobel 算子:使用 cv::Sobel() 函数两次:一次用于 x x x 方向,一次用于 y y y 方向。
    • 你需要指定源图像、输出图像、输出图像的深度(通常建议使用 CV_16SCV_32F 来处理导数可能产生的负值和更大的动态范围,之后再转换类型), x x x 方向的导数阶数 (dx),以及 y y y 方向的导数阶数 (dy)。对于标准的 Sobel, G x G_x Gx 对应 dx=1, dy=0 G y G_y Gy 对应 dx=0, dy=1
    • ksize 参数指定了 Sobel 核的大小 (例如, 3, 5, 7)。
  5. 计算梯度幅值: 为了得到一个单一的边缘图,你可以计算梯度幅值。
    • 方法一:分别计算 G x G_x Gx G y G_y Gy 的绝对值,然后按权重相加,例如使用 cv::convertScaleAbs() 分别处理 G x G_x Gx G y G_y Gy,然后用 cv::addWeighted() 合并。
    • 方法二:直接使用 G x G_x Gx G y G_y Gy (可能是 CV_16SCV_32F 类型) 计算精确的幅值,例如使用 cv::magnitude(grad_x, grad_y, abs_grad);,然后将结果转换为 CV_8U
  6. 转换到可显示类型: 如果你使用了 CV_16SCV_32F 作为输出深度,你可能需要将得到的梯度图像转换为 8 位无符号整数类型 (CV_8U) 以便显示,通常使用 cv::convertScaleAbs() 函数,它会取绝对值并缩放结果。
  7. 显示/保存结果: 显示原始图像和检测到的边缘。

C++ 代码示例

以下是一个使用 OpenCV 进行 Sobel 边缘检测的 C++ 代码片段:

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>int main(int argc, char** argv) {if (argc != 2) {std::cout << "用法: " << argv[0] << " <图片路径>" << std::endl;return -1;}// 1. 加载源图像cv::Mat src = cv::imread(argv[1], cv::IMREAD_COLOR);if (src.empty()) {std::cerr << "错误: 无法加载图像 " << argv[1] << std::endl;return -1;}// 2. 转换为灰度图cv::Mat gray;cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);// 3. 高斯模糊 (可选,但通常推荐用于降噪)cv::Mat blurred;cv::GaussianBlur(gray, blurred, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);// 4. 计算 X 方向和 Y 方向的梯度cv::Mat grad_x, grad_y;cv::Mat abs_grad_x, abs_grad_y;int scale = 1;int delta = 0;int ddepth = CV_16S; // 使用16位有符号类型,避免计算梯度时溢出int ksize = 3;       // Sobel核的大小// X方向梯度// cv::Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, cv::BORDER_DEFAULT);cv::Sobel(blurred, grad_x, ddepth, 1, 0, ksize, scale, delta, cv::BORDER_DEFAULT);cv::convertScaleAbs(grad_x, abs_grad_x); // 计算绝对值并转换到8位无符号// Y方向梯度// cv::Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, cv::BORDER_DEFAULT);cv::Sobel(blurred, grad_y, ddepth, 0, 1, ksize, scale, delta, cv::BORDER_DEFAULT);cv::convertScaleAbs(grad_y, abs_grad_y); // 计算绝对值并转换到8位无符号// 5. 合并梯度 (近似梯度幅值)cv::Mat grad;cv::addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);// 或者,更精确的梯度幅值计算:// cv::Mat grad_magnitude;// cv::magnitude(grad_x, grad_y, grad_magnitude); // grad_x, grad_y 应该是 CV_16S 或 CV_32F// cv::convertScaleAbs(grad_magnitude, grad);// 6. 显示结果cv::imshow("原始图像", src);cv::imshow("灰度图", gray);cv::imshow("Sobel X 梯度", abs_grad_x);cv::imshow("Sobel Y 梯度", abs_grad_y);cv::imshow("Sobel 合并梯度", grad);cv::waitKey(0);cv::destroyAllWindows();return 0;
}

代码解释

  1. cv::imread(argv[1], cv::IMREAD_COLOR): 加载彩色图像。
  2. cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY): 将彩色图像转换为灰度图像,因为 Sobel 通常在单通道图像上操作。
  3. cv::GaussianBlur(gray, blurred, cv::Size(3,3), 0, 0, cv::BORDER_DEFAULT): 对灰度图像进行高斯模糊,以减少噪声。cv::Size(3,3) 是高斯核的大小。
  4. cv::Sobel(blurred, grad_x, ddepth, 1, 0, ksize, scale, delta, cv::BORDER_DEFAULT):
    • blurred: 输入图像 (推荐使用模糊后的图像)。
    • grad_x: 输出的 x x x 方向梯度图像。
    • ddepth: 输出图像的深度。CV_16S 用于存储可能为负的梯度值,并且范围比 CV_8U 更大,可以防止信息丢失。
    • 1: x x x 方向的导数阶数。
    • 0: y y y 方向的导数阶数。
    • ksize: Sobel 核的大小,通常为 3。也可以是 1, 5, 7。
    • scale: 可选的缩放因子,默认为1。
    • delta: 可选的增量值,在存储之前添加到结果中,默认为0。
    • cv::BORDER_DEFAULT: 边界处理方式。
      类似的参数用于计算 grad_y ( y y y 方向梯度),其中 dx=0, dy=1
  5. cv::convertScaleAbs(grad_x, abs_grad_x):
    • 此函数首先计算输入数组 (grad_x) 中每个元素的绝对值。
    • 然后将结果缩放(如果指定了 alphabeta,这里使用默认值 alpha=1, beta=0)。
    • 最后将结果转换为 8 位无符号类型 (CV_8U)。这对于显示梯度图像非常有用。
  6. cv::addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad):
    • 这是一种近似计算总梯度幅值的方法,通过将 x x x 方向和 y y y 方向梯度的绝对值按权重(这里是各0.5)相加。
    • 0.5abs_grad_x 的权重。
    • 0.5abs_grad_y 的权重。
    • 0 是加到和上的标量 (gamma)。
    • grad 是输出的合并梯度图像。
  7. cv::imshow()cv::waitKey(): 用于显示图像并等待用户按键。

或者,使用 cv::magnitude 计算精确梯度幅值
如果你想计算更精确的梯度幅值 G x 2 + G y 2 \sqrt{G_x^2 + G_y^2} Gx2+Gy2 ,可以这样做:

// ... 计算 grad_x 和 grad_y (类型为 CV_16S 或 CV_32F) ...
cv::Mat grad_magnitude;
cv::magnitude(grad_x, grad_y, grad_magnitude); // grad_x, grad_y 应该是 CV_16S 或 CV_32F 类型
cv::Mat final_grad;
cv::convertScaleAbs(grad_magnitude, final_grad); // 转换为 CV_8U 以便显示
cv::imshow("Sobel 精确梯度幅值", final_grad);

在这种情况下,grad_xgrad_y 应该是应用 cv::Sobel 后且未调用 cv::convertScaleAbs 时的结果(即 CV_16SCV_32F 类型)。


编译

要编译此 C++ 代码,你需要 g++ (或任何兼容 C++11 的编译器) 和已安装的 OpenCV。

g++ -o sobel_app sobel_edge_detection.cpp `pkg-config --cflags --libs opencv4` -std=c++11

(如果你的 OpenCV 版本较旧,或者 pkg-config 的设置不同,请使用 opencv 替换 opencv4)。

运行:

./sobel_app <你的图片路径.jpg>

总结

Sobel 算子是边缘检测中一种经典且有效的方法。通过分别计算图像在 x x x y y y 方向上的梯度,并将其组合起来,我们可以识别出图像中的边缘。OpenCV 提供了易于使用的函数来实现这一过程,使得开发者可以快速地在自己的应用中集成边缘检测功能。记住,在应用 Sobel 算子之前进行高斯模糊通常能获得更好的结果,因为它可以减少噪声的干扰。

相关文章:

  • Leetcode 3557. Find Maximum Number of Non Intersecting Substrings
  • 如何通过PHPMyadmin对MYSQL数据库进行管理?
  • MQTT-Vue整合
  • 精益数据分析(87/126):市场-产品契合度重构——现有产品寻找新市场的实战指南
  • R 语言科研绘图 --- 热力图-汇总
  • 《软件工程》第 2 章 -UML 与 RUP 统一过程
  • 第11章1 扩展 MySQL
  • Linux连接服务器全攻略:从基础到进阶
  • Hadoop架构与核心模块解析
  • hadoop纠删码基本原理
  • Vue3监听对象数组属性变化方法
  • Qwen-Agent的使用示例-天气查询
  • 记录 | Android TextView 中的滚动方向
  • 常见小问题(Open Folder as PyCharm Project)
  • 重构开发范式!飞算JavaAI革新Spring Cloud分布式系统开发
  • OpenGL Chan视频学习-7 How I Deal with Shaders in OpenGL
  • 打造现代 Web 服务的终极选择:轻量级 Rust HTTP 框架
  • MyBatis 核心组件剖析:架构、协作与源码解读
  • NLP学习路线图(八):常见算法-线性回归、逻辑回归、决策树
  • AI时代新词-Transformer架构:开启AI新时代的关键技术
  • 网站右侧悬浮代码/谷歌广告代运营
  • 永康做网站/关键词排名关键词优化
  • 武汉光谷做网站哪家好/沈阳网页建站模板
  • 网站建设排行公司/沈阳seo搜索引擎
  • 卖网店哪个平台可靠/网站优化外包费用
  • 郑州区块链数字钱包网站开发多少钱/外链代发