阅读视图

发现新文章,点击刷新页面。

音视频学习笔记十六——图像处理之OpenCV基础一

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

opencv绘图.jpg

一、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查看引用的计数。

  • 浅拷贝:默认赋值或传参,共享数据内存

    cv::Mat shallow = mat;
    
  • 深拷贝:独立内存

    cv::Mat deep = mat.clone();
    // 或
    mat.copyTo(deep);
    

2.2.1. 访问和修改像素

  • 单通道(灰度):
uchar pixel = mat.at<uchar>(y, x); // 读取 (y,x) 处的值(注意行列顺序!)
mat.at<uchar>(y, x) = 255;         // 修改
  • 多通道(如 BGR 图像):
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);
旋转.jpg
  • 裁剪 ROI(Region of Interest):
int x = (cols - 200) / 2;
int y = (rows - 200) / 2;
cv::Rect roi_rect(x, y, 200, 200);
roi显示.jpg

矩阵运算

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);
红色通道.jpg

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);
融合.jpg

二值操作

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;
二值效果.jpg

三、形态学处理

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

burr.jpg

3.1. 腐蚀

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

腐蚀原理.jpg

代码如下:

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::morphologyEx(mat, mat, cv::MORPH_ERODE, kernel);

效果如图,整体变小了,毛刺少了很多。

腐蚀.jpg

3.2. 膨胀

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

膨胀原理.jpg

代码如下:

cv::Mat dilated;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::dilate(mat, dilated, kernel);

效果如图,整体变大了,毛刺变得更粗壮了。

膨胀.jpg

3.3. 开运算

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

开运算.jpg

代码如下:

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效果):

开运算效果.jpg

3.4. 闭运算

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

闭运算.jpg

代码如下:

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,5));
cv::morphologyEx(mat, mat, cv::MORPH_CLOSE, kernel);

效果如图,可以通过改变kernel大小调整效果(5x5效果),连通了毛刺中间的区域:

闭操作效果.jpg

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);

效果如图,获取到毛刺:

礼帽.jpg

3.6. 黑帽

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

黑帽原理.jpg

代码如下:

cv::Mat result;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::morphologyEx(mat, result, cv::MORPH_BLACKHAT, kernel);
❌