本文阐述 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πσ21e−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 流程
- 高斯滤波去噪;
- Sobel 算子计算梯度;
- 非极大值抑制(细化边缘);
- 双阈值检测(强 / 弱边缘分类);
- 边缘连接(弱边缘需与强边缘相连)。
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 多阶段优化 边缘细腻、定位准 计算耗时 高精度场景(如自动驾驶)
五、总结与扩展
- 噪声与滤波:根据噪声类型选择方法(椒盐噪声用中值滤波,高斯噪声用高斯滤波);
- 直方图:是图像分析的基础,均衡化可增强对比度,比较可实现图像匹配;
- 边缘检测:Canny 综合性能最优,Sobel 适合实时场景,Laplacian 适合细边缘检测。
扩展应用:
- 医学影像:CLAHE 增强病灶边缘,Canny 提取器官轮廓;
- 自动驾驶:Canny 检测车道线,直方图匹配适应光照变化;
- 安防监控:Sobel 实时检测运动目标边缘,结合掩膜聚焦 ROI。