Skip to content

拉普拉斯算子

在本教程中,你将学到如何:

  • 使用 OpenCV 函数 Laplacian() 实现拉普拉斯算子(Laplacian operator)的离散形式。
  1. 在上一篇教程中,我们学习了如何使用 Sobel 算子(Sobel Operator)。它基于这样一个事实:在边缘区域,像素强度会出现「跃迁」或剧烈的强度变化。对强度取一阶导数后,我们观察到边缘的特征是一个极大值。

  2. 那么……如果取二阶导数会怎样?

    可以观察到,在边缘处二阶导数为零!因此,我们也可以用这一准则来尝试检测图像中的边缘。但要注意,零并不只出现在边缘处(它们也可能出现在其他无意义的位置);这可以通过在需要的地方施加滤波来解决。

Laplace_Operator_Tutorial_Theory_Previous.jpg Laplace_Operator_Tutorial_Theory_ddIntensity.jpg

  1. 从上面的说明可以推断,二阶导数可以用来检测边缘。由于图像是「二维的」,我们需要在两个方向上求导。这时拉普拉斯算子就派上用场了。

  2. 拉普拉斯算子定义为:

Laplace(f)=∂2f∂x2+∂2f∂y2Laplace(f) = \dfrac{\partial^{2} f}{\partial x^{2}} + \dfrac{\partial^{2} f}{\partial y^{2}}

  1. 拉普拉斯算子在 OpenCV 中由函数 Laplacian() 实现。事实上,由于拉普拉斯算子要用到图像的梯度,它在内部会调用 Sobel 算子来完成计算。

本程序做什么?

  • 加载一幅图像
  • 通过高斯模糊去除噪声,然后把原图转换为灰度图
  • 对灰度图应用拉普拉斯算子,并保存输出图像
  • 在一个窗口中显示结果

C++:

本教程的代码如下所示。你也可以从 这里 下载。

/**
* @file Laplace_Demo.cpp
* @brief Sample code showing how to detect edges using the Laplace operator
* @author OpenCV team
*/
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
/**
* @function main
*/
int main( int argc, char** argv )
{
// Declare the variables we are going to use
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";
const char* imageName = argc >=2 ? argv[1] : "lena.jpg";
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if(src.empty()){
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default lena.jpg] \n");
return -1;
}
// Reduce noise by blurring with a Gaussian filter ( kernel size = 3 )
GaussianBlur( src, src, Size(3, 3), 0, 0, BORDER_DEFAULT );
cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to grayscale
/// Apply Laplace function
Mat abs_dst;
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
// converting back to CV_8U
convertScaleAbs( dst, abs_dst );
imshow( window_name, abs_dst );
waitKey(0);
return 0;
}

Java:

本教程的代码如下所示。你也可以从 这里 下载。

/**
* @file LaplaceDemo.java
* @brief Sample code showing how to detect edges using the Laplace operator
*/
import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class LaplaceDemoRun {
public void run(String[] args) {
// Declare the variables we are going to use
Mat src, src_gray = new Mat(), dst = new Mat();
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CvType.CV_16S;
String window_name = "Laplace Demo";
String imageName = ((args.length > 0) ? args[0] : "../data/lena.jpg");
src = Imgcodecs.imread(imageName, Imgcodecs.IMREAD_COLOR); // Load an image
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image");
System.out.println("Program Arguments: [image_name -- default ../data/lena.jpg] \n");
System.exit(-1);
}
// Reduce noise by blurring with a Gaussian filter ( kernel size = 3 )
Imgproc.GaussianBlur( src, src, new Size(3, 3), 0, 0, Core.BORDER_DEFAULT );
// Convert the image to grayscale
Imgproc.cvtColor( src, src_gray, Imgproc.COLOR_RGB2GRAY );
/// Apply Laplace function
Mat abs_dst = new Mat();
Imgproc.Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, Core.BORDER_DEFAULT );
// converting back to CV_8U
Core.convertScaleAbs( dst, abs_dst );
HighGui.imshow( window_name, abs_dst );
HighGui.waitKey(0);
System.exit(0);
}
}
public class LaplaceDemo {
public static void main(String[] args) {
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new LaplaceDemoRun().run(args);
}
}

Python:

本教程的代码如下所示。你也可以从 这里 下载。

"""
@file laplace_demo.py
@brief Sample code showing how to detect edges using the Laplace operator
"""
import sys
import cv2 as cv
def main(argv):
# Declare the variables we are going to use
ddepth = cv.CV_16S
kernel_size = 3
window_name = "Laplace Demo"
imageName = argv[0] if len(argv) > 0 else 'lena.jpg'
src = cv.imread(cv.samples.findFile(imageName), cv.IMREAD_COLOR) # Load an image
# Check if image is loaded fine
if src is None:
print ('Error opening image')
print ('Program Arguments: [image_name -- default lena.jpg]')
return -1
# Remove noise by blurring with a Gaussian filter
src = cv.GaussianBlur(src, (3, 3), 0)
# Convert the image to grayscale
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# Create Window
cv.namedWindow(window_name, cv.WINDOW_AUTOSIZE)
# Apply Laplace function
dst = cv.Laplacian(src_gray, ddepth, ksize=kernel_size)
# converting back to uint8
abs_dst = cv.convertScaleAbs(dst)
cv.imshow(window_name, abs_dst)
cv.waitKey(0)
return 0
if __name__ == "__main__":
main(sys.argv[1:])

C++:

// Declare the variables we are going to use
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";

Java:

// Declare the variables we are going to use
Mat src, src_gray = new Mat(), dst = new Mat();
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CvType.CV_16S;
String window_name = "Laplace Demo";

Python:

# Declare the variables we are going to use
ddepth = cv.CV_16S
kernel_size = 3
window_name = "Laplace Demo"

C++:

const char* imageName = argc >=2 ? argv[1] : "lena.jpg";
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if(src.empty()){
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default lena.jpg] \n");
return -1;
}

Java:

String imageName = ((args.length > 0) ? args[0] : "../data/lena.jpg");
src = Imgcodecs.imread(imageName, Imgcodecs.IMREAD_COLOR); // Load an image
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image");
System.out.println("Program Arguments: [image_name -- default ../data/lena.jpg] \n");
System.exit(-1);
}

Python:

imageName = argv[0] if len(argv) > 0 else 'lena.jpg'
src = cv.imread(cv.samples.findFile(imageName), cv.IMREAD_COLOR) # Load an image
# Check if image is loaded fine
if src is None:
print ('Error opening image')
print ('Program Arguments: [image_name -- default lena.jpg]')
return -1

C++:

// Reduce noise by blurring with a Gaussian filter ( kernel size = 3 )
GaussianBlur( src, src, Size(3, 3), 0, 0, BORDER_DEFAULT );

Java:

// Reduce noise by blurring with a Gaussian filter ( kernel size = 3 )
Imgproc.GaussianBlur( src, src, new Size(3, 3), 0, 0, Core.BORDER_DEFAULT );

Python:

# Remove noise by blurring with a Gaussian filter
src = cv.GaussianBlur(src, (3, 3), 0)

C++:

cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to grayscale

Java:

// Convert the image to grayscale
Imgproc.cvtColor( src, src_gray, Imgproc.COLOR_RGB2GRAY );

Python:

# Convert the image to grayscale
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)

C++:

Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );

Java:

Imgproc.Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, Core.BORDER_DEFAULT );

Python:

# Apply Laplace function
dst = cv.Laplacian(src_gray, ddepth, ksize=kernel_size)
  • 各参数含义:
    • src_gray:输入图像。
    • dst:目标(输出)图像。
    • ddepth:目标图像的深度。由于我们的输入是 CV_8U,我们把 ddepth 设为 CV_16S 以避免溢出。
    • kernel_size:内部要应用的 Sobel 算子的核大小。本例中使用 3。
    • scale、delta 和 BORDER_DEFAULT:保持默认值。

C++:

// converting back to CV_8U
convertScaleAbs( dst, abs_dst );

Java:

// converting back to CV_8U
Core.convertScaleAbs( dst, abs_dst );

Python:

# converting back to uint8
abs_dst = cv.convertScaleAbs(dst)

C++:

imshow( window_name, abs_dst );
waitKey(0);

Java:

HighGui.imshow( window_name, abs_dst );
HighGui.waitKey(0);

Laplace_Operator_Tutorial_Original_Image.jpg

Python:

cv.imshow(window_name, abs_dst)
cv.waitKey(0)

Laplace_Operator_Tutorial_Result.jpg

  1. 编译上面的代码后,我们可以把一幅图像的路径作为参数来运行它,例如传入一幅风景照片作为输入。

  2. 我们得到如下结果:树木和奶牛的轮廓大致都被很好地勾勒出来了(在强度非常相近的区域除外,例如奶牛头部附近)。另外要注意,树后方房屋的屋顶(右侧)被明显标出。这是因为该区域的对比度更高。