OpenCV学习探秘之二 :数字图像的矩阵原理,OpenCV图像类与常用函数接口说明,及其常见操作核心技术详解
一、图像处理基础概念
1.1数字图像的矩阵
如下图,这是我们看到的 Lena 的头像,但是计算机看来,这副图像只是一堆亮度各异的点。一副尺寸为 M × N 的图像可以用一个 M × N 的矩阵来表示,矩阵元素的值表示这个位置上的像素的亮度,一般来说像素值越大表示该点越亮。
一般来说,灰度图用 2 维矩阵表示;彩色(多通道)图像用 3 维矩阵(M × N × 3)表示。对于图像显示来说,目前大部分设备都是用无符号 8 位整数(类型为 CV_8U)表示像素亮度。
1.2数字图像的本质
图像在计算机中本质是多维矩阵:
- 彩色图像:三维矩阵(高度×宽度×3),通道顺序为BGR(非RGB)
- 灰度图像:二维矩阵(高度×宽度),像素值范围0-255(0黑→255白)
- 像素关系:相邻像素通过4-邻域、8-邻域空间连接,构成连通域
1.3图像处理的意义
- 降噪增强:提升图像质量(如医疗影像去噪)
- 特征提取:识别关键信息(如边缘、角点)
- 数据压缩:减少存储与计算量(如灰度化降75%内存);
二、OpenCV图像类
2.1 Mat类结构
Mat类是 OpenCV 用于存储和操作图像 / 矩阵数据的核心结构,其设计兼顾了内存效率与操作灵活性。从结构上看,Mat由头部(Header) 和数据块(Data Block) 两部分组成。
2.2 Mat类构造
opencv最核心的Mat类,Mat 是一个非常优秀的图像类,它同时也是一个通用的矩阵类,可以用来创建和操作多维矩阵。有多种方法创建一个 Mat 对象。常用的构造函数有:
- Mat::Mat()
无参数构造方法; - Mat::Mat(int rows, int cols, int type)
创建行数为 rows,列数为 col,类型为 type 的图像; - Mat::Mat(Size size, int type)
创建大小为 size,类型为 type 的图像; - Mat::Mat(int rows, int cols, int type, const Scalar& s)
创建行数为 rows,列数为 col,类型为 type 的图像,并将所有元素初始化为值 s; - Mat::Mat(Size size, int type, const Scalar& s)
创建大小为 size,类型为 type 的图像,并将所有元素初始化为值 s; - Mat::Mat(const Mat& m)
将 m 赋值给新创建的对象,此处不会对图像数据进行复制,m 和新对象共用图像数据; - Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
创建行数为 rows,列数为 col,类型为 type 的图像,此构造函数不创建图像数据所需内存,而是直接使用 data 所指内存,图像的行步长由 step指定。 - Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
创建大小为 size,类型为 type 的图像,此构造函数不创建图像数据所需内存,而是直接使用 data 所指内存,图像的行步长由 step 指定。 - Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
创建的新图像为 m 的一部分,具体的范围由 rowRange 和 colRange 指定,此构造函数也不进行图像数据的复制操作,新图像与 m 共用图像数据; - Mat::Mat(const Mat& m, const Rect& roi)
创建的新图像为 m 的一部分,具体的范围 roi 指定,此构造函数也不进行图像数据的复制操作,新图像与 m 共用图像数据。
这些构造函数中,很多都涉及到类型type。type可以是CV_8UC1,CV_16SC1,…,CV_64FC4 等。里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个通道的图像,C4 表示 4 个通道的图像,以此类推。
三 常用接口函数
3.1 图像读写与显示
3.1.1 读取图像
- imread(const string& filename, int flags=IMREAD_COLOR)
读取图像文件,flags可选:
IMREAD_COLOR:加载彩色图(默认);
IMREAD_GRAYSCALE:加载灰度图;
IMREAD_UNCHANGED:加载包含 Alpha 通道的图像;
3.1.2 保存图像
- imwrite(const string& filename, InputArray img)
保存图像到文件,支持格式:JPEG、PNG、BMP 等。
3.1.3 显示图像
- namedWindow(const string& winname, int flags=WINDOW_AUTOSIZE)
创建窗口,flags可选:
WINDOW_AUTOSIZE:窗口大小自适应图像;
WINDOW_NORMAL:窗口可调整大小; - imshow(const string& winname, InputArray mat)
在指定窗口显示图像。搭配waitKey(int delay=0)等待按键事件,delay为毫秒数,0 表示无限等待。
3.2 图像操作颜色空间转换
3.2.1 颜色空间转换
- cvtColor(InputArray src, OutputArray dst, int code)
转换颜色空间。
3.2.2 图像缩放
- resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
调整图像大小,interpolation可选:
INTER_LINEAR:双线性插值(默认);
INTER_NEAREST:最近邻插值;
INTER_CUBIC:双三次插值(放大更清晰);
3.2.3 图像翻转与旋转
- flip(InputArray src, OutputArray dst, int flipCode)
翻转图像,flipCode:
0:上下翻转
1:左右翻转
-1:上下 + 左右翻转 - rotate(InputArray src, OutputArray dst, int rotateCode)
旋转图像,rotateCode:
ROTATE_90_CLOCKWISE:顺时针 90 度;
ROTATE_180:180 度;
ROTATE_90_COUNTERCLOCKWISE:逆时针 90 度;
3.3 图像滤波与增强
3.3.1 平滑滤波
- blur(InputArray src, OutputArray dst, Size ksize)
均值滤波,ksize为核大小(如(3, 3))。 - GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0)
高斯滤波,sigmaX为 X 方向标准差。 - medianBlur(InputArray src, OutputArray dst, int ksize)
中值滤波,ksize为核大小(奇数)。
3.3.2 边缘检测
- Canny(InputArray image, OutputArray edges, double threshold1, double threshold2)
Canny 边缘检测,threshold1和threshold2为双阈值。
3.3.3 直方图均衡化
- equalizeHist(InputArray src, OutputArray dst)
增强图像对比度(仅适用于灰度图)。
3.4 形态学操作
- erode(InputArray src, OutputArray dst, InputArray kernel)
腐蚀操作,缩小前景物体。 - dilate(InputArray src, OutputArray dst, InputArray kernel)
膨胀操作,扩大前景物体。 - morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel)
形态学高级操作,op可选:
MORPH_OPEN:开运算(先腐蚀后膨胀)
MORPH_CLOSE:闭运算(先膨胀后腐蚀)
MORPH_GRADIENT:形态学梯度(膨胀 - 腐蚀)
3.5 图像算术与逻辑运算
- add(InputArray src1, InputArray src2, OutputArray dst)
图像加法(支持带权重的加法)。 - subtract(InputArray src1, InputArray src2, OutputArray dst)
图像减法。 - bitwise_and(InputArray src1, InputArray src2, OutputArray dst)
按位与(用于掩码操作)。 - bitwise_or(InputArray src1, InputArray src2, OutputArray dst)
按位或。 - bitwise_not(InputArray src, OutputArray dst)
按位取反。
3.6 几何变换
3.6.1 仿射变换
- warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize)
应用仿射变换,M为 2×3 变换矩阵。 - getRotationMatrix2D(Point2f center, double angle, double scale)
获取旋转矩阵(用于warpAffine)。
3.6.2 透视变换
- warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize)
应用透视变换,M为 3×3 变换矩阵。 - getPerspectiveTransform(const Point2f src[], const Point2f dst[])
计算透视变换矩阵。
3.7 特征提取
- findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method)
查找图像轮廓,mode为轮廓检索模式,method为轮廓近似方法。 - HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold)
Hough 直线检测。 - HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist)
Hough 圆检测。
3.8 其他常用操作
- split(const Mat& src, Mat* mvbegin)
将多通道图像分割为单通道(例如 BGR→B、G、R)。 - merge(const Mat* mv, size_t count, OutputArray dst)
将多个单通道图像合并为多通道。 - copyTo(InputArray src, OutputArray dst, InputArray mask)
带掩码的图像复制。 - setTo(InputOutputArray dst, const Scalar& value, InputArray mask=noArray())
将图像或 ROI 设置为指定值。
四 常见操作原理说明
4.1 灰度处理原理与效果
在计算机视觉中,灰度图像是指每个像素仅由一个数值表示其亮度的图像,数值范围通常为 0(黑色)到 255(白色)。将彩色图像转换为灰度图像的过程称为灰度处理,其核心是通过加权平均或特定算法将 RGB 三个通道的信息合并为单通道。
4.1.1 灰度处理原理
常见的灰度转换的数学原理算法有以下几种:
平均值法
将 RGB 三个通道的数值取平均:
Gray = (R + G + B) / 3
加权平均法(更符合人眼感知)
人眼对绿色更敏感,因此绿色通道权重更高:
Gray = 0.299*R + 0.587*G + 0.114*B
这也是 OpenCV 默认的转换公式(CV_BGR2GRAY)
最大值法
使用 RGB 中的最大值作为灰度值:
Gray = max(R, G, B)
4.1.2 灰度处理的效果与场景
效果
- 减少数据量:从三通道变为单通道,内存占用减少 1/3。
- 突出形状特征:消除颜色干扰,更专注于纹理、边缘等结构信息。
常见应用
- 人脸识别:预处理阶段常将图像转为灰度,降低计算复杂度。
- 边缘检测:灰度图像更适合 Canny、Sobel 等算子提取边缘。
- 模板匹配:灰度处理可提高匹配准确性。
- 图像压缩:灰度图像可使用更高效的压缩算法。
4.1.2 代码示例
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {// 读取彩色图像Mat colorImage = imread("input.jpg", IMREAD_COLOR);if (colorImage.empty()) {cout << "无法读取图像!" << endl;return -1;}// 创建用于存储灰度图像的Mat对象Mat grayImage;// 方法1: 使用OpenCV内置函数进行灰度转换cvtColor(colorImage, grayImage, COLOR_BGR2GRAY);// 方法2: 手动实现加权平均法Mat manualGray = Mat::zeros(colorImage.size(), CV_8UC1);for (int i = 0; i < colorImage.rows; i++) {for (int j = 0; j < colorImage.cols; j++) {Vec3b pixel = colorImage.at<Vec3b>(i, j);// 注意: OpenCV中颜色顺序为BGR而非RGBuchar gray = 0.299 * pixel[2] + 0.587 * pixel[1] + 0.114 * pixel[0];manualGray.at<uchar>(i, j) = gray;}}// 显示原图和灰度图imshow("彩色图像", colorImage);imshow("OpenCV灰度转换", grayImage);imshow("手动灰度转换", manualGray);// 保存灰度图像imwrite("output_gray.jpg", grayImage);// 等待按键退出waitKey(0);return 0;
}
4.2 颜色空间转换
颜色空间是描述颜色的数学模型,不同场景下需选择合适的颜色空间(如 RGB 适合显示,HSV 适合颜色分割)。OpenCV 通过cvtColor函数实现颜色空间转换,核心是基于数学公式的通道数值映射。
4.2.1 核心原理
颜色空间转换的本质是 通道数值的数学变换:通过预设公式将原颜色空间的通道值(如 RGB 的 R、G、B)转换为目标空间的通道值(如 HSV 的 H、S、V)。
OpenCV 支持很多种转换(通过ColorConversionCodes枚举指定),以下是常用类型及效果:
转换类型 | 适用场景 | 效果特点 |
---|---|---|
COLOR_BGR2RGB | 图像显示(OpenCV 默认 BGR 存储) | 交换 R 和 B 通道,解决 OpenCV 与其他库(如 Qt)的显示颜色偏差。 |
COLOR_BGR2GRAY | 预处理(减少计算量) | 转为单通道灰度图,保留亮度信息,消除颜色干扰。 |
COLOR_BGR2HSV | 颜色分割(如目标检测) | H 通道单独表示颜色,可通过阈值快速提取特定颜色(如红色物体)。 |
COLOR_BGR2YCrCb | 肤色检测 | Y 通道为亮度,Cr、Cb 通道对肤色敏感,适合提取人脸区域。 |
COLOR_BGR2Lab | 光照不变性场景 | Lab 空间对光照变化不敏感,适合光照不均时的颜色分析。 |
4.2.2 常见颜色转换示例:
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {// 1. 读取图像(OpenCV默认以BGR格式加载)Mat bgr_img = imread("test.jpg");if (bgr_img.empty()) {cout << "无法读取图像!" << endl;return -1;}// 2. 定义目标图像Mat rgb_img, gray_img, hsv_img, ycrcb_img;// 3. 颜色空间转换(核心函数:cvtColor)cvtColor(bgr_img, rgb_img, COLOR_BGR2RGB); // BGR→RGB(用于正确显示)cvtColor(bgr_img, gray_img, COLOR_BGR2GRAY); // BGR→灰度cvtColor(bgr_img, hsv_img, COLOR_BGR2HSV); // BGR→HSVcvtColor(bgr_img, ycrcb_img, COLOR_BGR2YCrCb); // BGR→YCrCb// 4. 显示结果imshow("原图(BGR)", bgr_img);imshow("RGB(用于显示校正)", rgb_img);imshow("灰度图(减少干扰)", gray_img);imshow("HSV(颜色分割友好)", hsv_img);imshow("YCrCb(肤色检测)", ycrcb_img);// 5. 保存结果imwrite("hsv_result.jpg", hsv_img);imwrite("gray_result.jpg", gray_img);// 等待按键退出waitKey(0);destroyAllWindows();return 0;
}
4.3 图像滤波与增强
4.3.1 图像滤波
图像滤波与增强是计算机视觉中,用于改善图像质量、突出特征或抑制噪声。OpenCV 提供了丰富的滤波与增强函数,核心是通过卷积操作或像素值映射实现。滤波是通过 卷积核(Kernel) 对图像进行邻域操作的过程。
- 线性滤波:输出像素是邻域像素的加权和(如均值滤波、高斯滤波)。
- 非线性滤波:输出像素由邻域像素的排序或统计值决定(如中值滤波、双边滤波)。
数学表达:输出像素值 = 卷积核 × 邻域像素矩阵(逐元素相乘后求和)。
常见滤波方法及效果
滤波类型 | 原理 | 效果与应用场景 |
---|---|---|
均值滤波 | 用邻域平均值替代中心像素 | 模糊图像、降噪,但会丢失细节 |
高斯滤波 | 用高斯函数加权邻域像素 | 平滑图像,保留更多细节,常用于预处理 |
中值滤波 | 用邻域像素的中值替代中心像素 | 有效去除椒盐噪声,保留边缘 |
双边滤波 | 同时考虑空间距离和像素值差异 | 平滑图像的同时保留边缘,适合美颜等场景 |
4.3.2 图像增强原理
增强是通过像素值映射函数调整图像对比度或亮度:
- 线性增强:直接缩放像素值范围(如直方图均衡化)。
- 非线性增强:通过对数、指数等函数调整(如伽马校正)。
增强类型 | 原理 | 效果与场景 |
---|---|---|
直方图均衡化 | 拉伸像素值分布,使直方图更均匀 | 增强整体对比度,适合低对比度图像 |
伽马校正 | 通过幂函数调整亮度(γ>1 变暗,γ<1 变亮) | 局部细节增强,适合调整过曝或过暗图像 |
代码示例
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {// 1. 读取图像(可替换为带噪声的图像)Mat src = imread("test.jpg", IMREAD_COLOR);if (src.empty()) {cout << "无法读取图像!" << endl;return -1;}// 2. 定义输出图像Mat blur_img, gaussian_img, median_img, bilateral_img;Mat equalized_img, gamma_corrected_img;// 3. 图像滤波blur(src, blur_img, Size(5, 5)); // 均值滤波(5×5核)GaussianBlur(src, gaussian_img, Size(5, 5), 0); // 高斯滤波medianBlur(src, median_img, 5); // 中值滤波(核大小必须为奇数)bilateralFilter(src, bilateral_img, 9, 75, 75); // 双边滤波// 4. 图像增强// 4.1 直方图均衡化(需先转为灰度图)Mat gray, equalized_gray;cvtColor(src, gray, COLOR_BGR2GRAY);equalizeHist(gray, equalized_gray);cvtColor(equalized_gray, equalized_img, COLOR_GRAY2BGR);// 4.2 伽马校正Mat src_f;src.convertTo(src_f, CV_32F, 1.0/255.0); // 归一化到[0,1]pow(src_f, 0.5, gamma_corrected_img); // γ=0.5(变亮)gamma_corrected_img.convertTo(gamma_corrected_img, CV_8U, 255.0);// 5. 显示结果imshow("原图", src);imshow("均值滤波", blur_img);imshow("高斯滤波", gaussian_img);imshow("中值滤波", median_img);imshow("双边滤波", bilateral_img);imshow("直方图均衡化", equalized_img);imshow("伽马校正(γ=0.5)", gamma_corrected_img);// 6. 保存结果imwrite("gaussian_result.jpg", gaussian_img);imwrite("equalized_result.jpg", equalized_img);waitKey(0);return 0;
}
4.4 边缘检测
边缘检测是计算机视觉中,用于识别图像中亮度突变的区域(即边缘),广泛应用于目标分割、特征提取和场景理解。OpenCV 提供了多种边缘检测算法,核心是基于一阶或二阶导数计算像素值的变化率。
图像边缘在对应像素值的梯度突变,数学上通过卷积核计算一阶导数(如 Sobel、Prewitt 算子)或二阶导数(如 Laplacian 算子)来检测这种变化。
4.4.1 常见边缘检测算法对比:
算法 | 原理 | 效果 | 适用场景 |
---|---|---|---|
Sobel | 一阶导数算子 | 计算水平和垂直梯度 | 边缘较粗,定位精度中等 快速检测、预处理 |
Canny | 多阶段优化(非极大值抑制 + 双阈值) | 边缘细、定位准、假边缘少 | 高精度场景(如工业检测) |
Laplacian | 二阶导数算子,检测梯度的零交叉点 | 对边缘方向不敏感,可能产生双边缘 | 简单场景、快速原型 |
Prewitt | 类似 Sobel,但权重均匀 | 计算简单,对噪声敏感 | 教学或低计算资源场景 |
4.4.2 代码示例
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {// 1. 读取图像并转为灰度图Mat src = imread("test.jpg", IMREAD_COLOR);if (src.empty()) {cout << "无法读取图像!" << endl;return -1;}Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);// 2. 高斯平滑(减少噪声)Mat blurred;GaussianBlur(gray, blurred, Size(3, 3), 0);// 3. 定义输出图像Mat sobelx, sobely, sobel_edges;Mat laplacian_edges;Mat canny_edges;Mat prewitt_edges;// 4. Sobel边缘检测Sobel(blurred, sobelx, CV_64F, 1, 0, 3); // x方向梯度Sobel(blurred, sobely, CV_64F, 0, 1, 3); // y方向梯度convertScaleAbs(sobelx, sobelx); // 转换为8位无符号整数convertScaleAbs(sobely, sobely);addWeighted(sobelx, 0.5, sobely, 0.5, 0, sobel_edges); // 合并梯度// 5. Laplacian边缘检测Laplacian(blurred, laplacian_edges, CV_8U, 3);// 6. Canny边缘检测(关键参数:阈值1和阈值2)Canny(blurred, canny_edges, 50, 150);// 7. Prewitt边缘检测(手动实现)Mat prewitt_x = (Mat_<int>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);Mat prewitt_y = (Mat_<int>(3, 3) << -1, -1, -1, 0, 0, 0, 1, 1, 1);Mat grad_x, grad_y;filter2D(blurred, grad_x, CV_16S, prewitt_x);filter2D(blurred, grad_y, CV_16S, prewitt_y);convertScaleAbs(grad_x, grad_x);convertScaleAbs(grad_y, grad_y);addWeighted(grad_x, 0.5, grad_y, 0.5, 0, prewitt_edges);// 8. 显示结果imshow("原图", src);imshow("Sobel边缘", sobel_edges);imshow("Laplacian边缘", laplacian_edges);imshow("Canny边缘", canny_edges);imshow("Prewitt边缘", prewitt_edges);// 9. 保存结果imwrite("canny_result.jpg", canny_edges);waitKey(0);return 0;
}
4.5 腐蚀与膨胀
腐蚀(Erosion)与膨胀(Dilation)是形态学图像处理的操作中,用于改变图像中物体的形状和大小。它们通过 结构元素(Structuring Element)对图像进行逐像素扫描,实现对前景物体的收缩或扩张。腐蚀和膨胀本质上是对图像中 “前景像素(通常为白色)” 的操作。
4.5.1 腐蚀(Erosion)
原理:结构元素在图像上滑动,若结构元素完全包含于前景区域,则保留中心像素,否则删除。
效果:前景物体收缩,孔洞扩大,细小连接被断开。
4.5.2 膨胀(Dilation)
原理:结构元素在图像上滑动,若结构元素与前景区域有交集,则中心像素被设为前景。
效果:前景物体扩张,孔洞缩小,断裂部分被连接。
4.5.3 应用对比
操作 | 典型应用 |
---|---|
腐蚀 | 去除小噪声点;分离相连物体;细化轮廓; |
膨胀 | 连接断裂部分;增强轮廓 |
4.5.4 代码示例:
#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main() {// 1. 读取图像并转为二值图(简化演示)Mat src = imread("test.jpg", IMREAD_GRAYSCALE);if (src.empty()) {cout << "无法读取图像!" << endl;return -1;}// 二值化处理(阈值操作)Mat binary;threshold(src, binary, 127, 255, THRESH_BINARY);// 2. 定义结构元素(可使用矩形、椭圆或十字形)Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));// 3. 腐蚀和膨胀操作Mat eroded, dilated;erode(binary, eroded, kernel); // 腐蚀dilate(binary, dilated, kernel); // 膨胀// 4. 高级形态学操作(开运算和闭运算)Mat opened, closed;morphologyEx(binary, opened, MORPH_OPEN, kernel); // 开运算 = 腐蚀+膨胀morphologyEx(binary, closed, MORPH_CLOSE, kernel); // 闭运算 = 膨胀+腐蚀// 5. 显示结果imshow("原图", src);imshow("二值图", binary);imshow("腐蚀", eroded);imshow("膨胀", dilated);imshow("开运算", opened);imshow("闭运算", closed);// 6. 保存结果imwrite("eroded.jpg", eroded);imwrite("dilated.jpg", dilated);waitKey(0);return 0;
}