什么是边缘检测
边缘检测是图像处理与计算机视觉中最重要的技术之一,其目的是检测识别出图像中亮度变化剧烈的像素点构成的集合。图像边缘的正确检测对于分析图像中的内容、实现图像中物体的分割、定位等具有重要的作用。边缘检测大大减少了源图像的数据量,剔除了与目标不相干的信息,保留了图像重要的结构属性。
边缘检测算子是利用图像边缘的突变性质来检测边缘的,通常情况下边缘检测有以下三种类型。
一阶微分:以一阶微分为基础的边缘检测,通过计算图像的梯度值来检测图像边缘,如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; }
实现效果
图像直接卷积实现 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; }
实现效果
图像卷积下非极大值抑制 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; }
实现效果
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; }
实现效果