十八、OpenCV中的滤波与卷积

文章目录

一、概述

滤波器指的是一种由一副图像I(x, y)根据像素点x, y附近的区域计算得到一副新图像I'(x, y)的算法。其中,模板规定了滤波器的形状以及这个区域内像素的值的组成规律,也称"滤波器"或者核。滤波器多数为线性核,也就是说I'(x, y)的像素值由I(x, y)及其周围的像素的值加权相加得来,这个过程图可以由下面的方程表示:

公式解析:

上述是图像卷积(convolution)或滤波(filtering) 的数学定义,在 OpenCV 中对应的函数是如:

  • cv::filter2D()
  • cv::blur()
  • cv::GaussianBlur()
  • cv::Sobel()
  • cv::Laplacian()

等都遵循这一原理。

二、自定义边框

opencv中的滤波操作(如cv::blur(),cv::erode(), cv::dilate()等)得到的输出图像与源图像的形状是一样的。为了实现这种效果, OpenCv采用的一种方法是在源图像周围添加虚拟像素。cv::blur()函数实现了对图像每个像素与周围像素进行均值操作,显然,在cv::b1ur()函数的处理过程中为源图像添加虚拟像素是非常必要的。那么,如何对缺少相邻像素点的边缘像素点计算出一个有效的结果?实际上,在没有公认方法的情况下,一般通过自定义的方式在某一场景中处理问间题。

cv::copyMakeBorderO就是一个为图像创建边框的函数,通过指定两幅图像,第一幅是源图像,第二幅是扩充之后的图像,同时指明填充方法,这个函数就会将第一幅图像填补后的结果保存在第二幅图像中。

函数原型:

cpp 复制代码
void cv::copyMakeBorder(
    InputArray src,       // 原图像
    OutputArray dst,      // 输出图像
    int top, int bottom,  // 上下边框的像素宽度
    int left, int right,  // 左右边框的像素宽度
    int borderType,       // 边框类型(如下表)
    const Scalar &value = Scalar()  // 当使用常数边框时的颜色值
);

常见边框类型(borderType):

示例代码
示例 1:添加常数边框

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    Mat src = imread("image.jpg");
    Mat dst;

    // 添加蓝色常数边框
    copyMakeBorder(src, dst, 20, 20, 30, 30, BORDER_CONSTANT, Scalar(255, 0, 0));

    imshow("Original", src);
    imshow("With Border", dst);
    waitKey(0);
    return 0;
}

效果:图像四周加上蓝色的外框。

示例 2:添加镜像边框(反射填充)

cpp 复制代码
copyMakeBorder(src, dst, 50, 50, 50, 50, BORDER_REFLECT);

效果:边缘像素被镜像复制,常用于卷积时避免边缘效应(边缘模糊、亮度突变等)。

示例 3:复制边缘像素填充

cpp 复制代码
copyMakeBorder(src, dst, 10, 10, 10, 10, BORDER_REPLICATE);

效果:图像边缘的像素被延展出去(最常见于滤波或卷积操作)。

三、自定义外推

自定义外推(Custom Extrapolation)在 OpenCV 中主要是指图像边界像素在卷积或滤波操作中超出边界时的处理方式,即:当卷积核(kernel)落在图像边缘,访问到"图像外"的区域时,OpenCV 会如何外推(extrapolate)这些不存在的像素。

什么是"外推"(Extrapolation)?

在卷积或滤波中,计算像素 (x, y) 的新值时,需要用到其邻域像素(比如 3×3、5×5 区域)。但当 (x, y) 靠近图像边缘时,部分邻域像素会"超出"图像范围:

cpp 复制代码
超出区域? → 没有真实像素值 → 需要"外推"

OpenCV中的边界外推策略(border extrapolation types)

这些枚举值在 OpenCV 中以 cv::BorderTypes 定义,用于指定外推方式。

常见函数中的外推参数

OpenCV 几乎所有涉及滤波或卷积的函数,都支持自定义外推类型:

示例:自定义外推类型
示例 1:镜像外推 (BORDER_REFLECT_101)

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    Mat src = imread("image.jpg", IMREAD_COLOR);
    Mat dst;

    // 使用卷积 + 镜像外推
    Mat kernel = (Mat_<float>(3,3) << 
                  1, 1, 1,
                  1, 1, 1,
                  1, 1, 1) / 9.0;
    filter2D(src, dst, -1, kernel, Point(-1,-1), 0, BORDER_REFLECT_101);

    imshow("Original", src);
    imshow("Mirror Extrapolated", dst);
    waitKey(0);
    return 0;
}

效果:边界像素使用对称镜像填充,避免边缘模糊。

示例 2:常数外推(自定义背景颜色)

cpp 复制代码
filter2D(src, dst, -1, kernel, Point(-1, -1), 0, BORDER_CONSTANT);

此时你还可以通过 copyMakeBorder() 设置外推区域的颜色,例如:

cpp 复制代码
copyMakeBorder(src, dst, 10, 10, 10, 10, BORDER_CONSTANT, Scalar(0, 255, 0));

效果:外推区域是绿色填充。

示例 3:复制边缘像素外推

cpp 复制代码
GaussianBlur(src, dst, Size(5,5), 0, 0, BORDER_REPLICATE);

效果:边缘像素向外延展,常用于避免"暗边"效应。

四、阈值化操作

阈值化(Thresholding)是什么?

阈值化就是:
将图像像素值与某个阈值(threshold)比较,按规则将像素设为一个新值。

简单来说:

  • 比如灰度值大于 128 的像素变成白色(255)
  • 小于等于 128 的像素变成黑色(0)

这样可以把图像中的前景(目标)和背景分离。

函数原型:

cpp 复制代码
double cv::threshold(
    InputArray src,       // 输入图像(必须是单通道)
    OutputArray dst,      // 输出图像
    double thresh,        // 阈值
    double maxval,        // 设定超过阈值时的最大值(一般是255)
    int type              // 阈值类型(见下表)
);

阈值类型(type 参数)

基础示例:

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    Mat src = imread("image.jpg", IMREAD_GRAYSCALE);
    Mat dst;

    // 普通二值化
    threshold(src, dst, 128, 255, THRESH_BINARY);

    imshow("Original", src);
    imshow("Thresholded", dst);
    waitKey(0);
    return 0;
}

效果:灰度 > 128 的像素变成 255(白),否则变成 0(黑)。

自适应阈值(Adaptive Thresholding)

当图像光照不均时,使用固定阈值会失败。此时可以使用自适应阈值(Adaptive Threshold):

函数原型:

cpp 复制代码
adaptiveThreshold(
    src, dst,
    255,                        // 最大值
    ADAPTIVE_THRESH_MEAN_C,     // 或 ADAPTIVE_THRESH_GAUSSIAN_C
    THRESH_BINARY,              // 二值化类型
    blockSize,                  // 邻域大小(必须是奇数)
    C                           // 修正常数,通常取 2~10
);

示例:自适应阈值

cpp 复制代码
adaptiveThreshold(src, dst, 255,
                  ADAPTIVE_THRESH_GAUSSIAN_C,
                  THRESH_BINARY,
                  11, 2);

效果:局部自动调整阈值,更好地处理阴影、亮度不均的图像。

比较不同阈值类型效果

五、图像形态学之膨胀

在 OpenCV 中,图像形态学(Morphological Transformations) 是针对二值图像或灰度图像的基本图像处理操作,常用于去噪、提取形状特征、增强目标结构等

什么是膨胀(Dilation)?

膨胀操作会使图像中的白色区域(前景)"变大"。它的主要作用是:

  • 填充物体内部的小黑洞
  • 连接相邻的白色区域
  • 扩大前景对象的边界

直观理解:"白色向外扩张" 或者 "形状变胖"。

膨胀的过程可以想象成:
拿着一个结构元素(小模板)在图像上滑动,只要模板覆盖的区域中有一个白点(255),中心点就设为白色。

膨胀是一种卷积操作,它将目标像素的值替换为卷积核覆盖区域的局部最大值。通常膨胀采用的核是一个四边形或圆形的实心核,其锚点在中点。膨胀如下所示:

形态学膨胀:在四边形核覆盖范围内取最大值。

膨胀的数学表达式如下:

意思是:对每个像素 (x, y),取其邻域(由核 kernel 定义)中的最大值作为输出像素值。也就是说,只要邻域内有一个白点(255),输出就为白色。

函数接口:

cpp 复制代码
void cv::dilate(
    InputArray src,          // 输入图像
    OutputArray dst,         // 输出图像
    InputArray kernel,       // 卷积核(结构元素)
    Point anchor = Point(-1, -1), // 锚点(默认在核中心)
    int iterations = 1,      // 膨胀次数
    int borderType = BORDER_CONSTANT,
    const Scalar& borderValue = morphologyDefaultBorderValue()
);

结构元素(kernel / structuring element)

结构元素定义了"膨胀"的形状和大小。通过 getStructuringElement() 创建:

cpp 复制代码
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));

常见结构元素类型:

示例代码

示例 1:基础膨胀操作

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    Mat src = imread("binary.png", IMREAD_GRAYSCALE);
    Mat dst;

    // 创建 3x3 矩形核
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

    // 执行膨胀
    dilate(src, dst, kernel);

    imshow("Original", src);
    imshow("Dilated", dst);
    waitKey(0);
    return 0;
}

效果:白色区域会变厚,边界向外扩展。

示例 2:多次膨胀

cpp 复制代码
dilate(src, dst, kernel, Point(-1, -1), 3);

示例 3:使用不同形状的核

cpp 复制代码
Mat rect = getStructuringElement(MORPH_RECT, Size(5,5));
Mat cross = getStructuringElement(MORPH_CROSS, Size(5,5));
Mat ellipse = getStructuringElement(MORPH_ELLIPSE, Size(5,5));

dilate(src, dst1, rect);
dilate(src, dst2, cross);
dilate(src, dst3, ellipse);

不同核形状影响膨胀方向:

  • 矩形 → 全方位均匀扩展
  • 十字形 → 水平/垂直方向增强
  • 椭圆 → 平滑圆形扩展效果

实际应用:

可视化示意(概念图)

假设原图(白色区域用 1 表示):

cpp 复制代码
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0

经过 3×3 核膨胀后:

cpp 复制代码
0 1 1 1 0
1 1 1 1 1
0 1 1 1 0

白色区域被"扩张"了。

六、图像形态学之腐蚀

腐蚀(Erosion)的原理介绍

腐蚀操作的作用是:"让前景区域(通常为白色)变小",或者说是"侵蚀掉"目标的边界部分。

它的基本思想如下:

  • 使用一个结构元素(kernel)在图像上滑动;
  • 对于每个位置,将结构元素覆盖的区域与原图像进行比较;
  • 如果结构元素覆盖的所有白色像素(255)在原图对应位置都为白色,则该中心像素保持白色;
  • 否则,该像素被设为黑色(0)。

通俗地讲:白色区域会被"收缩",黑色区域会"扩张"。

函数接口:

cpp 复制代码
void cv::erode(
    InputArray src,
    OutputArray dst,
    InputArray kernel,
    Point anchor = Point(-1, -1),
    int iterations = 1,
    int borderType = BORDER_CONSTANT,
    const Scalar &borderValue = morphologyDefaultBorderValue()
);

参数说明:

结构元素 kernel:

结构元素是一个小矩阵,定义了腐蚀时的"邻域形状",常用类型:

cpp 复制代码
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));   // 矩形
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(5, 5)); // 椭圆
Mat kernel = getStructuringElement(MORPH_CROSS, Size(3, 3));   // 十字形

示例代码:

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    Mat src = imread("binary_image.png", IMREAD_GRAYSCALE);
    if (src.empty()) return -1;

    // 创建结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

    // 腐蚀操作
    Mat dst;
    erode(src, dst, kernel);

    // 显示结果
    imshow("原图", src);
    imshow("腐蚀后", dst);
    waitKey(0);
    return 0;
}

应用场景:

与膨胀的对比:

七、图像形态学之开操作

在 OpenCV 中,"开操作(Opening)" 是一种常用的图像形态学(Morphological)运算,主要用于去除小的噪点、断开狭窄的连接、平滑物体边界等。它是由两个基本操作------腐蚀(Erosion) 和 膨胀(Dilation) 顺序组合而成的:
开操作 = 先腐蚀,再膨胀

原理介绍

设原图像为 𝐴,结构元素为 𝐵,则开操作定义为:


效果说明:

  • 先腐蚀:去掉小的高亮噪声点、收缩前景区域。
  • 再膨胀:恢复主干形状,平滑边界。
  • 综合效果:消除孤立的小亮点噪声、保留主要结构、平滑边缘、不显著改变目标面积。

实现开操作的函数为:

cpp 复制代码
cv::morphologyEx(
    InputArray src,          // 输入图像
    OutputArray dst,         // 输出图像
    int op,                  // 操作类型
    InputArray kernel,       // 结构元素
    Point anchor = Point(-1,-1), // 锚点
    int iterations = 1,      // 迭代次数
    int borderType = BORDER_CONSTANT,
    const Scalar& borderValue = morphologyDefaultBorderValue()
);

其中 op 参数选择:

cpp 复制代码
cv::MORPH_OPEN   // 开操作

代码示例:

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main()
{
    // 读取灰度图
    Mat src = imread("test.png", IMREAD_GRAYSCALE);
    if (src.empty()) return -1;

    // 创建结构元素(3x3 矩形核)
    Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));

    // 进行开操作
    Mat dst;
    morphologyEx(src, dst, MORPH_OPEN, element);

    // 显示结果
    imshow("Original", src);
    imshow("Opened", dst);
    waitKey(0);
    return 0;
}

常用结构元素:

cpp 复制代码
// 矩形核
Mat rectKernel = getStructuringElement(MORPH_RECT, Size(3,3));

// 椭圆核
Mat ellipseKernel = getStructuringElement(MORPH_ELLIPSE, Size(5,5));

// 十字核
Mat crossKernel = getStructuringElement(MORPH_CROSS, Size(3,3));

不同核形状会影响开操作的效果,具体选择应根据图像目标形态决定。

八、图像形态学之闭操作

在图像形态学处理中,闭操作(Closing) 是一种与"开操作"相对的形态学运算,主要用于填补目标中的小黑洞(黑色区域)、连接物体间的细小缝隙、平滑目标边界等。

闭操作的基本原理

闭操作(Closing)由两个基本操作组成:
闭操作 = 先膨胀(Dilation),再腐蚀(Erosion)

数学定义如下:

其中:

操作思想:


效果总结:

  • 填补小的黑色"洞";
  • 连接临近的白色目标;
  • 平滑目标边界;
  • 去除前景中的小黑点噪声。

函数接口:

cpp 复制代码
cv::morphologyEx(
    InputArray src,           // 输入图像
    OutputArray dst,          // 输出图像
    int op,                   // 操作类型(MORPH_CLOSE)
    InputArray kernel,        // 结构元素
    Point anchor = Point(-1, -1), // 锚点位置
    int iterations = 1,       // 迭代次数
    int borderType = BORDER_CONSTANT,
    const Scalar& borderValue = morphologyDefaultBorderValue()
);

操作类型:

cpp 复制代码
cv::MORPH_CLOSE   // 表示闭操作

示例代码:

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main()
{
    // 读取灰度图
    Mat src = imread("test.png", IMREAD_GRAYSCALE);
    if (src.empty()) {
        printf("Image not found!\n");
        return -1;
    }

    // 创建结构元素(5x5 椭圆核)
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));

    // 执行闭操作
    Mat dst;
    morphologyEx(src, dst, MORPH_CLOSE, kernel);

    // 显示结果
    imshow("Original", src);
    imshow("Closed (闭操作)", dst);
    waitKey(0);
    return 0;
}

常见结构元素:

cpp 复制代码
// 矩形核
Mat kernel1 = getStructuringElement(MORPH_RECT, Size(3, 3));

// 椭圆核
Mat kernel2 = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));

// 十字核
Mat kernel3 = getStructuringElement(MORPH_CROSS, Size(3, 3));

结构元素的形状和大小影响闭操作效果:

  • 核大 → 填补更大的孔洞;
  • 核小 → 保留细节更多;
  • 椭圆核 → 边界更平滑。

应用场景:

开操作 vs 闭操作对比

形象比喻

  • "开操作"像是用橡皮擦擦掉小亮点噪声。
  • "闭操作"像是用画笔把小黑洞补平滑。

九、形态学梯度

在 OpenCV 中,形态学梯度(Morphological Gradient) 是一种基于膨胀(dilation)与腐蚀(erosion)运算的图像处理操作,用来突出图像中物体的边缘轮廓。

形态学梯度的定义

形态学梯度的数学定义如下:

其中:

  • A 表示输入图像;
  • Dilation(A) 是膨胀操作;
  • Erosion(A) 是腐蚀操作;
  • 两者的差值结果即为形态学梯度。

简而言之:梯度 = 膨胀图像 - 腐蚀图像

形态学梯度的直观理解:

  • 膨胀会让前景(白色区域)变大,边界向外扩张;
  • 腐蚀会让前景变小,边界向内收缩;
  • 两者相减后,剩下的就是原始物体的"边缘部分"。

因此,形态学梯度常用于:

  • 提取轮廓
  • 边缘检测
  • 目标分割的预处理

函数说明:

cpp 复制代码
morphologyEx(InputArray src,
             OutputArray dst,
             int op,
             InputArray kernel,
             Point anchor = Point(-1,-1),
             int iterations = 1,
             int borderType = BORDER_CONSTANT,
             const Scalar& borderValue = morphologyDefaultBorderValue() );

其中 op 可取以下值:

十、顶帽(Top Hat)与黑帽(Black Hat)

顶帽(Top Hat) 的定义是:

其中:

  • A:原始图像
  • Opening(A):对A进行开运算(先腐蚀、后膨胀)

直观理解:

  • 开运算会去掉图像中比结构元素小的亮区域(高亮小物体被消除)。
  • 用原图减去开运算结果后,剩下的就是那些被去掉的"小亮点"或高亮细节。

因此,顶帽可以用来:

  • 提取比周围亮的小目标
  • 增强局部高亮细节
  • 在不均匀光照下提升亮物体的对比度

黑帽(Black Hat) 的定义是:

其中:Closing(A):对A进行闭运算(先膨胀、后腐蚀)

直观理解:

  • 闭运算会填补图像中比结构元素小的暗区域(黑色小坑被填平)。
  • 闭运算结果减去原图后,得到的就是原本较暗的小区域。

因此,黑帽可以用来:

  • 提取比周围暗的小目标
  • 检测阴影或凹陷区域
  • 改善不均匀背景下的暗物体显示

形态学关系总结对比:

OpenCV实现示例:

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    // 读取灰度图像
    Mat src = imread("example.jpg", IMREAD_GRAYSCALE);
    if (src.empty()) return -1;

    // 定义结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(9, 9));

    // 顶帽与黑帽结果
    Mat tophat, blackhat;

    // 顶帽运算
    morphologyEx(src, tophat, MORPH_TOPHAT, kernel);

    // 黑帽运算
    morphologyEx(src, blackhat, MORPH_BLACKHAT, kernel);

    // 显示结果
    imshow("Original", src);
    imshow("TopHat Result", tophat);
    imshow("BlackHat Result", blackhat);

    waitKey(0);
    return 0;
}

十一、自定义核

什么是"核(Kernel)

在图像处理中,核(Kernel) 是一个用于卷积操作的小矩阵,通常尺寸为3×3, 5×5, 7×7 等奇数大小。

核会在图像上滑动,对每个像素区域执行加权求和操作:

核的值决定了它对图像的效果,比如模糊、锐化、边缘检测等。

常见核与作用对比

OpenCV 提供了通用的卷积函数:

cpp 复制代码
void filter2D(InputArray src, OutputArray dst, int ddepth,
              InputArray kernel, Point anchor = Point(-1,-1),
              double delta = 0, int borderType = BORDER_DEFAULT);

实际示例
示例1:自定义模糊核(平均滤波)

cpp 复制代码
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    Mat src = imread("example.jpg");
    if (src.empty()) return -1;

    // 定义3x3平均滤波核
    Mat kernel = (Mat_<float>(3,3) <<
                   1, 1, 1,
                   1, 1, 1,
                   1, 1, 1) / 9.0;

    Mat dst;
    filter2D(src, dst, -1, kernel);

    imshow("Original", src);
    imshow("Custom Blur", dst);
    waitKey(0);
    return 0;
}

示例2:锐化核

cpp 复制代码
Mat kernel = (Mat_<float>(3,3) <<
              0, -1,  0,
             -1,  5, -1,
              0, -1,  0);

这个核能增强边缘,使图像更清晰。

示例3:浮雕效果(Emboss)

cpp 复制代码
Mat kernel = (Mat_<float>(3,3) <<
             -2, -1, 0,
             -1,  1, 1,
              0,  1, 2);
Mat dst;
filter2D(src, dst, -1, kernel);

示例4:自定义边缘检测核

cpp 复制代码
Mat kernel = (Mat_<float>(3,3) <<
              -1, -1, -1,
              -1,  8, -1,
              -1, -1, -1);
filter2D(src, dst, -1, kernel);

效果类似于 Laplacian 算子,可检测出强边缘。

自定义核常见应用场景

常见卷积核设计技巧

相关推荐
萧鼎3 小时前
深入掌握 OpenCV-Python:从图像处理到智能视觉
图像处理·python·opencv
柳鲲鹏10 小时前
OpenCV:BGR/RGB转I420(颜色失真),再转NV12
人工智能·opencv·计算机视觉
有为少年1 天前
告别乱码:OpenCV 中文路径(Unicode)读写的解决方案
人工智能·opencv·计算机视觉
初学小刘1 天前
基于 U-Net 的医学图像分割
python·opencv·计算机视觉
长沙红胖子Qt1 天前
案例分享:音视频录像综合应用(支持录制麦克风音频、录制摄像头视频、同步录制音视频,支持opencv对图形进行处理,录制mp4文件)
opencv·音视频·录音·音视频同步·录像·录像图像处理
星辰pid1 天前
基于ROS与YOLOv3的智能采购机器人设计(智能车创意组-讯飞智慧生活组)
人工智能·opencv·yolo·机器人
AI technophile1 天前
OpenCV计算机视觉实战(28)——深度学习初体验
深度学习·opencv·计算机视觉
hixiong1231 天前
C# OpencvSharp使用lpd_yunet进行车牌检测
开发语言·opencv·计算机视觉·c#
却道天凉_好个秋2 天前
OpenCV(十七):绘制多边形
opencv·计算机视觉