在本教程中,你将学到:
- 如何使用 OpenCV 函数 filter2D() 来创建你自己的线性滤波器。
从非常一般的意义上讲,相关是图像的每一个部分与一个算子(核,kernel)之间的运算。

核本质上是一个固定大小的数值系数数组,数组中还有一个锚点(anchor point),通常位于中心。
假设你想知道图像中某个特定位置的结果值。相关值的计算方式如下:
- 把核的锚点放在某个确定的像素之上,核的其余部分覆盖图像中对应的局部像素。
- 把核的系数与对应的图像像素值相乘,再求和。
- 把结果放到输入图像中锚点所在的位置。
- 让核在整个图像上扫描,对所有像素重复该过程。
把上面的过程用方程的形式表达出来,就是:
H(x,y)=∑i=0Mi−1∑j=0Mj−1I(x+i−ai,y+j−aj)K(i,j)
幸运的是,OpenCV 提供了 filter2D() 函数,你不必自己把这些运算都写出来。
- 加载一幅图像
- 执行一个归一化盒式滤波器。例如,对于大小为 size=3 的核,该核为:
1 & 1 & 1 \\
```cpp
1 & 1 & 1 \\
1 & 1 & 1
```
\end{bmatrix}$$
程序将分别使用大小为 3、5、7、9 和 11 的核执行滤波操作。
- 每个核的滤波输出将显示 500 毫秒。
## 代码
本教程的代码如下所示。
**C++:**
你也可以从
[这里](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/cpp/tutorial_code/ImgTrans/filter2D_demo.cpp)
下载。
```cpp
/**
* @file filter2D_demo.cpp
* @brief Sample code that shows how to implement your own linear filters by using filter2D function
* @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 variables
Mat src, dst;
Mat kernel;
Point anchor;
double delta;
int ddepth;
int kernel_size;
const char* window_name = "filter2D Demo";
const char* imageName = argc >=2 ? argv[1] : "lena.jpg";
// Loads an image
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
if( src.empty() )
{
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default lena.jpg] \n");
return EXIT_FAILURE;
}
// Initialize arguments for the filter
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
// Loop - Will filter the image with different kernel sizes each 0.5 seconds
int ind = 0;
for(;;)
{
// Update kernel size for a normalized box filter
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
// Apply filter
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
imshow( window_name, dst );
char c = (char)waitKey(500);
// Press 'ESC' to exit the program
if( c == 27 )
{ break; }
ind++;
```
}
return EXIT_SUCCESS;
}
```
**Java:**
你也可以从
[这里](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/java/tutorial_code/ImgTrans/Filter2D/Filter2D_Demo.java)
下载。
/**
* @file Filter2D_demo.java
* @brief Sample code that shows how to implement your own linear filters by using filter2D function
*/
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class Filter2D_DemoRun {
public void run(String[] args) {
// Declare variables
Mat src, dst = new Mat();
Mat kernel = new Mat();
Point anchor;
double delta;
int ddepth;
int kernel_size;
String window_name = "filter2D Demo";
String imageName = ((args.length > 0) ? args[0] : "../data/lena.jpg");
// Load an image
src = Imgcodecs.imread(imageName, Imgcodecs.IMREAD_COLOR);
// 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);
}
```
// Initialize arguments for the filter
anchor = new Point( -1, -1);
delta = 0.0;
ddepth = -1;
// Loop - Will filter the image with different kernel sizes each 0.5 seconds
int ind = 0;
while( true )
{
// Update kernel size for a normalized box filter
kernel_size = 3 + 2*( ind%5 );
Mat ones = Mat.ones( kernel_size, kernel_size, CvType.CV_32F );
Core.multiply(ones, new Scalar(1/(double)(kernel_size*kernel_size)), kernel);
```cpp
// Apply filter
Imgproc.filter2D(src, dst, ddepth , kernel, anchor, delta, Core.BORDER_DEFAULT );
HighGui.imshow( window_name, dst );
int c = HighGui.waitKey(500);
// Press 'ESC' to exit the program
if( c == 27 )
{ break; }
```
ind++;
}
System.exit(0);
```
}
}
public class Filter2D_Demo {
public static void main(String[] args) {
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new Filter2D_DemoRun().run(args);
```
}
}
```
**Python:**
你也可以从
[这里](https://raw.githubusercontent.com/opencv/opencv/4.x/samples/python/tutorial_code/ImgTrans/Filter2D/filter2D.py)
下载。
"""
@file filter2D.py
@brief Sample code that shows how to implement your own linear filters by using filter2D function
"""
import sys
import cv2 as cv
import numpy as np
def main(argv):
window_name = 'filter2D Demo'
imageName = argv[0] if len(argv) > 0 else 'lena.jpg'
# Loads an image
src = cv.imread(cv.samples.findFile(imageName), cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: filter2D.py [image_name -- default lena.jpg] \n')
return -1
# Initialize ddepth argument for the filter
ddepth = -1
# Loop - Will filter the image with different kernel sizes each 0.5 seconds
ind = 0
while True:
# Update kernel size for a normalized box filter
kernel_size = 3 + 2 * (ind % 5)
kernel = np.ones((kernel_size, kernel_size), dtype=np.float32)
kernel /= (kernel_size * kernel_size)
# Apply filter
dst = cv.filter2D(src, ddepth, kernel)
cv.imshow(window_name, dst)
c = cv.waitKey(500)
if c == 27:
break
```
ind += 1
```
return 0
if __name__ == "__main__":
main(sys.argv[1:])
```
## 说明
### 加载图像
**C++:**
```cpp
const char* imageName = argc >=2 ? argv[1] : "lena.jpg";
// Loads an image
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
if( src.empty() )
{
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default lena.jpg] \n");
return EXIT_FAILURE;
}
```
**Java:**
```java
String imageName = ((args.length > 0) ? args[0] : "../data/lena.jpg");
// Load an image
src = Imgcodecs.imread(imageName, Imgcodecs.IMREAD_COLOR);
// 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:**
```python
imageName = argv[0] if len(argv) > 0 else 'lena.jpg'
# Loads an image
src = cv.imread(cv.samples.findFile(imageName), cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: filter2D.py [image_name -- default lena.jpg] \n')
return -1
```
### 初始化参数
**C++:**
```cpp
// Initialize arguments for the filter
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
```
**Java:**
```java
// Initialize arguments for the filter
anchor = new Point( -1, -1);
delta = 0.0;
ddepth = -1;
```
**Python:**
```python
# Initialize ddepth argument for the filter
ddepth = -1
```
### 循环
执行一个无限循环,不断更新核的大小,并把我们的线性滤波器应用到输入图像上。下面来详细分析一下:
- 首先定义滤波器要使用的核。代码如下:
**C++:**
```cpp
// Update kernel size for a normalized box filter
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
```
**Java:**
```java
// Update kernel size for a normalized box filter
kernel_size = 3 + 2*( ind%5 );
Mat ones = Mat.ones( kernel_size, kernel_size, CvType.CV_32F );
Core.multiply(ones, new Scalar(1/(double)(kernel_size*kernel_size)), kernel);
```
**Python:**
```python
# Update kernel size for a normalized box filter
kernel_size = 3 + 2 * (ind % 5)
kernel = np.ones((kernel_size, kernel_size), dtype=np.float32)
kernel /= (kernel_size * kernel_size)
```
第一行把 *kernel_size* 更新为 $[3,11]$ 范围内的奇数值。
第二行才是真正地构造核:先填充一个全为 $1$ 的矩阵,再除以元素个数进行归一化。
- 设置好核之后,就可以用 **filter2D()** 函数生成滤波结果:
**C++:**
```cpp
// Apply filter
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
```
**Java:**
```java
// Apply filter
Imgproc.filter2D(src, dst, ddepth , kernel, anchor, delta, Core.BORDER_DEFAULT );
```
**Python:**
```python
# Apply filter
dst = cv.filter2D(src, ddepth, kernel)
```
- 各参数含义:
- *src*:源图像
- *dst*:目标图像
- *ddepth*:*dst* 的深度。负值(例如 $-1$)表示与源图像深度相同。
- *kernel*:要在图像上扫描的核
- *anchor*:锚点相对于其核的位置。位置 *Point(-1, -1)* 表示默认为中心。
- *delta*:相关过程中加到每个像素上的值。默认为 $0$。
- *BORDER_DEFAULT*:我们保持其默认值(更多细节见下一篇教程)。
- 我们的程序会执行一个 *while* 循环,每 500 ms 在指定范围内更新滤波器的核大小。
## 结果

1. 编译上面的代码后,你可以传入一幅图像的路径作为参数来运行它。结果应当是一个显示被归一化滤波器模糊后的图像的窗口。每隔 0.5 秒核大小就会变化,正如下面这组截图所示: