小知识点
- Qt中的 $$PWD:表示返回的路径是当前文件所在的路径;通常用于pri和pro文件里面,一般的配套使用为
|
|
- 下一级目录采用 /,切记!!!
- 工程的当前文件夹是生成的Dgbug文件里面,并不是源代码里面的文件夹。如要访问源代码文件夹,要加“../xxx/xxx”
- 如下图所示,上一个文件夹是源代码文件夹,下一个是生成的文件夹。
|
|
Qt快捷键
- F1 查看帮助
- F2 跳转到函数定义(和Ctrl+鼠标左键一样的效果)
- Shift+F2 声明和定义之间切换
- F4 头文件和源文件之间切换
- Ctrl+I 自动对齐
- Ctrl+/ 注释行,取消注释行
- Ctrl(按住) + Tab 快速切换已打开的文件
- Alt(按住)+ Enter,在cpp中添加该头文件函数的声明。
- Ctrl + Shift + R,修改类成员变量的名字,覆盖所有
Qt关于模块化设计的方法
-
方法:在源文件目录下设置新的文件夹,内含.h和.cpp和.pri文件;同时修改工程的.pro文件。
-
步骤:
-
创建新文件夹,同时编辑.pri文件如下图所示
-
在.pro文件编辑添加
1 2
INCLUDEPATH +=$$PWD/onepri #工程编译时,会去该目录下搜索文件 include($$PWD/onepri/onepri.pri) #include包含的文件会显示在工程中
-
添加的位置如下图,在SOURCES+=前面
-
关于在 windows 配置opencv
-
为什么需要cmake编译,那是因为需要contribute的非免费算法,同时为了适配更多的平台。
-
下载 cmake
-
注意是msi后缀,不自动配置环境变量,后期自己把bin文件夹放进系统变量path里面。
-
网上有很多示例。
-
下载opencv+contribute源码
-
下载source源码+win版本解压。
-
source源码是为了编译;win版本是编译后直接替换win版本部分的生成dll。
-
上面是opencv_contrib 的源码,注意要与opencv的版本一致。
-
cmake-opencv
-
需要注意的就是enable nofree需要打钩,添加好module目录。
-
同时注意勾选 BUILD_opencv_world!
-
然后点击Configure,选好vs版本以及x64,开始配置。
-
这里需要代理才能成功下载。
-
我这里使用clash代理,为了可以在cmd命令行使用代理下载,我在环境变量添加了如下代理。
-
没有报错之后(python报错不用管,cuda如果报错就不选)
-
点击Generate,生成。
-
VS 编译结果
-
使用vs打开opencv.sln解决方案。
-
首先选择 Debug x64 的平台
-
选择右边的 ALL_BUILD ,右键,选择 生成 或者 重新生成 然后静静等待就可以了。
-
生成完成后,点击下面的 INSTALL ,右键,仅用于当前项目-》仅生成install。
-
生成完成后把install的所有文件替换到win版本的build文件夹下。
-
VS 配置 + 环境变量
-
添加环境变量path
-
VS配置三样东西
-
属性管理器:包含目录+库目录
-
属性管理器:链接器
-
-
最后测试以下就好了。
QT中配置opencv
基于windows10
- 前提,已经使用cmake重新编译源代码,同时已经配置好环境变量,这里不再赘述
- 注意在.pri文件里面的路径分隔符请使用“/”。
- 在qt的源代码文件夹里面添加如下内容的xxx.pri文件
|
|
- 不同opencv源代码位置以上路径不一样。
- 结果如下所示
-
测试
1 2 3 4 5 6 7 8 9 10 11 12 13
#include "windowdelta.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); using namespace cv; Mat src=cv::imread("E:/pro_test/QT/DeltaImage/camera.jpg"); cv::imshow("tyc",src); waitKey(0); windowDelta w; w.show(); return a.exec(); }
单选按钮使用
-
如何保持互斥:
设置按钮属性,第一个红框勾选是设置按钮可选,第二个勾选就是设置自动互斥,当同一容器内的按钮勾选了这个选项就会自动互斥
- 注意要保持在同一个group里面
- 添加槽函数,可以在ui界面右键点击转到槽,里面有各种信号函数,Qt自动添加
Qt界面初始化
- 直接ui设置
- 或者在代码的窗口构造函数函数使用
|
|
读取文件和文件夹对话框
- 读取文件
|
|
- 读取文件夹
|
|
- QString转为opencv的String可以使用.toStdString()这个函数。
- 注意,windows环境下,最好采用英文路径,中文路径可能读取不到,读取乱码,这是字符编码的问题。
Mat、QImage、QPixmap
QImage的类型
- QImage::Format_Grayscale8 The image is stored using an 8-bit grayscale format. (added in Qt 5.5)
- QImage::Format_Grayscale16 The image is stored using an 16-bit grayscale format. (added in Qt 5.13)
- QImage::Format_RGB888 The image is stored using a 24-bit RGB format (8-8-8)
- QImage::Format_BGR888 The image is stored using a 24-bit BGR format. (added in Qt 5.14)
Mat转为QImage
- 默认情况opencv加载是BGR的彩色图像,所以把Mat格式改为QImage需要进行转换,代码如下
|
|
- 最简单的显示图像的方式是采用label控件,但其在处理显示的图像(比如缩放等等)方面根本没有灵活性。
|
|
QImage转为Mat
|
|
QImage转为QPixmap
- QPixmap::fromImage(image)函数
- QPixmap::convertFromImage函数
- QT显示图片,都要转换为QPixmap
场景、视图、图形对象元素
-
场景——QGraphicsScene:用于管理对象元素或者QGraphicsItem及其子类的实例。
-
视图——QGraphicsView:Qt的控件类,用于可视化和显示QGraphicsScene的内容。需要重点区别QGraphicsScene和QGraphicsView有不同的坐标系统,因此缩放要经历不同的转换,QGraphicsScene和QGraphicsView都提供了转换位置值的。
-
图形对象元素——QGraphicsItem及其子类的实例。
-
显示步骤
- 在框架类里面添加私有的场景类: QGraphicsScene scene;
- ui界面添加控件:QGraphicsView
- setup函数关联控件和场景类:
1 2 3 4 5 6 7 8 9 10
test_opencv::test_opencv(QWidget *parent) : QMainWindow(parent) , ui(new Ui::test_opencv) { ui->setupUi(this); //以下是窗口初始化代码 ui->median->setChecked(true); ui->checkBox->setChecked(false); ui->graphicsView->setScene(&scene);//关联控件 }
- 显示图像
1 2 3 4 5 6
//在场景中显示图像 scene.clear();//清除原本的元素,保证内存不溢出 QPixmap pixmap; if(pixmap.load(file_input)){ scene.addPixmap(pixmap); }
自定义绘图控件
-
创建一个继承QWidget的新类;
-
重写他的paintEvent函数;
-
用QPainter类在控件上完成绘制操作;
-
向窗口添加QWidget控件;
-
右键提升为新的创建控件。
-
注意:也可以创建一个ui界面,使用多个控件创建成一个控件
Opencv的自带的Directory类
-
可以用于读取文件夹内的文件
-
1 2 3 4 5 6 7
static std::vector< std::string > GetListFiles (const std::string &path, const std::string &exten=”*”, bool addPath=true) /* 这个函数只获取指定目录下的文件 path:string, 用于指定根目录 exten: string,这个是个正则表达式,匹配的返回,否则不返回。 addPath: bool,如果为true,返回的文件名会带path,如果为false,返回的仅是文件名。 */
-
然而只有2.x版本才有,3.x和4.x都没有了,但是可以迁移编译
-
在工程中建立contrib.hpp文件和contrib.cpp文件,在主程序中直接引用就好了。
-
contrib.hpp
|
|
- contrib.cpp
|
|
C++读取文件夹中指定类型的所有文件
|
|
C++读取txt文件
- 利用ifstream
- 利用eof函数判断是否在文件末尾,有个缺点,最后的数据会输出两次
|
|
C++写入txt文件
- 利用ofstream
|
|
Qt对话框显示string以及Opencv的Mat数据类型
- 其实类似ostream,stringstream一样可以直接把Mat类型送入数据流
|
|
Opencv坐标系
-
OPENCV 的图片坐标
-
OpenCV中像素坐标系的坐标原点在图像的左上角,这种坐标系在结构体Mat,Rect,Poin,Size中都是适用的。在Point(x,y)和Rect(x,y)以及Size(width,length)中,第一个参数x/width代表的是元素所在图像的列数,第二个参数y/length代表的是元素所在图像的行数。(先列后行)
坐标体系的X轴为图像矩形的上面那条水平线,从左往右;Y轴为图像矩形左边的那条垂直线,从上往下。
以图像左上角为原点建立以像素为单位的直接坐标系u-v。像素的横坐标u与纵坐标v分别是在其图像数组中所在的列数与所在行数。(在OpenCV中u对应x,v对应y)
-
row == height == Point.y
col == width == Point.x
-
然而一下构造方式是先行后列
|
|
- 读取像素也是先行后列
|
|
Opencv4基础学习
-
格式对应
1 2 3 4 5 6 7 8 9 10 11 12
Mat每个格子内的数据格式-----------Mat定义 Mat_<uchar>---------CV_8U Mat<char>-----------CV_8S Nat_<short>---------CV_16S Mat_<ushort>--------CV_16U Mat_<int>-----------CV_32S Mat_<float>----------CV_32F Mat_<double>--------CV_64F img1.at<vec3b>(i,j)[0]= 3.2f; // B 通道 img1.at<vec3b>(i,j)[1]= 3.2f; // G 通道 img1.at<vec3b>(i,j)[2]= 3.2f; // R 通道
-
读取mat文件的四种方法
-
1 2 3 4 5 6 7 8 9 10 11 12
//与常规数组不同,opencv里面读取基本采用(a,b),而不是[a,b] int value=mat.at<uchar>(0,0); //读取3通道的方法 Vec3b;;Vec3s;;Vec3w;;Vec3d;;Vec3f;;Vec3f;; uchar;;ushort;;short;;double;;float;;int Vec3b vec3=mat.at<Vec3b>(0,0); //读取2/4通道类似,八3改为2即可 //可以直接采用Point类 dst.at<Point2f>(i, j)
-
1 2
//使用ptr指针,因为mat类在内存的存放位置每一行都是挨着存放,找到第i行指针即可 uchar *ptr=mat.ptr<uchar>(i)
-
1 2 3 4 5
//使用迭代器访问 MatIterator_<uchar> it = imageL.begin<uchar>(); MatIterator_<uchar> itend = imageL.end<uchar>(); cout << (int)(*it) << endl; cout << (int)(*(itend-1)) << endl;
-
1 2 3 4 5
//按矩阵位置访问,直接用*,注意是几通道 mat.step[0] 为图象像素行的实际宽度,即像素数*通道数,字节数目 mat.step[1] 每像素占据字节数 mat,data uchar类型的指针,指向Mat数据矩阵的首地址。 (int)*(mat.data+b.step[0]*row+mat.step[1]*col+channel);
-
-
其他的数据类型
-
1 2 3
.Vec<>//定义向量的类 Vec<double,19> my_vector//19个double的一维向量 //类似上面的Vec3b只是一种重命名
-
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
.Matx<>//定义固定小矩阵的类,效率高,一般作为空间域的卷积核和滤波器,注意调用前必须定义,没有动态内存的分配 cv::Matx<int, 2, 2> matx1(2,2,2,2); //类似Vec,有一系列预定义 typedef Matx<float, 1, 2> Matx12f; typedef Matx<double, 1, 2> Matx12d; typedef Matx<float, 1, 3> Matx13f; typedef Matx<double, 1, 3> Matx13d; typedef Matx<float, 1, 4> Matx14f; typedef Matx<double, 1, 4> Matx14d; typedef Matx<float, 1, 6> Matx16f; typedef Matx<double, 1, 6> Matx16d; typedef Matx<float, 2, 1> Matx21f; typedef Matx<double, 2, 1> Matx21d; typedef Matx<float, 3, 1> Matx31f; typedef Matx<double, 3, 1> Matx31d; typedef Matx<float, 4, 1> Matx41f; typedef Matx<double, 4, 1> Matx41d; typedef Matx<float, 6, 1> Matx61f; typedef Matx<double, 6, 1> Matx61d; typedef Matx<float, 2, 2> Matx22f; typedef Matx<double, 2, 2> Matx22d; typedef Matx<float, 2, 3> Matx23f; typedef Matx<double, 2, 3> Matx23d; typedef Matx<float, 3, 2> Matx32f; typedef Matx<double, 3, 2> Matx32d; typedef Matx<float, 3, 3> Matx33f; typedef Matx<double, 3, 3> Matx33d; typedef Matx<float, 3, 4> Matx34f; typedef Matx<double, 3, 4> Matx34d; typedef Matx<float, 4, 3> Matx43f; typedef Matx<double, 4, 3> Matx43d; typedef Matx<float, 4, 4> Matx44f; typedef Matx<double, 4, 4> Matx44d; typedef Matx<float, 6, 6> Matx66f; typedef Matx<double, 6, 6> Matx66d; //同时有很多支持向量相乘、点积、叉积的函数,以及初始化0、1、单位阵的函数、矩阵转置、矩阵的逆和提取对角线函数 //但是不能和mat类型混用进行点积、叉积
-
1 2
.Scalar//四个元素的double向量,从Vec派生出来 //同理,和Vec一样,可以采用[]或()读取元素
-
1 2 3 4 5 6
.Point//二维或三维坐标值 Point2i; Point2f; Point3i; Point3f; //支持点乘和叉乘
-
1 2 3 4 5 6 7
.Size//用于指定图像或矩阵大小 //内含width和height //area()函数 cv::size; cv::size2i; cv::size2f; size(width,heigth);//先宽后高
-
1 2 3 4
.Rect //定于2D矩形 //左上角坐标和宽度和高度 Rect roi(0,0,100,100);
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1 .RotatedRect //定义一个旋转的矩形,参数有中心点、宽度和高度、旋转角(单位为度)组成 //有趣的是boundingBox函数,返回包含旋转矩形的Rect。 2 .Complex//复数类,与complex标准类兼容,可转换,但是有所不同 3 .Ptr//智能指针,类似share等 Ptr<Matx22f> p(new Matx22f); p = &matx1; //过后要释放,还不太明白 4 .InputArray .noArray() .OutputArray //指以上所列的各种数组的类,包含所有,方便计算。 //输入后可以用m.getmat()创造一个mat对象进行计算 //In不可改,Out可更改
-
-
常见的mat数组方式
-
1 2 3 4
//动态内存大数组 Mat mat1=Mat::eye(3, 3, CV_8UC1);//创建单位矩阵 Mat mat1=Mat::zeros(3, 3, CV_8UC1);//创建全零 Mat mat1=Mat::ones(3, 3, CV_8UC1);//创建全1
-
1 2 3 4 5 6
m1= mo.clone();//拷贝,分配全新内存 m0.copyTo(m1);//复制,分配全新内存 m1=m0;//赋值,只是复制了指针,不另开内存 //稀疏矩阵,不同于Mat,其访问机制和内置函数也完全不同 cv::SparesMat sm1;
-
1 2 3 4 5 6 7
//矩阵的运算 //转置、求逆、对角线 cv::abs()//每个元素绝对值 //矩阵乘法,矩阵逻辑运算与或非 //点积 //叉积(只适合3*1矩阵) //还有各种成员函数例如empty()等,参见《学习Opencv3》Chap5矩阵操作
-
-
image watch插件下载地址,适用于VS2017,下载直接安装重启VS即可
-
opencv 接口计算特征值与特征向量
1 2 3 4 5 6 7 8 9 10
bool eigen(InputArray src, OutputArray eigenvalues, OutputArray eigenvectors = noArray()); /* *功能:*获取特征值和特征向量 *参数:* src:原图或者数据 eigenvalues:特征值 eigenvectors:特征向量 */ //注意,输出的特征值是列向量,特征向量是横向量,与matlab的不同 //[V,D]=eig(A);%%%%特征值和特征向量求取,输出特征值列向量,特征向量为右向量而且是列向量 A*V=V*D
-
Opencv的convertTo函数的用法
1 2 3 4 5 6 7 8 9 10
//convertTo的用法 //功能:转换图片类型,例如吧8U改为32F /*参数: dst:目的矩阵; type:需要的输出矩阵类型,或者更明确的,是输出矩阵的深度,如果是负值(常用-1)则输出矩阵和输入矩阵类型相同; scale:比例因子; shift:将输入数组元素按比例缩放后添加的值; */ src.convertTo(dst, type, scale, shift) //src是一个Mat类型变量
-
Opencv的归一化函数normalize
|
|
- OpencvKeyPoint类中的默认构造函数如下:
|
|
- Opencv 的绘制特征点核心函数drawKeypoint()
|
|
-
convertScaleAbs()用于实现对整个图像数组中的每一个元素,进行如下操作:
该操作可实现图像增强等相关操作的快速运算:
1 2 3 4 5 6
void cv::convertScaleAbs( cv::InputArray src, // 输入数组 cv::OutputArray dst, // 输出数组 double alpha = 1.0, // 乘数因子 double beta = 0.0 // 偏移量 );
值得注意的是,输出变为CV_8U。
-
Opencv细化函数
|
|
-
Opencv的Point类数据是int类型的;但是KeyPoint类数据是float类型。
-
Opencv的Rect类型
|
|
- OpenCV circle函数
|
|
-
line函数
1 2 3 4 5 6 7 8 9 10 11 12 13
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0); /*@param img Image. @param pt1 The point the arrow starts from. @param pt2 The point the arrow points to. @param color Line color. @param thickness Line thickness. @param line_type Type of the line. See #LineTypes @param shift Number of fractional bits in the point coordinates. @param tipLength The length of the arrow tip in relation to the arrow length */ //设置前五个参数即可一般
-
前景是感兴趣的物体,背景是不感兴趣的像素点
-
1 2 3 4 5
double Xu = kernel.dot(gux); //为点积之后对矩阵求和,但要注意,两个矩阵类型需要一致。 Scalar sum1 = sum(kernel_core); //对矩阵求和,sum1(0)即为和
-
取整函数
|
|
-
图片缩放函数
- resize()函数:进行图片的缩放
-
颜色空间转换cv::cvtColor()
cv::cvtColor()用于将图像从一个颜色空间转换到另一个颜色空间的转换(目前常见的颜色空间均支持),并且在转换的过程中能够保证数据的类型不变,即转换后的图像的数据类型和位深与源图像一致。
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
void cv::cvtColor( cv::InputArray src, // 输入序列 cv::OutputArray dst, // 输出序列 int code, // 颜色映射码 int dstCn = 0 // 输出的通道数 (0='automatic') ); /* 参数解释: . InputArray src: 输入图像即要进行颜色空间变换的原图像,可以是Mat类 . OutputArray dst: 输出图像即进行颜色空间变换后存储图像,也可以Mat类 . int code: 转换的代码或标识,即在此确定将什么制式的图片转换成什么制式的图片, . int dstCn = 0: 目标图像通道数,如果取值为0,则由src和code决定 关于int code这个参数,比如常见的 COLOR_BGR2GRAY等,详细见下表 1、RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换 cv::COLOR_BGR2RGB cv::COLOR_RGB2BGR cv::COLOR_RGBA2BGRA cv::COLOR_BGRA2RGBA 2、向RGB和BGR图像中增添alpha通道 cv::COLOR_RGB2RGBA cv::COLOR_BGR2BGRA 3、从RGB和BGR图像中去除alpha通道 cv::COLOR_RGBA2RGB cv::COLOR_BGRA2BGR 4、从RBG和BGR颜色空间转换到灰度空间 cv::COLOR_RGB2GRAY cv::COLOR_BGR2GRAY cv::COLOR_RGBA2GRAY cv::COLOR_BGRA2GRAY 5、从灰度空间转换到RGB和BGR颜色空间 cv::COLOR_GRAY2RGB cv::COLOR_GRAY2BGR cv::COLOR_GRAY2RGBA cv::COLOR_GRAY2BGRA 6、RGB和BGR颜色空间与BGR565颜色空间之间的转换 cv::COLOR_RGB2BGR565 cv::COLOR_BGR2BGR565 cv::COLOR_BGR5652RGB cv::COLOR_BGR5652BGR cv::COLOR_RGBA2BGR565 cv::COLOR_BGRA2BGR565 cv::COLOR_BGR5652RGBA cv::COLOR_BGR5652BGRA 7、灰度空间域BGR565之间的转换 cv::COLOR_GRAY2BGR555 cv::COLOR_BGR5552GRAY 8、RGB和BGR颜色空间与CIE XYZ之间的转换 cv::COLOR_RGB2XYZ cv::COLOR_BGR2XYZ cv::COLOR_XYZ2RGB cv::COLOR_XYZ2BGR 9、RGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换 cv::COLOR_RGB2YCrCb cv::COLOR_BGR2YCrCb cv::COLOR_YCrCb2RGB cv::COLOR_YCrCb2BGR 10、RGB和BGR颜色空间与HSV颜色空间之间的相互转换 cv::COLOR_RGB2HSV cv::COLOR_BGR2HSV cv::COLOR_HSV2RGB cv::COLOR_HSV2BGR 11、RGB和BGR颜色空间与HLS颜色空间之间的相互转换 cv::COLOR_RGB2HLS cv::COLOR_BGR2HLS cv::COLOR_HLS2RGB cv::COLOR_HLS2BGR 12、RGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换 cv::COLOR_RGB2Lab cv::COLOR_BGR2Lab cv::COLOR_Lab2RGB cv::COLOR_Lab2BGR 13、RGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换 cv::COLOR_RGB2Luv cv::COLOR_BGR2Luv cv::COLOR_Luv2RGB cv::COLOR_Luv2BGR 14、Bayer格式(raw data)向RGB或BGR颜色空间的转换 cv::COLOR_BayerBG2RGB cv::COLOR_BayerGB2RGB cv::COLOR_BayerRG2RGB cv::COLOR_BayerGR2RGB cv::COLOR_BayerBG2BGR cv::COLOR_BayerGB2BGR cv::COLOR_BayerRG2BGR cv::COLOR_BayerGR2BGR */
- 注意,如果采用2.x的CV_RGB2GRAY这种格式,需要添加头文件opencv2/imgproc/types_c.h
-
Opencv的矩阵求逆函数
1 2 3 4 5 6 7 8 9 10 11 12 13
double invert(InputArray src, OutputArraydst, int flags=DECOMP_LU); /* 功能:用以求取一个矩阵的逆或者伪逆。 src: 输入,浮点型(32位或者64位)的M×N的矩阵,当参数3的使用方法为DECOMP_CHOLESKY DECOMP_LU DECOMP_EIG时函数功能为求逆,此时需保证M=N(参见参数flag)。 dst: 输出,与输入矩阵类型一致的N×M的矩阵。 flag:求逆方法,提供4种可选择的方法:DECOMP_CHOLESKY(基于CHOLESKY分解的方法), DECOMP_LU(基于LU分解的方法), DECOMP_EIG(基于特征值分解的方法), DECOMP_SVD(基于奇异值分解的方法)。其中,前三种方法要求输入的矩阵必须为方阵,此时计算结果为矩阵的逆;最后一种方法为对非方阵的伪逆计算,对矩阵的形状没有要求。函数接口的默认参数为DECOMP_LU方法(应该是效率较高的一种方法)。 */ //注意OPencv矩阵相乘可以直接用*,因为重载了运算符
-
直线拟合函数cv::fitLine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
void cv::fitLine( cv::InputArray points, // 二维点的数组或vector cv::OutputArray line, // 输出直线,Vec4f (2d)或Vec6f (3d)的vector int distType, // 距离类型 double param, // 距离参数 double reps, // 径向的精度参数 double aeps // 角度精度参数 ); /* 第一个参数是用于拟合直线的输入点集,可以是二维点的cv::Mat数组,也可以是二维点的STL vector。 第二个参数是输出的直线,对于二维直线而言类型为cv::Vec4f,对于三维直线类型则是cv::Vec6f,输出参数的前半部分给出的是直线的方向,而后半部分给出的是直线上的一点(即通常所说的点斜式直线)。 第三个参数是距离类型,拟合直线时,要使输入点到拟合直线的距离和最小化 第四个参数是距离参数,跟所选的距离类型有关,值可以设置为0,cv::fitLine()函数本身会自动选择最优化的值 第五、六两个参数用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为1e-2。 */
-
腐蚀、膨胀、闭操作、开操作
1 2 3 4 5 6 7 8 9 10 11 12 13
//腐蚀+闭运算 Mat struct1; struct1=getStructuringElement(0, Size(3, 3));//矩形结构元素 Mat erodeImg; erode(dst_otsu, erodeImg, struct1,Point(-1,-1),1);//腐蚀 Mat close_Img; morphologyEx(dst_otsu, close_Img, 3, struct1);//闭操作 //膨胀 Mat dilate_Img; Mat struct2; struct2 = getStructuringElement(1, Size(3, 3));//十字结构元素 dilate(erodeImg, dilate_Img, struct2, Point(-1, -1), 1);
-
图像细化thinning
1 2 3
#include <opencv2/ximgproc.hpp> //细化函数所需要的头文件 Mat thin; ximgproc::thinning(erodeImg, thin, 0);//使用Zhang细化方法
Opencv相机标定
概述
- 我们用xyz三个方向上的三个角度来表示三维旋转,同时可以用三个参数(x1,y1,z1)来表示三维平移,因此我们目前有六个外参数。
- 相机内参矩阵有四个参数(fx,fy,cx,cy)。(分别是像素焦距和仿射变换的平移量)
- 所以每个视图都需要求解10个参数(注意,相机内在参数在不同视图保持不变)。
- 在OpenCV中,我们有四个相机内在参数(fx,fy,cx,cy)和 五个(或更多个)畸变参数——由三个(或更多个)径向参数(k1,k2,k3[,k4,k5,k6])和两个切向参数(p1,p2)组成。
- OpenCV用于求解焦距和偏移的算法是基于Zhang的方法,但使用另一个基于Brown的方法来求解畸变参数。
常用函数
- findChessboardCorners:寻找棋盘格角点
|
|
- cornerSubPix() :棋盘角点的亚像素检测
|
|
- cv::drawChessboardCorners() :棋盘格角点的绘制
|
|
- double calibrateCamera():标定内参外参畸变系数函数
|
|
- cv::solvePnP():已知相机内参矩阵、畸变系数矩阵,计算外参数
|
|
- cv::solvePnPRansac()::已知相机内参矩阵、畸变系数矩阵,计算外参数
上述求外参函数鲁棒性不太好,所以提出一个新的函数。
|
|
- cv::undistort():矫正函数
我们既可以只用函数cv::undistort(),使用该算法一次性完成所需的任务,也可以用一对函数cv::initundistortRectifyMap()和cv::remap()函数来更有效地处理此事,这通常适用于初频或者同一相机中获取多个图像的应用中。
|
|
VS代码移植到Qt上
-
一般是VS验算算法,QT搭建框架,在windows上统一采用MSVC2017编译器。
-
报错时,一般是什么常量未定义等等。
-
解决办法:新建个记事本文件
.txt
,将各个源文件的代码复制到.txt
文件中,另存为编码格式为带有BOM的UTF-8文件
,再在Qt上添加现有文件编译即可。
Qt和c++
- QString和string
|
|
- qt创建新文件
|
|
|
|
Qt前端美化
-
添加程序图标
- 将ico图片文件复制到工程文件夹目录中(可在线转换图片为ico文件)
- 在项目文件的最后面添加一行代码:RC_ICONS = XXX.ico
-
添加资源文件的初始化
|
|
Qt串口通信
-
添加必要的头文件
1 2 3 4 5
#include <QSerialPort> //要在pro文件添加QT += serialport才有 #include <QSerialPortInfo> //.pro文件添加 QT += serialport
-
获取可以串口信息并打印
1 2 3 4
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts()) { qDebug()<<"serialPortName:"<<info.portName(); }
-
串口初始化
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
//设置成员变量.h QSerialPort m_serialPort;//串口通信 //**************************************.cpp QList<QSerialPortInfo> my_serial_name; QSerialPortInfo info_qstring; // foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) // { // qDebug()<<"serialPortName:"<<info.portName(); // info_qstring=info; // } my_serial_name=QSerialPortInfo::availablePorts(); info_qstring=my_serial_name[0]; m_serialPort.setPortName(info_qstring.portName()); if(m_serialPort.isOpen())//如果串口已经打开了 先给他关闭了 { m_serialPort.clear(); m_serialPort.close(); } if(!m_serialPort.open(QIODevice::ReadWrite)) { QMessageBox::warning(this, tr("open serial"), tr("open serial fail")); return; } //打开成功 m_serialPort.setBaudRate(QSerialPort::Baud9600,QSerialPort::AllDirections);//波特率9600和读取方向 m_serialPort.setDataBits(QSerialPort::Data8);//数据位8位 m_serialPort.setFlowControl(QSerialPort::NoFlowControl);//无流方向 m_serialPort.setParity(QSerialPort::NoParity);//没有校验位 m_serialPort.setStopBits(QSerialPort::OneStop);//1位停止位 QMessageBox::warning(this, tr("open serial"), tr("open serial successfully"));
-
串口读写
可以采用QByteArray,QString转化为char *,即可使用write函数
1 2 3
toLatin1、toLocal8Bit都是QString转QByteArray的方法,Latin1代表ASCII,Local8Bit代表unicode。 //返回值为const char*——指向字符常量的指针
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
//方式1 QByteArray test; test[0]=0xBB; //按照字节来使用char数组,截断常量值警告没问题 char* str = test.data(); m_serialPort.write(&str); //***************************************** //方式2,直接使用char,取地址 char str1=0xBB; m_serialPort.write(&str1); char str=0xDD; QThread::msleep(50); m_serialPort.write(&str); //读串口 QByteArray temp = m_serialPort.readAll(); int tem1=temp.toInt(); //结果是“25” qDebug()<<tem1<<endl; qDebug()<<temp; //方式3:qstring转为qbytearray QString temp=ui->lineEdit_send->text(); QByteArray temp2 = temp.toLatin1(); char* str = temp2.data(); m_serialPort_send.write(str);
Qt添加海康威视相机驱动链接库.lib文件
- 直接在项目右键--添加库
- 添加.lib位置
- 添加include文件夹路径
- 不要勾选自动为debug添加d前缀。
Qt多线程
常见四种方式
-
个人见解
- movtothread可以通过传参进行参数的传递,对于不太依赖类成员函数和变量的子线程,很推荐这种方式
- Qt QtConcurrent 之 Run 函数。不会马上执行,会根据调度自动优化,但是一定会执行,同时可以直接是成员函数。
- 我一直想,movtothread之前也是成员类,应该也是可以直接调用成员变量的。以后实验一下。
-
- 创建子类继承QThreat,重写run函数,但这个只有run函数是在子线程里面跑,子类的其他成员函数不是,就更不要说发出信号,主线程接收信号了。
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
#include <QObject> #include <QThread> class MyCameraThread: public QThread { Q_OBJECT public: MyCameraThread(); void run() override; void stop(); private: bool m_start; }; #endif // MYCAMERATHREAD_H //------------------------------------------------------------------------ #include "mycamerathread.h" #include <QDebug> MyCameraThread::MyCameraThread() { m_start=true; } void MyCameraThread::run(){ m_start=true; while(m_start){ qDebug()<<"hello world"<<endl; sleep(1); } } void MyCameraThread::stop(){ m_start=false; }
-
- 继承 QObject 调用 moveToThread
这是qt官方推荐的方式,但是也是使用槽函数和信号函数,不同的是整个类的成员函数都在子线程里面。
可以通过信号和槽发送指针进行调用类成员函数,不过没尝试过,思路应该没啥问题。
1 2 3 4 5 6 7 8 9 10
class Test : public QObject { Q_OBJECT public: void test(); } QThread th; Test test; test.moveToThread(&th);
-
- 继承 QRunnable 重新 run 函数,结合 QThreadPool 实现线程池。
-
- Qt QtConcurrent 之 Run 函数(大力推荐) Concurrent 是并发的意思,QtConcurrent 是一个命名空间,提供了一些高级的 API,使得所写的程序可根据计算机的 CPU 核数,自动调整运行的线程数目。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展
函数原型如下: QFuture
QtConcurrent::run(Function function, ...) QFuture QtConcurrent::run(QThreadPool *pool, Function function, ...) 简单来说,QtConcurrent::run() 函数会在一个单独的线程中执行,并且该线程取自全局 QThreadPool,该函数的返回值通过 QFuture API 提供
需要注意的是: 1)该函数可能不会立即运行; 函数只有在线程可用时才会运行 2)通过 QtConcurrent::run() 返回的 QFuture 不支持取消、暂停,返回的 QFuture 只能用于查询函数的运行/完成状态和返回值 3) Qt Concurrent 已经从 QtCore 中移除并成为了一个独立的模块,所以想要使用 QtConcurrent 需要在 pro 文件中导入模块: QT += concurrent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
//子线程函数1:采集图像 void Widget::myFunc(const QString & str) { while (m_test) { qDebug() << __FUNCTION__ << str << QThread::currentThreadId() << QThread::currentThread(); } } //开始采集按钮 void Widget::on_start_capture_clicked() { m_test=true; QFuture<void> f2 =QtConcurrent::run(this,&Widget::myFunc,QString("okk")); qDebug() << __FUNCTION__<< QThread::currentThreadId();//输出函数名+线程ID } //关闭采集按钮 void Widget::on_stop_capture_clicked() { m_test=false; }
这种方式可以直接调用主线程的成员函数,还不用传类成员指针!!!MFC和win32的线程都要传递一个dialog类指针,然后函数引用指针指向类的成员函数进行并行的。c++的thread要在主线程加入join或者detach才能开始,而且我也没找到如何在一个类里面直接使用thread调用该类的成员变量的方式,都是在类外面,创建一个类,然后再thread创建线程对象引用类的成员函数。
c++的类thread可以直接使用多继承,就可以在类的内部创建线程了,不过话说回来,这样一来和Qt的创建一个类继承 QObject 调用 moveToThread没什么区别了。
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
#include <thread> void threadfun1() { std::cout << "threadfun1 - 1\r\n" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "threadfun1 - 2" << std::endl; } void threadfun2(int iParam, std::string sParam) { std::cout << "threadfun2 - 1" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "threadfun2 - 2" << std::endl; } int main() { std::thread t1(threadfun1); std::thread t2(threadfun2, 10, "abc"); t1.join(); // 等待线程 t1 执行完毕 std::cout << "join" << std::endl; t2.detach(); // 将线程 t2 与主线程分离 std::cout << "detach" << std::endl; } 运行结果: threadfun1 - 1 threadfun2 - 1 threadfun1 - 2 join detach
继承QObject多线程
-
注意点
- 1.多线程初始化要在类的构造函数里面,不能在其他的成员函数里面,不然会报错
- 2.继承QObject多线程的方法线程的创建很简单,只要让QThread的start函数运行起来就行,但是需要注意销毁线程的方法
在线程创建之后,这个QObject的销毁不应该在主线程里进行,而是通过deleteLater槽进行安全的销毁,因此,继承QObject多线程的方法在创建时有几个槽函数需要特别关注:
- 一个是QThread的finished信号对接QObject的deleteLater使得线程结束后,继承QObject的那个多线程类会自己销毁
- 另外一个是QThread的finished信号对接QThread自己的deleteLater。(QThread的quit和wait结束后会释放finished信号)
- 3.主线程类析构函数要包含QThread的quit和wait函数以释放资源。
- 4.信号和槽函数的参数传递,传递this指针可以调用主线程的成员函数,同时注意在自定义的线程里面,一定是在cpp文件加主线程类的声明,不能在.h文件里面加,不然交叉编译会报错
- 5.信号和槽才能区分线程之间的调用,若直接在主线程的对象指针里面调用对象的成员函数,那个这个函数是在主线程里面的。即直接指针调用的线程是在主线程里面的。
-
例子
-
mythread.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
#ifndef MYTHREAD_H #define MYTHREAD_H #include <QObject> #include <QThread> //#include <QMutex> class MyThread : public QObject { Q_OBJECT public: explicit MyThread(QObject *parent = nullptr); public slots: void dosomething(void *pr); void stop(); signals: void finish_thread(); private: bool flag_stop=false; }; #endif // MYTHREAD_H
-
mythread.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 27 28 29 30
#include "mythread.h" #include <QtDebug> //切记,一定是在cpp文件加主线程类的声明,不能在.h文件里面加,不然交叉编译会报错 #include "windowdelta.h" MyThread::MyThread(QObject *parent) : QObject(parent) { } // 槽函数,线程函数 void MyThread::dosomething(void *pr) { //最好加一个互斥量保护flag flag_stop=true; windowDelta *pt=(windowDelta *)pr; while(flag_stop){ qDebug()<<"sub thread start :"<<QThread::currentThread(); pt->show_curr(); QThread::msleep(1000); } } //线程停止函数 void MyThread::stop() { flag_stop=false; qDebug()<<"sub thread stop:"<<QThread::currentThread(); }
-
windowdelta.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
#ifndef WINDOWDELTA_H #define WINDOWDELTA_H #include <QWidget> #include <QSerialPort> //要在pro文件添加QT += serialport才有 #include <QSerialPortInfo> #include <QMessageBox> #include <QDebug> #include "opencv2/opencv.hpp" #include "mythread.h" QT_BEGIN_NAMESPACE namespace Ui { class windowDelta; } QT_END_NAMESPACE class windowDelta : public QWidget { Q_OBJECT public: windowDelta(QWidget *parent = nullptr); ~windowDelta(); void on_start_clicked(); void on_stop_clicked(); //继承QObject多线程 signals: void start_message(void *pr); private: Ui::windowDelta *ui; MyThread *thread1; QThread *src_thread; public: void show_curr(); }; #endif // WINDOWDELTA_H
-
windowdelta.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 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 74 75 76 77 78 79 80
#include "windowdelta.h" #include "ui_windowdelta.h" //#include <QtCore/QTextCodec> #include <QThread> windowDelta::windowDelta(QWidget *parent) : QWidget(parent) , ui(new Ui::windowDelta) { ui->setupUi(this); //创建子线程 src_thread =new QThread(this); thread1=new MyThread(); thread1->moveToThread(src_thread); connect(src_thread,&QThread::finished,src_thread,&QObject::deleteLater); connect(src_thread,&QThread::finished,thread1,&QObject::deleteLater); //连接槽信号 connect(this,&windowDelta::start_message,thread1,&MyThread::dosomething); //启动线程 src_thread->start(); qDebug()<<"主线程号"<<QThread::currentThread()<<endl; } windowDelta::~windowDelta() { //删除线程资源 if(src_thread){ src_thread->quit(); src_thread->wait(); qDebug() << "end delete src_thread!"; } delete ui; qDebug() << "end delete ui!"; } void windowDelta::on_start_clicked() { emit start_message(this); qDebug()<<"start sub thread!"<<endl; } ////创建并启动线程,不能放在一个函数里面new //void windowDelta::start_thread() //{ // if(src_thread){ // return; // } // src_thread =new QThread(); // thread1=new MyThread(); // thread1->moveToThread(src_thread); // connect(src_thread,&QThread::finished,src_thread,&QObject::deleteLater); // connect(src_thread,&QThread::finished,thread1,&QObject::deleteLater); // //连接槽信号 // connect(this,&windowDelta::start_message,thread1,&MyThread::dosomething); // //启动线程 // src_thread->start(); // qDebug()<<"create sub thread!"<<endl; //} //关闭线程 void windowDelta::on_stop_clicked() { if(src_thread){ if(thread1){ //直接指针调用的线程是在主线程里面的 thread1->stop(); qDebug()<<"main thread number:"<<QThread::currentThread(); } } } void windowDelta::show_curr() { qDebug()<<"yes"<<QThread::currentThread()<<endl; }
-
结果展示
常见Qt操作
-
double转为QString
1 2 3
number()函数: double value = 1.2345 QString str = QString::number(value, 'f', 2);
常见C++函数
|
|