OpenCV入门学习-图像处理(二)

环境:

  • win11 x64
  • OpenCV 4.9.0
  • VS 2022 (v143)
  • C++ (ISO C++ 14 标准)

线性滤波器

涵盖以下内容:

  • 使用OpenCV的cv::filter2D()函数创建自定义线性滤波器
  • 理解图像处理中核(Kernel)的概念及相关运算(Correlation)
  • 实现归一化盒式滤波器(Normalized Box Filter)并观察不同核尺寸的效果

理论基础

相关运算与核

在图像处理中,相关运算(Correlation)是核与图像局部区域进行加权求和的操作。核是一个固定大小的数值矩阵,其中心通常定义为锚点(Anchor Point)。

核的工作流程如下:

  1. 将核的锚点对准图像中的目标像素
  2. 核系数与对应像素值相乘后求和
  3. 将结果写入输出图像的锚点位置
  4. 遍历所有像素完成计算

数学表达式为:
\[ H(x, y) = \sum_{i=0}^{M_i-1} \sum_{j=0}^{M_j-1} I(x + i - a_i, y + j - a_j) K(i, j) \nonumber \]

归一化盒式滤波器

以尺寸为3×3的归一化盒式滤波器为例,核矩阵为:
\[ K = \frac{1}{9} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} \nonumber \]
其作用是计算局部像素的平均值,实现图像模糊效果。


代码实现

使用cv::filter2D进行滤波操作:

  • src:输入图像
  • dst:输出图像
  • ddepth:输出图像深度(-1表示与输入一致)
  • kernel:自定义核
  • anchor:核锚点位置(默认中心)
  • delta:输出像素值的额外偏移量

以下代码展示了如何使用cv::filter2D()实现不同尺寸的归一化盒式滤波器:

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    // 加载图像
    cv::Mat src = cv::imread(cv::samples::findFile(argc >= 2 ? argv[1] : "cat.png"));
    if (src.empty()) {
        printf("Error opening image\n");
        return EXIT_FAILURE;
    }

    // 初始化参数
    cv::Mat dst, kernel;
    cv::Point anchor(-1, -1);
    double delta = 0;
    int ddepth = -1;
    const char* window_name = "filter2D Demo";

    // 循环应用不同尺寸的核
    int ind = 0;
    while (true) {
        // 核尺寸在[3, 11]范围内按奇数递增
        int kernel_size = 3 + 2 * (ind % 5);
        kernel = cv::Mat::ones(kernel_size, kernel_size, CV_32F) / (float)(kernel_size * kernel_size);

        // 应用滤波器
        cv::filter2D(src, dst, ddepth, kernel, anchor, delta, cv::BORDER_DEFAULT);
        cv::imshow(window_name, dst);

        // 按ESC退出
        char c = (char)cv::waitKey(500);
        if (c == 27) break;

        ind++;
    }
    return EXIT_SUCCESS;
}

添加图像边框

在图像处理中,为图像添加边框(padding)是常见的预处理操作。

核心函数与边框类型

函数原型

void cv::copyMakeBorder(
        cv::InputArray src, 
        cv::OutputArray dst,
        int top, int bottom, int left, int right,
        int borderType,
        const cv::Scalar& value = cv::Scalar()
);

支持的边框类型

  1. 常数填充(BORDER_CONSTANT
    使用固定颜色填充边框,默认值为黑色(0)。
  2. 边缘复制(BORDER_REPLICATE
    将图像边缘的像素值复制到扩展区域。

代码实现与解析

#include <iostream>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;

int main() {
    // 读取图像
    cv::Mat src = cv::imread("cat.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        std::cout << "Error: Image not loaded." << std::endl;
        return -1;
    }

    // 初始化参数
    int borderType = cv::BORDER_CONSTANT;
    const char* windowName = "Border Demo";
    cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE);

    // 计算边框大小(占原图尺寸的5%)
    int top = static_cast<int>(0.05 * src.rows);
    int bottom = top;
    int left = static_cast<int>(0.05 * src.cols);
    int right = left;

    cv::RNG rng(12345);  // 随机数生成器

    while (true) {
        // 生成随机颜色
        cv::Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));

        // 添加边框
        cv::Mat dst;
        cv::copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);

        // 显示结果
        cv::imshow(windowName, dst);

        // 处理键盘输入
        char key = static_cast<char>(cv::waitKey(500));
        if (key == 27)
            break;  // ESC退出
        else if (key == 'c')
            borderType = cv::BORDER_CONSTANT;
        else if (key == 'r')
            borderType = cv::BORDER_REPLICATE;
    }
    return 0;
}

步骤说明

  1. 图像读取与校验
    • 使用cv::imread加载图像,并通过src.empty()检查是否加载成功。
  2. 窗口创建
    • 调用cv::namedWindow创建显示窗口。
  3. 边框尺寸计算
    • 边框高度和宽度通过原图尺寸的5%确定:
      \[\nonumber\begin{array}{l}\mathrm{top}=0.05\times \mathrm{src}.\mathrm{rows}\\\mathrm{left}=0.05\times \mathrm{src}.\mathrm{cols}\\ \end{array}\]
  4. 动态交互
    • c切换为随机颜色的常数边框,按r切换为边缘复制模式,按ESC退出程序。

参考资料

[1] OpenCV 官方文档

-------------本文结束 感谢您的阅读-------------