OpenCV学习05-图像操作 £神魔★判官ぃ 2022-05-21 07:44 173阅读 0赞 ![70][] #include <opencv2\opencv.hpp> #include <iostream> using namespace std; using namespace cv; int main() { Mat src; src = imread("D:/demo01.jpg"); if (src.empty()) { cout << "could not find the image resource..." << endl; return -1; } namedWindow("My Image", CV_WINDOW_AUTOSIZE); imshow("My Image", src); Mat grayImage; //函数可以做下面类型的转换,需要说明的是在opencv2.x时颜色空间转换code用的宏定义是CV_前缀开头,而在opencv3.x版本其颜色空间转换code宏定义更改为COLOR_开头 cvtColor(src,grayImage,COLOR_BGR2GRAY); namedWindow("单通道灰度图像", CV_WINDOW_AUTOSIZE); imshow("单通道灰度图像", grayImage); //1).empty() 判断文件读取是否正确 //2).rows 获取图像行数(高度) //3).cols 获取图像列数(长度) //4).channels() 获取图像通道数 //5).depth() 获取图像位深度 //单通道 做反色处理 int height = grayImage.rows; int width = grayImage.cols; //Mat.ptr<uchar>(row):获取第row行的图像像素指针。图像的行数从0开始计数 //获取点P(row, col)的像素值:P(row.col) = Mat.ptr<uchar>(row)[col] for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int gray = grayImage.at<uchar>(row,col); grayImage.at<uchar>(row, col) = 255 - gray; } } namedWindow("单通道做反色处理", CV_WINDOW_AUTOSIZE); imshow("单通道做反色处理", grayImage); //多通道 Mat dst; dst.create(src.size(),src.type()); //height = src.rows; //width = src.cols; //int nc = src.channels(); //for (int row = 0; row < height; row++) //{ // for (int col = 0; col < width; col++) // { // if (1 == nc) // { // int gray = grayImage.at<uchar>(row, col); // grayImage.at<uchar>(row, col) = 255 - gray;// 表示对当前图像取反 // } // else if (3 == nc) // { // //src.at<Vec3b>(row, col)[0]; // //意味着从彩色3通道图像中row行col列第0个通道的颜色点 // //Vec3b 图像像素值类型 // int b = src.at<Vec3b>(row, col)[0]; // int g = src.at<Vec3b>(row, col)[1]; // int r = src.at<Vec3b>(row, col)[2]; // dst.at<Vec3b>(row, col)[0] = 255 - b; // dst.at<Vec3b>(row, col)[1] = 255 - g; // dst.at<Vec3b>(row, col)[2] = 255 - r; // } // } //} bitwise_not(src, dst); //这个是利用位取反函数对图像进行取反。 namedWindow("三通道做反色处理", CV_WINDOW_AUTOSIZE); imshow("三通道做反色处理", dst); waitKey(0); return 0; } # .图像基本运算 # 图像的基本运算有很多种,比如两幅图像可以相加、相减、相乘、相除、位运算、平方根、对数、绝对值等;图像也可以放大、缩小、旋转,还可以截取其中的一部分作为ROI(感兴趣区域)进行操作,各个颜色通道还可以分别提取及对各个颜色通道进行各种运算操作。总之,对于图像可以进行的基本运算非常的多,只是挑了些常用的操作详解。 1. void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype= \-1); *//dst = src1 + src2* 2. void subtract(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype= \-1); *//dst = src1 - src2* 3. void multiply(InputArray src1, InputArray src2,OutputArray dst, double scale=1, int dtype=\-1); *//dst = scale\*src1\*src2* 4. void divide(InputArray src1, InputArray src2, OutputArray dst,double scale=1, int dtype=\-1); *//dst = scale\*src1/src2* 5. void divide(double scale, InputArray src2,OutputArray dst, int dtype=\-1); *//dst = scale/src2* 6. void scaleAdd(InputArray src1, double alpha, InputArray src2, OutputArray dst); *//dst = alpha\*src1 + src2* 7. void addWeighted(InputArray src1, double alpha, InputArray src2,double beta, double gamma, OutputArray dst, int dtype=\-1); *//dst = alpha\*src1 + beta\*src2 + gamma* 8. void sqrt(InputArray src, OutputArray dst); *//计算每个矩阵元素的平方根* 9. void pow(InputArray src, double power, OutputArray dst); *//src的power次幂* 10. void exp(InputArray src, OutputArray dst); *//dst = e\*\*src(\*\*表示指数的意思)* 11. void log(InputArray src, OutputArray dst); *//dst = log(abs(src))* 上述的基本操作中都属于将基础数学运算应用于图像像素的处理中,下面将着重介绍 1. bitwise\_and、bitwise\_or、bitwise\_xor、bitwise\_not这四个按位操作函数。 2. void bitwise\_and(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); *//dst = src1 & src2* 3. void bitwise\_or(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); *//dst = src1 | src2* 4. void bitwise\_xor(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray()); *//dst = src1 ^ src2* 5. void bitwise\_not(InputArray src, OutputArray dst,InputArray mask=noArray()); *//dst = ~src* bitwise\_and是对二进制数据进行“与”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“与”操作,1&1=1,1&0=0,0&1=0,0&0=0 bitwise\_or是对二进制数据进行“或”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“或”操作,1|1=1,1|0=0,0|1=0,0|0=0 bitwise\_xor是对二进制数据进行“异或”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“异或”操作,1^1=0,1^0=1,0^1=1,0^0=0 **bitwise\_not是对二进制数据进行“非”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“非”操作,~1=0,~0=1** l Vec3b 对应三通道的顺序是 blue 、 green 、 red 的 uchar 类型数据。 l Vec3f 对应三通道的 float 类型数据 l 把 CV\_8UC1 转换到 CV32F1 实现如下: src.convertTo(dst,CV\_32F); # 6-两幅图像融合简单实现1;addWeighted()函数;两幅图像尺寸相同时; # 在图像处理的操作中经常会遇到将两幅图像融合成一张图像的问题,在opnecv里利用addWeighted()函数可以很容易实现这个功能。 * 在addWeighted()函数中,输入两个图像image1 和 image2。 * 两个图像可以是任何象素类型,只要它们的类型相同。它们可以是单通道或是三通道,只要它们相符。 * 且图像尺寸大小必须相同,否则编译会出错!!! ## 1.addWeighted()函数 ## 此函数的作用是将两幅图像进行融合。计算两个数组的加权和(dst = alpha\*src1 + beta\*src2 + gamma)。 首先看一下addWeighted()函数定义: 1. void addWeighted(InputArray src1, double alpha, InputArray src2, 2. double beta, double gamma, OutputArray dst, int dtype= \-1); 参数说明: * 第一个参数:src1,表示进行加权操作的第一个图像对象,即输入图片1。 * 第二个参数:double型的alpha,表示第一个图像的加权系数,即图片1的融合比例。 * 第三个参数:src2,表示进行加权操作的第二个图像对象,即输入图片2。 * 第四个参数:double型的beta,表示第二个图像的加权系数,即图片2的融合比例。很多情况下,有关系 alpha+beta=1.0。 * 第五个参数:double型的gamma,表示一个作用到加权和后的图像上的标量,可以理解为加权和后的图像的偏移量。 * 第六个参数:dst,表示两个图像加权和后的图像,尺寸和图像类型与src1和src2相同,即输出图像。 * 第七个参数:输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。 公式表达即为; * dst = src1\[I\] \* alpha + src\[2\] \* beta + gamma; 其中:alpha + beta =1 ## 程序说明 ## 注意:要融合的两个图像的尺寸与类型必须一致!!! 我们说到在两幅图像融合时,我们要求这两幅图像必须类型尺寸一致。但是,若遇到两幅图像尺寸不相同时,我们怎么处理呢? 这节,我们通过两种方式解决这个问题。 1. **重置其中一副图像的尺寸大小,使其两幅图像尺寸一致;** 2. **在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域;** 注意:第一种方式会造成其中一副图像发生形变;而第二种方式不会改变两幅图像的尺寸; ==============分割线=========== addWeighted()函数 作用:计算两个数组的加权和(dst = alpha\*src1 + beta\*src2 + gamma)。即将两幅图像进行融合。 1. void addWeighted(InputArray src1, double alpha, InputArray src2, 2. double beta, double gamma, OutputArray dst, int dtype= \-1); 参数说明: * 第一个参数:src1,表示进行加权操作的第一个图像对象,即输入图片1; * 第二个参数:double型的alpha,表示第一个图像的加权系数,即图片1的融合比例; * 第三个参数:src2,表示进行加权操作的第二个图像对象,即输入图片2; * 第四个参数:double型的beta,表示第二个图像的加权系数,即图片2的融合比例。很多情况下,有关系 alpha+beta=1.0; * 第五个参数:double型的gamma,表示一个作用到加权和后的图像上的标量,可以理解为加权和后的图像的偏移量; * 第六个参数:dst,表示两个图像加权和后的图像,尺寸和图像类型与src1和src2相同,即输出图像; * 第七个参数:输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。 ## 1-1-第一种方法代码演示 ## #include <opencv2\opencv.hpp> #include <iostream> using namespace cv; using namespace std; //我们说到在两幅图像融合时,我们要求这两幅图像必须类型尺寸一致。但是,若遇到两幅图像尺寸不相同时,我们怎么处理呢? //这节,我们通过两种方式解决这个问题。 //1.重置其中一副图像的尺寸大小,使其两幅图像尺寸一致; //2.在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域; //注意:第一种方式会造成其中一副图像发生形变;而第二种方式不会改变两幅图像的尺寸; int main() { //1 定义相关变量 Mat src1, src2, dst; //2 读取原始图像并检查图像是否读取成功 src1 = imread("D:/demo01.jpg"); //"D:/demo01.jpg" 或者"D:\\demo01.jpg" if (!src1.data) { cout << "读取图像有误,请重新输入正确路径!\n"; return -1; } src2 = imread("D:/demo02.jpg"); if (src2.empty())//如果数组没有元素 返回true { cout << "读取图像有误,请重新输入正确路径!\n"; return -1; } //3 显示原始图像 namedWindow("原始图像1",CV_WINDOW_AUTOSIZE); imshow("原始图像1",src1); //1.重置其中一副图像的尺寸大小,使其两幅图像尺寸一致; //调整src2的大小与src1的大小一致 融合函数addWeighted()要求输入的两个图像尺寸类型必须相同 // OpenCV提供了resize函数来改变图像的大小,函数原型如下: //void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR ); //src:输入,原图像,即待改变大小的图像; //dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已; //dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算: //dsize = Size(round(fx*src.cols), round(fy*src.rows)) // 其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。 // fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width / src.cols来计算; // fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height / src.rows来计算; resize(src2,src2,Size(src1.cols,src1.rows)); namedWindow("原始图像2", CV_WINDOW_AUTOSIZE); imshow("原始图像2", src2); //bool imwrite(const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>()); //imwrite("D:/demo02.jpg",src2); //利用addWeighted()函数对两幅图像进行融合 addWeighted(src1,0.6,src2,0.4,0,dst); namedWindow("图像1与图像2融合效果图"); imshow("图像1与图像2融合效果图", dst); //保持等待状态 waitKey(0); return 0; } ![70 1][] ## 第一种方法程序说明 ## 通过上面的结果,我们可以看出。 输入的是一大一小的两幅图像,通过将小图像(指的是:手的那副)利用resize()函数,重置其尺寸大小,让它与塔尖的那副大图像尺寸一致。 这样我们就可以利用addWeighted()函数进行融合了。 可以发现,这种方式,对小图像的尺寸进行改变。 OpenCV提供了resize函数来改变图像的大小,函数原型如下: void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR ); 先解释一下各个参数的意思: src:输入,原图像,即待改变大小的图像; dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已; dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算: dsize = Size(round(fx\*src.cols), round(fy\*src.rows)) 其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。 fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算; fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算; interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种: * INTER\_NEAREST - 最邻近插值 * INTER\_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法 * INTER\_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER\_NEAREST method. * INTER\_CUBIC - 4x4像素邻域内的双立方插值 * INTER\_LANCZOS4 - 8x8像素邻域内的Lanczos插值 使用注意事项: 1. dsize和fx/fy不能同时为0,要么你就指定好dsize的值,让fx和fy空置直接使用默认值,就像 resize(img, imgDst, Size(30,30)); 要么你就让dsize为0,指定好fx和fy的值,比如fx=fy=0.5,那么就相当于把原图两个方向缩小一倍! 2. 至于最后的插值方法,正常情况下使用默认的双线性插值就够用了。 几种常用方法的效率是:最邻近插值>双线性插值>双立方插值>Lanczos插值; 但是效率和效果成反比,所以根据自己的情况酌情使用。 3. 正常情况下,在使用之前dst图像的大小和类型都是不知道的,类型从src图像继承而来,大小也是从原图像根据参数计算出来。但是如果你事先已经指定好dst图像的大小,那么你可以通过下面这种方式来调用函数: resize(src, dst, dst.size(), 0, 0, interpolation); # 保存图像:imwrite()函数 # bool imwrite( const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>()); 第一个参数:将要另保存图像的名称,可以自己定义名称。 第二个参数:你要要保存的图像名称,是目前已经存在的图像。 第三个参数:来设置对于图像格式的参数,一般省略,不写。 ## 2-1-第二种方法代码演示 ## #include <opencv2\opencv.hpp> #include <iostream> using namespace cv; using namespace std; //我们说到在两幅图像融合时,我们要求这两幅图像必须类型尺寸一致。但是,若遇到两幅图像尺寸不相同时,我们怎么处理呢? //这节,我们通过两种方式解决这个问题。 //1.重置其中一副图像的尺寸大小,使其两幅图像尺寸一致; //2.在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域; //注意:第一种方式会造成其中一副图像发生形变;而第二种方式不会改变两幅图像的尺寸; int main() { //1 定义相关变量 Mat src1, src2, dst; //2 读取原始图像并检查图像是否读取成功 src1 = imread("D:/demo01.jpg"); //"D:/demo01.jpg" 或者"D:\\demo01.jpg" if (!src1.data) { cout << "读取图像有误,请重新输入正确路径!\n"; return -1; } src2 = imread("D:/demo02.jpg"); if (src2.empty())//如果数组没有元素 返回true { cout << "读取图像有误,请重新输入正确路径!\n"; return -1; } //3 显示原始图像 namedWindow("原始图像1",CV_WINDOW_AUTOSIZE); imshow("原始图像1",src1); namedWindow("原始图像2", CV_WINDOW_AUTOSIZE); imshow("原始图像2", src2); //第二种方法代码演示 //2.在较大的图像中设置感兴趣区域ROI,获得与较小的那个图像尺寸一致的区域; //利用ROI,获取将要较小的那个图像尺寸一致的区域 Mat imageROI; imageROI = src2(Rect(20, 40, src1.cols, src1.rows));//在src2图像左上角(20,40)处(即起点位置),获取同src1图像尺寸一致的区域 //利用addWeighted()函数对两幅图像进行融合 //void addWeighted(InputArray src1, double alpha, InputArray src2, //double beta, double gamma, OutputArray dst, int dtype = -1); addWeighted(src1, 0.6, imageROI, 0.4, 0., imageROI); namedWindow("图像1与图像2融合效果图"); imshow("图像1与图像2融合效果图", src2); //保持等待状态 waitKey(0); return 0; } ![70 2][] ## 第二种方法程序说明 ## 通过上面的结果,我们可以看出。 同样输入一大一小的两幅图像,我们在大图像上设置感兴趣区域ROI,此部分区域尺寸和小图像(指的的:手图像)尺寸一致。 这样我们就可以利用addWeighted()函数进行融合了。 可以发现,这种方式,对小图像的尺寸没有发生改变,同时我们还可以设置小图像在大图像的什么位置融合。 最近发现[OpenCV][]中的Rect类非常神奇,其中很多函数使用起来极其方便。一下列举一些比较实用的函数: 1. size()函数返回矩形的尺寸大小。返回类型为cv::Size。 2. area()函数返回矩形的面积,也就是矩形包含的像素点个数。也就是矩形的(宽\*高)的值。 3. contains(Point)能检测点是否在矩形内。 4. inside(Rect)检测矩形是否在矩形内。 5. tl()返回矩形左上角的点坐标。即top-left。 6. br()返回矩形右下角点坐标。即bottom-right。 还有更神奇的招数!如果要求两个矩形的交集与并集,opencv的Rect类提供了非常方便的方式。 \[cpp\] [view plain][] [copy][view plain] 1. Rect rect = rect1 & rect2; 2. Rect rect = rect1 | rect2; 如果想将Rect平移,可以这样写: \[cpp\] [view plain][] [copy][view plain] 1. Rect r1(0, 0, 5, 5); 2. Point p(2, 3); 3. Rect r2 = r1 + p;<span style="white-space:pre"> </span>//平移 如果想改变矩形的尺寸大小,可以这样写: \[cpp\] [view plain][] [copy][view plain] 1. Rect r1(0, 0, 5, 5); 2. Size s(-1, -1); 3. Rect r2 = r1 + s;<span style="white-space:pre"> </span>//改变尺寸大小 但是据我观察,rect裁出来的数据只是加上了坐标限制,但是data里存的数据还是全部的数据,所以如果需要访问data的话还是要单点拷贝了,就是写一个循环一个一个赋值。 //Rect类对象的创建示例 //Rect rect(40, 40, 60, 30); //参数解释: //参数1:创建矩形的最左角的 x - 坐标 ; //参数2:创建矩形的最左角的 y - 坐标; //参数3:创建矩形的宽; //参数4:创建矩形的高; #include <opencv2\opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main() { //【0】定义相关变量 Rect rect(40, 40, 60, 30); Point point(10, 10); //定义坐标在(10,10)的点 Point point2(70, 50); Rect rect1(20, 20, 30, 40); Rect rect2(90, 30, 20, 10); Size size(20, 20); //【1】Rect类的相关操作 cout << "矩形rect左上角的横坐标:" << rect.x << endl; cout << "矩形rect左上角的纵坐标:" << rect.y << endl; cout << "矩形rect的宽度:" << rect.width << endl; cout << "矩形rect的高度:" << rect.height << endl; cout << "矩形rect的尺寸:" << rect.size() << endl; cout << "矩形rect的面积:" << rect.area() << endl; //tl()返回矩形左上角的点坐标。即top-left。 cout << "\n矩形rect左上角的点坐标:" << rect.tl() << endl; //br()返回矩形右下角点坐标。即bottom-right cout << "矩形rect右下角的点坐标:" << rect.br() << endl; //该点在里面则为1,否则为0 //contains(Point)能检测点是否在矩形内 cout << "\n判断(10, 10)这个点在不在矩形rect内:" << rect.contains(point) << endl; cout << "判断(70, 50)这个点在不在矩形rect内:" << rect.contains(point2) << endl; cout << "\n矩阵rect与矩阵rect1的交集:" << (rect1 & rect) << endl; cout << "矩阵rect与矩阵rect1的并集:" << (rect | rect2) << endl; cout << "\n矩阵rect2进行平移操作:" << (rect2 + point) << endl; cout << "矩阵rect2进行平移操作:" << (rect2 - point) << endl; cout << "矩阵rect2改变尺寸大小操作:" << (rect2 + size) << endl; system("pause"); //作用:暂停黑窗口,否则窗口一闪而过,看不见信息 return 0; } [70]: /images/20220521/b70a4dbc8939494da54bff9e129294dc.png [70 1]: /images/20220521/da7d729d35c14976b767f0c6b80b617d.png [70 2]: /images/20220521/6cbde03afe8b41fcb7278caee7c65fce.png [OpenCV]: http://lib.csdn.net/base/opencv [view plain]: http://blog.csdn.net/q6324266/article/details/52403744#
还没有评论,来说两句吧...