大家好,又见面了,我是你们的朋友全栈君。
目录
1. Jetson Nano简介
2. Jetson Nano环境配置
2.1 开箱配件介绍
2.2 烧录系统
2.3 开机和基本设置
2.4 开发环境配置
2.4.1 更新源和软件
2.4.2 关屏时间设置
2.4.3 安装中文输入法
2.4.4 安装Code OSS
2.4.5 安装Qt5
3. 项目案例
3.1 人脸检测
3.1.1 安装pip
3.1.2 安装Python常用机器学习包
3.1.3 配置用于Python的Opencv
3.1.4 基于Opencv的人脸检测
3.2 二维码检测(制作扫码枪)
3.2.1 读取摄像头
3.2.2 二维码检测和识读
3.3 双色LED灯控制(GPIO)
3.3.1 Python实现
3.3.2 C 实现
4. 小结
1. Jetson Nano简介
Jetson Nano是一款体积小巧、功能强大的人工智能嵌入式开发板,于2019年3月由英伟达推出。预装Ubuntu 18.04LTS系统,搭载英伟达研发的128核Maxwell GPU,可以快速将AI技术落地并应用于各种智能设备。相比于Jetson之前的几款产品(Jetson TK1、Jetson TX1、Jetson TX2、Jetson Xavier),Jetson Nano售价仅需99美元,大幅减少了人工智能终端的研发成本。因此,一经推出,便受到了广泛的关注。其官网地址为:Jetson Nano Developer Kit for AI and Robotics | NVIDIA
下面详细列举一些Jetson Nano的优势:
(1) 体型小巧,性能强大,价格实惠,整体采用类似树莓派的硬件设计,支持一系列流行的AI框架,并且英伟达投入了大量的研发精力为其打造了与之配套的Jetpack SDK开发包,通过该开发包可以使学习和开发AI产品变得更加简单和便捷。
(2) 专为AI而设计,性能相比树莓派更强大,搭载四核Cortex-A57处理器,128核Maxwell GPU及4GB LPDDR内存,可为机器人终端、工业视觉终端带来足够的AI算力。
(3) 可提供472 GFLOP,支持高分辨率传感器,可以并行处理多个传感器,并可在每个传感器流上运行多个现代神经网络。
(4) 支持英伟达的NVIDIA JetPack组件包,其中包括用于深度学习、计算机视觉、GPU计算、多媒体处理等的板级支持包,CUDA,cuDNN和TensorRT软件库。
(5) 支持一系列流行的AI框架和算法,比如TensorFlow,PyTorch,Caffe / Caffe2,Keras,MXNet等,使得开发人员能够简单快速的将AI模型和框架集成到产品中,轻松实现图像识别,目标检测,姿势估计,语义分割,视频增强和智能分析等强大功能。
目前人工智能风口开始逐步进入落地应用阶段,更多产品希望能够将人工智能算力运用于实际终端,即实现所谓的边缘计算需求。从根本上来说近几年推动人工智能的核心在于深度学习算法,但是深度学习的推理加速离不开高速GPU的支持,而一般桌面PC或服务器级别的显卡(如英伟达1080Ti等)价格非常昂贵,不适合边缘计算需求,而且体积也过于庞大。因此,英伟达推出的这款嵌入式人工智能开发板Jetson Nano非常契合当前行业需求。
2. Jetson Nano环境配置
2.1 开箱配件介绍
直接购买的Jetson Nano仅包含一台裸机,如下图所示:
下图显示了Jetson Nano裸机的各个接口:
- 接口1:SD卡插槽,在背面插入,拔出时只要轻按一下即可。SD卡主要用来存储整个系统以及相关数据,类似于桌面PC硬盘的作用;
- 接口2: 40个pin角的GPIO接口,主要用于连接外部设备,如温控器、水平仪等;NVIDIA官方提供了了JetsonGPIO库(Python)可以方便的来控制GPIO,Jetson.GPIO库运用了跟树莓派RPi.GPIO库一样的API。
- 接口3:USB口的5V输入源,默认使用该接口作为电源输入;该接口也可以作为数据传输线,例如高速串口等;
- 接口4:有线网口;如果不够买无线网卡的话可以直接将网线接在该口上进行连网;
- 接口5: 4个USB3.0口,用于接入USB设备,例如USB型摄像头;
- 接口6:HDMI输出端口,可以用来接显示屏;
- 接口7:DP显示接口,可以用来接显示屏;(接口6和接口7都是用来接外设显示屏的,只是接口不同而已,实际使用时只采用一个即可,通常一般用HDMI接口)。如果想接VGA显示屏,可以购买一个HDMI转VGA的转换口然后使用接口6即可;
- 接口8: 直流5V输入电源;注意,Jetson Nano的电源输入方式有两种,一种是采用接口3的方式,只需要购买个5V的安卓手机充电器插上即可使用,这也是默认方式。另一种就是使用接口8,此时需要对J48进行短接,短接完成后就可以切换成接口8进行电源输入,如下图所示:
- 接口9: MIPI CSI 摄像头,可以直接购买用于树莓派的摄像头即可使用;
除了裸机以外,还需自行购买内存卡、键盘、鼠标、5V2A直流电源、显示器、无线网卡、摄像头、风扇、外壳等配件才能更好的进行开发,这些配件均可以在网上方便的够得。
下面对部分配件选型和安装进行说明。
- 内存卡:一般内存卡有16G、32G、64G、128G等,为了方便后期深度学习开发,建议选用32G及以上内存卡。
- 键盘鼠标可以直接使用支持USB3.0即可;
- 电源选择5V2A或5V3A电源,如下图所示。主要,如果采用下图所示电源接入方法,需要将J48连接才可,否则默认电源输入是通过USB方式接入。
- 显示器:建议使用HDMI的显示器直接连接,如果要接电脑VGA显示器,则可以购买一个HDMI转VGA的转换模块完成连接
- 无线网卡:联网可以采用有线和无线两种方式,通过接口4直接连上网线就可以上网。如果需要采用无线wifi上网方式,则需要单独购买无线wifi模块。建议购买Jetson Nano的标准配套无线Wifi模块,如下图所示,主要包括一对对偶天线和一个处理芯片,其兼容性可以得到保证。
该无线WIFI的安装稍微麻烦一些,需要拆卸裸机上的GPU部件。
首先将wifi两个天线连接到处理芯片上,在卡槽上按压即可将天线卡上,如下图所示:
然后按照下图所示方式将Jetson Nano裸机上的GPU组件拆卸下来:
卸下来以后将wifi芯片安装在卡槽中,如下图所示:
然后再重新安装上GPU组件,安装的时候要注意不要卡住两个天线的连接线。
- 摄像头:摄像头可以采用两种方式,一种是直接使用树莓派的CSI摄像头,还有就是使用USB摄像头。如果使用树莓派摄像头,则按照下图所示方式接入即可(注意正反面):
- 风扇:一般情况下,不需要使用风扇对Jetson Nano进行散热处理,但是如果用到深度学习技术,并且高频率的进行推理运算,那么最好需要在GPU组件上面加装一个散热风扇,如下图所示:
- 外壳:裸露的开发板容易短路,从安全性考虑,在安装完上述配件以后最好为整个Jetson Nano安装一个外壳。外壳种类多样,可以根据实际购买的外壳说明进行安装,下图是一种较为常见的Jetson Nano透明外壳:
2.2 烧录系统
英伟达官方为Jetson Nano提供了SD卡版本的系统镜像,并且一直在更新和维护,该镜像中包含对应的Ubuntu系统以及配置好的cuda环境和opencv环境,因此只需要下载和安装该镜像即可完成Jetson Nano的大部分环境配置。可以直接到官网进行镜像下载,下载地址为:Jetson Download Center | NVIDIA Developer,双击Image下载最新的镜像即可,如下图所示:
本文下载的是2019年12月17日更新的JP4.3版本镜像,下载下来的是一个zip压缩包,将该zip压缩包进行解压,可以得到后缀为img的文件,该文件即为我们需要的镜像文件。该镜像文件总共占大概12.5G空间,所有这些内容最后都需要存储在SD卡中,因此,建议选择容量较大的SD卡较佳,如64G或128G。一般情况下,如果SD卡是新的,可以直接烧写,但是有时候会需要对旧的SD卡重新烧写,这时候就需要预先对SD卡做一下格式化,避免在镜像过程中出错。如果之前是已经烧写过Jetson Nano镜像的SD卡,那么就需要先对SD卡进行分区删除和重新合并,这是因为经过Jetson Nano烧写过的SD卡会形成12个子分区,因此需要先用磁盘管理器对这些分区进行删除和合并,再进行新的镜像烧写(如果是新的SD卡则不需要这些操作),如下图所示:
如上图所示,旧的Jetson Nano镜像卡会形成12个子分区,对照上图中的磁盘2的12个分区,依次进行“删除卷”处理,然后为磁盘2重新“新建简单卷”,
如果是新的SD卡,则只需要格式化以下即可。接下来开始正式进行烧录。烧录工具很多,本文推荐使用Win32DiskImager,该工具可以直接在网上进行下载和安装。双击打开Win32DiskImager,选择刚才的img镜像,并配置好对应的SD卡盘符,如下图所所示:
单击“写入”即可完成镜像烧录,整个烧录时间大概在15分钟左右。
2.3 开机和基本设置
完成烧录后将SD卡插入到Jetson Nano背面的卡槽中,然后开机启动。
初次安装需要进行基本的配置,包括账户密码、Wifi密码、输入法、时区等,具体根据提示完成即可,配置完成后会默认进行一次更新操作Applying Changes,此更新需要联网,如果没有联网则先单击cancel取消等后面联网了再进行手动更新。更新时间较长,等待即可。最后会进入桌面,如下图所示:
2.4 开发环境配置
2.4.1 更新源和软件
安装完系统后首先应该更新源,否则后续更新和升级会非常慢。但是由于Jetson Nano采用的是aarch64架构的Ubuntu 18.04.2 LTS系统,与AMD架构的Ubuntu系统不同,因此需要替换成aarch64的源,这里一定要注意,不要替换成x86-64的源了。(最近尝试了最新的Jetpack4.6,个人感觉不用换源也没问题,因此这部分换源内容仅放在这里作为参考)
我们这里选择清华的源进行更新。首先备份原本的源,更改source.list文件的名字,以备不时之需:
代码语言:javascript复制sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo gedit /etc/apt/sources.list
然后删除所有内容,复制以下内容:
代码语言:javascript复制deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main multiverse restricted universe
到这里换源就结束了。
打开终端,输入下述命令进行更新:
代码语言:javascript复制sudo apt-get update
sudo apt-get full-upgrade
上述更新时间较长,中间可能由于网速的关系会更新失败,此时不要关机重新执行命令即可,会自动断点续传的。
2.4.2 关屏时间设置
Jetson Nano默认5分钟内不操作Jetson即会关闭屏幕,重新开启屏幕需要重新输入开机密码。由于在开发Jetson Nano的过程中经常需要等待,因此并不希望频繁的开启屏幕,而是希望屏幕一直打开。打开System Settings进入系统设置界面,如下图所所示:
单击Brightness & Lock,然后将 Turn screen off when inactive for 改为 Never即可,如下图所示:
2.4.3 安装中文输入法
由于在开发过程中经常需要使用中文搜索以及书写必要的中文注释,所以推荐为系统安装中文输入法。Jetson Nano自带ibus中文输入法,但是要简单的配置下才能进行中文的输入。在终端中直接输入命令ibus会出现下图所示界面,说明Jetson Nano已经自带了ibus输入法环境了。
下面为ibus下载拼音输入法,输入命令:
代码语言:javascript复制sudo apt-get install ibus-pinyin
上述下载和安装大概需要数十分钟的时间。安装完成后进入系统配置System Settings界面,选择语言支持选项Language Support ,如下图所示:
然后选择“添加或删除语言”界面,会系统选择语言支持,如下图所示:
此处选择“中文简体”然后单击Apply即可。这个Apply过程会安装一系列中文语言包,如下图所示:
安装完成后在语言支持界面将汉语调整到最前面,如下图所示:
然后单击“应用到整个系统”。最后将“键盘输入法系统”改为iBus即可。
重新启动系统(很重要!!!),然后在终端中输入下述命令进入ibus配置界面:
代码语言:javascript复制 ibus-setup
在配置界面中单击“添加”按钮,然后展开“汉语”选项,选择Intelligent Pinyin。这里如果找不到“汉语”选项则可以先关机重启,再重新查找。
添加完成后输入下面的命令重启ibus即可。
代码语言:javascript复制ibus restart
最后,将桌面顶任务栏中将输入法切成拼音输入法Pi,如下图所示:
此时就可以使用中文输入了。如下所示:
2.4.4 安装Code OSS
Visual Studio Code(VS Code)是一个免费的集成开发环境(IDE),适用于Windows,Mac和Linux。VS Code近年来获得了越来越多的关注,成为广大编程开发者的首选编译环境。它作为微软推出的开源项目,吸引了无数第三方开发者和终端用户,成为顶尖开源项目之一。它功能强大、速度快,更在拥有海量插件的情况下做到了简洁流畅的用户体验,属于一款非常优秀的IDE。
原生的VS Code并不适用于Jetson Nano,当前,还没有针对Jetson Nano这样的ARM设备的VS Code正式版本。但是,由于它是开源的,所以任何人都可以编译一个版本。其中,Code-OSS就是这样一款嵌入式环境下的“VS Code”。Code-OSS基于VS Code,它并不仅仅是一个代码编辑器,它具有用于管理整个项目文件夹而不是单个脚本的内置资源管理器功能以及丰富的第三方插件。实际上Code-OSS几乎具备了VS Code的所有完整功能,因此用它作为代码编辑器来编辑代码,例如python,会使得整个开发过程更加便捷。下面讲解具体的安装方法。
打开Chromiun浏览器,输入网址:
点击Packsges,查看列出来的包名,选择后缀带有arm64(aarch64)的,如下图所示:
单击后进入详情页面,找到对应的wget命令,如下图所示:
该命令演示了如何下载该安装包,具体如下:
代码语言:javascript复制wget --content-disposition https://packagecloud.io/headmelted/codebuilds/packages/debian/stretch/code-oss_1.42.0-1575969886_arm64.deb/download.deb
将该命令复制到终端中实现安装包下载。如下图所示:
此时,安装包已经下载到home根目录下,可以通过文件资源管理器查看下载的deb安装包,如下所示:
在终端中输入下述命令完成最终的安装:
代码语言:javascript复制sudo dpkg -i code-oss_1.42.0-1575969886_arm64.deb
安装完成后从可以在搜索中搜索Code OSS,会弹出Code OSS应用程序,这个即为我们需要的Python编程IDE。单击应用程序打开如下图所示:
下面简单演示下如何使用Code OSS执行Python脚本。
首先在Code OSS中安装Python插件,其插件安装方法和普通的VS Code完全相同,不熟悉VS Code的读者可以先在桌面PC上熟悉VS Code基本用法再切换到Jetson Nano环境中来。插件安装如下图所示,在Extensions面板中搜索python,选择第一个弹出的插件进行安装即可:
接下来在home目录下新建一个code文件夹,该文件夹用于存放Python代码脚本。然后在Code OSS中打开刚才创建的code文件夹,然后新建一个文件,按ctrl s键保存文件,将文件命名为main.py,然后输入下面的代码:
代码语言:javascript复制a = 36
b = 64
print(a b)
然后按ctrl F5键即可运行脚本,效果如下:
至此,已完成Python编辑器的安装和运行。
2.4.5 安装Qt5
在实际的产品部署阶段,考虑到终端设备速度、稳定性、内存占用等因素,一般会采用C 来开发最终的成品,而只有在产品模型设计阶段才会使用python进行算法开发。因此,需要一款能够在Jetson Nano中开发C 的编译器方便我们开发落地产品。VS Code本身可以开发C 应用,但是Code-OSS对于C 的支持并不好,因此,需要另外安装一个优秀的C 编译器来完成C 开发任务。本文推荐使用Qt。
Qt是一个跨平台的 C 开发库,主要用来开发图形用户界面(Graphical User Interface,GUI)程序,当然也可以开发不带界面的命令行(Command User Interface,CUI)程序。Qt 是纯 C 开发的,所以用它来开发C 应用具有天然的优势。Qt 支持的操作系统有很多,例如通用操作系统 Windows、Linux、Unix,智能手机系统 Android、iOS、WinPhone以及嵌入式系统 QNX、VxWorks 等等。当然,QT也完全支持Jetson Nano的Ubuntu环境。
Jetson Nano下安装QT比较简单,只需要输入命令:
代码语言:javascript复制sudo apt-get install qt5-default qtcreator -y
此时安装的是Qt5.9.5版本。
安装完成后,同样在搜索菜单中搜索Qt,然后会出现Qt Creator,这个即为Qt的IDE,打开它。接下来简单演示如何创建一个简单的C 控制台程序。
打开Qt Creator,如下图所示:
单击New Project创建一个新项目,这里选择Application 下的Qt COnsole Appliation应用,即创建一个Qt版的C 控制台程序:
然后工程命名为QTtest:
然后一直默认单击下一步即可完成项目的创建。可以看到,Qt已经为我们创建了一个C 文件main.cpp用于编写C 代码,并且还有一个QTtest.pro配置文件用于为整个项目进行配置,效果如下图所示:
此时可以直接按ctrl r键运行项目,但是由于我们并没有任何输出代码,所以弹出的终端也没有输出任何值。我们修改一下main.cpp的代码,同样来执行两个整数的相加并输出其结果,完成代码如下:
代码语言:javascript复制#include <QCoreApplication>
#include <QtDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int c=64;
int d=36;
qDebug() << (c d);
return a.exec();
}
此时,再重新按ctrl r键运行项目输出下图所示结果:
Qt Creator的详细使用请读者自行学习相关教程,这部分资源很多也很成熟,对于开发实际的嵌入式产品来说掌握Qt和C 的使用是一个必要的过程。本文不再对Qt尤其是Qt的界面编程作深入介绍。
最后我们再看一下在主目录下生成了一个与QTtest对照的debug可执行项目build-QTtest-unknown-Debug,在这个文件夹中生成来debug版本的QTtest可执行程序。通过终端cd命令进入到该文件夹,然后输入
代码语言:javascript复制./QTtest
会直接执行程序,如下图所示:
也就是说本质上我们已经成功的部署开发了一个应用,该应用功能很简单,仅仅实现了两个固定整数的相加。尽管简单,但是却梳理了我们正常开发人工智能产品的一种比较常见的形式,即先在VS Code中用python脚本进行算法验证,最后再用QT编写对应的C 应用,最后生成二进制可执行程序,这个最终生成的二进制可执行程序就是我们的“产品”,这个可执行程序代码是封装起来的、不可见的、可以直接运行的。
至此,我们已经完成了Jetson Nano的常规开发配置,接下来会进行几个小项目的演示,使读者可以更深入的学习Jetson Nano的开发方法。
3. 项目案例
3.1 人脸检测
本节首先使用Python来完成人脸检测算法,其中会讲解Python配置和使用Opencv的基本方法以及一些常用python库的安装。
3.1.1 安装pip
由于Jetson Nano中已经预装了Python3.6版本,所以可以直接安装pip。
在终端中输入下述命令进行安装:
代码语言:javascript复制sudo apt-get install python3-pip python3-dev
安装完成后此时的pip是9.01版本,需要对pip进行一下升级,否则后面在安装其它Python库的时候会出问题。升级命令如下:
代码语言:javascript复制python3 -m pip install --upgrade pip
升级后版本为19.0.3。尽管完成了升级,但是这时候pip3有个小bug需要手动修复一下。
首先使用下面的命令打开pip3文件:
代码语言:javascript复制sudo vim /usr/bin/pip3
键盘输入字符a进入插入模式,然后可以开始编辑文件,将:
代码语言:javascript复制from pip import main
if __name__ == '__main__':
sys.exit(main())
修改为:
代码语言:javascript复制from pip import __main__
if __name__ == '__main__':
sys.exit(__main__._main())
然后按Esc键进入到命令模式。最后按英文的”:”键进入末行模式,敲入wq按回车即可保存修改并退出编辑器。
3.1.2 安装Python常用机器学习包
代码语言:javascript复制sudo apt-get install python3-scipy
sudo apt-get install python3-pandas
sudo apt-get install python3-sklearn
3.1.3 配置用于Python的Opencv
有两种方法安装python下的opencv。一种是下载Opencv源码并且重新编译生成对应的python包,然后将该包拷贝到python的安装包路径中;另一种就是直接使用命令 sudo pip3 install python3-opencv。需要注意的是,第二种方式本质上安装的是已经编译好的opencv包,其opencv的版本是固定的,如果想要使用最新的opencv,比如opencv4,那么第二种方法就不合适。本小节先简单的采用第一种方式来安装。
原镜像中已经预装了opencv4.1.1,可以使用下述命令来查看当前Opencv版本号:
代码语言:javascript复制opencv_version
输出结果如下图所示:
因此,我们也不需要重新进行编译,直接使用即可。
3.1.4 基于Opencv的人脸检测
(1)python实现人脸检测
本小节首先编写一个python脚本用于检测图像中的人脸,使用Code OSS打开2.4.4节中创建的code文件夹,在该文件夹下新建一个python脚本,名为face_detect_test.py,代码如下所示:
代码语言:javascript复制import cv2
filepath = "test.jpg"
img = cv2.imread(filepath) # 读取图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换灰色
# OpenCV人脸识别分类器
classifier = cv2.CascadeClassifier( "haarcascade_frontalface_default.xml" )
color = (0, 255, 0) # 定义绘制颜色
# 调用识别人脸
faceRects = classifier.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
if len(faceRects): # 大于0则检测到人脸
for faceRect in faceRects: # 单独框出每一张人脸
x, y, w, h = faceRect
# 框出人脸
cv2.rectangle(img, (x, y), (x h, y w), color, 2)
cv2.imshow("image", img) # 显示图像
c = cv2.waitKey(10)
cv2.waitKey(0)
cv2.destroyAllWindows()
上述代码中filepath用于存放当前需要检测的图像路径,一般可以放在与源文件同目录下即可。在构造opencv人脸检测分类器时,需要对应的人脸检测配置文件,该文件存储了用于人脸检测算法的相关参数,此文件可以从opencv的安装目录找到:/usr/share/opencv4/。找到后将其拷贝到源文件目录下即可。
按ctrl F5运行,效果图如下所示:
(2)C 实现人脸检测
本小节编写一个C 应用,用于检测图像中的人脸,使用Qt5进行开发。相关实现方法与python版相同。主要讲解如何在QT下集成Opencv进行C 项目开发。
C 下开发Opencv需要进行一些额外的配置,先看一下opencv的位置。Jetson Nano预装的Opencv4.1.1的头文件位置如下图所示:
库文件放置在:
代码语言:javascript复制/usr/lib/aarch64-linux-gnu
因此,只需要在Qt的pro文件中将上述两个目录包含进来即可。
用Qt Creator重新打开2.4.5节创建的QTtest项目,编辑QTtest.pro文件如下:
代码语言:javascript复制QT -= gui
CONFIG = c 11 console
CONFIG -= app_bundle
CONFIG = C 11 # 添加对C 11的支持
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES = QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES = QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
INCLUDEPATH = /usr/include/opencv4 #添加头文件路径
LIBS = -L/usr/lib/aarch64-linux-gnu -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_highgui -lopencv_objdetect #添加需要链接的库
SOURCES = main.cpp
其中重点需要注意头文件和lib文件的添加方法。
接下来修改main.cpp文件,代码如下:
代码语言:javascript复制#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
using namespace std;
using namespace cv;
int main( int argc, char** argv )
{
std::string filepath( "test.jpeg" );
Mat img,gray;
img = imread( filepath, IMREAD_COLOR );
cvtColor(img, gray, CV_BGR2GRAY);
CascadeClassifier classifier;
classifier.load("haarcascade_frontalface_default.xml");
Scalar color=Scalar(0, 255, 255);
vector<Rect> faceRects;
classifier.detectMultiScale(gray,faceRects,1.2,3,0,Size(32,32));
for (size_t i = 0; i < faceRects.size(); i )
{
rectangle(img, faceRects[i], color);
}
namedWindow( "Display window", WINDOW_AUTOSIZE );
imshow( "Display window", img);
waitKey(0);
return 0;
}
重新生成整个项目,然后将test.jpeg和haarcascade_frontalface_default.xml文件放置在编译生成的build-QTtest-unknown-Debug文件夹中,运行项目效果图如下所示:
3.2 二维码检测(制作扫码枪)
现在支付宝和微信广泛使用二维码作为支付手段,在现实生活购物中我们经常会通过手机展示二维码给商家用于扫码,那么我们是否可以自行做一个扫码仪呢?有了Jetson Nano这款嵌入式人工智能开发板,我们就可以自己制作一个扫码枪。
3.2.1 读取摄像头
本小节我们希望能够通过摄像头读取图像,并且对图像中的二维码进行实时解析,也就是实现一个扫码仪的功能。本小节实现摄像头读取功能。摄像头一般有两种可选,一种是相对价格比较便宜的csi摄像头(树莓派摄像头),还有一种是USB摄像头。值得注意的是,如果采用USB摄像头,那么图像的读取和渲染则是会用到Jetson Nano的GPU,如果这个时候我们还在做一些深度学习的推理工作,那么很明显会占用掉一些GPU资源。相反,Jetson Nano对于csi摄像头的读取和渲染则会采用Gstreamer管道来处理,会使用特定的硬件加速,整个处理效果会更好。
本小节我们将详细介绍两种摄像头的读取方式。无论哪种方式,我们均采用Opencv这个强大的图像处理开源库作为基础来执行相关操作。
(1)读取CSI摄像头
使用Gstreamer读取CSI摄像头主要分为3个步骤:创建Gstreamer管道;将管道绑定opencv的视频流;逐帧提取和显示。下面首先给出基于Python的详细代码:
代码语言:javascript复制import cv2
# 设置gstreamer管道参数
def gstreamer_pipeline(
capture_width=1280, #摄像头预捕获的图像宽度
capture_height=720, #摄像头预捕获的图像高度
display_width=1280, #窗口显示的图像宽度
display_height=720, #窗口显示的图像高度
framerate=60, #捕获帧率
flip_method=0, #是否旋转图像
):
return (
"nvarguscamerasrc ! "
"video/x-raw(memory:NVMM), "
"width=(int)%d, height=(int)%d, "
"format=(string)NV12, framerate=(fraction)%d/1 ! "
"nvvidconv flip-method=%d ! "
"video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! "
"videoconvert ! "
"video/x-raw, format=(string)BGR ! appsink"
% (
capture_width,
capture_height,
framerate,
flip_method,
display_width,
display_height,
)
)
if __name__ == "__main__":
capture_width = 1280
capture_height = 720
display_width = 1280
display_height = 720
framerate = 60
flip_method = 0
# 创建管道
print(gstreamer_pipeline(capture_width,capture_height,display_width,display_height,framerate,flip_method))
#管道与视频流绑定
cap = cv2.VideoCapture(gstreamer_pipeline(flip_method=0), cv2.CAP_GSTREAMER)
if cap.isOpened():
window_handle = cv2.namedWindow("CSI Camera", cv2.WINDOW_AUTOSIZE)
# 逐帧显示
while cv2.getWindowProperty("CSI Camera", 0) >= 0:
ret_val, img = cap.read()
cv2.imshow("CSI Camera", img)
keyCode = cv2.waitKey(30) & 0xFF
if keyCode == 27:# ESC键退出
break
cap.release()
cv2.destroyAllWindows()
else:
print("打开摄像头失败")
紧接着3.1.4节中的第一部分内容,在Code-OSS中新建一个文件命名为csi_camera_test.py,然后将上述代码复制到该文件中,保存然后按ctrl F5运行脚本(前提:确保已经准确安装了CSI树莓派摄像头),运行效果如下所示:
可以看到已经可以正常的显示视频流图像了,但是由于树莓派摄像头本身的原因,其图像中还有很多的噪点,颜色也有些失真(真实工业场景中建议购买更好的摄像头)。下面我们同步的给出C 版本。紧接着3.1.4节中第二部分内容,修改main.cpp文件如下:
代码语言:javascript复制#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
#include <opencv4/opencv2/videoio.hpp>
using namespace std;
using namespace cv;
string gstreamer_pipeline (int capture_width, int capture_height, int display_width, int display_height, int framerate, int flip_method)
{
return "nvarguscamerasrc ! video/x-raw(memory:NVMM), width=(int)" to_string(capture_width) ", height=(int)"
to_string(capture_height) ", format=(string)NV12, framerate=(fraction)" to_string(framerate)
"/1 ! nvvidconv flip-method=" to_string(flip_method) " ! video/x-raw, width=(int)" to_string(display_width) ", height=(int)"
to_string(display_height) ", format=(string)BGRx ! videoconvert ! video/x-raw, format=(string)BGR ! appsink";
}
int main( int argc, char** argv )
{
int capture_width = 1280 ;
int capture_height = 720 ;
int display_width = 1280 ;
int display_height = 720 ;
int framerate = 60 ;
int flip_method = 0 ;
//创建管道
string pipeline = gstreamer_pipeline(capture_width,
capture_height,
display_width,
display_height,
framerate,
flip_method);
std::cout << "使用gstreamer管道: nt" << pipeline << "n";
//管道与视频流绑定
VideoCapture cap(pipeline, CAP_GSTREAMER);
if(!cap.isOpened())
{
std::cout<<"打开摄像头失败."<<std::endl;
return (-1);
}
//创建显示窗口
namedWindow("CSI Camera", WINDOW_AUTOSIZE);
Mat img;
//逐帧显示
while(true)
{
if (!cap.read(img))
{
std::cout<<"捕获失败"<<std::endl;
break;
}
imshow("CSI Camera",img);
int keycode = cv::waitKey(30) & 0xff ; //ESC键退出
if (keycode == 27) break ;
}
cap.release();
destroyAllWindows() ;
}
其中需要额外的添加opencv用于视频处理的头文件#include <opencv4/opencv2/videoio.hpp>。另外,还需要修改对pro文件,将视频处理对应的opencv_videoio库包含进来,完整的pro文件如下:
代码语言:javascript复制QT -= gui
CONFIG = c 11 console
CONFIG -= app_bundle
CONFIG = C 11 # 添加对C 11的支持
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES = QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES = QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
INCLUDEPATH = /usr/include/opencv4 #添加头文件路径
LIBS = -L/usr/lib/aarch64-linux-gnu -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_highgui -lopencv_objdetect -lopencv_videoio #添加需要链接的库
SOURCES = main.cpp
保存所有修改后重新构建项目并运行可以得到相同的结果,视频可以正确显示。
(2)读取USB摄像头
相比于读取CSI摄像头,读取USB摄像头更加简单,只需要两步:打开摄像头;逐帧提取。但是需要注意的是Jetson Nano并不是支持所有的USB摄像头,建议在采购的时候尽量选择Linux免驱的USB摄像头。本文采用的是一个4K高清摄像头。
下面给出Python版本的完整代码:
代码语言:javascript复制import cv2
#创建摄像头捕获模块
cap = cv2.VideoCapture(1)
#创建窗口
window_handle = cv2.namedWindow("USB Camera", cv2.WINDOW_AUTOSIZE)
# 逐帧显示
while cv2.getWindowProperty("USB Camera", 0) >= 0:
ret_val, img = cap.read()
print(img.shape)
# 图像太大需要调整
height, width = img.shape[0:2]
if width>800:
new_width=800
new_height=int(new_width/width*height)
img = cv2.resize(img, (new_width,new_height))
cv2.imshow("USB Camera", img)
keyCode = cv2.waitKey(30) & 0xFF
if keyCode == 27:# ESC键退出
break
#释放资源
cap.release()
cv2.destroyAllWindows()
上述代码在打开摄像头时使用了cap = cv2.VideoCapture(1),这里的参数1是因为当前的Jetson Nano还连接了CSI摄像头,CSI摄像头的标识为0,因此这个USB摄像头的标识为1,这个可以在实际使用时通过测试来得到。另外,上述代码中对图像的尺寸做了限制,如果宽度超过800,则等比的缩放图像再显示。效果图如下所示:
可以看到这个USB 4K摄像头对于图像的显示效果还是不错的,颜色更加真实,噪点少。后面我们会继续使用这个摄像头进行二维码检测。
下面给出C 版本代码,修改main.cpp文件,代码如下:
代码语言:javascript复制#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
#include <opencv4/opencv2/videoio.hpp>
using namespace std;
using namespace cv;
int main( int argc, char** argv )
{
//打开摄像头
VideoCapture cap(1);
//创建显示窗口
namedWindow("USB Camera", WINDOW_AUTOSIZE);
Mat img;
//逐帧显示
while(true)
{
if (!cap.read(img))
{
std::cout<<"捕获失败"<<std::endl;
break;
}
int new_width,new_height,width,height,channel;
width=img.cols;
height=img.rows;
channel=img.channels();
cout<<width<<" "<<height<<" "<<channel<<endl;
new_width=800;
if(width>800)
{
new_height=int(new_width*1.0/width*height);
}
resize(img, img, cv::Size(new_width, new_height));
imshow("USB Camera",img);
int keycode = cv::waitKey(30) & 0xff ; //ESC键退出
if (keycode == 27) break ;
}
cap.release();
destroyAllWindows() ;
}
效果如下所示:
3.2.2 二维码检测和识读
本小节将使用Opencv实现二维码检测和识读功能。在opencv4.0以后,已经集成了二维码识读模块,因此,我们可以采用最新的opencv来实现二维码检测和识读。二维码检测和识别主要分为3步:使用QRCodeDetector()函数创建二维码检测器;使用detectAndDecode函数对图像进行二维码检测和识别;将检测结果输出。
这里主要是读取视频流的每帧图像然后对图像进行检测,为了方便,我们仅给出针对USB摄像头的完整实例,对于CSI摄像头可以根据3.2.1节内容将相关二维码检测代码迁移过去即可。结合3.2.1节中获取USB摄像头视频的代码,给出完整的Python版二维码检测和识读代码:
代码语言:javascript复制import cv2
import numpy as np
#创建摄像头捕获模块
cap = cv2.VideoCapture(1)
#创建窗口
window_handle = cv2.namedWindow("USB Camera", cv2.WINDOW_AUTOSIZE)
#创建二维码检测器
qrDecoder = cv2.QRCodeDetector()
# 逐帧显示
while cv2.getWindowProperty("USB Camera", 0) >= 0:
ret_val, img = cap.read()
#print(img.shape)
# 图像太大需要调整
height, width = img.shape[0:2]
if width>800:
new_width=800
new_height=int(new_width/width*height)
img = cv2.resize(img, (new_width,new_height))
# 二维码检测和识别
data,bbox,rectifiedImage = qrDecoder.detectAndDecode(img)
if len(data)>0:
print("解码数据 : {}".format(data))
n = len(bbox)
for j in range(n):
cv2.line(img, tuple(bbox[j][0]), tuple(bbox[ (j 1) % n][0]), (255,0,0), 3)
else:
print("没有检测到二维码")
#显示图像
cv2.imshow("USB Camera", img)
keyCode = cv2.waitKey(30) & 0xFF
if keyCode == 27:# ESC键退出
break
#释放资源
cap.release()
cv2.destroyAllWindows()
效果图如下:
C 版本完整代码如下:
代码语言:javascript复制#include <iostream>
#include <string>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <opencv4/opencv2/objdetect.hpp>
#include <opencv4/opencv2/imgproc/types_c.h>
#include <opencv4/opencv2/videoio.hpp>
#include <opencv4/opencv2/imgcodecs.hpp>
using namespace std;
using namespace cv;
int main( int argc, char** argv )
{
//打开摄像头
VideoCapture cap(1);
//创建显示窗口
namedWindow("USB Camera", WINDOW_AUTOSIZE);
Mat img;
//创建二维码检测器
QRCodeDetector qrDecoder = QRCodeDetector();
//逐帧显示
while(true)
{
if (!cap.read(img))
{
std::cout<<"捕获失败"<<std::endl;
break;
}
int new_width,new_height,width,height,channel;
width=img.cols;
height=img.rows;
channel=img.channels();
//cout<<width<<" "<<height<<" "<<channel<<endl;
//调整图像大小
new_width=800;
if(width>800)
{
new_height=int(new_width*1.0/width*height);
}
resize(img, img, cv::Size(new_width, new_height));
//二维码检测和识读
Mat bbox, rectifiedImage;
std::string data = qrDecoder.detectAndDecode(img, bbox, rectifiedImage);
if(data.length()>0)
{
cout << "解码数据: " << data << endl;
int n = bbox.rows;
for(int i = 0 ; i < n ; i )
{
line(img, Point2i(bbox.at<float>(i,0),bbox.at<float>(i,1)), Point2i(bbox.at<float>((i 1) % n,0), bbox.at<float>((i 1) % n,1)), Scalar(255,0,0), 3);
}
}
else
cout << "没有检测到二维码" << endl;
imshow("USB Camera",img);
int keycode = cv::waitKey(30) & 0xff ; //ESC键退出
if (keycode == 27) break ;
}
cap.release();
destroyAllWindows() ;
}
效果如下图所示:
3.3 双色LED灯控制(GPIO)
3.3.1 Python实现
Jetson Nano很重要的一个功能模块就是硬件控制,既而实现诸多DIY物联网功能。在前面Jetson Nano实物图上我们看到有40个排列整齐的GPIO针脚,这些就是Jetson Nano用来对硬件设备进行信号控制的。那么如何使用这些GPIO接口?
这里Jetson Nano提供了现成的Python库:Jetson.GPIO。Jetson Nano初始系统已经默认预装了Jetson.GPIO库,如果不小心卸载了或者删除了那么可以通过下面的命令安装:
代码语言:javascript复制sudo pip3 install Jetson.GPIO
然后设置权限:
代码语言:javascript复制sudo groupadd -f -r gpio
sudo usermod -a -G gpio <your_user_name>
设置完以后重新启动即可:
代码语言:javascript复制sudo reboot
在具体使用GPIO前我们先了解下这些引脚的功能分布:
上图已经把各个引脚的功能标注出来了,中间两列Pin对应40个引脚,我们主要关注具体的GPIO口和GND口。GPIO可以理解为信息输入口,其高低电平可以由python进行代码控制,GND表示接地(在高中物理中,这里的GPIO可以这样简单理解,一旦GPIO被设置了高电平那么就相当于接通了电源的正极,而GND相当于电源的负极,中间加个小灯再加个电阻就串联成了一个基本电路结构)。本小节我们将学习如何使用GPIO来点亮一个LED小灯。该灯如下图所示(这种小灯某宝上随便都可以买到,非常便宜):
图中模块有三个管脚,其中左侧标注‘-’的管脚接GND,中间的管脚接GPIO,右侧标注”S”的管脚也接GPIO。
当中间管脚为高电平,则LED亮灯为一种颜色。
当S管脚为高电平,则LED亮灯为另一种颜色。
双色LED灯基本原理图如下所示:
对照上述原理图,我们要做的就很简单了。这里我们选用GPIO 13(对应pin22)和GPIO15(对应pin18)来作为两个颜色的控制信号,GND选择pin30即可。因此我们只需要将该LED的三个接口分别对接上即可。
代码语言:javascript复制# 导入GPIO库
import Jetson.GPIO as GPIO
# 导入时间库
import time
# 定义信号Pin
pin1 = 22
pin2 = 18
# 设置GPIO模式
GPIO.setmode(GPIO.BOARD)
# 设置初始pin信号初始值
GPIO.setup(pin1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(pin2, GPIO.OUT, initial=GPIO.LOW)
# 每隔两秒切换一次LED灯状态
print('按ctrl c键即可退出')
try:
while True:
time.sleep(2)
GPIO.output(pin1, GPIO.HIGH)
GPIO.output(pin2, GPIO.LOW)
time.sleep(2)
GPIO.output(pin2, GPIO.HIGH)
GPIO.output(pin1, GPIO.LOW)
except KeyboardInterrupt:
pass
# 停止后关灯
GPIO.setup(pin1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(pin2, GPIO.OUT, initial=GPIO.LOW)
# 清除资源
GPIO.cleanup()
最终效果如下图所示:
3.3.2 C 实现
首先需要编译和安装C 版的Jetson GPIO库。具体命令如下:
代码语言:javascript复制git clone https://github.com/pjueon/JetsonGPIO
cd JetsonGPIO/build
make all
sudo make install
安装完成后会自动将生成的libJetsonGPIO.a库文件拷贝到/usr/local/lib/目录下,将JetsonGPIO.h头文件拷贝到/usr/local/include/目录下面,后面我们编写C 程序就可以直接使用了。
安装完以后重启系统:
代码语言:javascript复制sudo reboot
重启后我们可以使用下面的命令看看C 版的GPIO库安装正不正常:
代码语言:javascript复制find /home -name JetsonGPIO.h
正常会输出对应头文件路径,如下所示:
代码语言:javascript复制/home/qb/code/JetsonGPIO/include/JetsonGPIO.h
接下来创建一个名为CGpioDemo的文件夹,然后在该文件夹中分别创建CMakelists.txt和CGpioDemo.cpp两个文件,其中CMakelists.txt内容如下:
代码语言:javascript复制cmake_minimum_required (VERSION 3.8)
project ("CGpioDemo")
#添加头文件路径
include_directories(/home/qb/code/JetsonGPIO/include)
#添加库文件路径
link_directories(/home/qb/code/JetsonGPIO/build)
# 将源代码添加到此项目的可执行文件。
add_executable (CGpioDemo "CGpioDemo.cpp")
# TODO: 如有需要,请添加测试并安装目标。
target_link_libraries( CGpioDemo -lJetsonGPIO -lpthread)
注意上述代码中/home/qb/code/JetsonGPIO/include和/home/qb/code/JetsonGPIO/build需要结合自己的路径进行修改。这两个路径就是我们刚才编译的JetsonGPIO库路径。
CGpioDemo.cpp内容如下:
代码语言:javascript复制//导入文件
#include <iostream>
#include <chrono>
#include <thread>
#include <signal.h>
//导入JetsonGPIO库文件
#include <JetsonGPIO.h>
using namespace std;
// 定义程序退出标志
bool done = false;
// 定义按键中断信号回调函数
void signalHandler(int s) {
done = true;
}
int main() {
// 定义Pin
int pin1 = 22;
int pin2 = 18;
//定义回调函数信号,当按下Ctrl C键时程序退出
signal(SIGINT, signalHandler);
//设置模式
GPIO::setmode(GPIO::BOARD);
//LED pin初始设置
GPIO::setup(pin1, GPIO::OUT, GPIO::LOW);
GPIO::setup(pin2, GPIO::OUT, GPIO::LOW);
std::cout << "按 CTRL C 退出程序" << std::endl;
int curr_value = GPIO::LOW;
//每隔2秒跳变一次
while (!done) {
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
GPIO::output(pin1, GPIO::HIGH);
GPIO::output(pin2, GPIO::LOW);
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
GPIO::output(pin2, GPIO::HIGH);
GPIO::output(pin1, GPIO::LOW);
}
//关闭灯光
GPIO::output(pin2, GPIO::LOW);
GPIO::output(pin1, GPIO::LOW);
//清除资源
GPIO::cleanup();
return 0;
}
然后按照C 的方式进行cmake编译即可。
4. 小结
本篇博客从Jetson Nano的安装开始讲解,一直到开发人脸检测、二维码扫码枪、双色LED灯等案例结束。本教程更多的从实际使用出发,从嵌入式产品理念教会读者如何一步步搭建人工智能产品,每个案例均包括python和c 两种版本。为了适应新读者上手,选取的案例都比较简单。从实际情况出发,目前人工智能更多的采用深度学习进行高精度的推理运算,采用深度学习较传统算法可以大幅提高图像检测、识别和语义分割精度,但是如何将深度学习算法有效落地成了当前最热门的风口,即如何高效的实现所谓的边缘计算。Jetson Nano作为英伟达推出的人工智能开发板,用其作为深度学习嵌入式的落地平台非常合适,尤其是结合英伟达推出的TensorRT开发包,可以将各类深度学习框架训练出来的模型进一步的加速推理。从英伟达公布的数据分析,使用TensorRT可以加速推理3倍以上时间。因此,未来使用Jetson Nano的一个重点方向就是深度学习的落地应用。
后面本教程会持续更新Jetson Nano深度学习相关内容。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/151747.html原文链接:https://javaforall.cn