Qt & Opnecv 开发笔记

小知识点

  • Qt中的 $$PWD​:表示返回的路径是当前文件所在的路径;通常用于pri和pro文件里面,一般的配套使用为
1
$$PWD/XXX
  • 下一级目录采用 /,切记!!!
  • 工程的当前文件夹是生成的Dgbug文件里面,并不是源代码里面的文件夹。如要访问源代码文件夹,要加“../xxx/xxx”
  • 如下图所示,上一个文件夹是源代码文件夹,下一个是生成的文件夹。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "test_opencv.h"
#include <QApplication>
#include"opencv2/opencv.hpp"
using namespace cv;
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    test_opencv w;
    w.show();
    Mat image=imread("../test_opencv/1.jpg");
    imshow("output",image);
    return a.exec();
}

Qt快捷键

  1. F1 查看帮助
  2. F2 跳转到函数定义(和Ctrl+鼠标左键一样的效果)
  3. Shift+F2 声明和定义之间切换
  4. F4 头文件和源文件之间切换
  5. Ctrl+I 自动对齐
  6. Ctrl+/ 注释行,取消注释行
  7. Ctrl(按住) + Tab 快速切换已打开的文件
  8. Alt(按住)+ Enter,在cpp中添加该头文件函数的声明。
  9. Ctrl + Shift + R,修改类成员变量的名字,覆盖所有

Qt关于模块化设计的方法

  • 方法:在源文件目录下设置新的文件夹,内含.h和.cpp和.pri文件;同时修改工程的.pro文件。

  • 步骤:

    • 创建新文件夹,同时编辑.pri文件如下图所示

    • 在.pro文件编辑添加

      1
      2
      
      INCLUDEPATH +=$$PWD/onepri  #工程编译时,会去该目录下搜索文件
      include($$PWD/onepri/onepri.pri) #include包含的文件会显示在工程中
      
    • 添加的位置如下图,在SOURCES+=前面

  • 参考 https://blog.csdn.net/ssaiwey/article/details/104192342

关于在 windows 配置opencv

  • 为什么需要cmake编译,那是因为需要contribute的非免费算法,同时为了适配更多的平台。

  • 下载 cmake

  • Download | CMake

  • 注意是msi后缀,不自动配置环境变量,后期自己把bin文件夹放进系统变量path里面。

  • 网上有很多示例。

  • 下载opencv+contribute源码

  • Releases - OpenCV

  • 下载source源码+win版本解压。

  • source源码是为了编译;win版本是编译后直接替换win版本部分的生成dll。

  • Tags · opencv/opencv_contrib (github.com)

  • 上面是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文件
1
2
3
4
5
6
7
INCLUDEPATH += d:/opencv_4.5.0/opencv/build/include
Debug:{
LIBS += -ld:/opencv_4.5.0/opencv/build/x64/vc15/lib/opencv_world450d
}
Release:{
LIBS += -ld:/opencv_4.5.0/opencv/build/x64/vc15/lib/opencv_world450
}
  • 不同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设置
  • 或者在代码的窗口构造函数函数使用
1
2
3
4
5
6
7
8
test_opencv::test_opencv(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::test_opencv)
{
    ui->setupUi(this);
    //以下是窗口初始化代码
    ui->gaussian->setChecked(true);
}

读取文件和文件夹对话框

  • 读取文件
1
2
3
4
5
6
7
//初始目录为当前输出debug文件夹
QString file_input=QFileDialog::getOpenFileName(this,"open input image",QDir::currentPath(),
                                                "Image(*.jpg *.jpg *.jpg)");
if(QFile::exists(file_input)){
    ui->input_lineEdit->setText(file_input);
}
src_image=cv::imread(file_input.toStdString());
  • 读取文件夹
1
2
3
4
5
6
7
8
//读取文件夹
QString dir = QFileDialog::getExistingDirectory(this, "Open Directory",
                                                QDir::currentPath(),
                                                QFileDialog::ShowDirsOnly
                                                | QFileDialog::DontResolveSymlinks);
QString add="/output.jpg";
dir=dir+add;
ui->saving_lineEdit->setText(dir);
  • 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需要进行转换,代码如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//前提是读取的图片为RGB三通道
Mat mat=imread("1.jpg");
cvtColor(mat,mat,CV_BGR2RGB);
QImage image=(mat.data,
             mat.cols,
             mat.rows,
             QImage::Format_RGB888);
//以下方式也可以
QImage image=(mat.data,
             mat.cols,
             mat.rows,
             mat.step,
             QImage::Format_RGB888);
  • 最简单的显示图像的方式是采用label控件,但其在处理显示的图像(比如缩放等等)方面根本没有灵活性。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Mat mat=imread("1.jpg");
cvtColor(mat,mat,CV_BGR2RGB);
QImage image=(mat.data,
             mat.cols,
             mat.rows,
             QImage::Format_RGB888);
//以下方式也可以
QImage image=(mat.data,
             mat.cols,
             mat.rows,
             mat.step,
             QImage::Format_RGB888);
//利用QPixmap以及Qlabel控件显示
ui->label1->setPixmap(QPixmap::fromImage(image))

QImage转为Mat

1
2
3
4
5
6
7
8
9
//前提均是RGB三通道
QImage image("1.jpg");
//以下是保证三通道
image=image.convertToFormat(QImage::Format_RGB888);
Mat mat=Mat(image.height(),
           image.width(),
           CV_8UC3,
           iamge.bits(),
           image.bytesPerLine());

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#ifndef __OPENCV_CONTRIB_HPP__
#define __OPENCV_CONTRIB_HPP__
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/objdetect/objdetect.hpp"
class CV_EXPORTS Directory
{
public:
	static std::vector<std::string> GetListFiles  ( const std::string& path, const std::string & exten = "*", bool addPath = true );
	static std::vector<std::string> GetListFilesR ( const std::string& path, const std::string & exten = "*", bool addPath = true );
	static std::vector<std::string> GetListFolders( const std::string& path, const std::string & exten = "*", bool addPath = true );
};
#endif
  • contrib.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
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include "contrib.hpp"
//#include <cvconfig.h>
#if defined(WIN32) || defined(_WIN32)
#include <windows.h>
#include <tchar.h>
#else
#include <dirent.h>
#endif
std::vector<std::string> Directory::GetListFiles( const std::string& path, const std::string & exten, bool addPath )
{
	std::vector<std::string> list;
	list.clear();
	std::string path_f = path + "/" + exten;
#ifdef WIN32
#ifdef HAVE_WINRT
	WIN32_FIND_DATAW FindFileData;
#else
	WIN32_FIND_DATAA FindFileData;
#endif
	HANDLE hFind;
#ifdef HAVE_WINRT
	wchar_t wpath[MAX_PATH];
	size_t copied = mbstowcs(wpath, path_f.c_str(), MAX_PATH);
	CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
	hFind = FindFirstFileExW(wpath, FindExInfoStandard, &FindFileData, FindExSearchNameMatch, NULL, 0);
#else
	hFind = FindFirstFileA((LPCSTR)path_f.c_str(), &FindFileData);
#endif
	if (hFind == INVALID_HANDLE_VALUE)
	{
		return list;
	}
	else
	{
		do
		{
			if (FindFileData.dwFileAttributes == FILE_ATTRIBUTE_NORMAL  ||
				FindFileData.dwFileAttributes == FILE_ATTRIBUTE_ARCHIVE ||
				FindFileData.dwFileAttributes == FILE_ATTRIBUTE_HIDDEN  ||
				FindFileData.dwFileAttributes == FILE_ATTRIBUTE_SYSTEM  ||
				FindFileData.dwFileAttributes == FILE_ATTRIBUTE_READONLY)
			{
				char* fname;
#ifdef HAVE_WINRT
				char fname_tmp[MAX_PATH] = {0};
				size_t copied = wcstombs(fname_tmp, FindFileData.cFileName, MAX_PATH);
				CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
				fname = fname_tmp;
#else
				fname = FindFileData.cFileName;
#endif
				if (addPath)
					list.push_back(path + "/" + std::string(fname));
				else
					list.push_back(std::string(fname));
			}
		}
#ifdef HAVE_WINRT
		while(FindNextFileW(hFind, &FindFileData));
#else
		while(FindNextFileA(hFind, &FindFileData));
#endif
		FindClose(hFind);
	}
#else
	(void)addPath;
	DIR *dp;
	struct dirent *dirp;
	if((dp = opendir(path.c_str())) == NULL)
	{
		return list;
	}
	while ((dirp = readdir(dp)) != NULL)
	{
		if (dirp->d_type == DT_REG)
		{
			if (exten.compare("*") == 0)
				list.push_back(static_cast<std::string>(dirp->d_name));
			else
				if (std::string(dirp->d_name).find(exten) != std::string::npos)
					list.push_back(static_cast<std::string>(dirp->d_name));
		}
	}
	closedir(dp);
#endif
	return list;
}
std::vector<std::string> Directory::GetListFolders( const std::string& path, const std::string & exten, bool addPath )
{
	std::vector<std::string> list;
	std::string path_f = path + "/" + exten;
	list.clear();
#ifdef WIN32
#ifdef HAVE_WINRT
	WIN32_FIND_DATAW FindFileData;
#else
	WIN32_FIND_DATAA FindFileData;
#endif
	HANDLE hFind;
#ifdef HAVE_WINRT
	wchar_t wpath [MAX_PATH];
	size_t copied = mbstowcs(wpath, path_f.c_str(), path_f.size());
	CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
	hFind = FindFirstFileExW(wpath, FindExInfoStandard, &FindFileData, FindExSearchNameMatch, NULL, 0);
#else
	hFind = FindFirstFileA((LPCSTR)path_f.c_str(), &FindFileData);
#endif
	if (hFind == INVALID_HANDLE_VALUE)
	{
		return list;
	}
	else
	{
		do
		{
#ifdef HAVE_WINRT
			if (FindFileData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY &&
				wcscmp(FindFileData.cFileName, L".") != 0 &&
				wcscmp(FindFileData.cFileName, L"..") != 0)
#else
			if (FindFileData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY &&
				strcmp(FindFileData.cFileName, ".") != 0 &&
				strcmp(FindFileData.cFileName, "..") != 0)
#endif
			{
				char* fname;
#ifdef HAVE_WINRT
				char fname_tmp[MAX_PATH];
				size_t copied = wcstombs(fname_tmp, FindFileData.cFileName, MAX_PATH);
				CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
				fname = fname_tmp;
#else
				fname = FindFileData.cFileName;
#endif
				if (addPath)
					list.push_back(path + "/" + std::string(fname));
				else
					list.push_back(std::string(fname));
			}
		}
#ifdef HAVE_WINRT
		while(FindNextFileW(hFind, &FindFileData));
#else
		while(FindNextFileA(hFind, &FindFileData));
#endif
		FindClose(hFind);
	}
#else
	(void)addPath;
	DIR *dp;
	struct dirent *dirp;
	if((dp = opendir(path_f.c_str())) == NULL)
	{
		return list;
	}
	while ((dirp = readdir(dp)) != NULL)
	{
		if (dirp->d_type == DT_DIR &&
			strcmp(dirp->d_name, ".") != 0 &&
			strcmp(dirp->d_name, "..") != 0 )
		{
			if (exten.compare("*") == 0)
				list.push_back(static_cast<std::string>(dirp->d_name));
			else
				if (std::string(dirp->d_name).find(exten) != std::string::npos)
					list.push_back(static_cast<std::string>(dirp->d_name));
		}
	}
	closedir(dp);
#endif
	return list;
}
std::vector<std::string> Directory::GetListFilesR ( const std::string& path, const std::string & exten, bool addPath )
{
	std::vector<std::string> list = Directory::GetListFiles(path, exten, addPath);
	std::vector<std::string> dirs = Directory::GetListFolders(path, exten, addPath);
	std::vector<std::string>::const_iterator it;
	for (it = dirs.begin(); it != dirs.end(); ++it)
	{
		std::vector<std::string> cl = Directory::GetListFiles(*it, exten, addPath);
		list.insert(list.end(), cl.begin(), cl.end());
	}
	return list;
}

C++读取文件夹中指定类型的所有文件

 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
#include <iostream> //输入输出流
#include <io.h>  //_finddata_t结构体的依赖
#include <string>  
#include <vector>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

/*
@param File_Directory 为文件夹目录
@param FileType 为需要查找的文件类型
@param FilesName 为存放文件名的容器
*/
void getFilesName(string &File_Directory, string &FileType, vector<string>&FilesName)
{
	string buffer = File_Directory + "\\*" + FileType;

	_finddata_t c_file;   // 存放文件名的结构体

	intptr_t hFile;
	hFile = _findfirst(buffer.c_str(), &c_file);   //找第一个文件名字

	if (hFile == -1L)   // 检查文件夹目录下存在需要查找的文件
		printf("No %s files in current directory!\n", FileType.c_str());
	else
	{
		string fullFilePath;
		do
		{
			fullFilePath.clear();

			//名字
			fullFilePath = File_Directory + "\\" + c_file.name;

			FilesName.push_back(fullFilePath);

		} while (_findnext(hFile, &c_file) == 0);  //如果找到下个文件的名字成功的话就返回0,否则返回-1  
		_findclose(hFile);
	}
}

int main() {
	string File_Directory1 = "E:\\pro_test\\C++\\calibration_v1\\main_cali\\main_cali\\chess_image";   //文件夹目录

	string FileType = ".jpg";    // 需要查找的文件类型

	vector<string>FilesName1;    //存放文件名的容器

	getFilesName(File_Directory1, FileType, FilesName1);   // 标定所用图像文件的路径

	size_t i = FilesName1.size();
	for (int j = 0; j < i; j++) {
		cout << FilesName1[j] << endl;
	}
	Mat src=imread(FilesName1[0]);

	//system("pause");
	return 0;
}

C++读取txt文件

  • 利用ifstream
  • 利用eof函数判断是否在文件末尾,有个缺点,最后的数据会输出两次
 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
	ifstream readFile("E:\\pro_test\\QT\\build-calibration_v1-Desktop_Qt_5_14_2_MSVC2017_64bit-Debug\\debug\\2021_10_10_20_57_54\\caliberation_result.txt");
	string temp;
	int matrix = 0;
	int vec_dist = 0;
	Mat mat_inner = Mat::zeros(3, 3, CV_64FC1);//创建全零
	Mat dist = Mat::zeros(5, 1, CV_64FC1);
   //最后会输出两次
	while (!readFile.eof())
	{
		readFile >> temp; //遇到空格输出停止,空格后的内容无法输出,'\0'是截止符,如图3所示
		if (matrix != 0) {
			int row = (matrix-1) / 3;
			int col = (matrix-1) % 3;
			if (matrix == 1) {
				temp.erase(temp.begin());
				temp.erase(temp.end()-1,temp.end());
				cout << temp << endl;
				stringstream tostd_str(temp);
				double temp1=0;
				tostd_str >>temp1;
				mat_inner.at<double>(row, col) = temp1;
				//cout << fixed << setprecision(10)<< temp1 << endl;
				matrix++;
			}
			else if (matrix == 9) {
				temp.erase(temp.end() - 1, temp.end());
				cout << temp << endl;
				stringstream tostd_str(temp);
				double temp1=0;
				tostd_str >> temp1;
				//cout << temp1 << endl;
				mat_inner.at<double>(row, col) = temp1;
				matrix=0;
			}
			else {
				temp.erase(temp.end() - 1, temp.end());
				cout << temp << endl;
				stringstream tostd_str(temp);
				double temp1=0;
				tostd_str >> temp1;
				//cout << temp1 << endl;
				mat_inner.at<double>(row, col) = temp1;
				matrix++;
			}
		}		
		if (temp == "cameraMatrix") {
			cout << "matrix" << endl;
			matrix ++;
		}
		if (vec_dist != 0) {
			if (vec_dist == 1) {
				temp.erase(temp.begin());
				temp.erase(temp.end() - 1, temp.end());
				cout << temp << endl;
				stringstream tostd_str(temp);
				double temp1 = 0;
				tostd_str >> temp1;
				//cout << temp1 << endl;
				dist.at<double>(vec_dist - 1, 0) = temp1;
				vec_dist++;
			}
			else if (vec_dist == 5) {
				temp.erase(temp.end() - 1, temp.end());
				cout << temp << endl;
				stringstream tostd_str(temp);
				double temp1 = 0;
				tostd_str >> temp1;
				//cout << temp1 << endl;
				dist.at<double>(vec_dist - 1, 0) = temp1;
				vec_dist = 0;
			}
			else {
				temp.erase(temp.end() - 1, temp.end());
				cout << temp << endl;
				stringstream tostd_str(temp);
				double temp1 = 0;
				tostd_str >> temp1;
				//cout << temp1 << endl;
				dist.at<double>(vec_dist - 1, 0) = temp1;
				vec_dist++;
			}
		}
		if (temp == "distCoeffs") {
			cout << "distCoeffs" << endl;
			vec_dist++;
		}
		
	}
	readFile.close();

C++写入txt文件

  • 利用ofstream
1
2
3
4
5
6
7
8
9
 // 写文件,每次写文件前无则创建,有则删除再创建
string save_dir = File_Directory + "\\caliberation_result.txt";
ofstream fout(save_dir,ios::out|ios::trunc);                       // 保存标定结果的文件
Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
fout << "cameraMatrix" << endl;
fout << cameraMatrix << endl << endl;
fout << "distCoeffs"<<endl;
fout << distCoeffs << endl << endl;
fout << "error" << endl;

Qt对话框显示string以及Opencv的Mat数据类型

  • 其实类似ostream,stringstream一样可以直接把Mat类型送入数据流
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//显示标定信息
Mat mat_innner;
stringstream s_temp;
s_temp << mat_inner << endl;
string output_cali;
output_cali="cameraMatrix\t\n"+s_temp.str()+"\t\ndistCoeffs\t\n";
s_temp.str("");
s_temp<<dist<<endl;
output_cali=output_cali+s_temp.str();
QMessageBox::warning(this, tr("calibration information"), QString::fromStdString(output_cali));

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

  • 然而一下构造方式是先行后列

1
2
3
4
  //一阶偏导数
  Mat m1, m2;
  m1 = (Mat_<float>(1, 2) << 1, -1);  //x偏导,一行两列,所以x方向
  m2 = (Mat_<float>(2, 1) << 1, -1);  //y偏导,一列两行,所以y方向
  • 读取像素也是先行后列
1
2
//与常规数组不同,opencv里面读取基本采用(a,b),而不是[a,b]
int value=mat.at<uchar>(0,0);//0行0列

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
C++: void normalize(InputArray src, tOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )
/*
参数:
norm_type有NORM_INF, NORM_MINMAX,NORM_L1和NORM_L2四种。 
1、在 NORM_MINMAX 模式下,alpha表示归一化后的最小值,beta表示归一化后的最大值。 
2、在NORM_L1、NORM_L2、NORM_INF 模式下,alpha表示执行相应归一化后矩阵的范数值,beta不使用。 
3、稀疏矩阵归一化仅支持非零像素。
*/
    
    normalize(Harris, harrisn, 0, 255, NORM_MINMAX);
  • OpencvKeyPoint类中的默认构造函数如下:
1
2
3
4
5
6
7
CV_WRAP KeyPoint() : pt(0,0), size(0), angle(-1), response(0), octave(0), class_id(-1) {}
/*
pt(x,y):关键点的点坐标;point2f类型;
size():该关键点邻域直径大小;
angle:角度,表示关键点的方向,值为[零,三百六十),负值表示不使用。
response:响应强度。
*/
  • Opencv 的绘制特征点核心函数drawKeypoint()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void drawKeypoints(const Mat& image, const vector& keypoints, Mat& outImg, const Scalar& color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT )
/*
image:输入图像;
keypoint:获取的特征点;
outimage:输出图像;
color:颜色,默认为随机颜色;
flsgs:绘制点的模式,有以下四种模式
cv2.DRAW_MATCHES_FLAGS_DEFAULT:默认值,只绘制特征点 的坐标点,显示在图像上就是一个个小圆点,每个小圆点的圆心坐标都是特征点的坐标;
cv2.DRAW_RICH_KEYPOINTS:绘制带方向的圆,同时显示坐标,size和方向;
cv2.DRAW_OVER_OUTIMAGE:只绘制特征点的坐标点;
cv2.NOT_DRAW_SINGLE_POINT:单点特征点不被绘制。
*/
  • 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细化函数

1
2
3
4
#include <opencv2/ximgproc.hpp> //细化函数所需要的头文件
//图像细化,但是耗时过长
Mat thin;
ximgproc::thinning(erodeImg, thin, 0);//使用Zhang细化方法
  • Opencv的Point类数据是int类型的;但是KeyPoint类数据是float类型。

  • Opencv的Rect类型

 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
//如果创建一个Rect对象rect(100, 50, 50, 100),那么rect会有以下几个功能:
rect.area();     //返回rect的面积 5000
rect.size();     //返回rect的尺寸 [50 × 100]
rect.tl();       //返回rect的左上顶点的坐标 [100, 50]
rect.br();       //返回rect的右下顶点的坐标 [150, 150]
rect.width();    //返回rect的宽度 50
rect.height();   //返回rect的高度 100
rect.contains(Point(x, y));  //返回布尔变量,判断rect是否包含Point(x, y)点
 
//还可以求两个矩形的交集和并集
rect = rect1 & rect2;
rect = rect1 | rect2;
 
//还可以对矩形进行平移和缩放  
rect = rect + Point(-100, 100);	//平移,也就是左上顶点的x坐标-100,y坐标+100
rect = rect + Size(-100, 100);	//缩放,左上顶点不变,宽度-100,高度+100
 
//还可以对矩形进行对比,返回布尔变量
rect1 == rect2;
rect1 != rect2;
 
//OpenCV里貌似没有判断rect1是否在rect2里面的功能,所以自己写一个吧
bool isInside(Rect rect1, Rect rect2)
{
	return (rect1 == (rect1&rect2));
}
 
//OpenCV貌似也没有获取矩形中心点的功能,还是自己写一个
Point getCenterPoint(Rect rect)
{
	Point cpt;
	cpt.x = rect.x + cvRound(rect.width/2.0);
	cpt.y = rect.y + cvRound(rect.height/2.0);
	return cpt;
}
 
//围绕矩形中心缩放
Rect rectCenterScale(Rect rect, Size size)
{
	rect = rect + size;	
	Point pt;
	pt.x = cvRound(size.width/2.0);
	pt.y = cvRound(size.height/2.0);
	return (rect-pt);
}

//实例
Mat image = imread("...");
Rect rect1(256, 256, 128, 128);
Rect rect2(224, 224, 128, 128);
 
Mat roi1;
image(rect1).copyTo(roi1); //方式一
Mat roi2(img, Rect(0, 0, 10, 10));//方式二
  • OpenCV circle函数
1
2
3
4
5
6
7
8
9
void circle(CV_IN_OUT Mat& img, Point center, int radius, const Scalar& color, int thickness=1, int lineType=8, int shift=0); 
//img为图像,单通道多通道都行,不需要特殊要求

//center为画圆的圆心坐标

//radius为圆的半径

//color为设定圆的颜色,比如用CV_RGB(255, 0,0)设置为红色, CV_RGB(255, 255,255)设置为白色,CV_RGB(0, 0,0)设置为黑色。
//也可用Scalar(255, 0, 0)设置(依次为BGR),或者直接灰度Scalar(255)表示白色
  • 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)即为和
    
  • 取整函数

1
2
3
4
5
6
7
函数cvRoundcvFloorcvCeil 都是用一种舍入的方法将输入浮点数转换成整数:

cvRound():返回跟参数最接近的整数值,即四舍五入;
cvFloor():返回不大于参数的最大整数值,即向下取整;
cvCeil():返回不小于参数的最小整数值,即向上取整;
    
CV_PI:圆周率 
  • 图片缩放函数

    • 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:寻找棋盘格角点
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool findChessboardCorners(InputArray image, 
                           Size patternSize, 
                           OutputArray corners,
                           int flags = CALIB_CB_ADAPTIVE_THRESH | 
                           CALIB_CB_NORMALIZE_IMAGE );
/*
第一个参数是输入的棋盘格图像(可以是8位单通道或三通道图像);

第二个参数是棋盘格内部的角点的行列数(注意:不是棋盘格的行列数,如棋盘格的行列数分别为4、8,而内部角点的行列数分别是3、7,因此这里应该指定为cv::Size(3, 7));

第三个参数是检测到的棋盘格角点,类型为std::vectorcv::Point2f。

第四个参数flag,用于指定在检测棋盘格角点的过程中所应用的一种或多种过滤方法,可以使用下面的一种或多种,如果都是用则使用|:
cv::CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值将图像转化成二值图像
cv::CALIB_CB_NORMALIZE_IMAGE:归一化图像灰度系数(用直方图均衡化或者自适应阈值)
cv::CALIB_CB_FILTER_QUADS:在轮廓提取阶段,使用附加条件排除错误的假设。(一旦图像阈值化,算法就会尝试从棋盘上的黑色方块的透视图中找出四边形。这是一个近似,因为假设四边形的每个边缘的线是直的,当图像中存在径向畸变时,就会不正确。如果设置了这个标志,就会对这些四边形应用各种附加约束,以防出现错误的四边形。)
cv::CALIB_CV_FAST_CHECK:当出现此标志时,则对图像进行快速扫描,以确保图像中存在角点。如果不存在角点,则直接跳过此图像。如果你确定输入数据是“干净的”并且每幅图像中都有棋盘,则此标志就不需要存在。否则,如果在输入中存在没有棋盘的图像,那么使用该标志将节省大量的时间。

返回值:如果可以找到并排序图案中的所有角点,返回值将被设置为true,否则为false。

函数内部已经调用一次cornerSubPix()函数。
*/
  • cornerSubPix() :棋盘角点的亚像素检测
 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
void cornerSubPix(InputArray image, 
                  InputOutputArray corners,
                  Size winSize, 
                  Size zeroZone,
                  TermCriteria criteria );
/*
第一个参数是输入图像,和findChessboardCorners中的输入图像是同一个图像。

第二个参数是检测到的角点,即是输入也是输出。

第三个参数是计算亚像素角点时考虑的区域的大小,大小为NXN; N=(winSize*2+1)。

第四个参数作用类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1))。

第五个参数用于表示计算亚像素时停止迭代的标准,可选的值有cv::TermCriteria::MAX_ITER 、cv::TermCriteria::EPS(可以是两者其一,或两者均选),前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。二者均使用cv::TermCriteria()构造函数进行指定。

typedef struct CvTermCriteria
 {
  int    type;     // CV_TERMCRIT_ITER 和CV_TERMCRIT_EPS二值之一,或者两者的组合 
  int    max_iter; // 最大迭代次数 
  double epsilon;  // 结果的精确性 
 }

*/

//指定亚像素计算迭代标注
cv::TermCriteria criteria = cv::TermCriteria(
    cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS,
    40,
    0.01);

//亚像素检测
cv::cornerSubPix(image_gray, corners, cv::Size(5, 5), cv::Size(-1, -1), criteria);


/*注意!!!
cv::findChessboardCorners()使用的内部算法仅提供角点的近似位置。因此,cv::cornerSubPix()由cv::findChessboardCorners()自动调用,以获得更准确的结果。这在实际中意味着这些位置是相对准确的。但是,如果你希望将它们定位到非常高的精度,则需要在输出上自己调用cv::cornerSubPix()(有效地再次调用它),但是需要更严格的终止条件
*/
  • cv::drawChessboardCorners() :棋盘格角点的绘制
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
drawChessboardCorners(InputOutputArray image, 
                      Size patternSize,
                      InputArray corners, 
                      bool patternWasFound );
/*
第一个参数:image为8-bit,三通道图像,单通道也可以,但无彩色,输入也是输出;

第二个参数:patternSize,每一行每一列的角;

第三个参数:corners,已经检测到的角;

第四个参数:patternWasFound,一般用findChessboardCorners的返回值。

如果没有找到所有的角点,则可用的角点将被表示为小的红色圆圈。如果整个图案上的角点都找到,那么角点将被绘制成不同的颜色(每一行都将有自己的颜色),并将点一定顺序用线连接起来。
*/
  • double calibrateCamera():标定内参外参畸变系数函数
 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
double calibrateCamera(InputArrayOfArrays objectPoints,
                       InputArrayOfArrays imagePoints,
                       Size imageSize,
                       InputOutputArray cameraMatrix, 
                       InputOutputArray distCoeffs,
                       OutputArrayOfArrays rvecs, 
                       OutputArrayOfArrays tvecs,
                       int flags = 0, 
                       TermCriteria criteria = TermCriteria(TermCriteria::COUNT + 
                                                            TermCriteria::EPS, 30, DBL_EPSILON) );

/*依次为:
objectPoints,世界坐标,用vector<vector>,输入x,y坐标,z坐标为0,各个角点世界坐标,如果要和实际的内参矩阵相对应,应用实际的方格边长,但如果只是计算畸变系数矩阵,只要比例对应即可。

imagePoints,图像坐标,vector<vector>,棋盘的角点坐标

imageSize,图像的大小用于初始化标定摄像机的image的size

cameraMatrix,内参数矩阵,3*3矩阵

distCoeffs,畸变矩阵,一般可以设置为5*1矩阵,依次为[k1,k2,p1,p2,k3]

rvecs,旋转向量R

tvecs,位移向量T

flags,标定标志,可以+或者|组合:
1:CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,将包含有效的fx,fy,cx,cy的估计值的内参矩阵cameraMatrix,作为初始值输入,然后函数对其做进一步优化。如果不使用这个参数,图像中心(cx, cy)的初始值直接从imageSize参数中得到,再使用最小二乘估算出fx,fy。注意,如果已知内部参数(内参矩阵和畸变系数),就不需要使用这个函数来估计外参,可以使用solvepnp()函数计算外参数矩阵。

2:CV_CALIB_FIX_PRINCIPAL_POINT:该标志可以与CV_CALIB_USE_INTRINSIC_GUESS结合使用,也可以单独使用。如果单独使用,则固定光轴点固定在图像中心;如果共同使用,则主点固定在cameraMatrix中提供的初始值。

3:CV_CALIB_FIX_ASPECT_RATIO:如果设置这个标志,那么在调用标定程序时,优化过程将一起改变fx和fy,并且它们的比值保持在cameraMatrix中设置的值。如果没有设置CV_CALIB_USE_INTRINSIC_GUESS标志,那么cameraMatrix中的fx和fy的值可以是任意值,只是它们的比值是相关的。(即固定fx/fy的比值,只将fy作为可变量,进行优化计算。当
CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy的实际输入值将会被忽略,只有fx/fy的比值被计算和使用。)

4:сV::CALIBFIX_FOCAL_LENGTH:该标志在优化时直接使用cameraMatrix中传递的fx和fy。

5:CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变系数在优化中保持不变。如果设置了CV_CALIB_USE_INTRINSIC_GUESS参数,就从提供的畸变系数矩阵中得到,否则,设置为0。

6:CV_CALIB_ZERO_TANGENT_DIST:切向畸变系数(P1,P2)被设置为零并保持为零。

7:CV_CALIB_RATIONAL_MODEL(理想模型):启用畸变k4,k5,k6三个畸变参数。使标定函数使用有理模型,返回8个畸变系数。如果没有设置,则只计算其它5个畸变参数。

8:CALIB_THIN_PRISM_MODEL (薄棱镜畸变模型):启用畸变系数S1、S2、S3和S4。使标定函数使用薄棱柱模型并返回12个畸变系数。如果不设置标志,则函数计算并返回只有5个失真系数。

9:CALIB_FIX_S1_S2_S3_S4 :优化过程中不改变薄棱镜畸变系数S1、S2、S3、S4。如果CV_CALIB_USE_INTRINSIC_GUESS被设置,使用提供的畸变系数矩阵中的值。否则,设置为0。

10:CALIB_TILTED_MODEL (倾斜模型):启用畸变系数tauX and tauY。标定函数使用倾斜传感器模型并返回14个畸变系数。如果不设置标志,则函数计算并返回只有5个失真系数。

11:CALIB_FIX_TAUX_TAUY :在优化过程中,倾斜传感器模型的系数不被改变。如果CV_CALIB_USE_INTRINSIC_GUESS设置,从提供的畸变系数矩阵中得到。否则,设置为0。


TermCriteria criteria:终止条件,一般为迭代次数、"epsilon”值或两者都有。在使用epsilon值的情况下,计算的是重投影误差。与cv::findHomography()一样,重映射误差是三维点到图像平面上的计算(投影)位置与原始图像中相应点的实际位置之间的距离平方之和。
*/
  • cv::solvePnP():已知相机内参矩阵、畸变系数矩阵,计算外参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
bool cv::solvePnP(  InputArray  objectPoints,
                    InputArray 	imagePoints,
                    InputArray 	cameraMatrix,
                    InputArray 	distCoeffs,//畸变系数,行向量和列向量均可,无区别
                    OutputArray 	rvec,//返回列向量
                    OutputArray 	tvec,//返回列向量
                    bool 	useExtrinsicGuess = false,
                    int 	flags = SOLVEPNP_ITERATIVE 
                 )
    
/*
cv::solvePnP()的参数与cv::calibrateCamera()的对应参数类似,但是有两个不同的地方。首先,objectPoints和imagePoints参数是来自物体的单个视图的参数(即,它们的类型为CV::InputArray,而不是cv::InputArrayOfArrays)。其次,内参矩阵和畸变系数是直接提供的而不必计算(即,它们是输入而不是输出)。所输出的旋转向量以Rodrigues形式表示:由三个部分组成的旋转向量表示棋盘或点旋转的三维坐标轴,向量的幅度或长度代表逆时针旋转角度。这个旋转向量可以通过cv::Rodrigues()函数转换成我们之前讨论过的3x3旋转矩阵。平移向量是相机坐标中棋盘原点的偏移量,同理可以用cv::Rodrigues()函数转换。

useExtrinsicGuess参数可以设置为true,以表示rvec和tvec参数中的当前值应被视为求解的初始猜测值。默认值为false.

最后一个参数flags可以设置一下三个之一,即cv::ITERATIVE,CV::P3P或cv::EPNP,以表明应该使用哪种方法来求解整个系统。当使用cv::ITERATIVE时,会使用Levenberg-Marquardt迭代优化方法来最小化输入imagePoints和objectPoints的投影值之间的重投影误差。当使用cv::P3P时,则使用基于[Gao03]的方法,在这种情况下,应提供四个对象和四个图像点。最后,当使用cv::EMP时,则使用[Moreno-Noguer07]中描述的方法。需要注意的是,后两种方法都不需要迭代,因此,要比cv::ITERATIVE快得多。

cv::SolvePNP的返回值只有在方法成功时才为true。
*/
  • cv::solvePnPRansac()::已知相机内参矩阵、畸变系数矩阵,计算外参数

上述求外参函数鲁棒性不太好,所以提出一个新的函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
bool cv::solvePnPRansac(InputArray 	objectPoints,
                        InputArray 	imagePoints,
                        InputArray 	cameraMatrix,
                        InputArray 	distCoeffs,
                        OutputArray 	rvec,
                        OutputArray 	tvec,
                        bool 	useExtrinsicGuess = false,
                        int 	iterationsCount = 100,
                        float 	reprojectionError = 8.0,
                        double 	confidence = 0.99,
                        OutputArray 	inliers = noArray(),
                        int 	flags = SOLVEPNP_ITERATIVE 
)	
/*
参数解释同上一个函数,其他多出的参数都有缺省值
*/
  • cv::undistort():矫正函数

我们既可以只用函数cv::undistort(),使用该算法一次性完成所需的任务,也可以用一对函数cv::initundistortRectifyMap()和cv::remap()函数来更有效地处理此事,这通常适用于初频或者同一相机中获取多个图像的应用中。

1
2
3
4
5
void undistort(InputArray src, //输入原图
               OutputArray dst,//输出矫正后的图像
               InputArray cameraMatrix,//内参矩阵
               InputArray distCoeffs,//畸变系数
               InputArray newCameraMatrix=noArray() );

VS代码移植到Qt上

  • 一般是VS验算算法,QT搭建框架,在windows上统一采用MSVC2017编译器。

  • 报错时,一般是什么常量未定义等等。

  • 解决办法:新建个记事本文件.txt,将各个源文件的代码复制到.txt文件中,另存为编码格式为带有BOM的UTF-8文件,再在Qt上添加现有文件编译即可。

Qt和c++

  • QString和string
1
2
3
4
5
1.QString转换String
string s = qstr.toStdString();

2.String转换QString
QString qstr2 = QString::fromStdString(s);
  • qt创建新文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <QDir>
void MainWindow::createFolderSlot()
{
    QDir *folder = new QDir;
    bool exist = folder->exists(lineEdit->text());
    if(exist)
    {
        QMessageBox::warning(this, tr("createDir"), tr("Dir is already existed!"));
    }
    else
    {
        //创建文件夹
        bool ok = folder->mkdir(lineEdit->text());
        if(ok)
            QMessageBox::warning(this, tr("CreateDir"), tr("Create Dir success!"));
        else
            QMessageBox::warning(this, tr("CreateDir"), tr("Create Dir fail"));
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//获取程序当前运行目录
QString fileName = QCoreApplication::applicationDirPath();
QDateTime time = QDateTime::currentDateTime();//获取系统现在的时间
QString str = time.toString("yyyy_MM_dd_hh_mm_ss"); //设置显示格式
QDir *folder = new QDir;
QString newdir=fileName+"/"+str;
bool exist = folder->exists(newdir);
if(exist)
{
    QMessageBox::warning(this, tr("createDir"), tr("Dir is already existed!"));
}
else
{
    //创建文件夹
    bool ok = folder->mkdir(newdir);
    if(ok)
        QMessageBox::warning(this, tr("CreateDir"), tr("Create Dir success!"));
    else
        QMessageBox::warning(this, tr("CreateDir"), tr("Create Dir fail"));
}
delete folder;

Qt前端美化

  • 添加程序图标

    • 将ico图片文件复制到工程文件夹目录中(可在线转换图片为ico文件)
    • 在项目文件的最后面添加一行代码:RC_ICONS = XXX.ico

  • 添加资源文件的初始化

1
2
3
4
5
6
ui->setupUi(this);
//初始化,一定setup下面
ui->ImageSrc->setScene(&scene);//关联控件
QPixmap pixmap;
bool ok=pixmap.load(":/pic/camera.jpg");//资源文件
scene.addPixmap(pixmap);

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
    
    toLatin1toLocal8Bit都是QStringQByteArray的方法,Latin1代表ASCIILocal8Bit代表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之前也是成员类,应该也是可以直接调用成员变量的。以后实验一下。
    1. 创建子类继承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;
    }
    
    1. 继承 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);
    
    1. 继承 QRunnable 重新 run 函数,结合 QThreadPool 实现线程池。
    1. 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++函数

1
2
3
4
5
6
7
8
memset()
/*
函数原型是extern void *memset(void *buffer, int c, int count)        
buffer:为指针或是数组,
c:是赋给buffer的值,
count:是buffer的长度.
这个函数在socket中多用于清空数组.如:原型是memset(buffer, 0, sizeof(buffer))
*/