Alt

在计算机视觉与数字图像处理领域,图像平滑与几何变换是构建复杂图像处理系统的基石。本文将深入剖析 OpenCV C++ 库中图像平滑与几何变换的核心技术。我们不仅会详细解读均值滤波、高斯滤波等平滑方法,以及平移、旋转、仿射变换和缩放等几何操作的原理与公式推导,还会结合具体的函数原型与示例代码,帮助读者快速上手实践。

一、图像平滑

1.1 平滑概念

图像平滑(Image Smoothing),也被称为图像模糊(Image Blurring),是数字图像处理中最基础且重要的操作之一。其核心目标是降低图像中的噪声减少图像细节,使图像看起来更加 “平滑” 或 “模糊”。

在实际应用中,图像平滑常用于以下场景:

  • 噪声去除:例如相机传感器产生的椒盐噪声、高斯噪声等

  • 预处理步骤:在边缘检测、特征提取等操作前减少噪声干扰

  • 图像简化:降低图像复杂度,突出主要特征

平滑的基本原理

图像平滑本质上是一种邻域运算,通过对像素及其周围邻域内的像素值进行加权平均来实现。这种运算可以用数学公式表示为:

g(x,y)=∑i,j∈Sw(i,j)⋅f(x+i,y+j)g(x,y) = \sum_{i,j \in S} w(i,j) \cdot f(x+i, y+j)g(x,y)=i,jSw(i,j)f(x+i,y+j)

其中:

  • f(x,y)f(x,y)f(x,y) 是原始图像在位置 (x,y)(x,y)(x,y) 处的像素值

  • g(x,y)g(x,y)g(x,y) 是处理后图像在相同位置的像素值

  • SSS 是定义的邻域范围(例如 3×3、5×5 的矩阵)

  • w(i,j)w(i,j)w(i,j) 是邻域内每个点的权重值,不同的平滑方法有不同的权重计算方式

1.2 均值滤波(Mean Filter)

函数原型(OpenCV)

void blur(InputArray src, OutputArray dst, Size ksize, 
          Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT);

参数说明:

  • src:输入图像

  • dst:输出图像,与输入图像大小和类型相同

  • ksize:卷积核大小,例如 Size(3,3) 表示 3×3 的矩阵

  • anchor:锚点位置,默认值 (-1,-1) 表示锚点位于核中心

  • borderType:边界处理方式

原理与公式

均值滤波是最简单的平滑方法,它用邻域内所有像素的平均值替换中心像素值。对于一个 k×kk \times kk×k 的卷积核,每个像素的权重都是 1k2\frac{1}{k^2}k21

数学公式表示为:

g(x,y)=1k2∑i,j∈Sf(x+i,y+j)g(x,y) = \frac{1}{k^2} \sum_{i,j \in S} f(x+i, y+j)g(x,y)=k21i,jSf(x+i,y+j)

例如,一个 3×3 的均值滤波卷积核为:

19[111111111]\frac{1}{9} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix}91 111111111

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    // 读取图像
    Mat image = imread("lena.jpg", IMREAD_COLOR);
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }
    
    // 均值滤波
    Mat blurred;
    blur(image, blurred, Size(5, 5)); // 使用5×5的卷积核
    
    // 显示原图和处理后的图像
    imshow("Original Image", image);
    imshow("Blurred Image (Mean Filter)", blurred);
    
    // 保存结果
    imwrite("mean_filter_result.jpg", blurred);
    
    waitKey(0);
    return 0;
}

1.3 高斯滤波(Gaussian Filter)

函数原型(OpenCV)

void GaussianBlur(InputArray src, OutputArray dst, Size ksize,
                  double sigmaX, double sigmaY=0,
                  int borderType=BORDER_DEFAULT);

参数说明:

  • srcdst:输入 / 输出图像

  • ksize:卷积核大小,必须是奇数

  • sigmaX:X 方向的高斯核标准差

  • sigmaY:Y 方向的高斯核标准差,默认与 sigmaX 相同

  • borderType:边界处理方式

原理与公式

高斯滤波是一种加权平均滤波,它根据高斯函数计算邻域内每个像素的权重。与均值滤波不同,高斯滤波对距离中心越近的像素赋予更高的权重,因此能在平滑图像的同时更好地保留边缘

二维高斯函数公式为:

G(x,y)=12πσ2e−x2+y22σ2G(x,y) = \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}}G(x,y)=2πσ21e2σ2x2+y2

其中 σ\sigmaσ 是标准差,控制高斯函数的 “宽度”。

例如,一个 3×3 的高斯卷积核(σ=1.0\sigma=1.0σ=1.0)可能是:

116[121242121]\frac{1}{16} \begin{bmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{bmatrix}161 121242121

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    // 读取图像
    Mat image = imread("lena.jpg", IMREAD_COLOR);
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }
    
    // 高斯滤波
    Mat gaussianBlur;
    GaussianBlur(image, gaussianBlur, Size(5, 5), 0, 0); // 5×5卷积核,标准差自动计算
    
    // 显示原图和处理后的图像
    imshow("Original Image", image);
    imshow("Gaussian Blur", gaussianBlur);
    
    // 保存结果
    imwrite("gaussian_filter_result.jpg", gaussianBlur);
    
    waitKey(0);
    return 0;
}

1.4 中值滤波(Median Filter)

函数原型(OpenCV)

void medianBlur(InputArray src, OutputArray dst, int ksize);

参数说明:

  • srcdst:输入 / 输出图像

  • ksize:卷积核大小,必须是奇数

原理

中值滤波是一种非线性滤波方法,它用邻域内所有像素值的中值替换中心像素值。具体步骤如下:

  1. 将邻域内的像素值按升序排列

  2. 选择中间值作为中心像素的新值

例如,对于以下 5×5 邻域内的像素值:

\[120, 200, 150, 90, 180,
170, 140, 160, 110, 130,
190, 210, 100, 125, 145,
220, 135, 165, 185, 205,
80, 105, 155, 175, 195]

排序后为:

\[80, 90, 100, 105, 110, 120, 125, 130, 135, 140,
145, 150, 155, 160, 165, 170, 175, 180, 185, 190,
195, 200, 205, 210, 220]

中值为第 13 个元素:150

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    // 读取图像
    Mat image = imread("lena.jpg", IMREAD_COLOR);
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }
    
    // 添加椒盐噪声
    Mat noisyImage = image.clone();
    int noiseAmount = 10000;
    for (int i = 0; i < noiseAmount; i++) {
        int x = rand() % noisyImage.cols;
        int y = rand() % noisyImage.rows;
        if (i % 2 == 0) {
            noisyImage.at<Vec3b>(y, x) = Vec3b(255, 255, 255); // 白点
        } else {
            noisyImage.at<Vec3b>(y, x) = Vec3b(0, 0, 0);       // 黑点
        }
    }
    
    // 中值滤波
    Mat medianFiltered;
    medianBlur(noisyImage, medianFiltered, 5); // 使用5×5邻域
    
    // 显示原图、噪声图和处理后的图像
    imshow("Original Image", image);
    imshow("Noisy Image", noisyImage);
    imshow("Median Filtered", medianFiltered);
    
    // 保存结果
    imwrite("median_filter_result.jpg", medianFiltered);
    
    waitKey(0);
    return 0;
}

1.5 双边滤波(Bilateral Filter)

函数原型(OpenCV)

void bilateralFilter(InputArray src, OutputArray dst, int d,
                     double sigmaColor, double sigmaSpace,
                     int borderType=BORDER_DEFAULT);

参数说明:

  • srcdst:输入 / 输出图像

  • d:过滤时使用的像素邻域直径

  • sigmaColor:颜色空间滤波器的标准差

  • sigmaSpace:坐标空间滤波器的标准差

  • borderType:边界处理方式

原理与公式

一些滤波器再去除噪声的同时会模糊掉图像边缘,为了一定程度上保留边缘,可以选择双边滤波,它是一种保边去噪的滤波方法,它同时考虑了空间距离颜色差异两个因素:

  • 空间域权重:与高斯滤波类似,距离越近权重越高

  • 值域权重:颜色差异越小权重越高

数学公式表示为:

g(x,y)=1Wp∑i,j∈Sf(i,j)⋅ws(i,j)⋅wr(i,j)g(x,y) = \frac{1}{W_p} \sum_{i,j \in S} f(i,j) \cdot w_s(i,j) \cdot w_r(i,j)g(x,y)=Wp1i,jSf(i,j)ws(i,j)wr(i,j)

其中:

  • WpW_pWp 是归一化系数

  • ws(i,j)w_s(i,j)ws(i,j) 是空间域权重,由高斯函数计算

  • wr(i,j)w_r(i,j)wr(i,j) 是值域权重,取决于颜色差异

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    // 读取图像
    Mat image = imread("lena.jpg", IMREAD_COLOR);
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }
    
    // 双边滤波
    Mat bilateralFiltered;
    bilateralFilter(image, bilateralFiltered, 15, 80, 80); 
    // d=15: 表示在过滤时选取的邻域直径为15像素
    // sigmaColor=80: 颜色空间滤波器的标准差,值越大表示颜色差异越大的像素会被混合在一起
    // sigmaSpace=80: 坐标空间滤波器的标准差,值越大表示距离越远的像素会相互影响
    
    // 显示原图和处理后的图像
    imshow("Original Image", image);
    imshow("Bilateral Filtered", bilateralFiltered);
    
    // 保存结果
    imwrite("bilateral_filter_result.jpg", bilateralFiltered);
    
    waitKey(0);
    return 0;
}

1.6 总结与对比

方法 类型 原理 优点 缺点 适用场景
均值滤波 线性 邻域平均值 速度快 模糊边缘 初步预处理
高斯滤波 线性 基于高斯函数的加权平均 抑制高斯噪声 计算复杂度较高 大多数场景
中值滤波 非线性 邻域中值替换 抑制椒盐噪声 不适合高斯噪声 椒盐噪声处理
双边滤波 非线性 考虑空间和颜色差异 保留边缘 计算复杂度极高 美颜、细节保留场景

在实际应用中,应根据具体需求选择合适的平滑方法。如果需要快速处理且对细节要求不高,可以选择均值滤波;如果需要保留边缘信息,高斯滤波或双边滤波是更好的选择;而处理椒盐噪声时,中值滤波通常是首选。

二、几何变换

在图像处理中,几何变换是改变图像形状、位置和大小的重要操作,它不改变图像的像素值,而是通过对像素坐标的重新映射实现图像的重新布局。常见的几何变换包括平移、旋转、仿射变换和缩放,这些操作在图像配准、目标检测、图像矫正等领域有着广泛应用。

2.1 平移(Translation)

原理与公式推导

平移是将图像中每个像素在xxx轴和yyy轴方向上移动固定的距离。设原始图像中像素点坐标为(x,y)(x, y)(x,y),在xxx轴方向平移txt_xtx,在yyy轴方向平移tyt_yty,则平移后的坐标(x′,y′)(x', y')(x,y)可通过以下公式计算:

{x′=x+txy′=y+ty\begin{cases}x' = x + t_x \\y' = y + t_y\end{cases}{x=x+txy=y+ty

为了统一矩阵运算形式,引入齐次坐标表示,将二维坐标(x,y)(x, y)(x,y)扩展为三维坐标(x,y,1)(x, y, 1)(x,y,1),此时平移变换可以用矩阵乘法表示:

[x′y′1]=[10tx01ty001][xy1]\begin{bmatrix}x' \\ y' \\ 1\end{bmatrix} = \begin{bmatrix}1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1 = 100010txty1 xy1

其中,左侧矩阵称为平移矩阵TTT,通过该矩阵与原始坐标向量相乘,即可得到平移后的坐标。

函数原型(OpenCV)

Mat getTranslationMatrix2D(Point2f offset);
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, 
                int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, 
                const Scalar& borderValue=Scalar());

getTranslationMatrix2D函数用于生成平移矩阵,参数offset是一个Point2f类型,表示在xxx轴和yyy轴方向上的平移量;warpAffine函数用于对图像进行仿射变换,参数src为输入图像,dst为输出图像,M为变换矩阵,dsize为输出图像的大小。

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    Mat image = imread("lena.jpg");
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    // 生成平移矩阵,x方向平移100像素,y方向平移50像素
    Mat T = getTranslationMatrix2D(Point2f(100, 50));
    Mat translatedImage;
    // 应用平移变换
    warpAffine(image, translatedImage, T, image.size());

    imshow("Original Image", image);
    imshow("Translated Image", translatedImage);
    waitKey(0);
    return 0;
}

2.2 旋转(Rotation)

原理与公式推导

旋转是将图像围绕一个指定点(通常是图像中心)旋转一定角度。设原始像素点坐标为(x,y)(x, y)(x,y),绕原点旋转θ\thetaθ角度后得到新坐标(x′,y′)(x', y')(x,y) 。根据三角函数关系,可推导出以下公式:

{x′=xcos⁡θ−ysin⁡θy′=xsin⁡θ+ycos⁡θ\begin{cases}x' = x \cos\theta - y \sin\theta \\y' = x \sin\theta + y \cos\theta\end{cases}{x=xcosθysinθy=xsinθ+ycosθ

转换为齐次坐标和矩阵乘法形式:

[x′y′1]=[cos⁡θ−sin⁡θ0sin⁡θcos⁡θ0001][xy1]\begin{bmatrix}x' \\y' \\1\end{bmatrix} =\begin{bmatrix}\cos\theta & -\sin\theta & 0 \\\sin\theta & \cos\theta & 0 \\0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\y \\1\end{bmatrix} xy1 = cosθsinθ0sinθcosθ0001 xy1

如果旋转中心不是原点,而是图像中心(cx,cy)(c_x, c_y)(cx,cy),则需要先将图像平移到原点,进行旋转后再平移回原来位置,完整的旋转矩阵为:

[x′y′1]=[cos⁡θ−sin⁡θ0sin⁡θcos⁡θ0001][xy1]\begin{bmatrix}x' \\ y' \\ 1\end{bmatrix} = \begin{bmatrix}\cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1 = cosθsinθ0sinθcosθ0001 xy1

函数原型(OpenCV)

Mat getRotationMatrix2D(Point2f center, double angle, double scale);
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, 
                int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, 
                const Scalar& borderValue=Scalar());

getRotationMatrix2D函数用于生成旋转矩阵,参数center表示旋转中心,angle表示旋转角度(单位为度),scale表示缩放比例;warpAffine函数同样用于应用变换。

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    Mat image = imread("lena.jpg");
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    Point2f center(image.cols / 2, image.rows / 2);
    double angle = 45;  // 旋转45度
    double scale = 1;
    // 生成旋转矩阵
    Mat R = getRotationMatrix2D(center, angle, scale);
    Mat rotatedImage;
    // 应用旋转变换
    warpAffine(image, rotatedImage, R, image.size());

    imshow("Original Image", image);
    imshow("Rotated Image", rotatedImage);
    waitKey(0);
    return 0;
}

2.3 仿射变换(Affine Transformation)

原理与公式推导

仿射变换是一种线性变换,它保持了图像中的 “直线性” 和 “平行性”,可以实现平移、旋转、缩放、错切等多种变换的组合。仿射变换的一般公式为:

{x′=a1x+a2y+a3y′=a4x+a5y+a6\begin{cases}x' = a_1 x + a_2 y + a_3 \\y' = a_4 x + a_5 y + a_6\end{cases}{x=a1x+a2y+a3y=a4x+a5y+a6

用齐次坐标和矩阵乘法表示为:

[x′y′1]=[a1a2a3a4a5a6001][xy1]\begin{bmatrix}x' \\y' \\1\end{bmatrix} =\begin{bmatrix}a_1 & a_2 & a_3 \\a_4 & a_5 & a_6 \\0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\y \\1\end{bmatrix} xy1 = a1a40a2a50a3a61 xy1

要确定仿射变换矩阵,需要已知原始图像中三个不共线的点及其在变换后图像中的对应点,通过解线性方程组即可得到矩阵中的六个参数。

函数原型(OpenCV)

Mat getAffineTransform(const Point2f src[], const Point2f dst[]);
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, 
                int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, 
                const Scalar& borderValue=Scalar());

getAffineTransform函数根据原始图像和目标图像中三个对应点对,计算仿射变换矩阵;warpAffine函数用于应用变换。

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    Mat image = imread("lena.jpg");
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    Point2f srcPoints[] = { Point2f(0, 0), Point2f(image.cols - 1, 0), Point2f(0, image.rows - 1) };
    Point2f dstPoints[] = { Point2f(50, 50), Point2f(image.cols - 50, 30), Point2f(20, image.rows - 80) };

    // 计算仿射变换矩阵
    Mat M = getAffineTransform(srcPoints, dstPoints);
    Mat affineImage;
    // 应用仿射变换
    warpAffine(image, affineImage, M, image.size());

    imshow("Original Image", image);
    imshow("Affine Transformed Image", affineImage);
    waitKey(0);
    return 0;
}

2.4 缩放(Scaling)

原理与公式推导

缩放是改变图像大小的操作,分为放大和缩小。设原始像素点坐标为(x,y)(x, y)(x,y),在xxx轴方向缩放比例为sxs_xsx,在yyy轴方向缩放比例为sys_ysy,则缩放后的坐标(x′,y′)(x', y')(x,y)计算公式为:

{x′=sx⋅xy′=sy⋅y\begin{cases}x' = s_x \cdot x \\y' = s_y \cdot y\end{cases}{x=sxxy=syy

转换为齐次坐标和矩阵乘法形式:

[x′y′1]=[sx000sy0001][xy1]\begin{bmatrix}x' \\y' \\1\end{bmatrix} =\begin{bmatrix}s_x & 0 & 0 \\0 & s_y & 0 \\0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\y \\1\end{bmatrix} xy1 = sx000sy0001 xy1

如果sx=sys_x = s_ysx=sy,则为等比例缩放;如果sx≠sys_x \neq s_ysx=sy,则为非等比例缩放,会导致图像变形。

函数原型(OpenCV)

void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, 
            int interpolation=INTER_LINEAR);

resize函数用于对图像进行缩放操作,参数src为输入图像,dst为输出图像,dsize为输出图像的大小;fxfy分别为xxx轴和yyy轴方向的缩放比例,如果指定了dsize,则fxfy会自动计算;interpolation表示插值方法,用于计算缩放后像素值,常见的有线性插值INTER_LINEAR、最近邻插值INTER_NEAREST等 。

示例代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    Mat image = imread("lena.jpg");
    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    Mat scaledImage;
    // 缩小为原来的0.5倍,使用线性插值
    resize(image, scaledImage, Size(), 0.5, 0.5, INTER_LINEAR);

    imshow("Original Image", image);
    imshow("Scaled Image", scaledImage);
    waitKey(0);
    return 0;
}

2.5 总结与对比

变换类型 核心公式 主要参数 应用场景 函数原型
平移 [x′y′1]=[10tx01ty001][xy1]\begin{bmatrix}x' \\ y' \\ 1\end{bmatrix} = \begin{bmatrix}1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1 = 100010txty1 xy1 平移量(tx,ty)(t_x, t_y)(tx,ty) 图像位置调整 getTranslationMatrix2DwarpAffine
旋转 [x′y′1]=[cos⁡θ−sin⁡θ0sin⁡θcos⁡θ0001][xy1]\begin{bmatrix}x' \\ y' \\ 1\end{bmatrix} = \begin{bmatrix}\cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1 = cosθsinθ0sinθcosθ0001 xy1 旋转中心、角度θ\thetaθ、缩放比例 图像方向调整 getRotationMatrix2DwarpAffine
仿射变换 [x′y′1]=[a1a2a3a4a5a6001][xy1]\begin{bmatrix}x' \\ y' \\ 1\end{bmatrix} = \begin{bmatrix}a_1 & a_2 & a_3 \\ a_4 & a_5 & a_6 \\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1 = a1a40a2a50a3a61 xy1 三对对应点坐标 图像矫正、复杂变形 getAffineTransformwarpAffine
缩放 [x′y′1]=[sx000sy0001][xy1]\begin{bmatrix}x' \\ y' \\ 1\end{bmatrix} = \begin{bmatrix}s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x \\ y \\ 1\end{bmatrix} xy1 = sx000sy0001 xy1 缩放比例(sx,sy)(s_x, s_y)(sx,sy)或目标大小 调整图像尺寸 resize

几何变换是图像处理的基础操作,通过理解其数学原理和 OpenCV 函数的使用,能够灵活应对各种图像变换需求。在实际应用中,需根据具体场景选择合适的变换类型和参数,以达到理想的处理效果。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐