上一个 教程: 为图像添加边框
下一个教程 : 拉普拉斯算子
原作者 | Ana Huamán |
---|---|
兼容性 | OpenCV >= 3.0 |
目标
在本教程中,您将学习如何
理论
注释
以下解释来自 Bradski 和 Kaehler 所著的《Learning OpenCV》一书。
- 在前两篇教程中,我们看到了卷积的应用实例。最重要的卷积之一是计算图像中的导数(或其近似值)。
- 为什么图像中的导数计算很重要?假设我们想要检测图像中的边缘。例如
您可以很容易地注意到,在边缘处,像素的强度会发生明显的变化。使用导数是表达变化的好方法。梯度变化大,表示图像发生了重大变化。
- 为了更加形象,我们假设有一幅一维图像。下图中强度的 "跳跃 "表示边缘:
- 如果我们求一阶导数(实际上,这里显示的是最大值),边缘 "跳跃 "就更容易看到了
- 因此,从上面的解释中,我们可以推断出,检测图像边缘的方法可以通过定位梯度高于其邻近像素的像素位置(或者概括地说,高于阈值)来实现。
- 更多详细解释,请参阅 Bradski 和 Kaehler 合著的《学习 OpenCV》一书。
Sobel算子
- 索贝尔算子是一种离散微分算子。它计算图像强度函数梯度的近似值。
- 索贝尔算子结合了高斯平滑和微分。
计算公式
假设要运算的图像为 I:
-
我们计算两个导数:
水平变化: 通过将 I 与奇数大小的核 Gx 相卷积来计算。例如,内核大小为 3 时,Gx 的计算公式为
G x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] ∗ I G_x=\left[ \begin{matrix} -1& 0& +1\\ -2& 0& +2\\ -1& 0& +1\\ \end{matrix} \right] *I Gx= −1−2−1000+1+2+1 ∗I垂直变化: 计算方法是将 I 与奇数大小的内核 Gy 相卷积。例如,内核大小为 3 时,Gy 的计算公式为
G y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] ∗ I G_y=\left[ \begin{matrix} -1& -2& -1\\ 0& 0& 0\\ +1& +2& +1\\ \end{matrix} \right] *I Gy= −10+1−20+2−10+1 ∗I -
在图像的每一点上,我们结合上述两个结果计算出该点的梯度近似值:
G = G x 2 + G x 2 G=\sqrt{G_x^2+G_x^2} G=Gx2+Gx2
尽管有时会使用以下更简单的等式:
G
=
∣
G
x
∣
+
∣
G
y
∣
G=|G_x|+|G_y|
G=∣Gx∣+∣Gy∣
注释
当核的大小为3
时,上图所示的 Sobel 核可能会产生明显的误差(毕竟 Sobel 只是导数的近似值)。OpenCV 使用
Scharr() 函数解决了核大小为 3 时的不准确性问题。该函数与标准 Sobel 函数相比,速度更快,精度更高。它实现了以下核:
G x = [ − 3 0 + 3 − 10 0 + 10 − 3 0 + 3 ] G_x=\left[ \begin{matrix} -3& 0& +3\\ -10& 0& +10\\ -3& 0& +3\\ \end{matrix} \right] Gx= −3−10−3000+3+10+3
G y = [ − 3 − 10 − 3 0 0 0 + 3 + 10 + 3 ] G_y=\left[ \begin{matrix} -3& -10& -3\\ 0& 0& 0\\ +3& +10& +3\\ \end{matrix} \right] Gy= −30+3−100+10−30+3
有关此函数的更多信息,请参阅
OpenCV 参考资料 - Scharr() 。此外,在下面的示例代码中,您会发现在 Sobel() 函数的代码上方也注释了
Scharr() 函数的代码。取消注释(显然也包括 Sobel 的注释)应该能让你了解这个函数是如何工作的。
代码
- 该程序有何作用?
- 应用 Sobel 运算器并生成图像输出,图像中检测到的边缘在较暗的背景上显得明亮。
- 教程代码如下所示。
C++
您也可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
cv::CommandLineParser parser(argc, argv,
"{@input |lena.jpg|input image}"
"{ksize k|1|ksize (hit 'K' to increase its value at run time)}"
"{scale s|1|scale (hit 'S' to increase its value at run time)}"
"{delta d|0|delta (hit 'D' to increase its value at run time)}"
"{help h|false|show help message}");
cout << "The sample uses Sobel or Scharr OpenCV functions for edge detection\n\n";
parser.printMessage();
cout << "\nPress 'ESC' to exit program.\nPress 'R' to reset values ( ksize will be -1 equal to Scharr function )";
// 首先我们声明要使用的变量
Mat image,src, src_gray;
Mat grad;
const String window_name = "Sobel Demo - Simple Edge Detector";
int ksize = parser.get<int>("ksize");
int scale = parser.get<int>("scale");
int delta = parser.get<int>("delta");
int ddepth = CV_16S;
String imageName = parser.get<String>("@input");
// 像往常一样,我们加载源图像 (src)
image = imread( samples::findFile( imageName ), IMREAD_COLOR ); // 加载图像
// 检查图像是否加载成功
if( image.empty() )
{
printf("Error opening image: %s\n", imageName.c_str());
return EXIT_FAILURE;
}
for (;;)
{
// 使用高斯滤波器模糊去除噪声(内核大小 = 3)
GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
// 将图像转换为灰度图像
cvtColor(src, src_gray, COLOR_BGR2GRAY);
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);
Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);
// 转换回 CV_8U
convertScaleAbs(grad_x, abs_grad_x);
convertScaleAbs(grad_y, abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
imshow(window_name, grad);
char key = (char)waitKey(0);
if(key == 27)
{
return EXIT_SUCCESS;
}
if (key == 'k' || key == 'K')
{
ksize = ksize < 30 ? ksize+2 : -1;
}
if (key == 's' || key == 'S')
{
scale++;
}
if (key == 'd' || key == 'D')
{
delta++;
}
if (key == 'r' || key == 'R')
{
scale = 1;
ksize = -1;
delta = 0;
}
}
return EXIT_SUCCESS;
}
Java
您也可以从这里下载
import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class SobelDemoRun {
public void run(String[] args) {
// 首先,我们声明将要使用的变量
Mat src, src_gray = new Mat();
Mat grad = new Mat();
String window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CvType.CV_16S;
// 像往常一样加载源图像 (src)
// 检查参数数量
if (args.length == 0){
System.out.println("Not enough parameters!");
System.out.println("Program Arguments: [image_path]");
System.exit(-1);
}
// 加载图像
src = Imgcodecs.imread(args[0]);
// 检查图像加载是否正常
if( src.empty() ) {
System.out.println("Error opening image: " + args[0]);
System.exit(-1);
}
// 使用高斯滤波器模糊去除噪音 ( 内核大小 = 3 )
Imgproc.GaussianBlur( src, src, new Size(3, 3), 0, 0, Core.BORDER_DEFAULT );
// 将图像转换为灰度图像
Imgproc.cvtColor( src, src_gray, Imgproc.COLOR_RGB2GRAY );
Mat grad_x = new Mat(), grad_y = new Mat();
Mat abs_grad_x = new Mat(), abs_grad_y = new Mat();
//Imgproc.Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, Core.BORDER_DEFAULT );
Imgproc.Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, Core.BORDER_DEFAULT );
//Imgproc.Scharr( src_gray、grad_y、ddepth、0、1、scale、delta、Core.BORDER_DEFAULT );
Imgproc.Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, Core.BORDER_DEFAULT );
// 转换回 CV_8U
通过www.DeepL.com/Translator(免费版)翻译
Core.convertScaleAbs( grad_x, abs_grad_x );
Core.convertScaleAbs( grad_y, abs_grad_y );
Core.addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
HighGui.imshow( window_name, grad );
HighGui.waitKey(0);
System.exit(0);
}
}
public class SobelDemo {
public static void main(String[] args) {
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new SobelDemoRun().run(args);
}
}
Python
您也可以从这里下载
"""
@file sobel_demo.py
@brief Sample code using Sobel and/or Scharr OpenCV functions to make a simple Edge Detector
"""
import sys
import cv2 as cv
def main(argv):
window_name = ('Sobel Demo - Simple Edge Detector')
scale = 1
delta = 0
ddepth = cv.CV_16S
if len(argv) < 1:
print ('Not enough parameters')
print ('Usage:\nmorph_lines_detection.py < path_to_image >')
return -1
# 加载图像
src = cv.imread(argv[0], cv.IMREAD_COLOR)
# 检查图片加载是否正常
if src is None:
print ('Error opening image: ' + argv[0])
return -1
src = cv.GaussianBlur(src, (3, 3), 0)
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
grad_x = cv.Sobel(gray, ddepth, 1, 0, ksize=3, scale=scale, delta=delta, borderType=cv.BORDER_DEFAULT)
# 梯度-Y
# grad_y = cv.Scharr(gray,ddepth,0,1)
grad_y = cv.Sobel(gray, ddepth, 0, 1, ksize=3, scale=scale, delta=delta, borderType=cv.BORDER_DEFAULT)
abs_grad_x = cv.convertScaleAbs(grad_x)
abs_grad_y = cv.convertScaleAbs(grad_y)
grad = cv.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)
cv.imshow(window_name, grad)
cv.waitKey(0)
return 0
if __name__ == "__main__":
main(sys.argv[1:])
说明
声明变量
C++
// First we declare the variables we are going to use
Mat image,src, src_gray;
Mat grad;
const String window_name = "Sobel Demo - Simple Edge Detector";
int ksize = parser.get<int>("ksize");
int scale = parser.get<int>("scale");
int delta = parser.get<int>("delta");
int ddepth = CV_16S;
加载源图像
C++
String imageName = parser.get<String>("@input");
// As usual we load our source image (src)
image = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if( image.empty() )
{
printf("Error opening image: %s\n", imageName.c_str());
return EXIT_FAILURE;
}
减少噪声
C++
// Remove noise by blurring with a Gaussian filter ( kernel size = 3 )
GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
灰度
C++
// Convert the image to grayscale
cvtColor(src, src_gray, COLOR_BGR2GRAY);
Sobel运算符
C++
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);
Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);
-
我们计算 x 和 y 方向的 “导数”。为此,我们使用函数 Sobel(),如下所示: 该函数接收以下参数
- src_gray: 在我们的示例中,输入图像。这里是 CV_8U
- grad_x / grad_y : 输出图像。
- ddepth:输出图像的深度: 输出图像的深度。我们将其设置为 CV_16S,以避免溢出。
- x_order: X 方向导数的阶数。
- y_order:导数的阶数: 在 Y 方向上的导数顺序。
- scale、delta 和 BORDER_DEFAULT:使用默认值。
请注意,计算 x 方向的梯度时,我们使用:xorder=1,yorder=0。
将输出转换为 CV_8U 图像
C++
// converting back to CV_8U
convertScaleAbs(grad_x, abs_grad_x);
convertScaleAbs(grad_y, abs_grad_y);
梯度
C++
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
我们尝试通过将两个方向的梯度相加来近似计算梯度(注意,这根本不是精确计算!但对于我们的目的来说,这是很好的)。
显示结果
C++
imshow(window_name, grad);
char key = (char)waitKey(0);
结果
- 以下是对 lena.jpg 应用基本检测器后的输出结果: