题记:前文介绍GPUImage滤镜链的原理,但实际上要写出效果,还需要理解其中图片处理的过程,所以本章开始会介绍一些OpenCV基础相关。图像处理需要用到很多专业的算法,本人业余学习略知皮毛,只是庶竭驽钝叙其所得,在音视频学习Demo有一些的示例。文章或代码若有错误,也希望大佬不吝赐教。

一、OpenCV简介
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,广泛应用于人脸识别与生物识别、自动驾驶、工业检测等,核心功能包括:
-
图像处理:滤波、边缘检测、几何变换、颜色空间转换、直方图均衡化等。
-
视频分析:运动检测、光流检测等。
-
特征提取与匹配:SIFT、SURF、ORB、角点检测等。
-
目标检测与识别:Haar级联分类器(人脸检测)、HOG+SVM(行人检测)、深度学习模型(YOLO、SSD)。
-
机器学习:支持向量机(SVM)、神经网络等算法。
二、基础操作
2.1. 输入/输出
// 读取图像
cv::Mat img = cv::imread("xxx/xxx.jpg", cv::IMREAD_COLOR);
// 保存图像
cv::imwrite("xxx/xxx.jpg", img);
cv::imshow(winname, img)
创建窗口显示,移动端没有实现,iOS端转换为UIImage:
- (UIImage *)matToUIImage:(const cv::Mat&)mat {
NSData *data = [NSData dataWithBytes:mat.data length:mat.elemSize() * mat.total()];
CGColorSpaceRef colorSpace = mat.channels() == 1 ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB();
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
CGImageRef imageRef = CGImageCreate(mat.cols, mat.rows, 8, 8 * mat.channels(), mat.step[0], colorSpace, kCGImageAlphaNone|kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return image;
}
2.2. Mat对象
Mat基本是OpenCV中基本操作单元,可以从图片中读取(channels为BGR
注意与移动端常用的RGB
区别),也可以创建空矩阵。
// 空矩阵
cv::Mat emptyMat;
// 指定尺寸和类型(行,列,数据类型)
cv::Mat mat(480, 640, CV_8UC3); // 3通道 8位无符号(BGR图像)
cv::Mat floatMat(100, 100, CV_32FC1); // 单通道浮点矩阵
// 初始化值
cv::Mat redMat(100, 100, CV_8UC3, cv::Scalar(0, 0, 255)); // 全红色图像
cv::Mat ones = cv::Mat::ones(3, 3, CV_32F); // 全1矩阵
Mat的数据实际存储在u(UMatData)
中,而data的内存管理,使用引用计数,可以使用mat.u->refcount
查看引用的计数。
2.2.1. 访问和修改像素
uchar pixel = mat.at<uchar>(y, x); // 读取 (y,x) 处的值(注意行列顺序!)
mat.at<uchar>(y, x) = 255; // 修改
cv::Vec3b& pixel = mat.at<cv::Vec3b>(y, x);
pixel[0] = 255; // 蓝色通道
pixel[1] = 0; // 绿色通道
pixel[2] = 0; // 红色通道
for (int i = 0; i < mat.rows; i++) {
uchar* row = mat.ptr<uchar>(i);
for (int j = 0; j < mat.cols; j++) {
row[j] = ...; // 修改像素
}
}
2.2.2. 图像处理操作
cv::Mat resized;
cv::resize(inputMat, resized, cv::Size(newWidth, newHeight));
cv::Mat gray;
cv::cvtColor(colorMat, gray, cv::COLOR_BGR2GRAY);
cv::Mat rotated;
cv::rotate(inputMat, rotated, cv::ROTATE_90_CLOCKWISE);

- 裁剪 ROI(Region of Interest):
int x = (cols - 200) / 2;
int y = (rows - 200) / 2;
cv::Rect roi_rect(x, y, 200, 200);

- 矩阵运算
cv::Mat A = ... , B = ... , C;
cv::add(A, B, C); // 矩阵加法
cv::multiply(A, B, C); // 逐元素乘法
C = A * B; // 矩阵乘法(非逐元素)
cv::transpose(A, C); // 转置
- 数据类型转换
cv::Mat floatMat;
mat.convertTo(floatMat, CV_32F, 1.0/255.0); // 转为浮点并归一化
cv::split
分离通道操作,注意是按照BGR
的顺序,所以R通道为channels[2]
。
std::vector<cv::Mat> channels;
cv::split(mat, channels);
// 创建零矩阵并合并三通道
cv::Mat zeroMat = cv::Mat::zeros(mat.size(), CV_8UC1);
std::vector<cv::Mat> mergedChannels{zeroMat, zeroMat, channels[2]};
cv::Mat des;
cv::merge(mergedChannels, des);

- Blend效果
// 调整img2尺寸与输入图像匹配
cv::resize(img2, img2, mat.size(), 0, 0, cv::INTER_LINEAR);
// 使用addWeighted进行混合
cv::addWeighted(mat, 0.6, img2, 0.4, 0.0, blended);

- 二值操作
cv::Mat gray, dst;
// 转换为灰度图像
cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY);
// 应用Otsu二值化
cv::threshold(gray, dst, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
return dst;

三、形态学处理
形态学是一类基于图像形状的图像处理技术,以下图为例,看一下形态学的变化。形态学是对实际上会对各个通道进行独立操作,默认是对单通道图像(如灰度图或二值图)操作
,所以一般使用二值图看效果。

3.1. 腐蚀
腐蚀操作原理是取邻域最小值,如下图,处理像素点1
时,检查周围像素点(MORPH_RECT
),取色值最小的点(右下),所以当前的点变成黑色。换个角度,黑色像素点会把周围点都变成黑色,像黑色来腐蚀了白色。

代码如下:
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::morphologyEx(mat, mat, cv::MORPH_ERODE, kernel);
效果如图,整体变小了,毛刺少了很多。

3.2. 膨胀
膨胀操作原理是取邻域最大值,就是和腐蚀相反的操作。如下图,处理像素点1
时,检查周围像素点(MORPH_RECT
),取色值最大的点(右下),所以当前的点变成白色。换个角度,白色像素点会把周围点都染白,像白色像素进行了膨胀。

代码如下:
cv::Mat dilated;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::dilate(mat, dilated, kernel);
效果如图,整体变大了,毛刺变得更粗壮了。

3.3. 开运算
上述两种运算都会原来的形状(变大或缩小),而先腐蚀后膨胀就是开运算。开运算一般用于去噪,如下图,先腐蚀会让黑色区域变大,从而中间的白色噪点消失,再膨胀白色区域恢复(原来的噪点消失)。

代码如下:
cv::Mat opened;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,5));
cv::morphologyEx(mat, opened, cv::MORPH_OPEN, kernel);
效果如图,可以通过改变kernel大小调整效果(5x5效果):

3.4. 闭运算
闭运算是开运算相反的操作先膨胀再腐蚀,运用孔洞填充,如下图字母T
,由于打印或拍摄问题,有些像素点缺失。先膨胀就可以把区域连通,再腐蚀恢复成原来大小。

代码如下:
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,5));
cv::morphologyEx(mat, mat, cv::MORPH_CLOSE, kernel);
效果如图,可以通过改变kernel大小调整效果(5x5效果),连通了毛刺中间的区域:

3.5. 礼帽
礼帽操作是用原图-开运算
,开运算作用是去毛刺,那么礼帽的作用就是获取图片中的毛刺,提取亮细节。
代码如下:
cv::Mat result;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::morphologyEx(mat, result, cv::MORPH_BLACKHAT, kernel);
效果如图,获取到毛刺:

3.6. 黑帽
黑帽操作是用闭运算-原图
,闭运算作用是连通,那么黑帽的作用就是提取暗细节。

代码如下:
cv::Mat result;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::morphologyEx(mat, result, cv::MORPH_BLACKHAT, kernel);