OpenCV编程- (图像基础处理:噪声、滤波、直方图与边缘检测)

本文阐述 OpenCV 中图像基础处理的核心技术,包括图像噪声的特性与生成、多种滤波算法的原理与实现、直方图的分析与增强技术,以及边缘检测的主流算法。通过理论解析、函数说明与代码示例,为读者提供从基础概念到实际应用的完整指导,并扩展讨论各技术的适用场景与优化策略。

一、图像噪声

图像噪声是成像过程中像素值的随机扰动,直接影响后续处理精度。理解噪声特性是选择去噪方法的前提。

1.1 噪声定义与分类

噪声本质为像素值的随机偏差,按分布特性可分为:

  • 椒盐噪声:随机出现的纯白(255)和纯黑(0)像素点,类似 "盐粒" 与 "胡椒粒"。

    • 特征:突发性、离散性,对局部细节破坏显著。
  • 高斯噪声:噪声值服从高斯分布(正态分布)的细密颗粒状干扰

  • 其他噪声

    • 泊松噪声:与光强相关,低光照环境显著。
    • 均匀噪声:噪声值在固定范围内均匀分布。
    • 量化噪声:图像量化过程中的精度损失。

1.2 噪声生成代码实现

1.2.1 椒盐噪声生成
cpp 复制代码
void addSaltPepperNoise(Mat &src, int n)
{
    for (int k = 0; k < n; k++)
    {
        // 随机生成椒盐位置
        int i = rand() % src.rows;
        int j = rand() % src.cols;

        // 随机生成椒盐类型
        int type = rand() % 2;

        // 添加椒盐噪声
        if (type == 0)
        {
            src.at<Vec3b>(i, j) = Vec3b(0, 0, 0); // 黑色
        }
        else
        {
            src.at<Vec3b>(i, j) = Vec3b(255, 255, 255); // 白色
        }
    }
    imshow("椒盐噪声", src);
}
1.2.2 高斯噪声生成
cpp 复制代码
void addGaussianNoise(Mat& image, double mean = 0, double stddev = 30) {
    Mat noise = Mat(image.size(), CV_32F);  // 噪声矩阵(32位浮点型避免溢出)
    randn(noise, mean, stddev);  // 生成高斯分布噪声

    if (image.type() == CV_8UC1) {  // 灰度图处理
        Mat temp;
        image.convertTo(temp, CV_32F);  // 转为32F避免溢出
        temp += noise;  // 叠加噪声
        temp.convertTo(image, CV_8U);  // 转回8U
    } else if (image.type() == CV_8UC3) {  // 彩色图处理(分通道)
        Mat channels[3];
        split(image, channels);  // 分离B、G、R通道
        for (int i = 0; i < 3; i++) {
            channels[i].convertTo(channels[i], CV_32F);
            channels[i] += noise;
            channels[i].convertTo(channels[i], CV_8U);
        }
        merge(channels, 3, image);  // 合并通道
    }
    imshow("高斯噪声图像", image);
}
cpp 复制代码
void addGaussianNoise(Mat &src, double mean, double sigma)
{
    // 需要与原图大小类型保持一致,为了合并。
    Mat noise = Mat::zeros(src.size(), src.type());
    randn(noise, mean, sigma);
    // addWeighted()函数将原图和噪声图加权相加
    // 参数1:输入图像1
    // 参数2:输入图像1的权重,(权重指的是原图所占比例)
    // 参数3:输入图像2
    // 参数4:输入图像2的权重,(权重指的是噪声所占比例)
    // 参数5:加到输出图像中的标量值
    addWeighted(src, 1, noise, 1, 0, src);
    imshow("高斯噪声", src);
}

二、图像平滑(滤波)

图像平滑通过抑制噪声或弱化细节实现去噪,OpenCV 提供多种滤波算法,适用于不同场景。

2.1 均值滤波(Mean Filtering)

2.1.1 原理

用像素邻域的算术平均值替换中心像素

2.1.2 函数说明
cpp 复制代码
void blur(
    InputArray src,        // 输入图像(单/多通道)
    OutputArray dst,       // 输出图像(与src同尺寸、类型)
    Size ksize,            // 滤波核大小(如Size(3,3))
    Point anchor = Point(-1,-1),  // 锚点(默认核中心)
    int borderType = BORDER_DEFAULT  // 边界填充方式
);
cpp 复制代码
void meanFilterDemo(Mat& src) {
    Mat dst3, dst5;
    blur(src, dst3, Size(3,3));  // 3×3核(轻微模糊)
    blur(src, dst5, Size(5,5));  // 5×5核(中等模糊)
    imshow("3×3均值滤波", dst3);
    imshow("5×5均值滤波", dst5);
}
2.1.4 特性
  • 优点:计算速度快,有效抑制高斯噪声。
  • 缺点:模糊边缘与细节(全局性平均导致)。
  • 适用场景:实时性要求高的简单去噪。
2.1.5 代码实现
cpp 复制代码
void smoothImage(Mat &src)
{
    // 均值模糊
    Mat dst;
    // 参数1:输入图像
    // 参数2:输出图像
    // 参数3:卷积核大小,
    blur(src, dst, Size(5, 5));
    imshow("均值模糊", dst);
}

2.2 高斯滤波(Gaussian Filtering)

2.2.1 原理

用高斯核(中心权重高、边缘权重低)对邻域像素加权平均,符合人眼对中心像素的关注特性。高斯函数:G(x,y)=2πσ21​e−2σ2(x2+y2)​

2.2.2 函数说明
cpp 复制代码
void GaussianBlur(
    InputArray src, 
    OutputArray dst, 
    Size ksize,            // 核大小(必须为正奇数,如3×3、5×5)
    double sigmaX,         // X方向标准差(控制水平模糊)
    double sigmaY = 0,     // Y方向标准差(默认与sigmaX相同)
    int borderType = BORDER_DEFAULT
);
2.2.3 代码示例
cpp 复制代码
void gaussianFilterDemo(Mat& src) {
    Mat dst3, dst5;
    GaussianBlur(src, dst3, Size(3,3), 0.8);  // 3×3核,σ=0.8(轻微模糊)
    GaussianBlur(src, dst5, Size(5,5), 1.5);  // 5×5核,σ=1.5(明显模糊)
    imshow("3×3高斯滤波", dst3);
    imshow("5×5高斯滤波", dst5);
}
2.2.4 特性
  • 优点:相比均值滤波,边缘保留更好,去噪效果更柔和。
  • 缺点:对椒盐噪声效果有限。
  • 适用场景:通用去噪(平衡去噪与细节保留)。
2.2.5 代码实现
cpp 复制代码
void gaussianBlurImage(Mat &src)
{
    // 高斯模糊
    Mat dst;
    // 参数1:输入图像
    // 参数2:输出图像
    // 参数3:卷积核大小
    // 参数4:x方向的标准差,为0时表示根据卷积核大小自动计算
    GaussianBlur(src, dst, Size(5, 5), 0);
    // GaussianBlur(src, dst, Size(7, 7), 0);
    imshow("高斯模糊", dst);
}

2.3 中值滤波(Median Filtering)

2.3.1 原理

用像素邻域的中值替换中心像素(排序后取中间值),是非线性滤波的典型代表。

2.3.2 函数说明
cpp 复制代码
void medianBlur(
    InputArray src, 
    OutputArray dst, 
    int ksize  // 核大小(正奇数,如3、5)
);
2.3.3 代码示例
cpp 复制代码
void medianFilterDemo(Mat& src) {
    // 假设src已添加椒盐噪声
    Mat dst3, dst5;
    medianBlur(src, dst3, 3);  // 3×3核
    medianBlur(src, dst5, 5);  // 5×5核
    imshow("3×3中值滤波", dst3);
    imshow("5×5中值滤波", dst5);
}
2.3.4 特性
  • 优点:有效去除椒盐噪声,边缘保留较好。
  • 缺点:计算耗时(需排序),核过大导致 "块状化"。
  • 适用场景:传感器噪声、传输错误修复。
2.3.5 代码实现
cpp 复制代码
void medianBlurImage(Mat &src)
{
    // 中值模糊
    Mat dst;
    // 参数1:输入图像
    // 参数2:输出图像
    // 参数3:卷积核大小
    medianBlur(src, dst, 5);
    imshow("中值模糊", dst);
}

2.4 滤波方法对比

方法 核心原理 适用噪声 边缘保留 速度 典型场景
均值滤波 算术平均 高斯噪声 最快 实时低精度去噪
高斯滤波 高斯加权平均 高斯噪声 通用去噪(平衡去噪与细节)
中值滤波 邻域中值替换 椒盐噪声 传感器噪声、传输错误修复
双边滤波 空间 + 像素值相似性加权 高斯噪声 极好 人像美化(保留边缘 + 模糊背景)

三、直方图

直方图是图像像素强度分布的统计表示,是分析图像亮度、对比度的核心工具,也是图像增强的基础。

3.1 基本概念

  • 定义:横轴为像素值(0~255),纵轴为该值出现的频数(像素数量)。
  • 关键术语
    • Bin(区间):0~255 的划分单位(如每 10 个值一个 Bin)。
    • 频数:每个 Bin 中的像素数量。
    • 作用:判断图像偏亮 / 偏暗 / 低对比度,为增强提供依据。

3.2 直方图计算核心函数(calcHist

cpp 复制代码
void calcHist(
    const Mat* images,    // 输入图像数组(指针)
    int nimages,          // 输入图像数量(通常为1)
    const int* channels,  // 需计算的通道(如{0}为灰度通道,{0,1,2}为BGR通道)
    InputArray mask,      // 掩膜(可选,指定计算区域)
    OutputArray hist,     // 输出直方图(1D/2D数组)
    int dims,             // 直方图维度(通常为1)
    const int* histSize,  // 每个维度的Bin数量(如{256})
    const float** ranges, // 每个维度的像素值范围(如{{0,256}})
    bool uniform = true,  // 是否均匀划分Bin
    bool accumulate = false // 是否累积计算
);

3.3 灰度直方图绘制

cpp 复制代码
void grayHistogramDemo(Mat& src) {
    // 1. 转为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    // 2. 计算直方图
    Mat hist;
    int histSize = 256;
    float range[] = {0, 256};
    const float* histRange = {range};
    calcHist(&gray, 1, 0, Mat(), hist, 1, &histSize, &histRange);

    // 3. 绘制直方图
    int hist_w = 512, hist_h = 400;
    int bin_w = cvRound((double)hist_w / histSize);
    Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));

    // 归一化(将频数缩放到画布高度)
    normalize(hist, hist, 0, hist_h, NORM_MINMAX, -1, Mat());

    // 绘制线段
    for (int i = 1; i < histSize; i++) {
        line(histImage,
             Point(bin_w*(i-1), hist_h - cvRound(hist.at<float>(i-1))),
             Point(bin_w*i, hist_h - cvRound(hist.at<float>(i))),
             Scalar(255, 0, 0), 2);
    }

    imshow("灰度直方图", histImage);
}

3.4 彩色直方图绘制

cpp 复制代码
void colorHistogramDemo(Mat& src) {
    vector<Mat> channels;
    split(src, channels);  // 分离B、G、R通道

    // 计算三通道直方图
    Mat b_hist, g_hist, r_hist;
    int histSize = 256;
    float range[] = {0, 256};
    const float* histRange = {range};
    calcHist(&channels[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange);
    calcHist(&channels[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange);
    calcHist(&channels[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange);

    // 绘制参数
    int hist_w = 512, hist_h = 400;
    int bin_w = cvRound((double)hist_w / histSize);
    Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));

    // 归一化
    normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX);
    normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX);
    normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX);

    // 绘制三通道直方图(B:蓝, G:绿, R:红)
    for (int i = 1; i < histSize; i++) {
        line(histImage, Point(bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1))),
             Point(bin_w*i, hist_h - cvRound(b_hist.at<float>(i))), Scalar(255,0,0), 2);
        line(histImage, Point(bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1))),
             Point(bin_w*i, hist_h - cvRound(g_hist.at<float>(i))), Scalar(0,255,0), 2);
        line(histImage, Point(bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1))),
             Point(bin_w*i, hist_h - cvRound(r_hist.at<float>(i))), Scalar(0,0,255), 2);
    }

    imshow("彩色直方图", histImage);
}

3.5 掩膜(Mask)应用

掩膜是二值图像(0 或 255),限定直方图计算区域(仅 255 区域参与)。

cpp 复制代码
void maskImage(Mat &src)
{
    // 掩膜,参数1:原图的大小,参数2:原图的类型
    Mat mask = Mat::zeros(src.size(), src.type());
    // 参数1:输入图像,参数2:输出图像,参数3:掩膜大小,参数4:掩膜位置,参数5:掩膜颜色
    // 使用相对坐标避免超出图像边界
    int rectWidth = src.cols * 0.5;  // 矩形宽度为图像宽度的50%
    int rectHeight = src.rows * 0.5; // 矩形高度为图像高度的50%
    Point topLeft(src.cols * 0.25, src.rows * 0.25);
    Point bottomRight(src.cols * 0.75, src.rows * 0.75);
    rectangle(mask, topLeft, bottomRight, Scalar(255, 255, 255), -1);
    imshow("掩膜", mask);
    // 掩膜图像
    Mat dst;
    // 参数1:输入图像,参数2:输出图像,参数3:掩膜
    bitwise_and(src, src, dst, mask);
    imshow("掩膜图像", dst);
}

3.6 直方图均衡化

3.6.1 全局均衡化(equalizeHist

通过重新分配像素值,拉伸直方图范围以增强对比度。

cpp 复制代码
void globalEqualizeDemo(Mat& src) {
    Mat gray, eqGray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    equalizeHist(gray, eqGray);  // 均衡化
    imshow("全局均衡化", eqGray);
}
3.6.2 自适应均衡化(CLAHE)

分块均衡化,限制对比度避免噪声放大,适用于局部对比度低的图像。

cpp 复制代码
void equalizeHistImage(Mat &src)
{
    // 均衡化
    Mat dst;
    // 参数1:输入图像
    // 参数2:输出图像
    equalizeHist(src, dst);
    imshow("均衡化图像", dst);
}

createCLAHE参数

  • clipLimit:对比度限制阈值(值越小,噪声抑制越强)。
  • tileGridSize:分块大小(8×8 默认,块越小细节越丰富)。

3.7 直方图比较

通过计算两个直方图的相似性,判断图像内容相关性。

cpp 复制代码
void compareHistDemo(Mat& img1, Mat& img2) {
    // 计算灰度直方图(步骤略)
    // ...

    // 归一化
    normalize(hist1, hist1, 0, 1, NORM_MINMAX);
    normalize(hist2, hist2, 0, 1, NORM_MINMAX);

    // 多种方法比较
    double correl = compareHist(hist1, hist2, HISTCMP_CORREL);       // 相关性(越大越相似)
    double chisqr = compareHist(hist1, hist2, HISTCMP_CHISQR);       // 卡方距离(越小越相似)
    double bhatta = compareHist(hist1, hist2, HISTCMP_BHATTACHARYYA); // 巴氏距离(越小越相似)

    cout << "相关性:" << correl << ",卡方距离:" << chisqr << ",巴氏距离:" << bhatta << endl;
}

四、边缘检测

边缘是像素值剧烈变化的区域(如物体边界),是高层视觉任务的核心特征。

4.1 边缘检测原理

  • 一阶导数:衡量像素变化率(梯度幅值越大,边缘越明显)。
  • 二阶导数:衡量变化率的变化(零交叉点对应边缘)。

4.2 Sobel 算子

4.2.1 原理

用水平(Gx)和垂直(Gy)卷积核计算梯度,合并梯度得到边缘强度。

4.2.2 函数说明
cpp 复制代码
void Sobel(
    InputArray src, 
    OutputArray dst, 
    int ddepth,    // 输出深度(推荐CV_16S,避免负值溢出)
    int dx,        // x方向导数阶数(1表示计算Gx)
    int dy,        // y方向导数阶数(1表示计算Gy)
    int ksize = 3  // 核大小(3/5/7,-1表示Scharr核)
);
4.2.3 代码示例
cpp 复制代码
void sobel(Mat &src, Mat &dst)
{
    // 1.将图像转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    // 2.初始化输出图像为灰度图
    dst.create(gray.size(), gray.type());

    // 3.计算x方向和y方向的梯度
    Mat grad_x, grad_y;
    Sobel(gray, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
    Sobel(gray, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);

    // 4.计算梯度的幅值和方向
    Mat abs_grad_x, abs_grad_y;
    convertScaleAbs(grad_x, abs_grad_x);
    convertScaleAbs(grad_y, abs_grad_y);
    // 加权融合
    addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
    // 5.将梯度幅值转换为8位无符号整型
    convertScaleAbs(dst, dst);
}

4.3 Laplacian 算子

基于二阶导数的零交叉点检测边缘,对细小边缘敏感,但对噪声敏感(需先去噪)。

cpp 复制代码
void laplacian(Mat &src, Mat &dst)
{
    // 1.将图像转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    // 2.初始化输出图像为灰度图
    dst.create(gray.size(), gray.type());
    // 3.计算拉普拉斯算子
    Laplacian(gray, dst, CV_16S, 3, 1, 0, BORDER_DEFAULT);
    // 4.将拉普拉斯算子转换为8位无符号整型
    convertScaleAbs(dst, dst);
}

4.4 Canny 边缘检测

多阶段最优边缘检测,实现 "低误检、高定位、单边缘响应"。

4.4.1 流程
  1. 高斯滤波去噪;
  2. Sobel 算子计算梯度;
  3. 非极大值抑制(细化边缘);
  4. 双阈值检测(强 / 弱边缘分类);
  5. 边缘连接(弱边缘需与强边缘相连)。
4.4.2 函数说明
cpp 复制代码
void Canny(
    InputArray image,      // 输入图像
    OutputArray edges,     // 输出二值边缘图
    double threshold1,     // 低阈值(通常为高阈值的1/2~1/3)
    double threshold2,     // 高阈值
    int apertureSize = 3,  // Sobel核大小
    bool L2gradient = false // 梯度计算方式(L2更精确)
);
4.4.3 代码示例(自适应阈值)
cpp 复制代码
/*canny算子(最优的检测之一)
    1. 定义:  Canny算子是一种常用的边缘检测算子,它通过计算图像像素的一阶导数和二阶导数来检测图像中的边缘
    2. 原理:  Canny算子通过计算图像像素的一阶导数和二阶导数来检测图像中的边缘
    3. 特点:
        1. 计算复杂
        2. 计算速度慢
        3. 对噪声不敏感
    4. 实际应用场景:
        1. 目标检测
        2. 图像分割
        3. 特征提取
    5. 区别:
        1. sobel算子和laplacian算子都是一阶和二阶导数的算子,canny算子是一阶和二阶导数的结合体
        2. sobel算子和laplacian算子都是计算图像的一阶导数,canny算子是计算图像的二阶导数
    6. 非极大值抑制
        1. 非极大值抑制是指在边缘检测的过程中,对边缘像素进行非极大值抑制,即保留边缘像素中灰度值最大的像素,删除其他像素
        2. 非极大值抑制的作用是将边缘检测的结果细化,即减少边缘检测的伪边缘
    7.双阈值检测
        1. 双阈值检测是指在边缘检测的过程中,对边缘像素进行双阈值检测,即设置两个阈值,一个阈值用于确定边缘像素,一个阈值用于确定非边缘像素
        2. 双阈值检测的作用是将边缘检测的结果细化,即减少边缘检测的伪边缘
    8.

*/
void canny(Mat &src, Mat &dst)
{
    // 1.将图像转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    // 2.初始化输出图像为灰度图
    dst.create(gray.size(), gray.type());
    // 3.计算canny算子,
    /*参数1:输入图像
      参数2:输出图像
      参数3:阈值1
      参数4:阈值2
      参数5:Sobel算子的大小
      参数6:是否使用L2范数
    */
    Canny(gray, dst, 100, 255, 3, false);
    // 4.将canny算子转换为8位无符号整型
    convertScaleAbs(dst, dst);
}

4.5 边缘检测算法对比

算法 核心原理 优势 劣势 适用场景
Sobel 一阶导数 抗噪声强,计算快 边缘较粗 实时场景(如监控)
Laplacian 二阶导数 对细小边缘敏感 对噪声极敏感 细纹理检测(如医学影像)
Canny 多阶段优化 边缘细腻、定位准 计算耗时 高精度场景(如自动驾驶)

五、总结与扩展

  1. 噪声与滤波:根据噪声类型选择方法(椒盐噪声用中值滤波,高斯噪声用高斯滤波);
  2. 直方图:是图像分析的基础,均衡化可增强对比度,比较可实现图像匹配;
  3. 边缘检测:Canny 综合性能最优,Sobel 适合实时场景,Laplacian 适合细边缘检测。

扩展应用

  • 医学影像:CLAHE 增强病灶边缘,Canny 提取器官轮廓;
  • 自动驾驶:Canny 检测车道线,直方图匹配适应光照变化;
  • 安防监控:Sobel 实时检测运动目标边缘,结合掩膜聚焦 ROI。
相关推荐
声网4 分钟前
B 站推进视频播客战略,「代号 H」AI创作工具同步研发;工业级开源记忆操作系统 MemOS,支持模型持续进化和自我更新丨日报
人工智能
神经星星11 分钟前
专治AI审稿?论文暗藏好评提示词,谢赛宁呼吁关注AI时代科研伦理的演变
人工智能·深度学习·机器学习
想要成为计算机高手15 分钟前
4. isaac sim4.2 教程-Core API-Hello robot
人工智能·python·机器人·英伟达·isaac sim·仿真环境
倔强的小石头_23 分钟前
AI 在生活中的应用:深度解析与技术洞察
人工智能
新加坡内哥谈技术28 分钟前
LLM探索的时代
人工智能
YFJ_mily42 分钟前
2025第二届机电一体化、机器人与控制系统国际会议(MRCS2025)即将来袭
大数据·人工智能·机器人·机电一体化
lucky_lyovo1 小时前
深度学习--tensor(创建、属性)
人工智能·深度学习
无聊的小坏坏1 小时前
单调栈通关指南:从力扣 84 到力扣 42
c++·算法·leetcode
说私域1 小时前
淘宝直播与开源链动2+1模式AI智能名片S2B2C商城小程序的融合发展研究
人工智能·小程序·开源
陈敬雷-充电了么-CEO兼CTO1 小时前
复杂任务攻坚:多模态大模型推理技术从 CoT 数据到 RL 优化的突破之路
人工智能·python·神经网络·自然语言处理·chatgpt·aigc·智能体