【OpenCv • c++】基础边缘检测算子 —— Sobel

简介: 【OpenCv • c++】基础边缘检测算子 —— Sobel

什么是边缘检测


边缘检测是图像处理与计算机视觉中最重要的技术之一,其目的是检测识别出图像中亮度变化剧烈的像素点构成的集合。图像边缘的正确检测对于分析图像中的内容、实现图像中物体的分割、定位等具有重要的作用。边缘检测大大减少了源图像的数据量,剔除了与目标不相干的信息,保留了图像重要的结构属性。


边缘检测算子是利用图像边缘的突变性质来检测边缘的,通常情况下边缘检测有以下三种类型。


一阶微分:以一阶微分为基础的边缘检测,通过计算图像的梯度值来检测图像边缘,如Sobel算子,Prewitt算子,Roberts算子及差分边缘检测。


二阶微分:以二阶微分为基础的边缘检测,通过寻求二阶导数中的过零点来检测边缘,如拉普拉斯算子,高拉普拉斯算子,Canny算子边缘检测。


混合一阶微分和二阶微分:以混合一阶微分和二阶微分为基础的边缘检测,综合利用一阶微分和二阶微分的特征,如Marr-Hildreth边缘检测算子。


什么是 Sobel 算子


Q:什么是Sobel 算子?

A: Sobel 算子常用于图像识别中的边缘检测,计算图像灰度函数的近似梯度。Sobel 算子在进行边缘检测时效率很高,当对精度要求不是很高的时候,可以酌情考虑使用。


非极大值抑制 Sobel 检测


非极大值抑制 Sobel 的边缘检测实现步骤如下:


将图像转为 32 位浮点型数据,定义水平或垂直方向的 Sobel 算子。


使用 filter2D 完成图像与算子的卷积操作,计算卷积结果的梯度幅值。


自适应计算出梯度幅值阈值,阈值设置不梯度幅值的均乘以 4,根据阈值对水平或垂直的领域区梯度进行比较。


判断当前邻域梯度是否大于水平或垂直邻域梯度,自适应完成边缘检测出二值化图像的操作。


参考代码


#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
bool SobelVerEdge(cv::Mat srcImage, cv::Mat& resultImage) {
  CV_Assert(srcImage.channels() == 1);
  srcImage.convertTo(srcImage, CV_32FC1);
  // 水平方向的 Sobel 算子
  cv::Mat sobelx = (cv::Mat_<float>(3, 3) << -0.125, 0, 0.125,-0.25, 0, 0.25,-0.125, 0, 0.125);
  cv::Mat ConResMat;
  // 卷积运算
  cv::filter2D(srcImage, ConResMat, srcImage.type(), sobelx);
  // 计算梯度的幅度
  cv::Mat graMagMat;
  cv::multiply(ConResMat, ConResMat, graMagMat);
  // 根据梯度幅度及参数设置阈值
  int scaleVal = 4;
  double thresh = scaleVal * cv::mean(graMagMat).val[0];
  cv::Mat resultTempMat = cv::Mat::zeros(graMagMat.size(), graMagMat.type());
  float* pDataMag = (float*)graMagMat.data;
  float* pDataRes = (float*)resultTempMat.data;
  const int nRows = ConResMat.rows;
  const int nCols = ConResMat.cols;
  for (int i = 1; i != nRows - 1; ++i) {
    for (int j = 1; j != nCols - 1; ++j) {
      // 计算该点梯度与水平或垂直梯度值大小比较结果
      bool b1 = (pDataMag[i * nCols + j] > pDataMag[i *
        nCols + j - 1]);
      bool b2 = (pDataMag[i * nCols + j] > pDataMag[i *
        nCols + j + 1]);
      bool b3 = (pDataMag[i * nCols + j] > pDataMag[(i - 1)
        * nCols + j]);
      bool b4 = (pDataMag[i * nCols + j] > pDataMag[(i + 1)
        * nCols + j]);
      // 判断邻域梯度是否满足大于水平或垂直梯度
      // 并根据自适应阈值参数进行二值化
      pDataRes[i * nCols + j] = 255 * ((pDataMag[i * nCols + j] > thresh) && ((b1 && b2) || (b3 && b4)));
    }
  }
  resultTempMat.convertTo(resultTempMat, CV_8UC1);
  resultImage = resultTempMat.clone();
  return true;
}
int main() {
  cv::Mat srcImage = cv::imread("cc.png",0);
  cv::Mat resultImage;
  if (!srcImage.data)
    return -1;
  cv::imshow("srcImage", srcImage);
  //非极大值抑制细化数值sobel检测
  SobelVerEdge(srcImage, resultImage);
  cv::namedWindow("非极大值抑制", cv::WINDOW_FREERATIO);
  cv::imshow("非极大值抑制", resultImage);
  cv::waitKey(0);
  return 0;
}


实现效果


97d8f22d28399995a0136ac47b682c9f_74cc2e677c78452fb7bf4084427cce19.png


图像直接卷积实现 Sobel

图像直接卷积 Sobel 边缘检测实现比较简单,首先定义水平或垂直方向的 Sobel 核因子,直接对源图像进行窗遍历,计算窗内的领域梯度幅值,然后根据梯度模场进行二值化操作,完成图像的水平或垂直方向的边缘检测。


参考代码


#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//图像直接卷积实现Sobel
bool sobelEdge(const cv::Mat image, cv::Mat &result, uchar threshold) {
  CV_Assert(image.channels() == 1);
  //初始化水平核因子
  cv::Mat sobelx = (cv::Mat_<float>(3, 3) << 1, 0, -1, 2, 0, -2, 1, 0, -1);
  //初始化垂直核因子
  cv::Mat sobely = (cv::Mat_<float>(3, 3) << 1, 2, 1, 0, 0, 0, -1, -2, -1);
  result = cv::Mat::zeros(image.rows - 2, image.cols - 2, image.type());
  double graMag = 0;
  for (int i = 1; i < image.rows - 1; i++) {
    for (int j = 1; j < image.cols - 1; j++) {
      float edgex = 0, edgey = 0;
      //遍历计算水平与垂直梯度
      for (int k = -1; k < 2; k++) {
        for (int p = -1; p < 2; p++) {
          edgex += (float)image.at<uchar>(k + i, p + j) * sobelx.at<float>(1 + k, 1 + p);
          edgey += (float)image.at<uchar>(k + i, p + j) * sobely.at<float>(1 + k, 1 + p);
        }
      }
      //计算梯度模长
      graMag = sqrt(pow(edgex, 2) + pow(edgey, 2));
      //二值化
      result.at<uchar>(i - 1, j - 1) = ((graMag > threshold) ? 255 : 0);
    }
  }
  return true;
}
int main() {
  cv:: Mat srcImage = cv::imread("cc.png",0);
  cv::Mat resultImage;
  if (!srcImage.data)
    return -1;
  cv::imshow("srcImage", srcImage);
  //图像直接卷积实现 sobel 检测
  sobelEdge(srcImage, resultImage, 100);
  cv::imshow("图像直接卷积", resultImage);
  cv::waitKey(0);
  return 0;
}


实现效果


b8e937a2bc1e04be02f5ddf22c77e76c_5fb590e225ea46b0b6631665227270cd.png


图像卷积下非极大值抑制 Sobel


图像卷积下非极大值抑制 Sobel 与 非极大值抑制 Sobel 检测实现方法类似,能够较好地剔除虚假边缘点。


参考代码


#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
bool sobelOptaEdge(const cv::Mat& srcImage, cv::Mat& resultImage, int flag) {
  CV_Assert(srcImage.channels() == 1);
  // 初始化sobel水平核因子
  cv::Mat sobelX = (cv::Mat_<double>(3, 3) << 1, 0, -1,
    2, 0, -2,
    1, 0, -1);
  // 初始化sebel垂直核因子
  cv::Mat sobelY = (cv::Mat_<double>(3, 3) << 1, 2, 1,
    0, 0, 0,
    -1, -2, -1);
  // 计算水平与垂直卷积
  cv::Mat edgeX, edgeY;
  filter2D(srcImage, edgeX, CV_32F, sobelX);
  filter2D(srcImage, edgeY, CV_32F, sobelY);
  // 根据传入参数确定计算水平或垂直边缘
  int paraX = 0;
  int paraY = 0;
  switch (flag)
  {
  case 0: paraX = 1;
    paraY = 0;
    break;
  case 1:  paraX = 0;
    paraY = 1;
    break;
  case 2:  paraX = 1;
    paraY = 1;
    break;
  default: break;
  }
  edgeX = abs(edgeX);
  edgeY = abs(edgeY);
  cv::Mat graMagMat = paraX * edgeX.mul(edgeX) + paraY * edgeY.mul(edgeY);
  // 计算阈值 
  int scaleVal = 4;
  double thresh = scaleVal * cv::mean(graMagMat).val[0];
  resultImage = cv::Mat::zeros(srcImage.size(), srcImage.type());
  for (int i = 1; i < srcImage.rows - 1; i++)
  {
    float* pDataEdgeX = edgeX.ptr<float>(i);
    float* pDataEdgeY = edgeY.ptr<float>(i);
    float* pDataGraMag = graMagMat.ptr<float>(i);
    // 阈值化和极大值抑制
    for (int j = 1; j < srcImage.cols - 1; j++)
    {
      if (pDataGraMag[j] > thresh && (
        (pDataEdgeX[j] > paraX * pDataEdgeY[j] && pDataGraMag[j] >
          pDataGraMag[j - 1] && pDataGraMag[j] > pDataGraMag[j + 1]) ||
          (pDataEdgeY[j] > paraY * pDataEdgeX[j] && pDataGraMag[j] >
            pDataGraMag[j - 1] && pDataGraMag[j] > pDataGraMag[j + 1])))
        resultImage.at<uchar>(i, j) = 255;
    }
  }
  return true;
}
int main() {
  cv::Mat srcImage = cv::imread("cc.png", 0);
  cv::Mat resultImage;
  if (!srcImage.data)
    return -1;
  cv::imshow("srcImage", srcImage);
  //非极大值抑制细化数值sobel检测
  sobelOptaEdge(srcImage, resultImage,2);
  cv::imshow("非极大值抑制", resultImage);
  cv::waitKey(0);
  return 0;
}


实现效果


10b83b0db988d263256e276fd279a07b_612ae1cc06cc4d039ed2f041f6cffc10.png


OpenCV 库调用 Sobel 函数


在 OpenCV 中提供了 Sobel 函数来计算图像边缘,详细如下:

void sobel(InputArray src, 
  OutputArray dst,
  int ddepth, 
  int dx, 
  int dy, 
  int ksize = 3, 
  double scale = 1, 
  double delta = 0, 
  int borderType = BORDER_DEFAULT)

其中,ddepth代表输出图像的深度,dx为 x方向的导数运算参数,dy为y方向导数运算参数,ksize为 Sobel 内核的大小,设置为奇数,默认参数为3,scale为可选的缩放导数的比例常数,data为可选的增量常数,被叠加到导数中,borderType用于判断图像边界的模式。


参考代码


#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main() {
  cv::Mat srcImage = cv::imread("cc.png", 0);
  cv::Mat edgeXMat, edgeYMat, resultImage;
  if (!srcImage.data)
    return -1;
  //X 方向 Sobel 边缘
  Sobel(srcImage, edgeXMat, CV_16S, 1, 0, 3, 1, 0, BORDER_DEFAULT);
  convertScaleAbs(edgeXMat, edgeXMat);
  //Y 方向 Sobel 边缘
  Sobel(srcImage, edgeYMat, CV_16S, 0, 1, 3, 1, 0, BORDER_DEFAULT);
  convertScaleAbs(edgeYMat, edgeYMat);
  //整合
  resultImage = edgeXMat + edgeYMat;
  //显示图像
  cv::imshow("srcImage", srcImage);
  cv::imshow("X", edgeXMat);
  cv::imshow("Y", edgeYMat);
  cv::imshow("直接调用 Sobel 库", resultImage);
  cv::waitKey(0);
  return 0;
}


实现效果


e5d06708918a47b87be5a364c56c78f1_39beb1caf98a459fa2c26291282e9cfb.png


相关文章
|
1月前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
275 3
|
5月前
|
算法 开发工具 计算机视觉
【零代码研发】OpenCV实验大师工作流引擎C++ SDK演示
【零代码研发】OpenCV实验大师工作流引擎C++ SDK演示
81 1
|
2月前
|
存储 计算机视觉 C++
在C++中实现Armadillo库与OpenCV库之间的数据格式转换
在C++中实现Armadillo库与OpenCV库之间的数据格式转换是一项常见且实用的技能。上述步骤提供了一种标准的方法来进行这种转换,可以帮助开发者在两个库之间高效地转移和处理数据。虽然转换过程相对直接,但开发者应留意数据类型匹配和性能优化等关键细节。
61 11
|
2月前
|
存储 计算机视觉 C++
在C++中实现Armadillo库与OpenCV库之间的数据格式转换
在C++中实现Armadillo库与OpenCV库之间的数据格式转换是一项常见且实用的技能。上述步骤提供了一种标准的方法来进行这种转换,可以帮助开发者在两个库之间高效地转移和处理数据。虽然转换过程相对直接,但开发者应留意数据类型匹配和性能优化等关键细节。
25 3
|
5月前
|
算法 计算机视觉
【Qt&OpenCV 图像边缘检测 Sobel/Laplace/Canny】
【Qt&OpenCV 图像边缘检测 Sobel/Laplace/Canny】
69 0
|
8天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
36 4
|
10天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
33 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1