一、前言
在公共衛(wèi)生事件頻發(fā)的當下,尤其是在全球性疫情爆發(fā)后,國家對公共空間的健康監(jiān)測和管理提出了更高的要求。醫(yī)院、疾病防控中心和發(fā)熱門診作為疫情防控的第一線,需要高效且精準地對進出人員進行健康篩查,以防止病毒傳播,保障醫(yī)護人員及患者的安全。傳統(tǒng)的手動體溫檢測方式不僅效率低下,而且存在交叉感染的風(fēng)險,開發(fā)一種能夠自動、快速、準確地進行人體溫度監(jiān)測與身份識別的系統(tǒng)顯得非常的重要。
當前文章會完整的介紹,如何采用香橙派AIpro
設(shè)計出一套醫(yī)院人臉紅外測溫系統(tǒng)。香橙派AIpro是一款高性價比的邊緣計算設(shè)備,搭載了昇騰 AI 處理器,可提供 8TOPS INT8 的計算能力,能夠運行Ubuntu 22.04操作系統(tǒng),這為部署復(fù)雜的深度學(xué)習(xí)算法提供了硬件基礎(chǔ)。本系統(tǒng)利用OpenCV和SSD算法模型進行人臉檢測,通過各方面的模型訓(xùn)練,能確保在復(fù)雜光線和遮擋條件下仍能有效識別個體;結(jié)合紅外測溫技術(shù),可以非接觸式地測量額頭溫度,避免了傳統(tǒng)接觸式測文章溫可能帶來的衛(wèi)生問題。
考慮到環(huán)境因素對測溫結(jié)果的影響,系統(tǒng)還配備了溫濕度傳感器,以實時監(jiān)測并校準測溫數(shù)據(jù)。為了實現(xiàn)數(shù)據(jù)的實時監(jiān)控與分析,系統(tǒng)通過MQTT協(xié)議將收集到的信息上傳至華為云物聯(lián)網(wǎng)云平臺,便于遠程監(jiān)控和數(shù)據(jù)分析,有助于疫情趨勢的預(yù)測和資源的合理調(diào)配。
本項目整體提供了一個智能化、自動化的人臉識別與體溫監(jiān)測解決方案,以提高公共衛(wèi)生領(lǐng)域的響應(yīng)速度和防控效率,減少人力資源的投入,同時降低潛在的感染風(fēng)險,為構(gòu)建安全健康的醫(yī)療環(huán)境貢獻力量。
本項目在完成最終的功能開發(fā)前,會先單個完成模塊的功能開發(fā),實現(xiàn)了單個模塊功能之后,最終在整體合在一起實現(xiàn)最終的項目開發(fā)。
整體項目會從搭建環(huán)境開始, 一步一步實現(xiàn)最終的項目效果。
下面的開發(fā)出來的最終人臉檢測設(shè)備最終設(shè)計效果:
二、主控板介紹
香橙派 AIpro
開發(fā)板是香橙派聯(lián)合華為精心打造的高性能 AI 開發(fā)板,搭載了昇騰 AI 處理器,可提供 8TOPS INT8 的計算能力,內(nèi)存提供了 8GB 和 16GB兩種版本??梢詫崿F(xiàn)圖像、視頻等多種數(shù)據(jù)分析與推理計算,可廣泛用于教育、機器人、無人機等場景。
下面是香橙派 AIpro
開發(fā)板的配置說明:
模塊 | 規(guī)格 |
---|---|
昇騰 AI 處理器 | 4 核 64 位 Arm 處理器 + AI 處理器 |
AI 算力 | 半精度(FP16):4 TFLOPS 整數(shù)精度(INT8):8 TOPS |
內(nèi)存 | 類型:LPDDR4X 容量:8GB 或 16GB |
存儲 | 板載 32MB 的 SPI Flash Micro SD 卡插槽 eMMC 插座:可外接 eMMC 模塊 M.2 M-Key 接口:可接 2280 規(guī)格的 NVMe SSD 或 SATA SSD |
以太網(wǎng) | 支持 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 等接口 |
按鍵 | 一個復(fù)位鍵,一個關(guān)機鍵,一個升級按鍵 |
撥碼開關(guān) | 2 個撥碼開關(guān):用于控制 SD 卡、eMMC 和 SSD 啟動選項 |
電源 | 支持 Type-C 供電,20V PD-65W 適配器 |
LED 燈 | 一個電源指示燈和一個軟件可控指示燈 |
風(fēng)扇接口 | 4pin,0.8mm 間距,用于接 12V 風(fēng)扇,支持 PWM 控制 |
電池接口 | 2pin,2.54mm 間距,用于接 3 串電池,支持快充 |
調(diào)試串口 | Micro USB 接口的調(diào)試串口 |
支持的操作系統(tǒng) | Ubuntu 22.04 和 openEuler 22.03 |
外觀規(guī)格介紹 | 產(chǎn)品尺寸:107*68mm 重量:82g |
下面是香橙派 AIpro
開發(fā)板的功能模塊介紹:
三、搭建開發(fā)環(huán)境
3.1 準備需要的配件
(1)準備一張至少32G的TFT卡,用來燒寫系統(tǒng)。
(2)準備一個讀卡器,方便插入TFT卡,好方便插入到電腦上拷貝系統(tǒng)
(3)香橙派 AIpro
主板一個
(4)一根網(wǎng)線(方便插路由器上與香橙派 AIpro
連接)
(5)一根type-C的電源線 + 電源插頭(3A電流),這個主板買回來是帶了電源的。 也可以用自己Android手機的數(shù)據(jù)線就行,拿手機充電器供電,因為目前Android手機電源線都是都是type-C
也支持快充的,電流也是滿足需求的。
(6)一個USB攝像頭,用于后續(xù)項目開發(fā)里獲取周圍的實時圖像,識別人臉。 (項目開發(fā)需要使用)
(7)一個串口協(xié)議的紅外測溫傳感器,用于后續(xù)項目開發(fā)里測量體溫。(項目開發(fā)需要使用)
(8)一個外放音箱,支持3.5mm的耳機插孔,方便后續(xù)項目開發(fā)里播放語音提示。(項目開發(fā)需要使用)
(9)一塊顯示屏(這個不是必須的,可以直接Windows遠程桌面訪問系統(tǒng),對前期開發(fā)來說沒有任何影響,只要做成最終的產(chǎn)品才需要配屏幕)。
3.2 開發(fā)板實物圖
拿回來的香橙派 AIpro
開發(fā)板實物是這樣的。
3.3 下載開發(fā)板資料
拿到板子之后,第一件事肯定是先去官網(wǎng)下載板子對應(yīng)的相關(guān)的資料。比如:用戶手冊、系統(tǒng)鏡像、原理圖、開發(fā)工具什么的。
官網(wǎng)地址:http://www.orangepi.cn/html/hardWare/computerAndMicrocontrollers/service-and-support/Orange-Pi-AIpro.html
翻到下面,找到資料下載地址,直接下載就行,下載會跳轉(zhuǎn)到網(wǎng)盤。
系統(tǒng)鏡像我選擇的 ubuntu22.04
。
資料下載下來之后,可以看到有一份官方的說明文檔,指導(dǎo)板子的基本使用。如何燒寫系統(tǒng),如何啟動系統(tǒng)等等。
3.4 下載系統(tǒng)燒寫工具
鏈接:https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/Atlas%20200I%20DK%20A2/DevKit/tools/latest/Ascend-devkit-imager_latest_win-x86_64.exe
下載下來之后,直接雙擊正常安裝,安裝好之后打開的界面如下。 (選擇本地制作)
然后將 TF卡
通過讀卡器插到電腦上,準備燒寫系統(tǒng)(就算有些電腦自帶了TF卡的插槽也建議用USB讀卡器,這個電腦自帶的TF卡槽燒寫系統(tǒng)無法啟動)。 TF卡的容量至少要32G,最好是64G。
選擇要燒寫的系統(tǒng)鏡像文件(就是剛剛通過網(wǎng)盤下載的ubuntu系統(tǒng)鏡像)。
然后點擊燒錄鏡像。
彈出提示框,選擇確認
。
然后可以看到,系統(tǒng)正在燒寫中了,精心等待即可。
燒錄成功之后,會彈窗提示彈出SD卡
。
將TF卡
從電腦上彈出,拔掉就行了。
3.5 設(shè)置開發(fā)板啟動模式
香橙派 AIpro
開發(fā)板支持從 TF 卡、eMMC 和 SSD(支持 NVMe SSD 和 SATA SSD)啟動。
具體從哪個設(shè)備啟動是由開發(fā)板背面的兩個撥碼(BOOT1 和 BOOT2)開關(guān)來控制的。
BOOT1 和 BOOT2 兩個撥碼開關(guān)都支持左右兩種設(shè)置狀態(tài),所以總共有 4 種設(shè)置狀態(tài),開發(fā)板目前只使用了其中的三種。不同的設(shè)置狀態(tài)對應(yīng)的啟動設(shè)備如下表所示:
將BOOT1 和 BOOT2 兩個撥碼開關(guān)
全部撥到靠右的位置就可以選擇從TF卡啟動了。
3.6 啟動系統(tǒng)
【1】將燒寫好的TF卡
插在板子上。
【2】插好網(wǎng)線。
網(wǎng)線一端接開發(fā)板的網(wǎng)口,一端接路由器,自己的筆記本電腦也是接的同一個路由器,讓板子與電腦在同一個局域網(wǎng)內(nèi),方便接下來遠程登錄開發(fā)板的系統(tǒng)。
【3】插好電源
板子是沒有電源開關(guān)的,電源線插好之后,系統(tǒng)就啟動了。剛啟動的時候風(fēng)扇的聲音會比較大,等待幾秒就正常了。
按下開發(fā)板左上角的PWR_OFF
可以關(guān)閉系統(tǒng),點擊旁邊的RESET
可以重啟系統(tǒng)。
3.7 SSH遠程登錄系統(tǒng)
系統(tǒng)啟動之后,會自動請求路由器分配IP地址,我們只需要登錄到開發(fā)板連接的路由器后臺,就可以看到新接入的設(shè)備。
我的用是小米路由器。直接在瀏覽器里輸入:192.168.31.1
即可進入到路由器的后臺終端。
從下面圖片里可以看到,香橙派 AIpro
已經(jīng)分配到IP地址了,192.168.31.136
。
為了方便遠程登錄到系統(tǒng)終端,可以下載安裝一個FinalShell
軟件。
下載地址:https://www.hostbuf.com/t/988.html
軟件安裝打開后,建立一個新的SSH
鏈接,具體看下圖的操作。
這里面的主機IP地址就是從路由器后臺看到的,分配給香橙派 AIpro
開發(fā)板的IP地址。 端口號是固定的22
,這是SSH協(xié)議的固定端口。
用戶名是root
,密碼是:Mind@123
這是燒寫的香橙派 AIpro
系統(tǒng)固定的用戶名和密碼,也就是系統(tǒng)內(nèi)置的。
雙擊剛才建立好的鏈接,就可以登錄到系統(tǒng)終端。 進去終端之后基本上就可以進行正常的開發(fā)了。
3.8 安裝xdrp工具
為了方便圖形化方式開發(fā),可以使用windows系統(tǒng)通過遠程桌面登錄香橙派 AIpro
,就可以看到界面了,不過需要先安裝工具。
進入到香橙派 AIpro
終端之后,輸入安裝命令:
sudo apt-get install xrdp
按下回車之后,會彈出確認窗口。輸入 y
之后,按下回車,繼續(xù)安裝。
下面是完整的命令安裝過程: 按順序執(zhí)行就行了。
#安裝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服務(wù)
sudo service xrdp restart
注意,如果之后斷電了,遠程桌面無法鏈接上??梢韵刃遁dxrdp
,再重新安裝即可。
sudo apt-get remove --purge xrdp
3.9 Window遠程登錄
在windows上打開運行命令的窗口,輸入mstsc
來打開遠程桌面。
輸入mstsc
,點擊確定。
彈出窗口后,填入IP地址(這就是你的香橙派 AIpro
開發(fā)板的IP地址),點擊連接。
正常登錄之后,就可以看到遠程桌面的界面了。
輸入賬號和密碼。
用戶名是root
,密碼是:Mind@123
這是燒寫的香橙派 AIpro
系統(tǒng)固定的用戶名和密碼,也就是系統(tǒng)內(nèi)置的。
登錄之后的界面:
好了。接下來就可以進行項目的正式開發(fā)了。
3.10 取消自動休眠
Ubuntu桌面鏡像會自動休眠,輸入以下指令禁用休眠。
sudo systemctl status sleep.target
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
四、安裝Qt開發(fā)環(huán)境
因為最終的項目需要使用界面,我的項目準備采用Qt進行開發(fā),這一章節(jié)進行安裝Qt開發(fā)環(huán)境。
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
4.2 啟動qtcreator
安裝好之后,就可以看到Qt軟件了,點擊即可打開。
這是啟動之后的效果。
4.3 配置編譯器
默認安裝Qt之后,編譯器是配置錯誤,無法正常使用。
【1】打開設(shè)置頁面
【2】選擇編譯器套件
【3】配置編譯器套件。
將編譯器改為GCC
。
4.4 新建工程Qt環(huán)境
【1】新建工程
【2】設(shè)置工程名稱和路徑(自己單獨建立一個文件夾存放工程)
【3】選擇繼承的基類
【4】選擇編譯器套件
【5】創(chuàng)建完成
這就是創(chuàng)建好的工程。
【6】點擊左下角綠色三角形編譯運行。
下面是正常運行的效果,已經(jīng)彈窗窗口,說明Qt的環(huán)境已經(jīng)OK了。
五、開發(fā)板初步測試
為了了解下開發(fā)板本身的性能,先采用開發(fā)板開發(fā)一些小項目測試測試效果。
現(xiàn)在系統(tǒng)根目錄創(chuàng)建一個work
目錄,方便存放接下來的項目文件。
5.1 項目1:開發(fā)一個基于HTTP協(xié)議的網(wǎng)絡(luò)攝像頭
【1】項目介紹
本項目主要采用C語言開發(fā),實現(xiàn)了一個網(wǎng)絡(luò)攝像頭項目,在橙派 AIpro開發(fā)板
上實現(xiàn)了一個HTTP服務(wù)器,,處理瀏覽器的請求,當瀏覽器訪問過來時,就將本地采集到的攝像頭畫面發(fā)送給瀏覽器,與瀏覽器建立長連接通信,直接傳輸JPG圖片,實現(xiàn)攝像頭畫面實時顯示效果。支持登錄頁面,做了一個賬號登錄界面,訪問服務(wù)器之后需要輸入賬號密碼才可以正常進入服務(wù)器查看共享的畫面。如果分享個攝像頭畫面,那是非常的方便的,想要查看分享的攝像頭畫面只需要瀏覽器里輸入服務(wù)器的IP地址登錄進去就可以看畫面了。
通過本項目的測試,可以了解到USB攝像頭的讀取效果,網(wǎng)絡(luò)傳輸?shù)男Ч?為后續(xù)的其他項目開發(fā)做一個參考。
基于香橙派 AIpro實現(xiàn)的網(wǎng)絡(luò)攝像頭項目-效果
【2】編寫項目代碼
項目開發(fā),代碼編寫先在Windows下進行,開發(fā)完畢,再拷貝到橙派 AIpro開發(fā)板
上。
這是在Windows下開發(fā)好的項目代碼:
【3】上傳項目代碼
打開FinalShell
終端,可以直接將開發(fā)好的項目源碼,整個目錄上傳到香橙派 AIpro
系統(tǒng)。
通過FinalShell
終端可以很方便的將香橙派 AIpro
系統(tǒng)文件下載到本地,也可以將本地的文件很方便的上傳上去。在開發(fā)項目的階段是很方便的。
【4】插入USB攝像頭
將USB攝像頭插入到開發(fā)板的USB口,然后ls /dev/video*
查看攝像頭的設(shè)備節(jié)點,確定攝像頭是否識別成功。
【4】編譯運行項目
項目里已經(jīng)構(gòu)建好了Makefile文件,直接make就可以編譯。
編譯之后,運行項目。
(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表示服務(wù)器的端口號。 /dev/video0
是攝像頭的端口號。
【5】瀏覽器訪問
在自己電腦瀏覽器地址欄里輸入:http://192.168.31.136:666
就可以看到登錄頁面。
登錄成功之后,可以看到攝像頭的實時畫面。
5.2 項目2: 基于華為云設(shè)計的智能家居控制系統(tǒng)
【1】項目介紹
基于香橙派 AIpro開發(fā)板設(shè)計的智能家居控制系統(tǒng),通過MQTT協(xié)議連接華為云物聯(lián)網(wǎng)云平臺;通過DHT11傳感器讀取環(huán)境溫濕度,將數(shù)據(jù)上傳到華為云物聯(lián)網(wǎng)云平臺。在華為云云平臺上也可以遠程控制硬件端連接的LED燈,控制3種顏色顯示。
通過本項目的完整開發(fā)測試,可以掌握香橙派的GPIO口的基本使用以及網(wǎng)絡(luò)的測試。為后續(xù)的其他項目開發(fā)做一個參考測試。
【2】安裝wiringPi
(1)安裝 wiringOP 前,請先確保 Linux 系統(tǒng)中存在/etc/orangepi-release 這個配置文件,里面的內(nèi)容為:BOARD=orangepiaipro。
(base) root@orangepiaipro:~/work/# cat /etc/orangepi-release
BOARD=orangepiaipro
(2)如果 Linux 中沒有/etc/orangepi-release 這個配置文件,可以使用下面的命令創(chuàng)建一個。
(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)編譯完之后,可以看到生成的文件
查看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 設(shè)置 wPi 為2的這個IO口為輸出模式
(base) root@orangepiaipro:~/work# ./gpio write 2 0 設(shè)置 wPi 為2的這個IO口輸出0 (低電平)
(base) root@orangepiaipro:~/work# ./gpio write 2 1 設(shè)置 wPi 為2的這個IO口輸出1 (高電平)
【3】GPIO口布局
【4】控制LED燈
通過香橙派 AIpro實現(xiàn)三色燈的控制(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)
{
//全部關(guān)閉
digitalWrite(LEDG,LOW);
digitalWrite(LEDB,LOW);
digitalWrite(LEDR,LOW);
//亮藍色
digitalWrite(LEDG,HIGH);
sleep(1);
//全部關(guān)閉
digitalWrite(LEDG,LOW);
digitalWrite(LEDB,LOW);
digitalWrite(LEDR,LOW);
//亮綠色
digitalWrite(LEDB,HIGH);
sleep(1);
//全部關(guān)閉
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
運行效果:
【5】DHT11溫濕度傳感器數(shù)據(jù)讀取
代碼:
#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:%dn", 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.%dn", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff);
printf("TMP:%d.%dn", (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
實物圖:
【6】注冊華為云設(shè)備
華為云物聯(lián)網(wǎng)平臺的整體就不再詳細展示了,可以直接看視頻。
B站的視頻鏈接:https://www.bilibili.com/video/BV1mr421c75S
手把手講解華為云物聯(lián)網(wǎng)云平臺的使用以及應(yīng)用側(cè)的開發(fā)(2024最新版)
(1)注冊產(chǎn)品
(2)注冊設(shè)備
(3)創(chuàng)建命令
(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
發(fā)布主題:$oc/devices/6693872aa559ef6226685350_dev1/sys/properties/report
發(fā)布數(shù)據(jù):{"services": [{"service_id": "stm32","properties":{"DHT11_T":23,"DHT11_H":80}}]}
【7】編寫整體項目
接下來就編寫代碼,連接華為云物聯(lián)網(wǎng)平臺,完成數(shù)據(jù)上傳。 將采集的溫濕度數(shù)據(jù)上傳到華為云物聯(lián)網(wǎng)云平臺。 同時支持在華為云物聯(lián)網(wǎng)平臺下發(fā)命令遠程控制設(shè)備端的LED燈。
代碼是采用純C語言編寫,實現(xiàn)了MQTT協(xié)議,完成了與物聯(lián)網(wǎng)云平臺交互。
關(guān)于MQTT協(xié)議的整體編寫過程,可以直接看視頻:https://www.bilibili.com/video/BV1BN4y1Y7cf
從0開始編寫MQTT協(xié)議代碼連接標準MQTT服務(wù)器(精講MQTT協(xié)議)
(1)這是寫好的項目代碼
完整的代碼:
#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
//服務(wù)器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"http://密文
//訂閱主題:
#define SET_TOPIC "$oc/devices/6693872aa559ef6226685350_dev1/sys/messages/down"http://訂閱
//發(fā)布主題:
#define POST_TOPIC "$oc/devices/6693872aa559ef6226685350_dev1/sys/properties/report"http://發(fā)布
char mqtt_message[1024*1024];//上報數(shù)據(jù)緩存區(qū)
char request_id[100];
char mqtt_cmd_message[100];
char mqtt_cmd_data[100];
int sockfd;
/*獲取平臺下發(fā)數(shù)據(jù)*/
void *pth_work_func(void *arg)
{
char buff[1024];
int size=0;
int i=0;
while(1)
{
size=Client_GetData(buff);
printf("size=%drn",size);
if(size<0)break;
for(i=0;i<size;i++)
{
printf("%c ",buff[i]);
}
buff[size]='?';
if(size>5)
{
printf("%srn",buff+5);
if(strstr((char*)&buff[5],"sys/commands/request_id="))
{
char *p=NULL;
p=strstr((char*)&buff[5],"request_id");
if(p)
{
//解析數(shù)據(jù)
//$oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"gps"}
strncpy(request_id,p,47);
}
//上報數(shù)據(jù)
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("應(yīng)答-發(fā)布主題:%srn",mqtt_cmd_data);
printf("應(yīng)答-發(fā)布數(shù)據(jù):%srn",mqtt_cmd_message);
}
if(strstr((char*)&buff[5],""LED_SW":1"))
{
//全部關(guān)閉
digitalWrite(LEDG,LOW);
digitalWrite(LEDB,LOW);
digitalWrite(LEDR,LOW);
//亮藍色
digitalWrite(LEDG,HIGH);
}
if(strstr((char*)&buff[5],""LED_SW":2"))
{
//全部關(guān)閉
digitalWrite(LEDG,LOW);
digitalWrite(LEDB,LOW);
digitalWrite(LEDR,LOW);
//亮綠色
digitalWrite(LEDB,HIGH);
}
if(strstr((char*)&buff[5],""LED_SW":3"))
{
//全部關(guān)閉
digitalWrite(LEDG,LOW);
digitalWrite(LEDB,LOW);
digitalWrite(LEDR,LOW);
//亮紅色
digitalWrite(LEDR,HIGH);
}
if(strstr((char*)&buff[5],""LED_SW":0"))
{
//全部關(guān)閉
digitalWrite(LEDG,LOW);
digitalWrite(LEDB,LOW);
digitalWrite(LEDR,LOW);
}
}
printf("rn");
}
}
/*信號處理函數(shù)*/
void signal_func(int sig)
{
//printf("捕獲的信號:%dn",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;// 環(huán)境溫度
unsigned int DHT11_H;// 環(huán)境濕度
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("網(wǎng)絡(luò)套接字打開失敗n");
return 0;
}
signal(SIGPIPE,SIG_IGN);/*忽略SIGPIPE信號*/
signal(SIGALRM,signal_func);/*鬧鐘信號*/
/*連接服務(wù)器*/
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);//服務(wù)器IP
if(connect(sockfd, (struct sockaddr *)&addr,sizeof(struct sockaddr_in))==0)
{
printf("server connect okn");
MQTT_Init();
while(1)
{
/*登錄服務(wù)器*/
if(MQTT_Connect(ClientID,Username,Password)==0)
{
break;
}
sleep(1);
printf("server connect ....n");
}
printf("MQTT_Connect OKrn");
//訂閱物聯(lián)網(wǎng)平臺數(shù)據(jù)
stat=MQTT_SubscribeTopic(SET_TOPIC,1,1);
if(stat)
{
close(sockfd);
printf("MQTT_SubscribeTopic ERRORrn");
exit(0);
}
printf("MQTT_SubscribeTopic okrn");
/*創(chuàng)建線程*/
pthread_t id;
pthread_create(&id, NULL,pth_work_func,NULL);
pthread_detach(id);//設(shè)置分離屬性
//發(fā)送心跳包
// alarm(5);//鬧鐘函數(shù),時間到達會產(chǎn)生SIGALRM信號
while(1)
{
//讀取DHT11溫濕度數(shù)據(jù)
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.%dn", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff);
printf("TMP:%d.%dn", (databuf >> 8) & 0xff, databuf & 0xff);
//溫度整數(shù)部分
DHT11_H=((databuf >> 24) & 0xff);
printf("DHT11_T:%drn",DHT11_T);
//濕度整數(shù)部分
DHT11_T=((databuf >> 8) & 0xff);
printf("DHT11_H:%drn",DHT11_H);
databuf = 0;
}
else
{
printf("Sensor dosent ans!n");
databuf = 0;
}
//組合傳感器狀態(tài)數(shù)據(jù)
sprintf(mqtt_message,"{"services": [{"service_id": "stm32","properties":{"DHT11_T":%d,"DHT11_H":%d}}]}",DHT11_T,DHT11_H);//溫度
//上報數(shù)據(jù)
MQTT_PublishData(POST_TOPIC,mqtt_message,0);
printf("MQTT_PublishData....rn");
sleep(2);
}
}
}
(2)這是編譯運行后的效果
(3)在華為云物聯(lián)網(wǎng)平臺后臺,可以看到設(shè)備已經(jīng)在線了,同時也實時收到設(shè)備端上傳的數(shù)據(jù)。
(4)下發(fā)命令測試。 通過命令下發(fā)控制設(shè)備端的LED燈。
5.3 項目3:OpenCV+卷積神經(jīng)網(wǎng)絡(luò)實現(xiàn)人臉識別
本項目通過OpenCV加載訓(xùn)練好的SSD模型,實現(xiàn)人臉檢測,能夠在圖像中找到并標記出人臉的位置和置信度。
通過本項目,可以驗證整個系統(tǒng)的算法運行速度。為后續(xù)的項目開發(fā)做參考。
(1)安裝python (燒寫的系統(tǒng)本身自帶了完整的Python環(huán)境,可以不需要安裝,如果沒有才需要安裝)
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)
# 設(shè)置 blob 作為模型的輸入
model.setInput(blob)
# 進行推斷并獲取輸出
start_time = time.time()
output = model.forward()
end_time = time.time()
# 遍歷檢測結(jié)果
font_scale = 1.0
for i in range(output.shape[2]):
confidence = output[0, 0, i, 2]
# 通過置信度閾值過濾弱檢測結(jié)果
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)
# 顯示和保存帶有檢測結(jié)果的圖像
cv2.imwrite("beauty_detected.jpg", image)
# 輸出識別耗時
print(f"識別耗時:{end_time - start_time:.3f} 秒")
(3)運行效果
(4)將圖片下載下來打開
5.4 項目4:OpenCV+YOLOv3實現(xiàn)目標檢測
本項目通過OpenCV加載YOLOV3官方的模型,實現(xiàn)目標。
通過本項目,可以驗證整個系統(tǒng)的算法運行速度。為后續(xù)的項目開發(fā)做參考。
實現(xiàn)代碼:
#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() {
//加載網(wǎng)絡(luò)模型
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 : %sn", 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);
}
# 保存結(jié)果圖片
cv2.imwrite('detections.jpg', frame)
waitKey(0);
return;
}
將識別的圖片結(jié)果拷貝下來,查看效果。
五、測溫項目開發(fā)
5.1 外設(shè)模塊選型
需要用到的傳感器如下:
(1)LU90614非接觸式紅外測溫模塊(串口協(xié)議),用于測量體溫。
(2)DHT11溫濕度傳感器,用于測量環(huán)境的溫濕度。
(3)USB攝像頭,用于捕獲圖像,檢測人臉。
(4)一塊香橙派 AIpro
主控板。
(5)一個三色LED燈,用于顯示檢測的體溫狀態(tài)。 紅、綠、藍 三種顏色。
5.2 整體的項目代碼
整體項目是采用Qt開發(fā)的,因為需要通過顯示屏展示界面,在界面上顯示人臉的識別效果,溫度測量效果等信息。
【1】技術(shù)實現(xiàn)方式說明
(1)這里面的LU90614非接觸式紅外測溫模塊
采用USB-TTL模塊接入系統(tǒng)的。在/dev
目錄下的節(jié)點是ttyUSB0
。沒有使用開發(fā)板本身的IO口。
(2)本項目是先在Windows下開發(fā)完成后,再上傳到香橙派 AIpro
開發(fā)板運行,在香橙派 AIpro
里安裝了Qt的開發(fā)環(huán)境。
(3)體溫傳感器的串口數(shù)據(jù)讀取,沒有采用Qt本身的串口接口,而是采用了標準Linux下的方式讀取串口數(shù)據(jù)。
(4)攝像頭的采集沒有采用Qt的內(nèi)置接口,而是采用了Linux下V4L2框架完成的圖像采集。
(5)MQTT協(xié)議沒有采用第三方庫,是自己基于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;
// 打開攝像頭設(shè)備
fd = open("/dev/video0", O_RDWR);
if (fd == -1) {
perror("打開/dev/video0失敗");
return 1;
}
// 設(shè)置格式
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("設(shè)置格式失敗");
close(fd);
return 1;
}
// 請求緩沖區(qū)
memset(&req, 0, sizeof(req));
req.count = 1; // 緩沖區(qū)數(shù)量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
perror("請求緩沖區(qū)失敗");
close(fd);
return 1;
}
// 分配并映射緩沖區(qū)
buffers = calloc(req.count, sizeof(*buffers));
if (!buffers) {
perror("分配緩沖區(qū)內(nèi)存失敗");
close(fd);
return 1;
}
// 查詢緩沖區(qū)
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
perror("查詢緩沖區(qū)失敗");
close(fd);
return 1;
}
// 內(nèi)存映射
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("內(nèi)存映射失敗");
close(fd);
return 1;
}
// 開始流式傳輸
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
perror("開始流式傳輸失敗");
close(fd);
return 1;
}
// 捕獲循環(huán)(示例:捕獲一幀)
while (1) {
// 入隊緩沖區(qū)
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
perror("入隊緩沖區(qū)失敗");
close(fd);
return 1;
}
// 出隊緩沖區(qū)
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
perror("出隊緩沖區(qū)失敗");
close(fd);
return 1;
}
// 處理幀(轉(zhuǎn)換為RGB888格式)
// 示例:將YUYV轉(zhuǎn)換為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的簡化轉(zhuǎn)換(實際應(yīng)用中可能需要更復(fù)雜的算法)
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; // 示例中僅處理一幀,所以退出循環(huán)
}
// 停止流式傳輸
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
perror("停止流式傳輸失敗");
close(fd);
return 1;
}
// 解除內(nèi)存映射
munmap(buffers[0].start, buf.length);
// 清理和關(guān)閉
free(buffers);
close(fd);
return 0;
}
【3】體溫數(shù)據(jù)采集(串口)
下面是采集體溫傳感器的代碼。
#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 = "xFAxC5xBF"; // 發(fā)送體溫模式指令
char recvbuf[8];
struct termios options;
struct pollfd pfd;
int timeout = 1000; // 超時時間,單位毫秒
// 打開串口設(shè)備文件
if ((fd = open(SERIAL_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) {
perror("open serial port failed");
exit(1);
}
// 設(shè)置串口參數(shù)
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結(jié)構(gòu)體
pfd.fd = fd;
pfd.events = POLLIN;
// 發(fā)送命令
write(fd, sendbuf, 3);
while (1) {
// 使用poll等待數(shù)據(jù)
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: %sn", strerror(errno));
}
} else {
printf("No data received in %d msn", timeout);
}
}
close(fd);
return 0;
}
【4】人臉識別圖像處理
下面是完成人臉識別處理的代碼。
#include "image_handle.h"
#pragma execution_character_set("utf-8")
//關(guān)閉線程
void ImageHandle::close()
{
run_flag=0;
this->quit();
this->wait();
}
//線程執(zhí)行函數(shù)
void ImageHandle::run()
{
QImage use_image;
while(run_flag)
{
//如果沒有圖像可以處理
if(start_run==0)
{
//休眠100毫秒
msleep(100);
continue;
}
//表示已經(jīng)處理過
start_run=0;
//表示開始處理圖像
Handle_flag=1;
//調(diào)用圖像處理算法 對 image 的圖像進行處理
//1. 人臉識別
opencv_face(m_image);
//處理完畢之后
//將圖像傳出去給UI界面顯示
emit HandleSend(m_image);
//處理完畢
Handle_flag=0;
}
}
//傳入待處理的圖片數(shù)據(jù)
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) {
// 將人臉區(qū)域縮小為一定比例,以增加馬賽克效果
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;
// 對縮小后的人臉區(qū)域進行馬賽克處理
Mat mosaic = image(smallRoi);
//可以調(diào)整數(shù)字,調(diào)整馬賽克的像素大小
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) {
//記錄人臉數(shù)量
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);
}
}
//傳遞出人臉數(shù)量
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);
//轉(zhuǎn)為QImage
QImage out_image=Mat_to_QImage(frame);
ss_log_text(tr("耗時:%1 msn").arg(time.elapsed()));
//qDebug()<<"子線程:"<<QThread::currentThread();
//保存結(jié)果
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()) {
// 創(chuàng)建QImage對象,并分配內(nèi)存
image = QImage(mat.cols, mat.rows, QImage::Format_ARGB32);
// 根據(jù)Mat的類型和通道數(shù)來設(shè)置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();
}
?
六、總結(jié)
本項目利用香橙派 AIpro
開發(fā)了一個創(chuàng)新的健康監(jiān)測解決方案,能夠提升醫(yī)院、疾病防控中心和發(fā)熱門診等關(guān)鍵場所的公共衛(wèi)生管理水平。系統(tǒng)利用香橙派AIpro的強大計算能力,搭載Ubuntu 22.04操作系統(tǒng),實現(xiàn)了高效的人臉識別與非接觸式體溫測量功能,顯著增強了疾病早期預(yù)警和控制的能力。
通過整體項目開發(fā)完成后,這塊基于香橙派 AIpro
的性能是完全滿足了要求;運行了10幾個小時, 整個板子不發(fā)燙,只是啟動的時候風(fēng)扇有明顯噪聲,正常進入系統(tǒng)之后,風(fēng)扇的聲音就正常,基本處于靜音狀態(tài)。 板子構(gòu)造小巧,很容易集成,進行項目開發(fā)