香橙派 AIpro设计医院人脸红外测温系统:从零开始开发实战指南

文章目录

  • 一、前言
  • 二、主控板介绍
  • 三、搭建开发环境
  • 3.1 准备需要的配件
  • 3.2 开发板实物图
  • 3.3 下载开发板资料
  • 3.4 下载系统烧写工具
  • 3.5 设置开发板启动模式
  • 3.6 启动系统
  • 3.7 SSH远程登录系统
  • 3.8 安装xdrp工具
  • 3.9 Window远程登录
  • 3.10 取消自动休眠
  • 四、安装Qt开发环境
  • 4.1 安装qtcreator
  • 4.2 启动qtcreator
  • 4.3 配置编译器
  • 4.4 新建工程Qt环境
  • 五、开发板初步测试
  • 5.1 项目1:开发一个基于HTTP协议的网络摄像头
  • 【1】项目介绍
  • 【2】编写项目代码
  • 【3】上传项目代码
  • 【4】插入USB摄像头
  • 【4】编译运行项目
  • 【5】浏览器访问
  • 5.2 项目2: 基于华为云设计的智能家居控制系统
  • 【1】项目介绍
  • 【2】安装wiringPi
  • 【3】GPIO口布局
  • 【4】控制LED灯
  • 【5】DHT11温湿度传感器数据读取
  • 【6】注册华为云设备
  • 【7】编写整体项目
  • 5.3 项目3:OpenCV+卷积神经网络实现人脸识别
  • 5.4 项目4:OpenCV+YOLOv3实现目标检测
  • 五、测温项目开发
  • 5.1 外设模块选型
  • 5.2 整体的项目代码
  • 【1】技术实现方式说明
  • 【2】摄像头图像采集代码
  • 【3】体温数据采集(串口)
  • 【4】人脸识别图像处理
  • 六、总结
  • 一、前言

    在公共卫生事件频发的当下,尤其是在全球性疫情爆发后,国家对公共空间的健康监测和管理提出了更高的要求。医院、疾病防控中心和发热门诊作为疫情防控的第一线,需要高效且精准地对进出人员进行健康筛查,以防止病毒传播,保障医护人员及患者的安全。传统的手动体温检测方式不仅效率低下,而且存在交叉感染的风险,开发一种能够自动、快速、准确地进行人体温度监测与身份识别的系统显得非常的重要。

    当前文章会完整的介绍,如何采用香橙派AIpro设计出一套医院人脸红外测温系统。香橙派AIpro是一款高性价比的边缘计算设备,搭载了昇腾 AI 处理器,可提供 8TOPS INT8 的计算能力,能够运行Ubuntu 22.04操作系统,这为部署复杂的深度学习算法提供了硬件基础。本系统利用OpenCV和SSD算法模型进行人脸检测,通过各方面的模型训练,能确保在复杂光线和遮挡条件下仍能有效识别个体;结合红外测温技术,可以非接触式地测量额头温度,避免了传统接触式测文章温可能带来的卫生问题。

    考虑到环境因素对测温结果的影响,系统还配备了温湿度传感器,以实时监测并校准测温数据。为了实现数据的实时监控与分析,系统通过MQTT协议将收集到的信息上传至华为云物联网云平台,便于远程监控和数据分析,有助于疫情趋势的预测和资源的合理调配。

    本项目整体提供了一个智能化、自动化的人脸识别与体温监测解决方案,以提高公共卫生领域的响应速度和防控效率,减少人力资源的投入,同时降低潜在的感染风险,为构建安全健康的医疗环境贡献力量。

    本项目在完成最终的功能开发前,会先单个完成模块的功能开发,实现了单个模块功能之后,最终在整体合在一起实现最终的项目开发。

    整体项目会从搭建环境开始, 一步一步实现最终的项目效果。

    image-20240714173609690

    下面的开发出来的最终人脸检测设备最终设计效果:

    image-20240713185744390

    image-20240713184816028

    二、主控板介绍

    香橙派 AIpro开发板是香橙派联合华为精心打造的高性能 AI 开发板,搭载了昇腾 AI 处理器,可提供 8TOPS INT8 的计算能力,内存提供了 8GB 和 16GB两种版本。可以实现图像、视频等多种数据分析与推理计算,可广泛用于教育、机器人、无人机等场景。

    下面是香橙派 AIpro开发板的配置说明:

    模块 规格
    昇腾 AI 处理器 4 核 64 位 Arm 处理器 + AI 处理器
    AI 算力 半精度(FP16):4 TFLOPS
    整数精度(INT8):8 TOPS
    内存 类型:LPDDR4X 容量:8GB 或 16GB
    存储 板载 32MB 的 SPI Flash
    Micro SD 卡插槽
    eMMC 插座:可外接 eMMC 模块
    M.2 M-Key 接口:可接 2280 规格的 NVMe SSD 或 SATA SSD
    以太网 支持 10/100/1000Mbps
    板载 PHY 芯片:RTL8211F
    Wi-Fi+蓝牙 支持 2.4G 和 5G 双频 WIFI
    BT4.2
    模组:欧智通 62221BUUC
    USB 2 个 USB3.0 Host 接口
    1 个 Type-C 接口(只支持 USB3.0,不支持 USB2.0)
    摄像头 2 个 MIPI CSI 2 Lane 接口
    显示 2 个 HDMI 接口 1 个 MIPI DSI 2 Lane 接口
    音频 一个 3.5mm 耳机孔,支持音频输入输出
    2 个 HDMI 音频输出
    40 pin 扩展口 用于扩展 UART、I2C、SPI、PWM 和 GPIO 等接口
    按键 一个复位键,一个关机键,一个升级按键
    拨码开关 2 个拨码开关:用于控制 SD 卡、eMMC 和 SSD 启动选项
    电源 支持 Type-C 供电,20V PD-65W 适配器
    LED 灯 一个电源指示灯和一个软件可控指示灯
    风扇接口 4pin,0.8mm 间距,用于接 12V 风扇,支持 PWM 控制
    电池接口 2pin,2.54mm 间距,用于接 3 串电池,支持快充
    调试串口 Micro USB 接口的调试串口
    支持的操作系统 Ubuntu 22.04 和 openEuler 22.03
    外观规格介绍 产品尺寸:107*68mm 重量:82g

    下面是香橙派 AIpro开发板的功能模块介绍:

    image-20240713184019802

    image-20240713233418646

    三、搭建开发环境

    3.1 准备需要的配件

    (1)准备一张至少32G的TFT卡,用来烧写系统。

    (2)准备一个读卡器,方便插入TFT卡,好方便插入到电脑上拷贝系统

    (3)香橙派 AIpro 主板一个

    (4)一根网线(方便插路由器上与香橙派 AIpro 连接)

    (5)一根type-C的电源线 + 电源插头(3A电流),这个主板买回来是带了电源的。 也可以用自己Android手机的数据线就行,拿手机充电器供电,因为目前Android手机电源线都是都是type-C 也支持快充的,电流也是满足需求的。

    (6)一个USB摄像头,用于后续项目开发里获取周围的实时图像,识别人脸。 (项目开发需要使用)

    (7)一个串口协议的红外测温传感器,用于后续项目开发里测量体温。(项目开发需要使用)

    (8)一个外放音箱,支持3.5mm的耳机插孔,方便后续项目开发里播放语音提示。(项目开发需要使用)

    (9)一块显示屏(这个不是必须的,可以直接Windows远程桌面访问系统,对前期开发来说没有任何影响,只要做成最终的产品才需要配屏幕)。

    3.2 开发板实物图

    拿回来的香橙派 AIpro 开发板实物是这样的。

    image-20240713193221825

    image-20240713193411984

    3.3 下载开发板资料

    拿到板子之后,第一件事肯定是先去官网下载板子对应的相关的资料。比如:用户手册、系统镜像、原理图、开发工具什么的。

    官网地址:http://www.orangepi.cn/html/hardWare/computerAndMicrocontrollers/service-and-support/Orange-Pi-AIpro.html

    翻到下面,找到资料下载地址,直接下载就行,下载会跳转到网盘。

    image-20240713193822577

    系统镜像我选择的 ubuntu22.04

    image-20240713193900175

    资料下载下来之后,可以看到有一份官方的说明文档,指导板子的基本使用。如何烧写系统,如何启动系统等等。

    image-20240713194235872

    3.4 下载系统烧写工具

    链接:https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/Atlas%20200I%20DK%20A2/DevKit/tools/latest/Ascend-devkit-imager_latest_win-x86_64.exe

    下载下来之后,直接双击正常安装,安装好之后打开的界面如下。 (选择本地制作)

    image-20240713194924116

    然后将 TF卡通过读卡器插到电脑上,准备烧写系统(就算有些电脑自带了TF卡的插槽也建议用USB读卡器,这个电脑自带的TF卡槽烧写系统无法启动)。 TF卡的容量至少要32G,最好是64G。

    image-20240713195607403

    选择要烧写的系统镜像文件(就是刚刚通过网盘下载的ubuntu系统镜像)。

    image-20240713195815959

    然后点击烧录镜像。

    image-20240713200023532

    弹出提示框,选择确认

    image-20240713200052668

    然后可以看到,系统正在烧写中了,精心等待即可。

    image-20240713200113792

    烧录成功之后,会弹窗提示弹出SD卡

    image-20240713210720997

    TF卡从电脑上弹出,拔掉就行了。

    image-20240713210748529

    image-20240713193457415

    3.5 设置开发板启动模式

    香橙派 AIpro开发板支持从 TF 卡、eMMC 和 SSD(支持 NVMe SSD 和 SATA SSD)启动。

    具体从哪个设备启动是由开发板背面的两个拨码(BOOT1 和 BOOT2)开关来控制的。

    image-20240713211059551

    BOOT1 和 BOOT2 两个拨码开关都支持左右两种设置状态,所以总共有 4 种设置状态,开发板目前只使用了其中的三种。不同的设置状态对应的启动设备如下表所示:

    image-20240713211152112

    BOOT1 和 BOOT2 两个拨码开关 全部拨到靠右的位置就可以选择从TF卡启动了。

    3.6 启动系统

    【1】将烧写好的TF卡插在板子上。

    image-20240713211425933

    【2】插好网线。

    网线一端接开发板的网口,一端接路由器,自己的笔记本电脑也是接的同一个路由器,让板子与电脑在同一个局域网内,方便接下来远程登录开发板的系统。

    image-20240713211553314

    image-20240713213052792

    【3】插好电源

    板子是没有电源开关的,电源线插好之后,系统就启动了。刚启动的时候风扇的声音会比较大,等待几秒就正常了。

    按下开发板左上角的PWR_OFF可以关闭系统,点击旁边的RESET可以重启系统。

    image-20240713211543200

    3.7 SSH远程登录系统

    系统启动之后,会自动请求路由器分配IP地址,我们只需要登录到开发板连接的路由器后台,就可以看到新接入的设备。

    我的用是小米路由器。直接在浏览器里输入:192.168.31.1 即可进入到路由器的后台终端。

    从下面图片里可以看到,香橙派 AIpro 已经分配到IP地址了,192.168.31.136

    image-20240713223645033

    为了方便远程登录到系统终端,可以下载安装一个FinalShell 软件。

    下载地址:https://www.hostbuf.com/t/988.html

    软件安装打开后,建立一个新的SSH链接,具体看下图的操作。

    image-20240713224703844

    这里面的主机IP地址就是从路由器后台看到的,分配给香橙派 AIpro开发板的IP地址。 端口号是固定的22,这是SSH协议的固定端口。

    用户名是root,密码是:Mind@123 这是烧写的香橙派 AIpro系统固定的用户名和密码,也就是系统内置的。

    双击刚才建立好的链接,就可以登录到系统终端。 进去终端之后基本上就可以进行正常的开发了。

    image-20240713225229527

    3.8 安装xdrp工具

    为了方便图形化方式开发,可以使用windows系统通过远程桌面登录香橙派 AIpro,就可以看到界面了,不过需要先安装工具。

    进入到香橙派 AIpro终端之后,输入安装命令:

    sudo apt-get install xrdp
    

    按下回车之后,会弹出确认窗口。输入 y之后,按下回车,继续安装。

    下面是完整的命令安装过程: 按顺序执行就行了。

    #安装xrdp
    sudo apt-get install xrdp
    #安装vnc4server
    sudo apt-get install vnc4server tightvncserver
    #安装xubuntu-desktop
    sudo apt-get install xubuntu-desktop
    #向xsession中写入xfce4-session
    echo “xfce4-session” >~/.xsession
    #开启xrdp服务
    sudo service xrdp restart
    

    注意,如果之后断电了,远程桌面无法链接上。可以先卸载xrdp,再重新安装即可。

    sudo apt-get remove --purge xrdp
    

    3.9 Window远程登录

    在windows上打开运行命令的窗口,输入mstsc来打开远程桌面。

    image-20240713225908482

    输入mstsc,点击确定。

    image-20240713225941156

    弹出窗口后,填入IP地址(这就是你的香橙派 AIpro 开发板的IP地址),点击连接。

    image-20240713230025713

    正常登录之后,就可以看到远程桌面的界面了。

    image-20240713230144585

    输入账号和密码。

    用户名是root,密码是:Mind@123 这是烧写的香橙派 AIpro系统固定的用户名和密码,也就是系统内置的。

    登录之后的界面:

    image-20240713231948302

    image-20240713232200871

    好了。接下来就可以进行项目的正式开发了。

    3.10 取消自动休眠

    Ubuntu桌面镜像会自动休眠,输入以下指令禁用休眠。

    sudo systemctl status sleep.target
    sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
    

    四、安装Qt开发环境

    因为最终的项目需要使用界面,我的项目准备采用Qt进行开发,这一章节进行安装Qt开发环境。

    4.1 安装qtcreator

    在命令行终端分别输入以下命令安装qtcreator:

    root@orangepiaipro:~# sudo apt-get update
    root@orangepiaipro:~# sudo apt-get install qtcreator
    root@orangepiaipro:~# sudo apt-get install qtmultimedia5-dev
    root@orangepiaipro:~# sudo apt-get install libqt5serialport5-dev
    

    image-20240714000307615

    4.2 启动qtcreator

    安装好之后,就可以看到Qt软件了,点击即可打开。

    image-20240714000353422

    这是启动之后的效果。

    image-20240714000438029

    4.3 配置编译器

    默认安装Qt之后,编译器是配置错误,无法正常使用。

    【1】打开设置页面

    image-20240714001025326

    【2】选择编译器套件

    image-20240714001100905

    【3】配置编译器套件。

    将编译器改为GCC

    image-20240714000644413

    4.4 新建工程Qt环境

    【1】新建工程

    image-20240714001302590

    image-20240714001327984

    【2】设置工程名称和路径(自己单独建立一个文件夹存放工程)

    image-20240714001351170

    image-20240714001442561

    【3】选择继承的基类

    image-20240714001509921

    image-20240714001548171

    【4】选择编译器套件

    image-20240714001610071

    【5】创建完成

    image-20240714001639213

    这就是创建好的工程。

    image-20240714001706696

    【6】点击左下角绿色三角形编译运行。

    下面是正常运行的效果,已经弹窗窗口,说明Qt的环境已经OK了。

    image-20240714001757927

    image-20240714001912150

    五、开发板初步测试

    为了了解下开发板本身的性能,先采用开发板开发一些小项目测试测试效果。

    现在系统根目录创建一个work目录,方便存放接下来的项目文件。

    image-20240714131053362

    5.1 项目1:开发一个基于HTTP协议的网络摄像头

    【1】项目介绍

    本项目主要采用C语言开发,实现了一个网络摄像头项目,在橙派 AIpro开发板上实现了一个HTTP服务器,,处理浏览器的请求,当浏览器访问过来时,就将本地采集到的摄像头画面发送给浏览器,与浏览器建立长连接通信,直接传输JPG图片,实现摄像头画面实时显示效果。支持登录页面,做了一个账号登录界面,访问服务器之后需要输入账号密码才可以正常进入服务器查看共享的画面。如果分享个摄像头画面,那是非常的方便的,想要查看分享的摄像头画面只需要浏览器里输入服务器的IP地址登录进去就可以看画面了。

    通过本项目的测试,可以了解到USB摄像头的读取效果,网络传输的效果。 为后续的其他项目开发做一个参考。

    基于香橙派 AIpro实现的网络摄像头项目-效果

    【2】编写项目代码

    项目开发,代码编写先在Windows下进行,开发完毕,再拷贝到橙派 AIpro开发板上。

    这是在Windows下开发好的项目代码:

    image-20240714131824132

    【3】上传项目代码

    打开FinalShell终端,可以直接将开发好的项目源码,整个目录上传到香橙派 AIpro系统。

    image-20240714131909001

    通过FinalShell终端可以很方便的将香橙派 AIpro系统文件下载到本地,也可以将本地的文件很方便的上传上去。在开发项目的阶段是很方便的。

    image-20240714132128503

    【4】插入USB摄像头

    将USB摄像头插入到开发板的USB口,然后ls /dev/video* 查看摄像头的设备节点,确定摄像头是否识别成功。

    image-20240714135456599

    【4】编译运行项目

    项目里已经构建好了Makefile文件,直接make就可以编译。

    image-20240714135115279

    编译之后,运行项目。

    (base) root@orangepiaipro:~/work/http_camera# make
    (base) root@orangepiaipro:~/work/http_camera# ./http_app 
    ./server <server_port> </dev/videoX>
    (base) root@orangepiaipro:~/work/http_camera# ./http_app 666 /dev/video0 
    

    运行命令的含义:./http_app 666 /dev/video0 666表示服务器的端口号。 /dev/video0是摄像头的端口号。

    【5】浏览器访问

    在自己电脑浏览器地址栏里输入:http://192.168.31.136:666

    就可以看到登录页面。

    image-20240714135834059

    登录成功之后,可以看到摄像头的实时画面。

    image-20240714135805025

    5.2 项目2: 基于华为云设计的智能家居控制系统

    【1】项目介绍

    基于香橙派 AIpro开发板设计的智能家居控制系统,通过MQTT协议连接华为云物联网云平台;通过DHT11传感器读取环境温湿度,将数据上传到华为云物联网云平台。在华为云云平台上也可以远程控制硬件端连接的LED灯,控制3种颜色显示。

    通过本项目的完整开发测试,可以掌握香橙派的GPIO口的基本使用以及网络的测试。为后续的其他项目开发做一个参考测试。

    【2】安装wiringPi

    (1)安装 wiringOP 前,请先确保 Linux 系统中存在/etc/orangepi-release 这个配置文件,里面的内容为:BOARD=orangepiaipro。

    (base) root@orangepiaipro:~/work/# cat /etc/orangepi-release
    BOARD=orangepiaipro
    

    (2)如果 Linux 中没有/etc/orangepi-release 这个配置文件,可以使用下面的命令创建一个。

    (base) root@orangepiaipro:~/work/# echo "BOARD=orangepiaipro" | sudo tee /etc/orangepi-release
    

    (3)下载 wiringOP 的代码。

    (base) root@orangepiaipro:~/work/# sudo apt-get update
    (base) root@orangepiaipro:~/work/# sudo apt-get install -y git
    (base) root@orangepiaipro:~/work/# git clone https://github.com/orangepi-xunlong/wiringOP.git -b next
    

    (4)然后编译安装 wiringOP。

    (base) root@orangepiaipro:~/work/# sudo apt-get install -y gcc make build-essential
    (base) root@orangepiaipro:~/work/# cd wiringOP
    (base) root@orangepiaipro:~/work/wiringOP# sudo ./build clean
    (base) root@orangepiaipro:~/work/wiringOP# sudo ./build
    

    (5)编译完之后,可以看到生成的文件

    image-20240714144232399

    image-20240714144341068

    查看GPIO口信息。

    (base) root@orangepiaipro:~/work/wiringOP# cd gpio/
    (base) root@orangepiaipro:~/work/wiringOP/gpio# ./gpio readall
           
     +------+-----+----------+--------+---+  AI PRO  +---+--------+----------+-----+------+
     | GPIO | wPi |   Name   |  Mode  | V | Physical | V |  Mode  | Name     | wPi | GPIO |
     +------+-----+----------+--------+---+----++----+---+--------+----------+-----+------+
     |      |     |     3.3V |        |   |  1 || 2  |   |        | 5V       |     |      |
     |   76 |   0 |     SDA7 |    OFF | 0 |  3 || 4  |   |        | 5V       |     |      |
     |   75 |   1 |     SCL7 |    OFF | 0 |  5 || 6  |   |        | GND      |     |      |
     |  226 |   2 | GPIO7_02 |    OFF | 0 |  7 || 8  | 0 | OFF    | UTXD0    | 3   | 14   |
     |      |     |      GND |        |   |  9 || 10 | 0 | OFF    | URXD0    | 4   | 15   |
     |   82 |   5 | GPIO2_18 |    OFF | 0 | 11 || 12 | 0 | OFF    | GPIO7_03 | 6   | 227  |
     |   38 |   7 | GPIO1_06 |     IN | 1 | 13 || 14 |   |        | GND      |     |      |
     |   79 |   8 | GPIO2_15 |     IN | 1 | 15 || 16 | 1 | IN     | GPIO2_16 | 9   | 80   |
     |      |     |     3.3V |        |   | 17 || 18 | 0 | IN     | GPIO0_25 | 10  | 25   |
     |   91 |  11 | SPI0_SD0 |    OFF | 0 | 19 || 20 |   |        | GND      |     |      |
     |   92 |  12 | SPI0_SDI |    OFF | 0 | 21 || 22 | 1 | IN     | GPIO0_02 | 13  | 2    |
     |   89 |  14 | SPI0_CLK |    OFF | 0 | 23 || 24 | 0 | OFF    | SPI0_CS  | 15  | 90   |
     |      |     |      GND |        |   | 25 || 26 | 0 | IN     | GPIO2_19 | 16  | 83   |
     |      |     |     SDA6 |        |   | 27 || 28 |   |        | SCL6     |     |      |
     |  231 |  17 |    URXD7 |    OFF | 0 | 29 || 30 |   |        | GND      |     |      |
     |   84 |  18 | GPIO2_20 |     IN | 0 | 31 || 32 | 0 | IN     | GPIO1_01 | 19  | 35   |
     |  128 |  20 | GPIO4_00 |     IN | 1 | 33 || 34 |   |        | GND      |     |      |
     |  228 |  21 | GPIO7_04 |    OFF | 0 | 35 || 36 | 0 | OFF    | GPIO2_17 | 22  | 81   |
     |    3 |  23 | GPIO0_03 |     IN | 1 | 37 || 38 | 0 | IN     | GPIO7_06 | 24  | 230  |
     |      |     |      GND |        |   | 39 || 40 | 0 | OFF    | GPIO7_05 | 25  | 229  |
     +------+-----+----------+--------+---+----++----+---+--------+----------+-----+------+
     | GPIO | wPi |   Name   |  Mode  | V | Physical | V |  Mode  | Name     | wPi | GPIO |
     +------+-----+----------+--------+---+  AI PRO  +---+--------+----------+-----+------+
    

    可以通过命令行测试GPIO口:

    (base) root@orangepiaipro:~/work# ./gpio mode 2 out   设置 wPi  为2的这个IO口为输出模式
    (base) root@orangepiaipro:~/work# ./gpio write 2 0    设置 wPi  为2的这个IO口输出0 (低电平)
    (base) root@orangepiaipro:~/work# ./gpio write 2 1    设置 wPi  为2的这个IO口输出1 (高电平)
    

    【3】GPIO口布局

    image-20240714145819245

    【4】控制LED灯

    image-20240714154247320

    通过香橙派 AIpro实现三色灯的控制(GPIO口控制效果)

    编写的测试代码:

    #include <stdio.h>
    #include <wiringPi.h>
    #include <stdlib.h>
    #include <string.h>
    /*控制继电器高低电平亮灯*/
    
    #define LEDG 0
    #define LEDB 1
    #define LEDR 2
    
    
    int main()
    {
    	wiringPiSetup();  //置引脚编号方式为wiringPi编码
    	pinMode(LEDG,OUTPUT);
    	pinMode(LEDB,OUTPUT);
    	pinMode(LEDR,OUTPUT);
    
    
    	while(1)
    	{
    		//全部关闭
    		digitalWrite(LEDG,LOW);
    		digitalWrite(LEDB,LOW);
    		digitalWrite(LEDR,LOW);
    		//亮蓝色
    		digitalWrite(LEDG,HIGH);
    		sleep(1);
    		
    		
    		//全部关闭
    		digitalWrite(LEDG,LOW);
    		digitalWrite(LEDB,LOW);
    		digitalWrite(LEDR,LOW);
    		//亮绿色
    		digitalWrite(LEDB,HIGH);
    		sleep(1);
    		
    		
    		//全部关闭
    		digitalWrite(LEDG,LOW);
    		digitalWrite(LEDB,LOW);
    		digitalWrite(LEDR,LOW);
    		//亮红色
    		digitalWrite(LEDR,HIGH);
    		sleep(1);
    		
    		
    		
    	}
    	
         return 0;
    }
    
    

    编译代码:

    (base) root@orangepiaipro:~/work# gcc led.c -lwiringPi
    

    运行代码:

    (base) root@orangepiaipro:~/work# ./a.out 
    

    运行效果:

    image-20240714154950089

    【5】DHT11温湿度传感器数据读取

    image-20240714155252689

    代码:

    #include <wiringPi.h>
    #include <stdio.h>
    #include <stdlib.h>
     
    //编译:gcc -Wall -o dht11 dht11.c -lwiringPi -o app
     
     
    typedef unsigned char uint8;
    typedef unsigned int  uint16;
    typedef unsigned long uint32;
     
    #define HIGH_TIME 32
     
    int pinNumber = 5;
    uint32 databuf;
      
    uint8 readSensorData(void)
    {
        uint8 crc; 
        uint8 i;
      
        pinMode(pinNumber, OUTPUT); // set mode to output
        digitalWrite(pinNumber, 0); // output a high level 
        delay(25);
        digitalWrite(pinNumber, 1); // output a low level 
        pinMode(pinNumber, INPUT); // set mode to input
        pullUpDnControl(pinNumber, PUD_UP);
     
        delayMicroseconds(27);
        if (digitalRead(pinNumber) == 0) //SENSOR ANS
        {
            while (!digitalRead(pinNumber))
                ; //wait to high
     
            for (i = 0; i < 32; i++)
            {
                while (digitalRead(pinNumber))
                    ; //data clock start
                while (!digitalRead(pinNumber))
                    ; //data start
                delayMicroseconds(HIGH_TIME);
                databuf *= 2;
                if (digitalRead(pinNumber) == 1) //1
                {
                    databuf++;
                }
            }
     
            for (i = 0; i < 8; i++)
            {
                while (digitalRead(pinNumber))
                    ; //data clock start
                while (!digitalRead(pinNumber))
                    ; //data start
                delayMicroseconds(HIGH_TIME);
                crc *= 2;  
                if (digitalRead(pinNumber) == 1) //1
                {
                    crc++;
                }
            }
            return 1;
        }
        else
        {
            return 0;
        }
    }
      
    int main(void)
    {
        printf("PIN:%d\n", pinNumber);
     
        wiringPiSetup();  //置引脚编号方式为wiringPi编码
       
        pinMode(pinNumber, OUTPUT); // set mode to output
        digitalWrite(pinNumber, 1); // output a high level 
     
        printf("Starting...\n");
        while (1) 
        {
            pinMode(pinNumber, OUTPUT); // set mode to output
            digitalWrite(pinNumber, 1); // output a high level 
            delay(3000);
            if (readSensorData())
            {
                printf("Sensor data read ok!\n");
                printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff); 
                printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff);
                databuf = 0;
            }
            else
            {
                printf("Sensor dosent ans!\n");
                databuf = 0;
            }
        }
        return 0;
    }
    

    编译代码:

    (base) root@orangepiaipro:~/work# gcc dht11.c -lwiringPi
    

    运行代码:

    (base) root@orangepiaipro:~/work# ./a.out 
    PIN:5
    Starting...
    RH:68.4
    TMP:30.4
    RH:68.5
    TMP:30.2
    RH:68.2
    TMP:30.4
    RH:68.1
    TMP:30.3
    RH:68.1
    TMP:30.6
    RH:68.1
    TMP:30.4
    

    实物图:

    image-20240714155731504

    【6】注册华为云设备

    华为云物联网平台的整体就不再详细展示了,可以直接看视频。

    B站的视频链接:https://www.bilibili.com/video/BV1mr421c75S

    手把手讲解华为云物联网云平台的使用以及应用侧的开发(2024最新版)

    (1)注册产品

    image-20240714162050418

    (2)注册设备

    image-20240714160920091

    (3)创建命令

    image-20240714161446789

    image-20240714162113037

    (4)得到MQTT三元组

    IP地址:117.78.5.125
    端口号:1883
    
    ClientId   6693872aa559ef6226685350_dev1_0_0_2024071408
    Username   6693872aa559ef6226685350_dev1
    Password   8ce1b26a6fac2c52402d2911a9a951efe6026f71041037a963bebdd4a099190f
    
    订阅主题:$oc/devices/6693872aa559ef6226685350_dev1/sys/messages/down
    
    发布主题:$oc/devices/6693872aa559ef6226685350_dev1/sys/properties/report
    发布数据:{"services": [{"service_id": "stm32","properties":{"DHT11_T":23,"DHT11_H":80}}]}
    

    【7】编写整体项目

    接下来就编写代码,连接华为云物联网平台,完成数据上传。 将采集的温湿度数据上传到华为云物联网云平台。 同时支持在华为云物联网平台下发命令远程控制设备端的LED灯。

    代码是采用纯C语言编写,实现了MQTT协议,完成了与物联网云平台交互。

    关于MQTT协议的整体编写过程,可以直接看视频:https://www.bilibili.com/video/BV1BN4y1Y7cf

    从0开始编写MQTT协议代码连接标准MQTT服务器(精讲MQTT协议)

    (1)这是写好的项目代码

    image-20240714163848551

    image-20240714164238391

    完整的代码:

    #include <stdio.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h> /* superset of previous */
    #include <arpa/inet.h>
    #include <poll.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <signal.h>
    #include "mqtt.h"
    #include "main.h"
    #include <netdb.h>
    #include <stdio.h>
    #include <wiringPi.h>
    #include <stdlib.h>
    #include <string.h>
    
    
    /*控制继电器高低电平亮灯*/
    
    #define LEDG 0
    #define LEDB 1
    #define LEDR 2
    
    
    
    //服务器IP
    #define SERVER_IP "117.78.5.125"
    #define SERVER_PORT 1883 //端口号
    
    //MQTT三元组
    #define ClientID "6693872aa559ef6226685350_dev1_0_0_2024071408"
    #define Username "6693872aa559ef6226685350_dev1"
    #define Password "8ce1b26a6fac2c52402d2911a9a951efe6026f71041037a963bebdd4a099190f"//密文 
    
    //订阅主题:
    #define SET_TOPIC  "$oc/devices/6693872aa559ef6226685350_dev1/sys/messages/down"//订阅
    //发布主题:
    #define POST_TOPIC "$oc/devices/6693872aa559ef6226685350_dev1/sys/properties/report"//发布
    
    
    char mqtt_message[1024*1024];//上报数据缓存区
    char request_id[100];
    char mqtt_cmd_message[100];
    char mqtt_cmd_data[100];
    
    int sockfd;
    /*获取平台下发数据*/
    void *pth_work_func(void *arg)
    {
         char buff[1024];
         int size=0;
         int i=0;
         while(1)
        {
            size=Client_GetData(buff);
    		printf("size=%d\r\n",size);
            if(size<0)break;
            for(i=0;i<size;i++)
            {
                printf("%c ",buff[i]);
            }
    		buff[size]='\0';
    		if(size>5)
    		{
    			printf("%s\r\n",buff+5);
    			
    			if(strstr((char*)&buff[5],"sys/commands/request_id="))
    			{
    				char *p=NULL;
    				p=strstr((char*)&buff[5],"request_id");
    				if(p)
    				{        
    					//解析数据
    					//$oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"gps"} 
    					strncpy(request_id,p,47);      
    				}
    				
    				//上报数据
    				sprintf(mqtt_cmd_message,"{\"result_code\":0,\"response_name\":\"COMMAND_RESPONSE\",\"paras\":{\"result\":\"success\"}}");
    				
    				sprintf(mqtt_cmd_data,"$oc/devices/6693872aa559ef6226685350_dev1/sys/commands/response/%s",
    				request_id);
    				
    				MQTT_PublishData(mqtt_cmd_data,mqtt_cmd_message,0);
    				
    				printf("应答-发布主题:%s\r\n",mqtt_cmd_data);
    				printf("应答-发布数据:%s\r\n",mqtt_cmd_message);
    			} 
    			if(strstr((char*)&buff[5],"\"LED_SW\":1")) 
    			{
    				//全部关闭
    				digitalWrite(LEDG,LOW);
    				digitalWrite(LEDB,LOW);
    				digitalWrite(LEDR,LOW);
    				//亮蓝色
    				digitalWrite(LEDG,HIGH);
    			}
    			if(strstr((char*)&buff[5],"\"LED_SW\":2")) 
    			{
    				//全部关闭
    				digitalWrite(LEDG,LOW);
    				digitalWrite(LEDB,LOW);
    				digitalWrite(LEDR,LOW);
    				//亮绿色
    				digitalWrite(LEDB,HIGH);
    			} 
    			if(strstr((char*)&buff[5],"\"LED_SW\":3")) 
    			{
    				//全部关闭
    				digitalWrite(LEDG,LOW);
    				digitalWrite(LEDB,LOW);
    				digitalWrite(LEDR,LOW);
    				//亮红色
    				digitalWrite(LEDR,HIGH);
    			} 
    			if(strstr((char*)&buff[5],"\"LED_SW\":0")) 
    			{
    				//全部关闭
    				digitalWrite(LEDG,LOW);
    				digitalWrite(LEDB,LOW);
    				digitalWrite(LEDR,LOW);
    
    			} 
    		}
            printf("\r\n");
        }
    }
     
     
    /*信号处理函数*/
     void signal_func(int sig)
    {
    	//printf("捕获的信号:%d\n",sig);
    	if(sig==SIGALRM)
    	{
            MQTT_SentHeart();//心跳包
    		alarm(5);
    	}
    }
    
    
    
    typedef unsigned char uint8;
    typedef unsigned int  uint16;
    typedef unsigned long uint32;
     
    #define HIGH_TIME 32
     
    int pinNumber = 5;
    uint32 databuf;
      
    uint8 readSensorData(void)
    {
        uint8 crc; 
        uint8 i;
      
        pinMode(pinNumber, OUTPUT); // set mode to output
        digitalWrite(pinNumber, 0); // output a high level 
        delay(25);
        digitalWrite(pinNumber, 1); // output a low level 
        pinMode(pinNumber, INPUT); // set mode to input
        pullUpDnControl(pinNumber, PUD_UP);
     
        delayMicroseconds(27);
        if (digitalRead(pinNumber) == 0) //SENSOR ANS
        {
            while (!digitalRead(pinNumber))
                ; //wait to high
     
            for (i = 0; i < 32; i++)
            {
                while (digitalRead(pinNumber))
                    ; //data clock start
                while (!digitalRead(pinNumber))
                    ; //data start
                delayMicroseconds(HIGH_TIME);
                databuf *= 2;
                if (digitalRead(pinNumber) == 1) //1
                {
                    databuf++;
                }
            }
     
            for (i = 0; i < 8; i++)
            {
                while (digitalRead(pinNumber))
                    ; //data clock start
                while (!digitalRead(pinNumber))
                    ; //data start
                delayMicroseconds(HIGH_TIME);
                crc *= 2;  
                if (digitalRead(pinNumber) == 1) //1
                {
                    crc++;
                }
            }
            return 1;
        }
        else
        {
            return 0;
        }
    }
      
    
    
    unsigned int DHT11_T;// 	环境温度
    unsigned int DHT11_H;//	环境湿度
    
    
    int main()
    {
    	
    	wiringPiSetup();  //置引脚编号方式为wiringPi编码
    	pinMode(LEDG,OUTPUT);
    	pinMode(LEDB,OUTPUT);
    	pinMode(LEDR,OUTPUT);
    	
    	
    	//DHT11温湿度初始化
    	pinMode(pinNumber, OUTPUT); // set mode to output
        digitalWrite(pinNumber, 1); // output a high level 
    	
        int stat;
        sockfd=socket(AF_INET,SOCK_STREAM,0);
        if(sockfd==-1)
        {
            printf("网络套接字打开失败\n");
            return 0;
        }
        signal(SIGPIPE,SIG_IGN);/*忽略SIGPIPE信号*/
        signal(SIGALRM,signal_func);/*闹钟信号*/
    	
    	
        /*连接服务器*/
         struct sockaddr_in addr;
         addr.sin_family=AF_INET;//IPV4
         addr.sin_port=htons(SERVER_PORT);/*端口号*/
         addr.sin_addr.s_addr=inet_addr(SERVER_IP);//inet_addr(ip);//服务器IP
         if(connect(sockfd, (struct sockaddr *)&addr,sizeof(struct sockaddr_in))==0)
         {
            printf("server connect ok\n");
    		 
    		MQTT_Init();
    		  
             while(1)
             {
               
                /*登录服务器*/
                if(MQTT_Connect(ClientID,Username,Password)==0)
                {
                    break;
                }
                sleep(1);
                printf("server connect ....\n");
             }
            printf("MQTT_Connect OK\r\n");
            //订阅物联网平台数据
            stat=MQTT_SubscribeTopic(SET_TOPIC,1,1);
            if(stat)
            {
                close(sockfd);
                printf("MQTT_SubscribeTopic ERROR\r\n");  
                exit(0);
            }
    		printf("MQTT_SubscribeTopic ok\r\n");
            /*创建线程*/
            pthread_t id;
            pthread_create(&id, NULL,pth_work_func,NULL);
            pthread_detach(id);//设置分离属性
           
    	   
    	   //发送心跳包
    	   // alarm(5);//闹钟函数,时间到达会产生SIGALRM信号
    
            while(1)
            {
    			//读取DHT11温湿度数据
    			pinMode(pinNumber, OUTPUT); // set mode to output
    			digitalWrite(pinNumber, 1); // output a high level 
    			delay(3000);
    			if (readSensorData())
    			{
    				printf("DHT11 Sensor data read ok!\n");
    				printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff); 
    				printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff);
    				
    				
    				//温度整数部分
    				DHT11_H=((databuf >> 24) & 0xff);
    				printf("DHT11_T:%d\r\n",DHT11_T);
    				//湿度整数部分
    				DHT11_T=((databuf >> 8) & 0xff);
    				printf("DHT11_H:%d\r\n",DHT11_H);
    				
    				databuf = 0;
    				
    			}
    			else
    			{
    				printf("Sensor dosent ans!\n");
    				databuf = 0;
    			}
    		
    
    			
    			//组合传感器状态数据
    			sprintf(mqtt_message,"{\"services\": [{\"service_id\": \"stm32\",\"properties\":{\"DHT11_T\":%d,\"DHT11_H\":%d}}]}",DHT11_T,DHT11_H);//温度
                
    			//上报数据
    			MQTT_PublishData(POST_TOPIC,mqtt_message,0);
    			printf("MQTT_PublishData....\r\n");
                sleep(2);
            }
         }
    }
    

    (2)这是编译运行后的效果

    image-20240714163715479

    (3)在华为云物联网平台后台,可以看到设备已经在线了,同时也实时收到设备端上传的数据。

    image-20240714164311508

    (4)下发命令测试。 通过命令下发控制设备端的LED灯。

    image-20240714164457620

    5.3 项目3:OpenCV+卷积神经网络实现人脸识别

    本项目通过OpenCV加载训练好的SSD模型,实现人脸检测,能够在图像中找到并标记出人脸的位置和置信度。

    通过本项目,可以验证整个系统的算法运行速度。为后续的项目开发做参考。

    (1)安装python (烧写的系统本身自带了完整的Python环境,可以不需要安装,如果没有才需要安装)

    sudo apt update
    sudo apt install python3
    

    (2)编写代码加载模型识别人脸

    import cv2
    import numpy as np
    import time
    
    prototxt_path = "./deploy.prototxt.txt"
    model_path = "./res10_300x300_ssd_iter_140000_fp16.caffemodel"
    image_path = "6.jpg"
    
    # 加载模型
    model = cv2.dnn.readNetFromCaffe(prototxt_path, model_path)
    
    # 读取图像
    image = cv2.imread(image_path)
    h, w = image.shape[:2]
    
    # 准备模型输入的 blob
    blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0), swapRB=False)
    
    # 设置 blob 作为模型的输入
    model.setInput(blob)
    
    # 进行推断并获取输出
    start_time = time.time()
    output = model.forward()
    end_time = time.time()
    
    # 遍历检测结果
    font_scale = 1.0
    for i in range(output.shape[2]):
        confidence = output[0, 0, i, 2]
    
        # 通过置信度阈值过滤弱检测结果
        if confidence > 0.5:
            box = output[0, 0, i, 3:7] * np.array([w, h, w, h])
            (start_x, start_y, end_x, end_y) = box.astype("int")
    
            # 绘制边界框和置信度
            cv2.rectangle(image, (start_x, start_y), (end_x, end_y), (255, 0, 0), 2)
            text = f"{confidence * 100:.2f}%"
            cv2.putText(image, text, (start_x, start_y - 10), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 0, 0), 2)
    
    # 显示和保存带有检测结果的图像
    cv2.imwrite("beauty_detected.jpg", image)
    
    # 输出识别耗时
    print(f"识别耗时:{end_time - start_time:.3f} 秒")
    
    

    (3)运行效果

    image-20240714171723373

    (4)将图片下载下来打开

    image-20240714171744817

    5.4 项目4:OpenCV+YOLOv3实现目标检测

    本项目通过OpenCV加载YOLOV3官方的模型,实现目标。

    通过本项目,可以验证整个系统的算法运行速度。为后续的项目开发做参考。

    实现代码:

    #include <opencv2/opencv.hpp>
    #include <opencv2/dnn.hpp>
    
    #include <fstream>
    #include <iostream>
    #include <algorithm>
    #include <cstdlib>
    
    using namespace std;
    using namespace cv;
    using namespace cv::dnn;
    void image_detection();
    
    
    String yolo_cfg = "./yolov3.cfg";
    String yolo_model = "./yolov3.weights";
    
    int main(int argc, char** argv)
    {
    	image_detection();
    }
    
    void image_detection() {
    	//加载网络模型
    	Net net = readNetFromDarknet(yolo_cfg, yolo_model);
    
    	//net.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE);
    	net.setPreferableTarget(DNN_TARGET_CPU);
    	std::vector<String> outNames = net.getUnconnectedOutLayersNames();
    	for (int i = 0; i < outNames.size(); i++) {
    		printf("output layer name : %s\n", outNames[i].c_str());
    	}
    
    	vector<string> classNamesVec;
    	ifstream classNamesFile("./coco.names");
    	if (classNamesFile.is_open())
    	{
    		string className = "";
    		while (std::getline(classNamesFile, className))
    			classNamesVec.push_back(className);
    	}
    
    	// 加载图像 
    	Mat frame = imread("6.jpg");
    	Mat inputBlob = blobFromImage(frame, 1 / 255.F, Size(416, 416), Scalar(), true, false);
    	net.setInput(inputBlob);
    
    	// 检测
    	std::vector<Mat> outs;
    	net.forward(outs, outNames);
    	vector<double> layersTimings;
    	double freq = getTickFrequency() / 1000;
    	double time = net.getPerfProfile(layersTimings) / freq;
    	ostringstream ss;
    	ss << "detection time: " << time << " ms";
    	putText(frame, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));
    	vector<Rect> boxes;
    	vector<int> classIds;
    	vector<float> confidences;
    	for (size_t i = 0; i < outs.size(); ++i)
    	{
    		// Network produces output blob with a shape NxC where N is a number of
    		// detected objects and C is a number of classes + 4 where the first 4
    		// numbers are [center_x, center_y, width, height]
    		float* data = (float*)outs[i].data;
    		for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
    		{
    			Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
    			Point classIdPoint;
    			double confidence;
    			minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
    			if (confidence > 0.5)
    			{
    				int centerX = (int)(data[0] * frame.cols);
    				int centerY = (int)(data[1] * frame.rows);
    				int width = (int)(data[2] * frame.cols);
    				int height = (int)(data[3] * frame.rows);
    				int left = centerX - width / 2;
    				int top = centerY - height / 2;
    
    				classIds.push_back(classIdPoint.x);
    				confidences.push_back((float)confidence);
    				boxes.push_back(Rect(left, top, width, height));
    			}
    		}
    	}
    
    	vector<int> indices;
    	NMSBoxes(boxes, confidences, 0.5, 0.2, indices);
    	for (size_t i = 0; i < indices.size(); ++i)
    	{
    		int idx = indices[i];
    		Rect box = boxes[idx];
    		String className = classNamesVec[classIds[idx]];
    		putText(frame, className.c_str(), box.tl(), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 0, 0), 2, 8);
    		rectangle(frame, box, Scalar(0, 0, 255), 2, 8, 0);
    	}
    
         # 保存结果图片
        cv2.imwrite('detections.jpg', frame)
    	waitKey(0);
    	return;
    }
    

    将识别的图片结果拷贝下来,查看效果。

    image-20240714180036886

    五、测温项目开发

    5.1 外设模块选型

    需要用到的传感器如下:

    (1)LU90614非接触式红外测温模块(串口协议),用于测量体温。

    (2)DHT11温湿度传感器,用于测量环境的温湿度。

    (3)USB摄像头,用于捕获图像,检测人脸。

    (4)一块香橙派 AIpro主控板。

    (5)一个三色LED灯,用于显示检测的体温状态。 红、绿、蓝 三种颜色。

    5.2 整体的项目代码

    整体项目是采用Qt开发的,因为需要通过显示屏展示界面,在界面上显示人脸的识别效果,温度测量效果等信息。

    【1】技术实现方式说明

    (1)这里面的LU90614非接触式红外测温模块 采用USB-TTL模块接入系统的。在/dev目录下的节点是ttyUSB0。没有使用开发板本身的IO口。

    (2)本项目是先在Windows下开发完成后,再上传到香橙派 AIpro开发板运行,在香橙派 AIpro里安装了Qt的开发环境。

    (3)体温传感器的串口数据读取,没有采用Qt本身的串口接口,而是采用了标准Linux下的方式读取串口数据。

    (4)摄像头的采集没有采用Qt的内置接口,而是采用了Linux下V4L2框架完成的图像采集。

    (5)MQTT协议没有采用第三方库,是自己基于Linux下的socket,从0开始编写的。

    (6)显示屏采用HDMI接口的7寸显示屏。作为整个项目的界面终端。

    【2】摄像头图像采集代码

    下面是采集USB实时画面的代码。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <linux/videodev2.h>
    
    #define WIDTH 640
    #define HEIGHT 480
    
    struct buffer {
        void *start;
        size_t length;
    };
    
    int pthread_run()
    {
        int fd;
        struct v4l2_format fmt;
        struct v4l2_requestbuffers req;
        struct v4l2_buffer buf;
        enum v4l2_buf_type type;
        struct buffer *buffers;
        unsigned char *rgb888_buffer;
        
        // 打开摄像头设备
        fd = open("/dev/video0", O_RDWR);
        if (fd == -1) {
            perror("打开/dev/video0失败");
            return 1;
        }
        
        // 设置格式
        memset(&fmt, 0, sizeof(fmt));
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmt.fmt.pix.width = WIDTH;
        fmt.fmt.pix.height = HEIGHT;
        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 使用YUYV格式,常见于USB摄像头
        fmt.fmt.pix.field = V4L2_FIELD_NONE;
        if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
            perror("设置格式失败");
            close(fd);
            return 1;
        }
        
        // 请求缓冲区
        memset(&req, 0, sizeof(req));
        req.count = 1; // 缓冲区数量
        req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory = V4L2_MEMORY_MMAP;
        if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
            perror("请求缓冲区失败");
            close(fd);
            return 1;
        }
        
        // 分配并映射缓冲区
        buffers = calloc(req.count, sizeof(*buffers));
        if (!buffers) {
            perror("分配缓冲区内存失败");
            close(fd);
            return 1;
        }
        
        // 查询缓冲区
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = 0;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
            perror("查询缓冲区失败");
            close(fd);
            return 1;
        }
        
        // 内存映射
        buffers[0].length = buf.length;
        buffers[0].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffers[0].start == MAP_FAILED) {
            perror("内存映射失败");
            close(fd);
            return 1;
        }
        
        // 开始流式传输
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
            perror("开始流式传输失败");
            close(fd);
            return 1;
        }
        
        // 捕获循环(示例:捕获一帧)
        while (1) {
            // 入队缓冲区
            if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
                perror("入队缓冲区失败");
                close(fd);
                return 1;
            }
            
            // 出队缓冲区
            if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
                perror("出队缓冲区失败");
                close(fd);
                return 1;
            }
            
            // 处理帧(转换为RGB888格式)
            // 示例:将YUYV转换为RGB888
            rgb888_buffer = (unsigned char *)malloc(WIDTH * HEIGHT * 3);
            for (int i = 0, j = 0; i < WIDTH * HEIGHT * 2; i += 4, j += 6) {
                // YUYV到RGB888的简化转换(实际应用中可能需要更复杂的算法)
                unsigned char Y0 = ((unsigned char *)buffers[0].start)[i + 0];
                unsigned char U = ((unsigned char *)buffers[0].start)[i + 1];
                unsigned char Y1 = ((unsigned char *)buffers[0].start)[i + 2];
                unsigned char V = ((unsigned char *)buffers[0].start)[i + 3];
                
                rgb888_buffer[j + 0] = Y0 + 1.402 * (V - 128); // 红色分量
                rgb888_buffer[j + 1] = Y0 - 0.344 * (U - 128) - 0.714 * (V - 128); // 绿色分量
                rgb888_buffer[j + 2] = Y0 + 1.772 * (U - 128); // 蓝色分量
                
                rgb888_buffer[j + 3] = Y1 + 1.402 * (V - 128); // 红色分量
                rgb888_buffer[j + 4] = Y1 - 0.344 * (U - 128) - 0.714 * (V - 128); // 绿色分量
                rgb888_buffer[j + 5] = Y1 + 1.772 * (U - 128); // 蓝色分量
            }
            
            // 使用rgb888_buffer进行进一步处理(如保存到文件、显示)
            // 示例:保存到文件
            FILE *fp = fopen("frame.rgb", "wb");
            if (fp) {
                fwrite(rgb888_buffer, 1, WIDTH * HEIGHT * 3, fp);
                fclose(fp);
            }
            
            free(rgb888_buffer);
            break; // 示例中仅处理一帧,所以退出循环
        }
        
        // 停止流式传输
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
            perror("停止流式传输失败");
            close(fd);
            return 1;
        }
        
        // 解除内存映射
        munmap(buffers[0].start, buf.length);
        
        // 清理和关闭
        free(buffers);
        close(fd);
        
        return 0;
    }
    

    【3】体温数据采集(串口)

    下面是采集体温传感器的代码。

    image-20240714182058655

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/select.h>
    #include <poll.h>
    
    #define SERIAL_DEVICE "/dev/ttyUSB0"
    #define BAUDRATE B9600
    
    int temp_read_pthread() {
        int fd;
        char *sendbuf = "\xFA\xC5\xBF"; // 发送体温模式指令
        char recvbuf[8];
        struct termios options;
        struct pollfd pfd;
        int timeout = 1000; // 超时时间,单位毫秒
    
        // 打开串口设备文件
        if ((fd = open(SERIAL_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) {
            perror("open serial port failed");
            exit(1);
        }
    
        // 设置串口参数
        tcgetattr(fd, &options);
        cfmakeraw(&options);
        options.c_cflag |= (CLOCAL | CREAD);
        options.c_cflag &= ~CSIZE;
        options.c_cflag |= CS8;
        options.c_cflag &= ~PARENB;
        options.c_cflag &= ~CSTOPB;
        options.c_iflag &= ~(IXON | IXOFF | IXANY);
        options.c_oflag &= ~OPOST;
        cfsetispeed(&options, BAUDRATE);
        cfsetospeed(&options, BAUDRATE);
        tcsetattr(fd, TCSANOW, &options);
    
        // 初始化poll结构体
        pfd.fd = fd;
        pfd.events = POLLIN;
    
        // 发送命令
        write(fd, sendbuf, 3);
    
        while (1) {
            // 使用poll等待数据
            if (poll(&pfd, 1, timeout) > 0) {
                int nread = read(fd, recvbuf, sizeof(recvbuf));
                if (nread > 0) {
                    printf("Received data: ");
                    for (int i = 0; i < nread; i++) {
                        printf("%02x ", recvbuf[i]);
                    }
                    printf("\n");
                } else {
                    printf("Read error: %s\n", strerror(errno));
                }
            } else {
                printf("No data received in %d ms\n", timeout);
            }
        }
    
        close(fd);
        return 0;
    }
    

    【4】人脸识别图像处理

    下面是完成人脸识别处理的代码。

    #include "image_handle.h"
    #pragma execution_character_set("utf-8")
    
    //关闭线程
    void ImageHandle::close()
    {
        run_flag=0;
        this->quit();
        this->wait();
    }
    
    
    
    //线程执行函数
    void ImageHandle::run()
    {
        QImage use_image;
    
        while(run_flag)
        {
            //如果没有图像可以处理
            if(start_run==0)
            {
                //休眠100毫秒
                msleep(100);
                continue;
            }
            //表示已经处理过
            start_run=0;
    
            //表示开始处理图像
            Handle_flag=1;
    
            //调用图像处理算法 对 image  的图像进行处理
            //1. 人脸识别
            opencv_face(m_image);
    
            //处理完毕之后
            //将图像传出去给UI界面显示
            emit HandleSend(m_image);
    
            //处理完毕
            Handle_flag=0;
        }
    }
    
    
    
    //传入待处理的图片数据
    void ImageHandle::SetImage(QImage &image)
    {
        if(Handle_flag==0)
        {
            start_run=1; //表示有图像可以处理了
            //保存待处理的原图像
            m_image=image;
        }
    }
    
    
    
    void printMatInfo(const cv::Mat& mat)
    {
        QTextStream out(stdout);
        out << "Type: " << mat.type() << endl;
        out << "Channels: " << mat.channels() << endl;
        out << "Size: " << mat.size().width << "x" << mat.size().height << endl;
        out << "Depth: " << mat.depth() << endl;
        out << "Element Size: " << mat.elemSize() << " bytes" << endl;
        out << "Total Size: " << mat.total() * mat.elemSize() << " bytes" << endl;
    }
    
    
    bool saveMatToFile(const cv::Mat& mat, const std::string& filename)
    {
        // 将cv::Mat保存为图像文件
        bool success = cv::imwrite(filename, mat);
    
        if (!success) {
            // 保存失败时输出错误信息
            std::cerr << "Failed to save image: " << filename << std::endl;
        }
    
        return success;
    }
    
    
    // 绘制马赛克
    void drawMosaic(Mat& image, Rect roi) {
        // 将人脸区域缩小为一定比例,以增加马赛克效果
        Rect smallRoi = roi;
        smallRoi.x += smallRoi.width * 0.1;
        smallRoi.y += smallRoi.height * 0.1;
        smallRoi.width -= smallRoi.width * 0.2;
        smallRoi.height -= smallRoi.height * 0.2;
    
        // 对缩小后的人脸区域进行马赛克处理
        Mat mosaic = image(smallRoi);
        //可以调整数字,调整马赛克的像素大小
        resize(mosaic, mosaic, Size(smallRoi.width / 20, smallRoi.height / 20), INTER_NEAREST);
        resize(mosaic, image(smallRoi), smallRoi.size(), 0, 0, INTER_NEAREST);
    }
    
    #include "widget.h"
    
    //人脸检测代码
    void ImageHandle::opencv_face(QImage qImage)
    {
        QTime time;
        time.start();
    
        //(1)包含必要的头文件和命名空间:
    
        //(2)加载人脸检测模型
        std::string cnn_file_path= OpenCV_CNN_MODEL_FILE_PATH;  //CNN模型文件路径
        std::string prototxt_path = cnn_file_path+"/deploy.prototxt.txt";
        std::string model_path = cnn_file_path+"/res10_300x300_ssd_iter_140000_fp16.caffemodel";
        cv::dnn::Net model = cv::dnn::readNetFromCaffe(prototxt_path, model_path);
    
        //(3)加载图片:
        //Mat frame = imread("D:\\1.png");  // 替换为你的图片路径
    
        Mat frame = QImage_to_cvMat(qImage);
        if (frame.empty())
        {
            ss_log_text("待识别的图片加载失败...\n");
            // 处理图片加载失败的情况
            return;
        }
    
        int h = frame.rows;
        int w = frame.cols;
        cv::Mat blob = cv::dnn::blobFromImage(frame, 1.0, cv::Size(300, 300), cv::Scalar(104.0, 177.0, 123.0));
    
    
        //(4)进行人脸检测:
        model.setInput(blob);
        cv::Mat output = model.forward();
        cv::Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr<float>());
    
        //(5)给每个检测到的人脸绘制马赛克:
    
        int face_number=0;
    
        for (int i = 0; i < detectionMat.rows; ++i) {
            float confidence = detectionMat.at<float>(i, 2);
            if (confidence > 0.5) {
    
                //记录人脸数量
                face_number++;
    
                int start_x = static_cast<int>(detectionMat.at<float>(i, 3) * w);
                int start_y = static_cast<int>(detectionMat.at<float>(i, 4) * h);
                int end_x = static_cast<int>(detectionMat.at<float>(i, 5) * w);
                int end_y = static_cast<int>(detectionMat.at<float>(i, 6) * h);
    
                // 马赛克处理
                cv::Rect roi(start_x, start_y, end_x - start_x, end_y - start_y);
                cv::Mat face_roi = frame(roi);
                cv::resize(face_roi, face_roi, cv::Size(), 0.05, 0.05, cv::INTER_LINEAR);
                cv::resize(face_roi, frame(roi), roi.size(), 0, 0, cv::INTER_NEAREST);
    
                // 绘制边框和文字
                cv::rectangle(frame, cv::Point(start_x, start_y), cv::Point(end_x, end_y), cv::Scalar(255, 0, 0), 2);
                std::ostringstream ss;
                ss << confidence * 100 << "%";
                cv::putText(frame, ss.str(), cv::Point(start_x, start_y - 5), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 0, 0), 2);
            }
        }
    
        //传递出人脸数量
        emit ss_face_number(face_number);
    
        // 在图像上显示识别消耗的时间
        std::ostringstream time_ss;
        time_ss << "Time: " << time.elapsed() << " ms";
        cv::putText(frame, time_ss.str(), cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 0, 0), 2);
    
    
        //转为QImage
        QImage out_image=Mat_to_QImage(frame);
    
        ss_log_text(tr("耗时:%1 ms\n").arg(time.elapsed()));
    
        //qDebug()<<"子线程:"<<QThread::currentThread();
    
        //保存结果
        m_image=out_image.copy();
    }
    
    QImage convertToRGB888(const QImage& image)
    {
        if (image.format() == QImage::Format_RGB888) {
            return image;  // Already in RGB888 format
        }
    
        QImage convertedImage = image.convertToFormat(QImage::Format_RGB888);
        return convertedImage;
    }
    
    
    //可以用。 OpenCV4.0已测试。
    //
    Mat ImageHandle::QImage_to_cvMat(QImage image)
    {
        cv::Mat mat;
            //qDebug() << image.format();
            switch(image.format())
            {
            case QImage::Format_ARGB32:
            case QImage::Format_RGB32:
            case QImage::Format_ARGB32_Premultiplied:
                mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine());
                break;
            case QImage::Format_RGB888:
                mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
    
                //opencv 3.x以及一下,颜色用 CV_BGR2RGB
                cv::cvtColor(mat, mat, COLOR_BGR2RGB);
                break;
            case QImage::Format_Indexed8:
                mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine());
                break;
            }
            return mat;
    }
    
    
    
    QImage ImageHandle::Mat_to_QImage(Mat mat)
    {
    #if 0
        QImage image;
    
       // 检查矩阵是否有效
       if (!mat.empty()) {
           // 创建QImage对象,并分配内存
           image = QImage(mat.cols, mat.rows, QImage::Format_ARGB32);
    
           // 根据Mat的类型和通道数来设置Qt图像格式
           switch (mat.type()) {
               case CV_8UC4:
                   image = QImage(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_ARGB32);
                   break;
    
               case CV_8UC3:
                   image = QImage(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_RGB888);
                   break;
    
               case CV_8UC1:
                   image = QImage(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_Indexed8);
                   break;
           }
    
           // 对象回收
           if (image.format() != QImage::Format_RGB32) {
               image = image.convertToFormat(QImage::Format_RGB32);
           }
       }
    #else
        // Check if the image is valid
        if (mat.empty())
            return QImage();
    
        // Convert the image color space
        cv::Mat rgbMat;
        cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB);
    
        // Create the QImage
        QImage image(rgbMat.data, rgbMat.cols, rgbMat.rows, static_cast<int>(rgbMat.step), QImage::Format_RGB888);
    
    #endif
       return image.copy();
    }
    

    六、总结

    本项目利用香橙派 AIpro开发了一个创新的健康监测解决方案,能够提升医院、疾病防控中心和发热门诊等关键场所的公共卫生管理水平。系统利用香橙派AIpro的强大计算能力,搭载Ubuntu 22.04操作系统,实现了高效的人脸识别与非接触式体温测量功能,显著增强了疾病早期预警和控制的能力。

    通过整体项目开发完成后,这块基于香橙派 AIpro的性能是完全满足了要求;运行了10几个小时, 整个板子不发烫,只是启动的时候风扇有明显噪声,正常进入系统之后,风扇的声音就正常,基本处于静音状态。 板子构造小巧,很容易集成,进行项目开发

    作者:DS小龙哥

    物联沃分享整理
    物联沃-IOTWORD物联网 » 香橙派 AIpro设计医院人脸红外测温系统:从零开始开发实战指南

    发表回复