卷积运算和相关运算是咱们图像处理中最基本的两种线性运算,可以说,图像处理中的绝大部分算法,特别是与滤波有关的算法都是建立这两种运算之上的。

MATLAB提供了函数imfilter()实现对这两种运算的支持,其语法如下:

B = imfilter(A,h)
B = imfilter(A,h,options,...)

options的可选值如下:

opencv 引导滤波 opencv guidedfilter_相关运算


所以当我们需要作卷积操作时可以像下面这样写:

imfilter(A,h,'conv')

当我们需要作相关操作时可以像下面这样写:

imfilter(A,h,'corr')

OpenCV作为图像处理的强有力工具,自然也少不了对这两种运算的支持。
OpenCV提供了函数filter2D()作图像(矩阵)的相关运算,注意是相关运算,而不是卷积运算。

它的运算公式如下:

opencv 引导滤波 opencv guidedfilter_相关运算_02

上面这个式子看起来很复杂,实际上就是博文https://www.hhai.cc/thread-180-1-1.html中介绍的相关运算的公式化表达。公式中的anchor代表kernel核的锚点,关于锚点的介绍,可以参考我的博文 https://www.hhai.cc/thread-177-1-1.html 大家如果通过阅读上面两篇博文理解了相关运算及锚点的概念,那么这个公式是不难理解的。

那如果我们要做卷积运算怎么办呢?
根据卷积运算的定义,我们将核旋转180度再做相关运算就是卷积运算了。那么怎么将核旋转180度呢?根据博文我们知道将矩阵180度等效于先作一个水平镜像,再作一个垂直镜像,矩阵的镜像运算我们可以用函数flip()完成,函数flip()的原型如下:

void cv::flip(InputArray src, OutputArray dst, int flipCode)

这个函数的使用示例见博文
我们只需要将参数flipCode的值写为负数值,即可实现对矩阵进行180度的旋转。

接下来看下函数filter2D()的原型:
C++原型如下:

void cv::filter2D(	InputArray 	src,
					OutputArray dst,
					int 	ddepth,
					InputArray 	kernel,
					Point 	anchor = Point(-1,-1),
					double 	delta = 0,
					int 	borderType = BORDER_DEFAULT)

Python原型如下:

dst	= cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])

各参数意义如下:
src—输入图像
dst—目标图像
ddepth—目标图像的数据深度
kernel—相关运算的运算核
anchor—kernel的锚点
delta—对运算结果的偏移处理,比如某个像素的相关运算结果为1,delta=2,那么最终在目标图像中这个像素的值为1+2=3。
borderType—边界扩展标志位,关于边界扩展,可以参考博文

示例代码如下:


# !/usr/bin/env python
# -*- coding: utf-8 -*-
# OpenCV的版本为4.1

import numpy as np
import cv2 as cv

A1 = np.array([[1, 1, 7, 3],
              [5, 5, 2, 7],
              [9, 9, 6, 1],
              [7, 7, 4, 9]], dtype='uint8')

kernel1 = np.array([[1, 1, 1],
                    [1, 1, 1],
                    [1, 1, 1]], dtype='uint8')

B1 = cv.filter2D(A1, cv.CV_8U, kernel1, anchor=(0, 0), borderType=cv.BORDER_CONSTANT)

B2 = cv.filter2D(A1, cv.CV_8U, kernel1, anchor=(-1, -1), borderType=cv.BORDER_CONSTANT)

运行结果如下:

opencv 引导滤波 opencv guidedfilter_卷积运算_03


opencv 引导滤波 opencv guidedfilter_卷积运算_04


opencv 引导滤波 opencv guidedfilter_OpenCV-filter2D_05


结果分析:B1和B2的不同在于核的锚点不一样,B1用的是核左上角的点,B2用的是核的中心点。边界扩展方式为填充0值处理。

手工验证结果如下:

B1[0,0]=1*1+1*1+7*1+5*1+5*1+2*1+9*1+9*1+6*1=45

opencv 引导滤波 opencv guidedfilter_卷积运算_06

B1[0,3]=0*1+0*1+0*1+7*1+3*1+0*1+2*1+7*1+0*1=11

opencv 引导滤波 opencv guidedfilter_卷积运算_07


可见,B1的手工结果和程序运行的结果一致。

再来看B2:

B2[0,0]=0*1+0*1+0*1+0*1+1*1+1*1+0*1+5*1+5*1=12

opencv 引导滤波 opencv guidedfilter_opencv 引导滤波_08

B2[0,3]=0*1+0*1+0*1+0*1+1*1+1*1+0*1+5*1+5*1=19

可见,B12的手工结果也和程序运行的结果一致。

opencv 引导滤波 opencv guidedfilter_OpenCV-filter2D_09


补充说明:

函数filter2D()还支持in-place operation(就地操作)哦,关于这个操作的详情见博文