? ?拖更了n久的備賽日記終于來(lái)啦,最近實(shí)現(xiàn)了關(guān)于K210圖像識(shí)別并將所需數(shù)據(jù)(即目標(biāo)類別,目標(biāo)在圖像中的加權(quán)坐標(biāo))其中,加權(quán)坐標(biāo)指K210識(shí)別到的目標(biāo)并框出的框的寬和高與框左上頂點(diǎn)的坐標(biāo)加權(quán),希望以此來(lái)判斷目標(biāo)所處的位置并方便后續(xù)進(jìn)行諸如尋跡,目標(biāo)跟隨等任務(wù)。其中涉及包括YOLO網(wǎng)絡(luò)的訓(xùn)練,上位機(jī)K210進(jìn)行目標(biāo)檢測(cè)并利用串口對(duì)數(shù)據(jù)進(jìn)行發(fā)出。下位機(jī)STM32則要接收到K210傳出的數(shù)據(jù),對(duì)數(shù)據(jù)進(jìn)行解碼,存入對(duì)應(yīng)數(shù)組便于后續(xù)引用,在此次實(shí)驗(yàn)中再利用串口二將接收數(shù)據(jù)傳給PC端利用串口助手進(jìn)行數(shù)據(jù)顯示。
? ?我們一步一步來(lái),從K210的YOLO網(wǎng)絡(luò)訓(xùn)練開始講起,K210中常用的YOLO網(wǎng)絡(luò)訓(xùn)練包括利用spieed公司的線上訓(xùn)練平臺(tái),本地訓(xùn)練中除了深度學(xué)習(xí)大佬依舊可以利用之前常用的訓(xùn)練方法進(jìn)行訓(xùn)練外,身為初學(xué)者,沒有那么多的經(jīng)歷也沒有能力完成深度學(xué)習(xí)環(huán)境的搭建與代碼的編寫,可以選擇大佬開發(fā)好的MX-YOLO進(jìn)行訓(xùn)練。本次實(shí)驗(yàn)就是采用MX-YOLO進(jìn)行本地訓(xùn)練(跑例程的mask檢測(cè)),開發(fā)的模型,在本次日記中不做過多贅述,后續(xù)會(huì)和相關(guān)訓(xùn)練方法同期更新出。在模型運(yùn)行中遇到了諸如內(nèi)存不足的問題(感覺是運(yùn)行內(nèi)存不足),在重刷了spieed的最小固件后得以解決。也建議大家在使用K210跑深度學(xué)習(xí)模型的時(shí)候刷最小固件來(lái)執(zhí)行程序,這樣能容納稍微大一點(diǎn)的模型。一般能支持2兆到3兆左右(模型存至sd卡)。
? ?模型導(dǎo)入進(jìn)行目標(biāo)識(shí)別后,我們需要啟動(dòng)K210的串口將數(shù)據(jù)發(fā)出,要注意K210的串口不能單獨(dú)發(fā)出數(shù)字,所以此處選擇定義了一個(gè)一個(gè)元組參數(shù)來(lái)統(tǒng)一發(fā)送數(shù)據(jù)。為了讓STM32更好的接收數(shù)據(jù),我們定義了一個(gè)簡(jiǎn)單的通信協(xié)議。長(zhǎng)這樣
幀頭1:0xfe
幀頭2: 0xfd
數(shù)據(jù)1:classid
數(shù)據(jù)2:cx
數(shù)據(jù)3:cy
幀尾:0xff
其中之所以選擇0xfe 0xfd 0xff作為幀頭,是因?yàn)槲覀兊牟蹲降膱D像大小為224*224,皆小于0xfd,不會(huì)出現(xiàn)加權(quán)后數(shù)據(jù)大于0xff從而影響通信的情況。
下面就是我們K210端的全部代碼啦,燒即用,只需要修改對(duì)應(yīng)模型以及對(duì)應(yīng)的anchor文件和lable文件即可。(注意我選用的spieed的固件包,選用canmv或者其它固件包大概率會(huì)無(wú)法使用喔)
#此代碼中0為未帶口罩,1為戴口罩
import sensor
import image
import lcd
import KPU as kpu
from machine import UART
from fpioa_manager import fm
import ustruct
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
sensor.set_vflip(1)#設(shè)置攝像頭后置即所見即所得
sensor.run(1)
#初始化串口,pin6為串口1RX口,pin7為串口1TX口
fm.register(6,fm.fpioa.UART1_RX)
fm.register(7,fm.fpioa.UART1_TX)
def sending_data(cc,cx,cy):
global uart;
data = ustruct.pack("<bbhhhb", #格式為倆個(gè)字符倆個(gè)短整型(2字節(jié))
0xfe,#幀頭1
0xfd,#幀頭2
int(cc), #數(shù)據(jù)1為傳入類型 #四字節(jié)
int(cx), #數(shù)據(jù)2為框與頂點(diǎn)坐標(biāo)的加權(quán)的長(zhǎng)#四字節(jié)
int(cy), #數(shù)據(jù)3為框與頂點(diǎn)坐標(biāo)的加權(quán)的高#四字節(jié)
0xff)
uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=4096)
uart.write(data); #必須要傳入一個(gè)字節(jié)數(shù)組
task = kpu.load("/sd/mask.kmodel")
info_list = kpu.netinfo(task)
f=open("mask.anchors.txt","r")
anchor_txt=f.read()
L=[]
for i in anchor_txt.split(","):
L.append(float(i))
anchor=tuple(L)
f.close()
a = kpu.init_yolo2(task, 0.6, 0.3, 5, anchor)
f=open("mask.lable.txt","r")
labels_txt=f.read()
labels = labels_txt.split(",")
f.close()
while(True):
img = sensor.snapshot()
code = kpu.run_yolo2(task, img)
if code:#識(shí)別到對(duì)應(yīng)目標(biāo)
for i in code:
#print(i.w())
obj_x=(i.x()+i.w())/2
obj_y=(i.y()+i.h())/2#數(shù)據(jù)加權(quán)平均計(jì)算x,y
sending_data(i.classid(),obj_x,obj_y)#發(fā)送數(shù)據(jù)
if i.classid():#檢測(cè)目標(biāo)為mask,僅適用于二分類問題運(yùn)用此行
a=img.draw_rectangle(i.rect(),(0,255,0),2)
a = lcd.display(img)
for i in code:
lcd.draw_string(i.x()+45, i.y()-20, labels[i.classid()]+" "+'%.2f'%i.value(), lcd.WHITE,lcd.GREEN)
else:#檢測(cè)目標(biāo)為un_mask
a=img.draw_rectangle(i.rect(),(255,0,0),2)
a = lcd.display(img)
for i in code:
lcd.draw_string(i.x()+45, i.y()-20, labels[i.classid()]+" "+'%.2f'%i.value(), lcd.WHITE,lcd.RED)
else:#沒有要檢測(cè)的目標(biāo)出現(xiàn)
a = lcd.display(img)
a = kpu.deinit(task)
? K210的數(shù)據(jù)會(huì)按ASCII形式發(fā)出(其實(shí)無(wú)所謂,對(duì)應(yīng)都有十六進(jìn)制形式),這里要留意,一個(gè)整形是四字節(jié),也就是比如我classid為01,但實(shí)際發(fā)出的數(shù)據(jù)是 00 01
? ?然后就是stm32的部分了,為了方便移植和開發(fā),本程序中選用hal庫(kù)開發(fā),對(duì)于較底層的解讀后續(xù)更新。
? ?目前用到的所有初始化都是最基礎(chǔ)的cubemx初始化串口的步驟,并無(wú)特殊操作,要注意需要使能串口一的中斷用于接收中斷。同時(shí)將串口模式設(shè)置為asynchronous(異步)。下面簡(jiǎn)單介紹一下具體配置過程。
? ??
首先我們需要配置系統(tǒng)的RCC,將高速時(shí)鐘配置成Crystal/Ceramic Resonator:外部無(wú)源晶振(陶瓷晶振)
? 編輯時(shí)鐘樹? 配置對(duì)應(yīng)串口
這里只展示了配置串口1,串口2同串口1一致,注意串口1要打開中斷,即NVIC那塊enable
中斷優(yōu)先級(jí)可以不動(dòng),讓系統(tǒng)自動(dòng)按中斷號(hào)跑就行。
初始化就是這樣,我們就可以生成代碼啦。
將我們這里需要的一些定義代碼寫在此處,注意一定一定要把代碼寫在user code里,這樣重新配置文件的時(shí)候不會(huì)被覆蓋掉。這里有如此多的hello world主要是為了測(cè)試程序是否正常執(zhí)行??梢赃M(jìn)行刪除。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
uint8_t aRxBuffer; //接收中斷緩沖
uint8_t Uart1_RxBuffer[9] = {0}; //接收緩沖
uint8_t Uart1_Rx_Cnt = 0; //接收緩沖計(jì)數(shù)
uint8_t Uart1_RxFlag = 0;
uint8_t hello[]={"hello,world\r\n"};
uint8_t hello1[]={"hello1,world\r\n"};
uint8_t hello2[]={"hello2,world\r\n"};
uint8_t hello3[]={"hello3,world\r\n"};
uint8_t hello4[]={"hello4,world\r\n"};
uint8_t hello5[]={"hello5,world\r\n"};
uint8_t hello6[]={"hello6,world\r\n"};
uint8_t error3[]={"please check the end of the frame\r\n"};
uint8_t error2[]={"please check the end of the header1\r\n"};
uint8_t error1[]={"please check the end of the header1\r\n"};
uint8_t classid;
uint8_t obj_x;
uint8_t obj_y;
uint8_t jieguo[3]={0};
/* USER CODE END PTD */
下面是main函數(shù)中的部分,要記得在main函數(shù)中除了生成的初始化函數(shù),我們還要開啟對(duì)應(yīng)的接收中斷。
HAL_UART_Transmit(&huart2, hello,sizeof(hello) , 1000);
HAL_UART_Receive_IT(&huart1,Uart1_RxBuffer, 9);
這里將串口1接收的數(shù)據(jù)直接存儲(chǔ)進(jìn)數(shù)組Uart1_RxBuffer中要注意這里其實(shí)需要的是一個(gè)地址,但是我們知道數(shù)組的第一位可以代表他整個(gè)數(shù)組的地址,所以這里只需要這么寫,9代表接收數(shù)據(jù)的長(zhǎng)度為9個(gè)字節(jié)(參考上述的通信協(xié)議,總共是九個(gè)字節(jié))
串口接收滿9個(gè)字節(jié)的數(shù)據(jù)后會(huì)引起中斷,本程序目前while循環(huán)中無(wú)任何代碼,如果有需要也可以將數(shù)據(jù)解析任務(wù)放置在main函數(shù)的while循環(huán)中。
引起串口中斷后會(huì)執(zhí)行串口中斷服務(wù)函數(shù),在服務(wù)函數(shù)中調(diào)用串口中斷回調(diào)函數(shù)
此處串口中斷回調(diào)函數(shù)代碼如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
//HAL_UART_Transmit(&huart2, hello, sizeof(hello),0xFFFF);
HAL_UART_Transmit(&huart2, hello1, sizeof(hello1),0xFFFF);
if(Uart1_RxBuffer[0]==0XFE)//判斷幀頭1
{
HAL_UART_Transmit(&huart2, hello2, sizeof(hello2),0xFFFF);
if(Uart1_RxBuffer[1]==0XFD)//判斷幀頭2
{
HAL_UART_Transmit(&huart2, hello3, sizeof(hello2),0xFFFF);
jieguo[0]=Uart1_RxBuffer[3];
jieguo[1]=Uart1_RxBuffer[5];
jieguo[2]=Uart1_RxBuffer[7];
if(Uart1_RxBuffer[8]==0xFF) //判斷幀尾
{
HAL_UART_Transmit(&huart2, hello4, sizeof(hello4),0xFFFF);
}
else
{
HAL_UART_Transmit(&huart2, error3, sizeof(error3), 1000);
}
}
else
{
HAL_UART_Transmit(&huart2, error2, sizeof(error2), 1000);
}
}
else
{
HAL_UART_Transmit(&huart2, error1, sizeof(error1), 1000);
}
HAL_UART_Transmit(&huart2, jieguo, sizeof(jieguo), 1000);
memset(jieguo,0x00,sizeof(jieguo)); //清空數(shù)組
memset(Uart1_RxBuffer,0x00,sizeof(Uart1_RxBuffer)); //清空數(shù)組
HAL_UART_Transmit(&huart2, Uart1_RxBuffer, sizeof(Uart1_RxBuffer), 1000);
HAL_UART_Receive_IT(&huart1,Uart1_RxBuffer, 9); //再開啟接收中斷
HAL_UART_Transmit(&huart2, hello5, sizeof(hello5), 1000);
}
此處要注意必須用memset清除數(shù)組,如果不用memset清除數(shù)組,則串口接收數(shù)據(jù)無(wú)法更新(我也不懂,明明進(jìn)中斷了但是不會(huì)覆蓋第一次的數(shù)據(jù)結(jié)果。有懂的大佬麻煩給我講一下),同時(shí)一定要在中斷回調(diào)函數(shù)中再次使能接收中斷。
進(jìn)行完上述所有,就可正常接收K210傳輸?shù)臄?shù)據(jù)并將接收數(shù)據(jù)通過串口2利用USB轉(zhuǎn)TTL傳輸回PC機(jī)串口助手顯示。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-444662.html
項(xiàng)目原工程參考gitee鏈接cointreau/cointreau - 碼云 - 開源中國(guó) (gitee.com)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-444662.html
到了這里,關(guān)于電賽備賽日記(一):K210與STM32串口通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!