上一篇文章介绍了如何配置AirSim+UE4这样的无人驾驶仿真平台,过程稍微有点繁琐。
这里再总结一下。配置这个平台,实际上我们就是得到两个东西: 1) 可运行的场景; 2) 可以与场景中的无人机进行通信的库。
下面开始介绍如何通过代码来与仿真场景中的无人机进行通信了。先考虑一下程序架构。
我们需要两个基本的功能: 1) 通过键盘来控制无人机的移动; 2) 实时的拿到搭载在无人机上的相机拍摄的图像。
如果直接用一个线程实现这两个功能的话,整个程序顺序执行,显然不可以实时的拿到搭载相机拍摄的图像。所以,在程序中我们用两个线程实现: 1) 主线程负责监听键盘; 2) 另一个线程不断的读取无人机上的相机拍摄的图像。
程序很清楚了,还有一个东东要介绍一下: 我们从拿到的图像数据其实只是码流,还要对它解码得到图像,这里直接通过调用opencv中的函数进行解码。重要的点还是要理解如何与仿真场景中的无人机通信嘛!!!
今天先不给出控制代码了。先介绍一下需要的基础知识: 多线程程序和计算机视觉库OpenCV入门。
一、c++如何编写多线程程序
先来看一下线程定义把:
线程(英語:thread)是操作系统能夠進行運算调度的最小單位。它被包含在进程之中,是进程中的實際運作單位。一条线程指的是进程中一个单一顺序的控制流,一個进程中可以並行多個线程,每条线程并行执行不同的任务。一个进程可以有很多线程,每条线程并行执行不同的任务。
上面的定义来自百度百科。大致的意思就是: 一个线程是一个单一的顺序控制流,一个进程中可以含有多个线程,线程与线程之间是并行执行的,它们之间的通信可以使用共享变量来完成。这与我们的需求就很一致了: 读取图像的线程和键盘按键监听的线程并行执行。
C++编写多线程程序可以使用标准库中的thread类,使用时包含进来就可以了。
使用thread来编写多线程程序时非常方便的。
1)首先要导入头文件#include <thread>并且声明使用std名字空间using namespace std;。然后要注意的是线程需要有一个“开始”的地方,就是线程入口的函数,也可以直接称为线程函数。当线程函数返回时,线程也就随之终止了。
2)启动线程的两个方法:join()或者detach()
- 如果选用join()方法时,主线程(可以认为main函数也是一个线程)会阻塞住,直接该子线程退出为止,然后主线程继续顺序执行。
- 如果选择detach()方式:执行的线程从线程对象中被独立独立运行,主线程丧失对子线程的控制权。(可能main已经执行结束了,但是子线程还没有结束)
下面是一个很简单的多线程线程的例子:
#include <iostream> #include <thread> #include <string> void thread_func(std::string tName) { for (int i = 0; i < 10; i++) { std::cout << "线程" << tName << "执行了 " << i << " 次" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } int main() { std::thread t1(thread_func, "A"); std::thread t2(thread_func, "B"); t1.join(); t2.join(); std::cout << "main结束" << std::endl; }
下面是运行结果:
从运行结果可以看到:两个线程都结束了之后,main函数继续向下执行,输出“main结束”。但是现在出现问题了:
std::cout << "线程" << tName << "执行了 " << i << " 次" << std::endl;
比如说线程A执行这条语句,但是还没有执行完,就被B“抢”过去执行了。
要解决这个问题,可以对其进行加锁---信号量机制。
c++11提供了std::mutex类,在头文件mutex中声明,因此要首#include<mutex>。
代码修改如下:
#include <iostream> #include <thread> #include <string> #include <mutex> std::mutex tLock; void thread_func(std::string tName) { for (int i = 0; i < 10; i++) { tLock.lock(); std::cout << "线程" << tName << "执行了 " << i << " 次" << std::endl; tLock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } int main() { std::thread t1(thread_func, "A"); std::thread t2(thread_func, "B"); t1.join(); t2.join(); std::cout << "main结束" << std::endl; }
运行结果如下:
二、OpenCV入门
OpenCV是一个非常流行的计算机视觉库,几乎每一个做图像的都知道这个库。由于在控制程序中需要用到这个库,因此在这里先通过一篇文章简单的向大家介绍一下如何使用OpenCV。
维基百科上对OpenCV的说明如下:
OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发,以BSD许可证授权发行,可以在商业和研究领域中免费使用。OpenCV可用于开发实时的图像处理、计算机视觉以及模式识别程序。
我们可以直接在OpenCV的官网上下载编译好的OpenCV库,也可以下载源码自己进行编译。我已经有了编译好的OpenCV了,他的文件结构大概是下面这个样子:
在使用OpenCV之前,需要在电脑上配置OpenCV的环境变量,这里程序执行时才能自动找到相应的dll文件。
这里,只介绍一下vs2015如何使用OpenCV。vs2015使用OpenCV时,需要配置三个地方: 头文件路径、库文件路径、依赖库的名称。
头文件路径和库文件路径的配置如下图所示:
依赖库的名称配置如下所示:
配置完成之后,写一个Hello World程序,如果能运行就成功了。
#include <opencv2\opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main(int argc, char *argv[]) { Mat image = imread("lena.jpg", 1); if (!image.empty()) { cv::imshow("lena", image); cv::waitKey(0); } else { cout << "Open image failed!" << endl; } return 0; }
运行如果能读取图像并在窗口中显示,说明配置成功。
说到这里,后面还可以向大家介绍一下如何给图像加特效,比如: 倒影、镜面、马赛克、花雕之类的。如果有需要的可以在下方评论。
好了,今天要介绍的内容就这么多,主要是为后面的控制程序打个小基础。有兴趣的可以深入了解多线程相关的概念,这应该是做程序的人永远都避不开的一个概念把。
如果对我的推文有兴趣,欢迎转载分享。也可以推荐给朋友关、注哦。只推干货,宁缺毋滥。