OpenCV2学习笔记(一)

最后更新于:2022-04-01 06:35:47

##图像的基本操作 一直没有一个系统的时间来深入学习OpenCV,鉴于项目需要,记录一下一些要点以供日后查阅。  OpenCV是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。其1.0版本于2006年面世,而在2009年又发布了重要的版本:OpenCV2,带来了新的C++接口;现在,OpenCV3也发布了,据说其Python接口大大增强、并且加入了Python 3.x的支持,还带来了许多新的提升,不过这并不在这里的讨论范围之内。这里使用的是:OpenCV2.4.9+Qt5.3.2。  OpenCV库自2.2版本起就被划分成多个模块,在进行开发之前,需要将这些模块编译成库文件,然后在lib文件夹中找到这些模块:  opencv_core模块:其中包含OpenCV基本数据结构、动态数据结构、绘图与数组操作的相关函数、辅助功能与系统函数、基本的算法函数等核心功能。  opencv_improc模块:包含图像处理函数,主要包含图像滤波、图像的几何变换、直方图、特征检测、目标跟踪等内容。  opencv_highgui模块:高层GUI图形用户界面,包含媒体的I/O输入输出函数,读写图像及视频的函数,以及操作图形用户界面函数。  opencv_features2d模块:即2D功能框架,包含兴趣点检测子,描述子以及兴趣点匹配框架。  opencv_calib3d模块:Calibration(校准)加3D这两个词的组合缩写。这个模块主要是相机校准和三维重建相关的内容,包含相机标定,双目几何估计,物体姿态估计以及立体视觉等函数。  opencv_video模块:包含运动估算,特征跟踪以及前景提取函数与相关的类。  opencv_objdetect模块:主要由级联分类(Cascade Classification)和Latent SVM这两个部分。其中包括物体检测函数,如脸部和行人检测。  opencv_stitching模块:OpenCV2.4.0新增的模块,其主要功能是实现图像拼接。  lopencv_superres模块:即SuperResolution,利用多种算法实现超分辨率技术的相关功能模块。  opencv_ml模块:机器学习模块,主要包括统计模型 (Statistical Models)、一般贝叶斯分类器 (Normal Bayes Classifier)、K-近邻 (K-NearestNeighbors)、支持向量机 (Support Vector Machines)、决策树 (Decision Trees)、提升(Boosting)、梯度提高树(Gradient Boosted Trees)、随机树 (Random Trees)、超随机树 (Extremely randomized trees)、期望最大化 (Expectation Maximization)、神经网络 (Neural Networks)等内容。  opencv_flann模块:高维的近似近邻快速搜索算法库, 主要由两个部分组成:快速近似最近邻搜索和聚类。  opencv_contrib模块:第三方代码,包括一些新添加的不太稳定的可选功能,如新型的人脸识别、立体匹配、人工视网膜模型等技术。  opencv_nonfree模块:包含一些拥有专利的算法,如SIFT、SURF函数源码。  这些模块都对有一个单独的头文件(位于include文件夹)。在Qt中推荐的声明方式如下: ~~~ #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> ~~~ 而在Qt中,为了使程序能通过编译,必须指定OpenCV的库文件和头文件的路径。因此在创建Qt工程之后,需要在后缀为.pro的项目文件中添加用于构建OpenCV的应用信息: ~~~ INCLUDEPATH+=C:\OpenCV\install\include\opencv\ C:\OpenCV\install\include\opencv2\ C:\OpenCV\install\include LIBS+=C:\OpenCV\lib\libopencv_calib3d249.dll.a\ C:\OpenCV\lib\libopencv_contrib249.dll.a\ C:\OpenCV\lib\libopencv_core249.dll.a\ C:\OpenCV\lib\libopencv_features2d249.dll.a\ C:\OpenCV\lib\libopencv_flann249.dll.a\ C:\OpenCV\lib\libopencv_gpu249.dll.a\ C:\OpenCV\lib\libopencv_highgui249.dll.a\ C:\OpenCV\lib\libopencv_imgproc249.dll.a\ C:\OpenCV\lib\libopencv_legacy249.dll.a\ C:\OpenCV\lib\libopencv_ml249.dll.a\ C:\OpenCV\lib\libopencv_nonfree249.dll.a\ C:\OpenCV\lib\libopencv_objdetect249.dll.a\ C:\OpenCV\lib\libopencv_ocl249.dll.a\ C:\OpenCV\lib\libopencv_video249.dll.a\ C:\OpenCV\lib\libopencv_photo249.dll.a\ C:\OpenCV\lib\libopencv_stitching249.dll.a\ C:\OpenCV\lib\libopencv_superres249.dll.a\ C:\OpenCV\lib\libopencv_ts249.a\ C:\OpenCV\lib\libopencv_videostab249.dll.a ~~~ 二、读取、显示图片并对图片进行简单的阈值分割  这里使用Qt设计一个简单窗口,如图1所示,该窗口包含打开图像和处理图像这两个按钮,同时在下方显示原图像和处理后的图像。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a74697c9f.jpg) 首先创建一个类,主要实现图像的阈值分割: colordetector.h: ~~~ #ifndef COLORDETECTOR_H #define COLORDETECTOR_H #include <QFileDialog> #include <QMainWindow> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> class ColorDetector { public: // 最小可接受距离 int minDist; void setTargetColor(unsigned char red, unsigned char green, unsigned char blue); cv::Mat process(const cv::Mat &image); private: // 目标颜色 cv::Vec3b target; // 输出结果图像 cv::Mat result; void setTargetColor(cv::Vec3b color); void colorDetector(const cv::Mat &image, cv::Mat &result); void setColorDistanceThreshold(int distance); int getColorDistanceThreshold(); int getDistance(const cv::Vec3b& color); cv::Vec3b getTargetColor() const; }; #endif // COLORDETECTOR_H ~~~ colordetector.cpp: ~~~ #include "colordetector.h" int ColorDetector::getDistance(const cv::Vec3b& color) { return abs(color[0] - target[0])+ abs(color[1] - target[1]) + abs(color[1] - target[1]); } void ColorDetector::colorDetector(const cv::Mat &image, cv::Mat &result) { cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>(); cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>(); cv::Mat_<uchar>::iterator itout = result.begin<uchar>(); // 对每个像素进行处理,计算每个像素距离目标颜色的距离 for(; it<itend; ++it,++itout) { if(getDistance(*it)<minDist) // 判断距离是否小于最小可接受距离 { *itout = 0; } else *itout = 255; } } void ColorDetector::setColorDistanceThreshold(int distance) { // 设置色彩距离阈值,阈值必须为正 if(distance < 0) distance = 0; minDist = distance; } int ColorDetector::getColorDistanceThreshold(){ // 获取色彩距离的阈值 return minDist; } void ColorDetector::setTargetColor(unsigned char red, unsigned char green, unsigned char blue) { // BGR顺序 target[2] = red; target[1] = green; target[0] = blue; } void ColorDetector::setTargetColor(cv::Vec3b color) { // 设置需检测的颜色 target = color; } cv::Vec3b ColorDetector::getTargetColor() const { // 获取需检测的颜色 return target; } // Process阈值分割方法的定义 cv::Mat ColorDetector::process(const cv::Mat &image) { result.create(image.rows, image.cols, CV_8U); colorDetector(image, result); return result; } ~~~ cv::Mat image表示输入图像,result为输出图像,使用迭代器遍历图像的像素点,每个迭代计算当前像素颜色与设定的目标颜色的距离(用函数getDistance()实现),判断是否在minDist所定义的容忍度之内。判断为真,则将当前像素赋值为0(黑色),否则为255(白色)。当然,计算像素点距离的方法有很多,如计算RGB三个分量的欧拉距离。  在这里,提供两种设置目标颜色的方法。分别是设置RGB图像的三个颜色分量,和使用cv::Vec3b来保存颜色值,在函数setTargetColor()中可以看出。 第二步,进入Qt的ui设计模式拖入两个Push Button和两个label,调整布局并修改按钮和label名称,分别右击”Open Image”和”Process”转到槽,编写槽函数: ~~~ void MainWindow::on_pushButton_clicked() // Open Image槽函数 { QString fileName = QFileDialog::getOpenFileName(this, tr("Open Image"), ".", tr("Image Files(*.png *.jpg *.jpeg *.bmp)")); image = cv::imread(fileName.toLatin1().data()); if(image.data){ //cv::namedWindow("Original Image"); //cv::imshow("Original Image", image); cv::Mat imageclone = image.clone(); // 由于要改变颜色通道顺序,要避免原始图像被改变,使用深拷贝 cv::cvtColor(imageclone, imageclone, CV_BGR2RGB); // 改变颜色通道的顺序 QImage img1 = QImage((unsigned char*)(imageclone.data), image.cols, image.rows, QImage::Format_RGB888); // 显示在label中 ui -> label ->setPixmap(QPixmap::fromImage(img1)); // 改变label尺寸以自适应图像 ui -> label ->resize(ui->label->pixmap()->size()); } else qDebug() << tr("没有输入图像!"); } ~~~ ~~~ void MainWindow::on_pushButton_2_clicked() { if(image.data) { ColorDetector detect; // 设置输入参数 detect.minDist = 100; // 最小可接受距离 detect.setTargetColor(130,190,130); // 选定的阈值 // cv::namedWindow("result"); // cv::imshow("result", detect.process(image)); image = detect.process(image); // 对图像做阈值分割 cv::cvtColor(image, image, CV_GRAY2RGB); // 改变颜色通道的顺序 QImage img = QImage((unsigned char*)(image.data), image.cols, image.rows, QImage::Format_RGB888); // 显示在label中 ui -> label_2 ->setPixmap(QPixmap::fromImage(img)); // 改变label尺寸以自适应图像 ui -> label_2 ->resize(ui->label_2->pixmap()->size()); } else qDebug() << tr("无输入图像,无法进行阈值分割"); } ~~~ opencv2中用于存储图像数据为Mat类型,而在opencv1中使用的是IplImage,其优点在于Mat是一个类,定义的类类型可以自动分配和释放内存空间,而IplImage需手动为其分配和释放内存空间,当图像较多时,可能会造成内存泄露。  在判断图片是否加载成功时使用了image.data,这是一个指向已分配的内存块的指针,当图片没有加载进来为NULL。 最后对main函数进行简单修改即可: ~~~ #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.setWindowTitle("简单的阈值分割"); w.show(); return a.exec(); } ~~~ 结果:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a746adaeb.jpg) 三:图像复制中的深浅拷贝  需要注意的一点小问题,在复制图像时需要考虑是否想更改原图像,这需要牵涉到浅拷贝和深拷贝的概念:  浅拷贝的主要方式:  Mat A;  A = image ; // 第一种方式  Mat B(image); // 第二种方式  这两种方式之所以称为浅拷贝,是因为它们虽然有不同的名称,但是它们指向相同的内存空间。当其中一个图像矩阵发生变化时,另外一个也会发生变化。  深拷贝的主要方式:  Mat A,B;  A = image.clone(); // 第一种方式  image.copyTo(B); // 第二种方式  深拷贝是真正的复制了一个新的图像矩阵,此时image,A,B三者无论谁发生了改变,其他都不受影响。 最后感谢博客:[http://blog.csdn.net/lu597203933](http://blog.csdn.net/lu597203933) 给出的一些讲解。
';