OpenCV 笔记(9):常用的边缘检测算子—— Roberts、Prewitt、Sobel

2023-12-04 20:12

本文主要是介绍OpenCV 笔记(9):常用的边缘检测算子—— Roberts、Prewitt、Sobel,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在本文开始之前,我们先了解一下算子的概念。

算子英语是 Operator,它是一个函数空间到函数空间上的映射 O:X→X。广义上的算子可以推广到任何空间。

函数是从数到数的映射。

泛函是从函数到数的映射。

算子是从函数到函数的映射。

算子不等同于函数,也不等同于算法。算法是更为广泛的概念,它包含了算子。

Part11. Roberts 算子

我们知道用 L1 范数来近似梯度的幅度:

其中,

在 x 方向,由偏导公式可知,其实就是相邻两个像素的值相减。同理,y 方向也是如此。因此可以得到如下算子。

24a83f231c07ef2874138519558abc1d.jpeg
图像的垂直和水平梯度.png

类似地,还有对角线方向。对于对角线方向梯度,公式和算子如下:

Roberts 卷积核:

38d2b8e7060c65446e3d510bce54fd0e.jpeg
Roberts 卷积核.png

我们可以实现一个基于 roberts 算子的边缘检测

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;void roberts(Mat& input, Mat& output, Mat& kernel_x, Mat& kernel_y)
{int height = input.rows;int width = input.cols;int height_x = kernel_x.rows;int width_x = kernel_x.cols;int height_y = kernel_y.rows;int width_y = kernel_y.cols;for (int row = 1; row < height - 1; row++){for (int col = 1; col < width - 1; col++){float G_X = 0;for (int h = 0; h < height_x; h++){for (int w = 0; w < width_x; w++){G_X += input.at<uchar>(row + h, col + w) * kernel_x.at<float>(h, w);}}float G_Y = 0;for (int h = 0; h < height_y; h++){for (int w = 0; w < width_y; w++){G_Y += input.at<uchar>(row + h, col + w) * kernel_y.at<float>(h, w);}}output.at<uchar>(row, col) = saturate_cast<uchar>(cv::abs(G_X) + cv::abs(G_Y));}}
}int main(int argc,char *argv[])
{Mat src = imread(".../street.jpg");imshow("src",src);Mat gray;cv::cvtColor(src, gray, COLOR_BGR2GRAY);Mat kernelRoX = (cv::Mat_<float>(2,2) << -1,0,0,1);Mat kernelRoY = (cv::Mat_<float>(2,2) << 0,-1,1,0);Mat dst;dst.create(gray.rows,gray.cols,gray.type());roberts(gray, dst, kernelRoX, kernelRoY);imshow("Roberts",dst);waitKey(0);return 0;
}
6244459e2870d499772b2eaa003a8deb.jpeg
自定义实现的roberts边缘检测.png

也可以用 OpenCV 自定义的滤波器 filter2D() 函数,来实现 Roberts 边缘检测:

int main(int argc,char *argv[])
{Mat src = imread(".../street.jpg");imshow("src",src);Mat gray;cv::cvtColor(src, gray, COLOR_BGR2GRAY);Mat kernelRoX = (cv::Mat_<float>(2,2) << -1,0,0,1);Mat kernelRoY = (cv::Mat_<float>(2,2) << 0,-1,1,0);Mat dstRoX;Mat dstRoY;cv::filter2D(gray,dstRoX,-1,kernelRoX);cv::filter2D(gray,dstRoY,-1,kernelRoY);imshow("Roberts X", dstRoX);imshow("Roberts Y", dstRoY);dstRoX = cv::abs(dstRoX);dstRoY = cv::abs(dstRoY);Mat dst;add(dstRoX,dstRoY,dst);imshow("Roberts", dst);waitKey(0);return 0;
}
e5d6b0f270f3089786da23086afb61d2.jpeg
roberts x和y方向.png
e20350fb7cd778bf78a23ffbd57d1a1b.jpeg
roberts.png

Part22. Prewitt 算子

在下图的 3×3 区域,Roberts 算子利用和,实现对角差分。

40ab590c78bdcb4224ef2c8e4c4647b1.jpeg
3*3模版.png

但是 2×2 大小的核概念上很简单,但在计算边缘方向时,它们不如中心对称的核有用,中心对称核的最小尺寸为 3×3。

Prewitt 算子的设计思想:真正的边界点在水平方向垂直方向上的相邻点应该也同样为边界点,因此以更大的边缘检测滤波器,考虑周围更多的点会使得边缘检测更准确。

Prewitt 卷积核:

0b53749d7610fcfd3de5eeec3bd9c4c7.jpeg
Prewitt卷积核.png

Prewitt 算子如下:

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;void prewitt(Mat& input, Mat& output, Mat& kernel_x, Mat& kernel_y)
{int height = input.rows;int width = input.cols;int height_x = kernel_x.rows;int width_x = kernel_x.cols;int height_y = kernel_y.rows;int width_y = kernel_y.cols;for (int row = 1; row < height - 1; row++){for (int col = 1; col < width - 1; col++){float G_X = 0;for (int h = 0; h < height_x; h++){for (int w = 0; w < width_x; w++){G_X += input.at<uchar>(row + h - 1, col + w - 1) * kernel_x.at<float>(h, w);}}float G_Y = 0;for (int h = 0; h < height_y; h++){for (int w = 0; w < width_y; w++){G_Y += input.at<uchar>(row + h - 1, col + w - 1) * kernel_y.at<float>(h, w);}}output.at<uchar>(row, col) = saturate_cast<uchar>(cv::abs(G_X) + cv::abs(G_Y));}}
}int main(int argc,char *argv[])
{Mat src = imread(".../street.jpg");imshow("src",src);Mat gray;cv::cvtColor(src, gray, COLOR_BGR2GRAY);Mat kernelPrewittX = (cv::Mat_<float>(3,3) << -1,0,1,-1,0,1,-1,0,1);Mat kernelPrewittY = (cv::Mat_<float>(3,3) << -1,-1,-1,0,0,0,1,1,1);Mat dst;dst.create(gray.rows,gray.cols,gray.type());prewitt(gray, dst, kernelPrewittX, kernelPrewittY);imshow("Prewitt", dst);waitKey(0);return 0;
}
2f50418c2d10dba43af36ee98864a6a5.jpeg
自定义实现的prewitt边缘检测.png

用 OpenCV 自定义的滤波器 filter2D() 函数,来实现 Prewitt 边缘检测:

int main(int argc,char *argv[])
{Mat src = imread(".../street.jpg");imshow("src",src);Mat gray;cv::cvtColor(src, gray, COLOR_BGR2GRAY);Mat kernelPrewittX = (cv::Mat_<float>(3,3) << -1,0,1,-1,0,1,-1,0,1);Mat kernelPrewittY = (cv::Mat_<float>(3,3) << -1,-1,-1,0,0,0,1,1,1);Mat dstPrewittX;Mat dstPrewittY;cv::filter2D(gray,dstPrewittX,-1,kernelPrewittX);cv::filter2D(gray,dstPrewittY,-1,kernelPrewittY);imshow("Prewitt X", dstPrewittX);imshow("Prewitt Y", dstPrewittY);dstPrewittX = cv::abs(dstPrewittX);dstPrewittY = cv::abs(dstPrewittY);Mat dst;add(dstPrewittX,dstPrewittY,dst);imshow("Prewitt", dst);waitKey(0);return 0;
}

Prewitt X 检测垂直的边界,Prewitt Y 检测水平的边界。

2b8591cbb72ca96e54744d25e03cb6e1.jpeg
prewitt x 和 y方向

下图展示的是联合梯度。

a5799e1881f08912f80d5ad45432bdb8.jpeg
prewitt.png

Part33. Sobel 算子

Roberts 算子按照对角线两个方向的梯度确定边缘点,Prewitt 算子按照水平和垂直方向的梯度确定边缘点。

上面我们通过使用 cv::filter2D() 函数来实现这2种边缘检测,都展示了其对两个方向对边缘的检测的效果图,我们发现不同方向梯度的边缘检测效果各有特点。

融合多种方向的梯度,能够有效提升边缘检测效果。Sobel 算子考虑了水平、垂直和两个对角,共计4个方向对的梯度加权求和。

e9a38a142bb9b9da32bbc8145aa3a324.jpeg
Sobel各个方向的梯度.png

Sobel 的作者定义了一个给定邻域方向的梯度矢量 g 的幅度为:

像素灰度差分像素距离

其中,像素距离使用曼哈顿距离进行计算。

之前在第六篇介绍过曼哈顿距离,表示从像素 p 向像素 q 出发,每次能走的点必须是在当前像素点的 4 邻域中。一步一步走到 q 点后,一共经过的像素点数就是曼哈顿距离。

矢量 g 的方向可以通过中心像素 z5 相关邻域的单位矢量给出,这里的邻域是对称出现的,即四个方向对:(z1,z9),(z2,z8),(z3,z7),(z6,z4)。沿着4个方向对上求其梯度矢量和,可以得到像素 z5 的平均梯度估计。

公式中 4个单位向量 [1, 1],[-1, 1],[0, 1], [1, 0] 控制差分的方向,系数 1/4, 1/2 为距离反比权重。例如 z1 到 z9 的曼哈顿距离是 4,z2 到 z8 的曼哈顿距离是 2。

对于上述公式,为了避免具有小数的乘除计算,因此对 G 乘上缩放因子 4

可得,Sobel 算子如下:

Sobel 卷积核:

3159de7284f871c4b508b097b0b5d83e.jpeg
Sobel卷积核.png
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;using namespace cv;void sobel(Mat& input, Mat& output, Mat& kernel_x, Mat& kernel_y)
{int height = input.rows;int width = input.cols;int height_x = kernel_x.rows;int width_x = kernel_x.cols;int height_y = kernel_y.rows;int width_y = kernel_y.cols;for (int row = 1; row < height - 1; row++){for (int col = 1; col < width - 1; col++){float G_X = 0;for (int h = 0; h < height_x; h++){for (int w = 0; w < width_x; w++){G_X += input.at<uchar>(row + h - 1, col + w - 1) * kernel_x.at<float>(h, w);}}float G_Y = 0;for (int h = 0; h < height_y; h++){for (int w = 0; w < width_y; w++){G_Y += input.at<uchar>(row + h - 1, col + w - 1) * kernel_y.at<float>(h, w);}}output.at<uchar>(row, col) = saturate_cast<uchar>(cv::abs(G_X) + cv::abs(G_Y));}}
}int main(int argc,char *argv[])
{Mat src = imread(".../street.jpg");imshow("src",src);Mat gray;cv::cvtColor(src, gray, COLOR_BGR2GRAY);Mat kernelSobelX = (cv::Mat_<float>(3,3) << -1,0,1,-2,0,2,-1,0,1);Mat kernelSobelY = (cv::Mat_<float>(3,3) << -1,-2,-1,0,0,0,1,2,1);Mat dst;dst.create(gray.rows,gray.cols,gray.type());sobel(gray, dst, kernelSobelX, kernelSobelY);imshow("Sobel", dst);waitKey(0);return 0;
}
8dee28502649386d80a5ba852e9cae54.jpeg
自定义实现的sobel边缘检测.png

用 OpenCV 自带的 Sobel() 函数来实现其边缘检测

int main(int argc,char *argv[])
{Mat src = imread(".../street.jpg");imshow("src",src);Mat gray;cv::cvtColor(src, gray, COLOR_BGR2GRAY);Mat dstSobelX;Mat dstSobelY;Sobel(gray, dstSobelX, CV_16S, 1, 0, 3);Sobel(gray, dstSobelY, CV_16S, 0, 1, 3);convertScaleAbs(dstSobelX, dstSobelX);convertScaleAbs(dstSobelY, dstSobelY);imshow("Sobel X", dstSobelX);imshow("Sobel Y", dstSobelY);Mat dst;add(dstSobelX,dstSobelX,dst);imshow("Sobel", dst);waitKey(0);return 0;
}
b89bc2c7b61e2c6d9a1ea6c1d07a5d2a.jpeg
sobel x和y方向.png
ecc0646d592f5d9d31f3762177a69040.jpeg
sobel.png

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:表示输出梯度的数据类型

  • 若 src.depth() = CV_8U,则 ddepth =-1/CV_16S/CV_32F/CV_64F

  • 若 src.depth() = CV_16U/CV_16S,则 ddepth =-1/CV_32F/CV_64F

  • 若 src.depth() = CV_32F,则 ddepth =-1/CV_32F/CV_64F

  • 若 src.depth() = CV_64F,则 ddepth = -1/CV_64F

第四个参数 dx:导数在 x 轴方向的阶数。dx=1, dy=0 表示对 x 方向计算梯度。

第五个参数 dy:导数在 y 轴方向的阶数。dx=0, dy=1 表示对 y 方向计算梯度。

第六个参数 ksize:Sobel 卷积核的大小,使用 3、5、7、9、11 等等。

Part44.  总结

在实际的图像分割中,我们往往只用一阶、二阶导数,他们都各有优势。 Roberts、Prewitt、Sobel 算子是一阶导数的边缘算子,通过卷积核与图像的每个像素点做卷积和运算,然后选取合适的阈值来提取图像的边缘。本文分别用这些算子对同一幅图像进行边缘检测,可以看到不同的效果,等到介绍完所有常用的算子后会对他们做一个总结。

另外,我们之前介绍过  Laplace 算子,它是二阶导数算子,后面的文章还会继续介绍二阶导数的边缘算子。

Java与Android技术栈】公众号

关注 Java/Kotlin 服务端、桌面端 、Android 、机器学习、端侧智能

更多精彩内容请关注:

这篇关于OpenCV 笔记(9):常用的边缘检测算子—— Roberts、Prewitt、Sobel的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/454766

相关文章

Java 枚举的常用技巧汇总

《Java枚举的常用技巧汇总》在Java中,枚举类型是一种特殊的数据类型,允许定义一组固定的常量,默认情况下,toString方法返回枚举常量的名称,本文提供了一个完整的代码示例,展示了如何在Jav... 目录一、枚举的基本概念1. 什么是枚举?2. 基本枚举示例3. 枚举的优势二、枚举的高级用法1. 枚举

opencv实现像素统计的示例代码

《opencv实现像素统计的示例代码》本文介绍了OpenCV中统计图像像素信息的常用方法和函数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 统计像素值的基本信息2. 统计像素值的直方图3. 统计像素值的总和4. 统计非零像素的数量

IDEA常用插件之代码扫描SonarLint详解

《IDEA常用插件之代码扫描SonarLint详解》SonarLint是一款用于代码扫描的插件,可以帮助查找隐藏的bug,下载并安装插件后,右键点击项目并选择“Analyze”、“Analyzewit... 目录SonajavascriptrLint 查找隐藏的bug下载安装插件扫描代码查看结果总结Sona

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/

30常用 Maven 命令

Maven 是一个强大的项目管理和构建工具,它广泛用于 Java 项目的依赖管理、构建流程和插件集成。Maven 的命令行工具提供了大量的命令来帮助开发人员管理项目的生命周期、依赖和插件。以下是 常用 Maven 命令的使用场景及其详细解释。 1. mvn clean 使用场景:清理项目的生成目录,通常用于删除项目中自动生成的文件(如 target/ 目录)。共性规律:清理操作