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) 给出的一些讲解。