前面一部分笔记都是学习一个大佬的教程做的,但是大佬用的python,我在笔记里都转换成了c++
基础配置
图片基本读取操作 电脑上的彩色图是以 RGB(红 - 绿-蓝,Red-Green-Blue) 颜色模式显示的,但 OpenCV 中彩色图是以 B-G-R 通道顺序存储的,灰度图只有一个通道。
图像坐标的起始点是在左上角,所以行对应的是 y,列对应的是 x:
加载图片
imread()
两个参数:
参数 1:图片的文件名 如果图片放在当前文件夹下,直接写文件名就行,如”lena.jpg” 否则需要给出绝对路径,如”C:/Users/lxcqm/Desktop/图片和视频/11111.png”
参数 2:读入方式,省略即采用默认值 cv2.IMREAD_COLOR:彩色图,默认值(1)or(IMREAD_COLOR) cv2.IMREAD_GRAYSCALE:灰度图(0)or(IMREAD_GRAYSCALE) cv2.IMREAD_UNCHANGED:包含透明通道的彩色图(-1)or(IMREAD_UNCHANGED)
1 2 Mat img = imread("C:/Users/lxcqm/Desktop/images/11111.png" ,0 );
经验之谈:路径中不能有中文噢,并且没有加载成功的话是不会报错的,print(img)的结果为 None,后面处理才会报错,算是个小坑。
显示图片 使用imshow()
显示图片,窗口会自适应图片的大小:
imshow()
1 2 3 4 Mat img = imread("C:/Users/lxcqm/Desktop/images/11111.png" ,0 ); imshow("pic" , img); waitKey(0 );
参数 1 是窗口的名字,参数 2 是要显示的图片。不同窗口之间用窗口名区分,所以窗口名相同就表示是同一个窗口,显示结果如下:效果:
waitKey()
是让程序暂停的意思,参数是等待时间(毫秒 ms)。 时间一到,会继续执行接下来的程序,传入 0 的话表示一直等待。等待期间也可以获取用户的按键输入:k = waitKey(0)
我们也可以先用namedWindow()
创建一个窗口,之后再显示图片:
1 2 3 namedWindow("pic2" , WINDOW_NORMAL); imshow("pic2" , img); waitKey(0 );
参数 1 依旧是窗口的名字,参数 2 默认是WINDOW_AUTOSIZE
,表示窗口大小自适应图片,也可以设置为WINDOW_NORMAL
,表示窗口大小可调整。图片比较大的时候,可以考虑用后者。
保存图片
imwrite()
使用imwrite()
保存图片,参数 1 是包含后缀名的文件名:
1 imwrite("pictest.jpg" , img);
会保存在目录文件夹里
摄像头 打开摄像头 要使用摄像头,需要使用VideoCapture capture(0);
参数 0
指的是摄像头的编号,如果你电脑上有两个摄像头的话,访问第 2 个摄像头就可以传入 1
,依此类推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int main () { VideoCapture capture (0 ) ; if (!capture.isOpened()) { return -1 ; } while (true ) { Mat frame; capture >> frame; Mat gray; cvtColor(frame, gray, COLOR_BGR2GRAY); imshow("frame" , gray); if (waitKey(1 ) == 'q' ) { break ; } } return 0 ; }
cv2.cvtColor()
用来转换颜色,这里将彩色图转成灰度图。 通过cap.get(propId)可以获取摄像头的一些属性,比如捕获的分辨率,亮度和对比度等。propId 是从 0~18 的数字,代表不同的属性,完整的属性列表可以参考:VideoCaptureProperties(https://docs.opencv.org/4.0.0/d4/d15/group__videoio__flags__base.html#gaeb8dd9c89c10a5c63c139bf7c4f5704d ) 也可以使用cap.set(propId,value)来修改属性值。比如说,我们在 while 之前添加下面的代码:
1 2 3 4 5 6 double width = capture.get(CAP_PROP_FRAME_WIDTH);double height = capture.get(CAP_PROP_FRAME_HEIGHT);std ::cout << "Original width: " << width << ", height: " << height << std ::endl ;capture.set (CAP_PROP_FRAME_WIDTH, width * 2 ); capture.set (CAP_PROP_FRAME_HEIGHT, height * 2 );
std::cout << Original width: << width <<
这行代码使用了C++中的流输出操作符”<<”,它用于将数据插入到输出流中。在这个语句中:
std::cout
: 是C++标准库中的标准输出流对象,用于将数据输出到控制台。"Original width: "
、width
、", height: "
、height
、std::endl
都是要输出的具体内容。<<
:是流输出操作符,用于将后面的内容插入到输出流中。所以,std::cout << "Original width: " << width << ", height: " << height << std::endl;
这行代码的作用是将描述原始宽度和高度的字符串以及相应的变量值输出到控制台,并在最后换行。
输出
经验之谈:某些摄像头设定分辨率等参数时会无效,因为它有固定的分辨率大小支持,一般可在摄像头的资料页中找到。
播放本地视频 跟打开摄像头一样,如果把摄像头的编号换成视频的路径就可以播放本地视频了。回想一下waitKey()
,它的参数表示暂停时间,所以这个值越大,视频播放速度越慢,反之,播放速度越快,通常设置为 25
或 30
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <opencv2/opencv.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> using namespace std ; using namespace cv; int main () { VideoCapture capture ("C:/Users/lxcqm/Desktop/WeChat_20240325194731.mp4" ) ; if (!capture.isOpened()) { cout << "Error opening video file" << endl ; return -1 ; } Mat frame, gray; while (true ) { if (!capture.read(frame)) { break ; } cvtColor(frame, gray, COLOR_BGR2GRAY); imshow("frame" , gray); if (waitKey(30 ) == 'q' ) { break ; } } capture.release(); destroyAllWindows(); return 0 ; }
录制视频 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <opencv2/opencv.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> using namespace std ; using namespace cv; int main () { VideoCapture capture (0 ) ; VideoWriter outfile ("output.avi" , VideoWriter::fourcc('M' , 'J' , 'P' , 'G' ), 25. , Size(640 , 480 )) ; while (capture.isOpened()) { Mat frame; capture.read(frame); if (!frame.empty()) { outfile.write(frame); imshow("frame" , frame); if (waitKey(1 ) == 'q' ) { break ; } } else { break ; } } return 0 ; }
基本操作 Mat创建图像(矩阵) 创建图像(矩阵):Mat 使用Mat创建图像(矩阵)的常用形式有:
创建一个空图像,大小为0
指定矩阵大小,指定数据类型:
1 Mat image1 (100 ,100 ,CV_8U) ;
参数:矩阵行数,矩阵列数,数据类型
其中数据类型有很多种,常用的应该有:
CV_8U:8位无符号型(0~255),即灰度图像;
CV_8UC3:三通道8位无符号型,这里三通道指B(蓝)G(绿)R(红),与matlab中的RGB正好相反。
这里创建矩阵时未指定矩阵的值,发现默认值的大小为205.
指定矩阵大小,指定数据类型,设置初始值:
1 Mat image1 (100 ,100 ,CV_8U, 100 ) ;
这里包含四个参数:矩阵行数,矩阵列数,数据类型,初始值; 对于灰度图像:可以直接给出初始值,也可以使用Scalar();
1 2 Mat image1 (100 ,100 ,CV_8U, 100 ) ; Mat image1 (100 ,100 ,CV_8U, Scalar(100 )) ;
对于三通道图像:使用Scalar();
1 Mat image1 (100 ,100 ,CV_8UC3, Scalar(100 ,100 ,100 )) ;
获取图像信息 获取图像的宽度(列数),高度(行数),尺寸和通道数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std ; int main () { Mat image1 = imread("lena.png" ); if (image1.empty()) { cout << "读取错误" << endl ; return -1 ; } imshow("image1" , image1); cout << "图像的行数为: " << image1.rows << endl ; cout << "图像的列数为: " << image1.cols << endl ; cout << "图像的通道数为: " << image1.channels() << endl ; cout << "图像的尺寸为: " << image1.size << endl ; waitKey(0 ); return 0 ; }
输出
感兴趣区域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std ; int main () { Mat image1 = imread("fire.jpg" ); if (image1.empty()) { cout << "读取错误" << endl ; return -1 ; } imshow("image1" , image1); Mat imageROI (image1, Rect(0 , 0 , 10 , 10 )) ; waitKey(0 ); return 0 ; }
其中Rect()有四个参数,Rect(a,b,c,d):
a:感兴趣区域列(cols)的起点;
b:感兴趣区域行(rows)的起点;
c:感兴趣区域的列数(cols);
d:感兴趣区域的行数(rows);
通过鼠标点击操作获取图像的像素坐标和像素值 创建鼠标操作函数的头文件 onMouse.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #pragma once #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std ; void onMouse (int event, int x, int y, int flags, void * param) ; void onMouse (int event, int x, int y, int flags, void * param) { Mat* im = reinterpret_cast<Mat*>(param); switch (event) { case EVENT_LBUTTONDOWN: if (static_cast<int >(im->channels()) == 1 ) { switch (im->type()) { case 0 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<uchar>(Point(x, y))) << endl ; break ; case 1 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<char >(Point(x, y))) << endl ; break ; case 2 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<ushort>(Point(x, y))) << endl ; break ; case 3 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<short >(Point(x, y))) << endl ; break ; case 4 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<int >(Point(x, y))) << endl ; break ; case 5 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<float >(Point(x, y))) << endl ; break ; case 6 : cout << "at (" << x << ", " << y << " ) value is: " << static_cast<int >(im->at<double >(Point(x, y))) << endl ; break ; } } else { cout << "at (" << x << ", " << y << ")" << " B value is: " << static_cast<int >(im->at<Vec3b>(Point(x, y))[0 ]) << " G value is: " << static_cast<int >(im->at<Vec3b>(Point(x, y))[1 ]) << " R value is: " << static_cast<int >(im->at<Vec3b>(Point(x, y))[2 ]) << endl ; } break ; } }
主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <opencv2/opencv.hpp> #include "onMouse.h" using namespace cv; using namespace std ; int main () { Mat image1 = imread("fire.jpg" ); if (image1.empty()) { cout << "读取错误" << endl ; return -1 ; } imshow("image1" , image1); setMouseCallback("image1" , onMouse, reinterpret_cast<void *>(&image1)); waitKey(0 ); return 0 ; }
效果
访问图像像素 访问(j,i)处像素 以8位(0~255)灰度图像和BGR彩色图像为例,用at可以访问图像像素:
1 2 3 4 5 6 image.at<uchar>(j, i) image.at<Vec3b>(j, i)[0 ] image.at<Vec3b>(j, i)[1 ] image.at<Vec3b>(j, i)[2 ]
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std ; int main () { Mat image1 = imread("fire.jpg" ); imshow("image1" , image1); int i = 50 , j = 50 ; int b = image1.at<Vec3b>(j, i)[0 ]; int g = image1.at<Vec3b>(j, i)[1 ]; int r = image1.at<Vec3b>(j, i)[2 ]; cout << "图像该点的像素(BGR):" <<b<<"," << g << "," << r << endl ; waitKey(0 ); return 0 ; }
输出:
在图像中加入白色椒盐噪声 创建Salt头文件 Salt.h
1 2 3 4 5 6 7 8 9 10 #pragma once #include <iostream> #include <opencv2/opencv.hpp> #include <random> using namespace cv; using namespace std ; void Salt (Mat image, int n) ;
创建Salt源文件 Salt.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "Salt.h" void Salt (Mat image, int n) { default_random_engine generater; uniform_int_distribution<int >randomRow(0 , image.rows - 1 ); uniform_int_distribution<int >randomCol(0 , image.cols - 1 ); int i, j; for (int k = 0 ; k < n; k++) { i = randomCol(generater); j = randomRow(generater); if (image.channels() == 1 ) { image.at<uchar>(j, i) = 255 ; } else if (image.channels() == 3 ) { image.at<Vec3b>(j, i)[0 ] = 255 ; image.at<Vec3b>(j, i)[1 ] = 255 ; image.at<Vec3b>(j, i)[2 ] = 255 ; } } }
main main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <opencv2/opencv.hpp> #include "Salt.h" using namespace cv; using namespace std ; int main () { Mat image1 = imread("lena.png" ); if (image1.empty()) { cout << "读取错误" << endl ; return -1 ; } imshow("image1" , image1); Salt(image1, 5000 ); imshow("image2" , image1); waitKey(0 ); return 0 ; }
效果
遍历图像像素 指针扫描 以下面模板对图像进行扫描运算为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std ; int main () { Mat image1, output_image; image1 = imread("lena.png" ); if (image1.empty()) { cout << "读取错误" << endl ; return -1 ; } output_image = Mat(image1.size(), image1.type()); output_image = image1.clone(); int rows = image1.rows; int stepx = image1.channels(); int cols = (image1.cols) * image1.channels(); for (int row =1 ; row < (rows - 1 ); row++) { const uchar* previous = image1.ptr<uchar>(row - 1 ); const uchar* current = image1.ptr<uchar>(row); const uchar* next = image1.ptr<uchar>(row + 1 ); uchar* output = output_image.ptr<uchar>(row); for (int col = stepx; col < (cols- stepx); col++) { output[col] = saturate_cast<uchar>(5 *current[col] - (previous[col]+ current[col- stepx]+ current[col + stepx]+ next[col])); } } imshow("image1" , image1); imshow("output_image" , output_image); waitKey(0 ); return 0 ; }
输出
把卷积核中间改成4: 输出
opencv自带的卷积运算:filter2D 上面的方法可以简化为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std ; int main () { Mat image1, output_image; image1 = imread("lena.png" ); if (image1.empty()) { cout << "读取错误" << endl ; return -1 ; } Mat kernel = (Mat_<char >(3 ,3 ) << 0 , -1 , 0 , -1 , 5 , -1 , 0 , -1 , 0 ); filter2D(image1, output_image, image1.depth(), kernel); imshow("image1" , image1); imshow("output_image" , output_image); waitKey(0 ); return 0 ; }
在filter2D函数中,image1.depth()是用来获取图像image1的深度(depth)信息的函数调用。
图像的深度表示图像中每个像素值的数据类型。在OpenCV中,常见的图像深度包括:
CV_8U:8位无符号整数(即Unsigned char),对应于灰度图像的深度
CV_16U:16位无符号整数
CV_16S:16位有符号整数
CV_32S:32位有符号整数
CV_32F:32位浮点数
CV_64F:64位浮点数