一、介绍
今天是这个系列《C++之 Opencv 入门到提高》得第三篇文章。今天这篇文章也不难,主要介绍如何使用 Opencv 对图像进行掩膜处理,提高图像的对比度。在这个过程中,我们可以学到如何获取图像指针、如何处理像素值越界等问题。我们一步一个脚印的走,收获就会越来越多。虽然操作很简单,但是要下功夫理解每个技术点,把基础打扎实,才能让以后得学习过程更顺利一点。OpenCV 具体的简介内容,我就不多说了,网上很多,大家可以自行脑补。
OpenCV 的官网地址:https://opencv.org/,组件下载地址:https://opencv.org/releases/。
OpenCV 官网学习网站:https://docs.opencv.ac.cn/4.10.0/index.html
我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10(64位)
开发组件:OpenCV – 4.10.0
开发工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
开发语言:C++(VC16)
二、实例学习
这一节的内容不多,接口 API 其实也是挺简单的。但是我们学习接口,不能冷冰冰的只是学API、一些基础知识也是要掌握的。
图像中的掩膜(Mask)是什么?在图像处理中,掩膜(Mask)是一种用于控制图像处理区域或处理过程的特殊图像。它通常是一个与原始图像同样大小的二维矩阵,用于选择性地遮盖或显示图像的特定区域。掩膜可以用于多种图像处理任务,如图像分割、特征提取、增强等。
在数字图像处理中,掩膜通常是一个二进制图像,其中像素值为1的区域表示要保留的区域,像素值为0的区域表示要排除的区域。通过将掩膜与原始图像进行逻辑运算,可以创建新的图像,其中只有掩膜中标记为1的区域被保留,其他区域被排除。
掩膜在图像处理中有多种应用。例如,在图像分割中,掩膜可用于选择性地突出显示感兴趣的区域,以便进一步处理或分析。在特征提取中,掩膜可用于提取图像中的特定形状或结构。此外,掩膜还可以用于图像增强,例如通过模糊或锐化特定区域来改善图像质量。
数字图像处理中,掩模为二维矩阵数组,有时也用多值图像,图像掩模主要用于:
①提取感兴趣区,用预先制作的感兴趣区掩模与待处理图像相乘,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0。
②屏蔽作用,用掩模对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计。
③结构特征提取,用相似性变量或图像匹配方法检测和提取图像中与掩模相似的结构特征。
④特殊形状图像的制作。
什么是位图与掩膜的与运算?
其实就是原图中的每个像素和掩膜中的每个对应像素进行与运算。比如1 & 1 = 1;1 & 0 = 0;
比如一个3 * 3的图像与3 * 3的掩膜进行运算,得到的结果图像就是:
说白了,我们使用掩膜(mask)位图来选择哪个像素允许拷贝,哪个像素不允许拷贝。如果mask像素的值是非0的,我就拷贝它,也就是保留下,否则不拷贝,不保留。
在所有图像基本运算的操作函数中,凡是带有掩膜(mask)的处理函数,其掩膜都参与运算(输入图像运算完之后再与掩膜图像或矩阵运算)。
1 #include <opencv2/opencv.hpp> 2 #include <iostream> 3 #include <math.h> 4 5 using namespace std; 6 using namespace cv; 7 8 9 int main() 10 { 11 Mat src, dst; 12 src = imread("D:\\360MoveData\\Users\\Administrator\\Desktop\\TestImage\\4.jpg", IMREAD_UNCHANGED); 13 if (!src.data) 14 { 15 cout << "图片加载错误!!!" << endl; 16 return -1; 17 } 18 19 namedWindow("原始图像", WINDOW_AUTOSIZE); 20 imshow("原始图像", src); 21 22 //1、获取图形像素指针 23 //CV_Assert(myImage.depth()==CV_8U); 24 //Mat.ptr<uchar>(int i=0)获取像素矩阵的指针,索引 i 表示第几行,从0开始计数。 25 //获取当前行指针 const uchar* current=myImage.ptr<uchar>(row); 26 //获取当前像素点 p(row,col) 的像素值 p(row,col)=current[col]; 27 28 //2、像素范围处理 saturate_cast<uchar> 29 //saturate_cast<uchar>(-100),返回 0. 30 //saturate_cast<uchar>(288),返回 255, 31 //saturate_cast<uchar>(100),返回 100. 32 // 这个函数的功能是确保 RGB 值的范围在 0-255 之间。 33 34 double startDate = getTickCount(); 35 36 //第一种实现对比度 37 /*int cols = (src.cols - 1) * src.channels(); 38 int offerts = src.channels(); 39 int rows = src.rows; 40 41 dst = Mat(src.size(), src.type()); 42 43 for (int row = 1; row < (rows - 1); row++) 44 { 45 const uchar* previous = src.ptr<uchar>(row - 1); 46 const uchar* current = src.ptr<uchar>(row); 47 const uchar* next = src.ptr<uchar>(row + 1); 48 uchar* output = dst.ptr<uchar>(row); 49 for (int col = offerts; col < cols; col++) 50 { 51 output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offerts] + current[col + offerts] + previous[col] + next[col])); 52 } 53 }*/ 54 55 //3、定义掩膜 56 // Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); 57 //filter2D(src, dst, src.depth(), kernel):src 是原图,dst 是目标图,src.depth() 表示位图深度,有 32,24,8 等。 58 59 //第二种实现对比度 60 Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); 61 filter2D(src, dst, src.depth(), kernel); 62 63 double totalTime = (getTickCount() - startDate) / getTickFrequency(); 64 65 cout << "消费时间:"<< totalTime << endl; 66 67 namedWindow("对比度图像", WINDOW_AUTOSIZE); 68 imshow("对比度图像", dst); 69 70 71 waitKey(0); 72 73 return 0; 74 }
生成的效果图如下:
当然了,在源码中,提供了两种实现,效果都是一样的。一种是自己实现的,一种是通过调用接口实现的。这也说明了一个问题,只要你掌握的够深入,和接口效果一样的问题,你也可以写得出。
如果我们吧注释的代码打开,把【第二种实现】注释掉,源码:
1 int cols = (src.cols - 1) * src.channels(); 2 int offerts = src.channels(); 3 int rows = src.rows; 4 5 dst = Mat(src.size(), src.type()); 6 7 for (int row = 1; row < (rows - 1); row++) 8 { 9 const uchar* previous = src.ptr<uchar>(row - 1); 10 const uchar* current = src.ptr<uchar>(row); 11 const uchar* next = src.ptr<uchar>(row + 1); 12 uchar* output = dst.ptr<uchar>(row); 13 for (int col = offerts; col < cols; col++) 14 { 15 output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offerts] + current[col + offerts] + previous[col] + next[col])); 16 } 17 }
而且,自己写的性能更好。对比如图:
再看看我们自己写的运行时间:
我们自己写实现的效果:
三、总结
这是 C++ 使用 OpenCV 的第三篇文章,概念挺难懂的,其实操作起来也没那么难,当然了,这只是我们入门的开始,那就继续吧。皇天不负有心人,不忘初心,继续努力,做自己喜欢做的,开心就好。