四时宝库

程序员的知识宝库

无人驾驶仿真研究平台介绍-2-如何用代码控制无人机-基础篇?

上一篇文章介绍了如何配置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()

  1. 如果选用join()方法时,主线程(可以认为main函数也是一个线程)会阻塞住,直接该子线程退出为止,然后主线程继续顺序执行。
  2. 如果选择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;
}

运行如果能读取图像并在窗口中显示,说明配置成功。

说到这里,后面还可以向大家介绍一下如何给图像加特效,比如: 倒影、镜面、马赛克、花雕之类的。如果有需要的可以在下方评论。


好了,今天要介绍的内容就这么多,主要是为后面的控制程序打个小基础。有兴趣的可以深入了解多线程相关的概念,这应该是做程序的人永远都避不开的一个概念把。


如果对我的推文有兴趣,欢迎转载分享。也可以推荐给朋友关、注哦。只推干货,宁缺毋滥。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接