擼狗初體驗 | 手把手教你上手 HuskyLens 哈士奇人工智能攝像頭

視頻封面
直接上視頻聽我 BB:

https://www.bilibili.com/video/av75675708

下面開始編故事……

某個週末,走在去加班的路上,腳底突然被某個東西咯噔一下,擡腳一看,竟然是……
踩到哈士奇
撿起來一看……
哈士奇
哈士奇!哈士奇!哈士奇!

竟然是 DF 還在預售的 HuskyLens 人工智能攝像頭(中文名:哈士奇) !

這個故事告訴我們:喜歡加班的創客,運氣不會太差。

HuskyLens 功能介紹

HuskyLens 是什麼?

這裏簡單截取官網介紹的一部分:

HuskyLens 是一款簡單易用的人工智能攝像頭(視覺傳感器),內置 6 種功能:人臉識別、物體追蹤、物體識別、巡線追蹤、顏色識別、標籤(二維碼)識別。僅需一個按鍵即可完成 AI 訓練,擺脫繁瑣的訓練和複雜的視覺算法,讓你更加專注於項目的構思和實現。

哈士奇功能
詳細介紹,可以查看官網說明: https://www.dfrobot.com.cn/goods-2050.html

哈士奇官方介紹視頻,請直接跳轉 B 站:

http://player.bilibili.com/player.html?aid=66695069&cid=115655109

用 3 個單詞就可以概括 HuskyLens 的特性:ClickLearn & Play

總之:簡單、易用、真香!

這麼香的工具,到底該怎麼玩呢?請繼續往下看。

注意:以下章節偏技術性,是對體驗視頻的文字性詳細描述,會略微無聊,請收藏後慢慢閱讀。

HuskyLens 數據讀取測試

由於 HuskyLens 目前還沒有正式上市,所以關於它的資料非常少,DF 暫時也沒有放出 HuskyLens 對應的函數庫,更不用說在 Mind+ 等圖形化編程工具中調用了。不過相信不久,我們就可以在 Mind+ 中看到它了,到那時,它的使用就會更加簡單、更加小白了!

暫時我們只能根據它的通信協議,徒手來擼代碼了。

HuskyLens 默認採用串口通信,當然可以在它的設置中改成 I2C 通信。默認的串口波特率爲 57600 bps,格式爲 8N1( 8 位數據位、無校驗、1位停止位 ),默認通信地址爲 0x11。本文中,我帶領大家使用掌控板來讀取 HuskyLens 的數據。

當然你也可以選擇使用 Arduino、Raspberry Pi、LattePanda、micro:bit 等控制器來讀取 HuskyLens 的數據,原理都是一樣的,就留作課後作業吧,看文本文後,你能不能用其他主控板來讀取 HuskyLens 的數據呢?

電路連接圖如下:
哈士奇電路接線圖

關於掌控板串口的補充說明:

掌控板的主控芯片是 ESP32,ESP32 有 3 個串口,分別是 SerialSerial1Serial2Serial 我們一般用來下載程序, Serial1 默認使用了 GPIO 9GPIO 10 ,但是 ESP32 的 GPIO 6~11 一般用於連接外部 Flash 芯片,所以我們這裏使用 Serial2 與 HuskyLens 連接通信。另外,ESP32 可以將串口 RX 映射到幾乎所有 IO 口上,TX 映射到 GPIO 0~31 上(此處沒有進行驗證)。

所以,這裏我們將掌控板 Serial2 的 RX 映射到 P14 引腳,將 TX 映射到 P13 引腳。

參考資料: https://blog.csdn.net/Naisu_kun/article/details/86004049

我們來看一下 HuskyLens 的通信協議。它主要有兩種模式,正如視頻中看到的那樣,大部分情況下, HuskyLens 屏幕上會顯示一個方框(Block 模式),在巡線模式下,它會顯示一個箭頭(Arrow 模式)。這兩種模式下,數據的長度和格式基本是一致的,這裏我們以 Block 模式爲例進行講解,Arrow 模式原理一樣,此處不再贅述。

我們來看一下它的數據格式,可以看到它是以0x550xAA開頭的一串數據,緊接着是它的通信地址 Address、數據長度 Data Length、命令代碼 Command,以及我們最關心的數據 Data。最後是一個校驗位。
哈士奇通信協議
每個數據的含義如下:

Block 模式:
哈士奇通信協議說明Block模式

Arrow 模式:
哈士奇通信協議說明Arrow模式

上面兩個表格中紅框框出來的是相應的數據 Data 1 ~ Data n,其中:

  • X Center 表示方框 Block 的幾何中心的 X 座標;
  • Y Center 表示方框 Block 的幾何中心的 Y 座標;
  • Width 表示方框 Block 的寬度;
  • Height 表示方框 Block 的高度;
  • LearnedIndex 表示識別目標的編號。

我們根據通信協議,編寫程序來讀取一下 HuskyLens 返回的數據。

首先打開 Mixly 自帶的 Arduino IDE,選擇 Arduino HandBit (掌控板)進行編程。

最新版 Mixly 1.0 下載地址: https://mixly.readthedocs.io/zh_CN/latest/basic/02Installation-update.html

選擇掌控板編寫程序
程序如下:

void setup()
{
    Serial.begin(57600);
    Serial2.begin(57600, SERIAL_8N1, P14, P13);
}

void loop()
{
    if (Serial2.available() > 0) {
      Serial.println(Serial2.read(), HEX);
    }
}

將程序上傳到掌控板,打開串口監視器,可以看到會返回類似下圖中的數據:
哈士奇數據讀取測試
但是這些數據代表着什麼意思呢?我們將關鍵數據圈出來:0x55、0xAA、0x11、0x0A、0x10、0xA3、0x00、0x7B、0x00、0x46、0x00、0x46、0x00、0x01、0x00、0xD5。其中:

  • 0x55 爲 Header;
  • 0xAA 爲 Header 2;
  • 0x11 爲 Address;
  • 0x0A 爲 Data Length;
  • 0x10 爲 Command,Block 模式下爲 0x10,Arrow 模式下爲 0x11
  • 0xA30x00 分別爲 X Center 的低 8 位和高 8 位,0x00A3 換算後爲 163,代表 X 座標爲 163;
  • 0x7B0x00 爲 Y Center 的低 8 位和高 8 位,0x007B 換算後爲 123,代表 Y 座標爲 123;
  • 0x460x00 爲 Width 的低 8 位和高 8 位,0x0046 換算後爲 70,代表方框寬度爲 70;
  • 0x460x00 爲 Height 的低 8 位和高 8 位,0x0046 換算後爲 70,代表方框高度爲 70;
  • 0x010x00 爲 LearnedIndex 的低 8 位和高 8 位,0x0001換算後爲 1,代表識別物體的編號爲 1;
  • 0xD5 爲 Checksum 的低 8 位,我們將上面所有數據相加求和:0x55 + 0xAA + 0x11 + 0x0A + 0x10 + 0xA3 + 0x00 + 0x7B + 0x00 + 0x46 + 0x00 + 0x46 + 0x00 + 0x01 + 0x00 =0x02D5,低 8 位是 0xD5 說明校驗通過。

至此,我們就完成了 HuskyLens 數據的簡單讀取。

但是每次都要這麼去讀取數據,然後再進行手工計算麼?這還怎麼做人工智能項目啊?

當然不是的,我們發現,讀取這些數據就是都是一件件重複的事情,而程序最擅長的就是做重複的事情了。

HuskyLens 數據解析

我們將上面的程序簡單調整一下,duang~ 就完成了!(程序源代碼詳見文末下載鏈接,建議配套代碼一起閱讀下文)
哈士奇數據讀取完整程序
我們先定義了一些變量,用來存放數據。這些變量的名字,基本可以自解釋其含義,此處不再贅述說明。

#define LENG 15 // 0x55 + 15 bytes equal to 16 bytes
unsigned char buf[LENG];

int xCenterOrXOrigin = 0;
int yCenterOrYOrigin = 0;
int widthOrXTarget = 0;
int heightOrYTarget = 0;
int learnedIndex = 0;

setup() 中,主要是對兩個串口進行初始化:

void setup()
{
  Serial.begin(57600);
  Serial2.begin(57600, SERIAL_8N1, P14, P13);
}

然後在 loop() 中,不斷去讀取掌控板串口 2(Serial2)中返回的數據。首先要判斷第 1 個讀到的數據是否是默認的 Header 0x55,我們用了find()函數:

// Header
// Read data until get the first header of data packet 0x55
if (Serial2.find(0x55))
{
    // ......
}

如果讀取到0x55,那麼就把剩下的 15 個數據都讀取進來,並存儲到 buf 變量中。因爲一個有效的通信命令共16個數據,大家可以在通信協議中數一下。

// Read the next 15 data
Serial2.readBytes(buf, LENG);

接着去檢查第 2 個數據是否是默認的 Header 2 0xAA。這裏需要注意的是,我們並沒有把第 1 個 Header 數據 0x55 存儲到 buf 變量中,所以 buf[0] 不是 0x55,而是 0xAA

// Header 2
// Check the second header of data packet 0xAA
if (buf[0] == 0xAA)
{
    // ......
}

如果第 2 個數據也是默認的包頭的話,再去對剩下的數據進行校驗,這裏調用校驗和函數 checkSum(),具體這個函數怎麼實現的,下面再講。

// Checksum
if (checkSum(buf, LENG))
{
    // ......
} else {
    Serial.println("Checksum Errorrrrr!");
}

校驗和通過後,就可以對數據進行處理和計算了。在下面的程序中,我們先把原始數據打印出來:

// print the command list
Serial.print(0x55, HEX);
Serial.print(" ");
for (int i = 0; i < LENG; i++)
{
   Serial.print(buf[i], HEX);
   Serial.print(" ");
}
Serial.println();

然後通過 5 個函數分別去計算 Data 中的幾個值。最後再將這些數據在串口監視器中打印出來。

// get the values
xCenterOrXOrigin = getX(buf);
yCenterOrYOrigin = getY(buf);
widthOrXTarget = getWidthOrXTarget(buf);
heightOrYTarget = getHeightOrYTarget(buf);
learnedIndex = getLearnedIndex(buf);

// print the values
Serial.print("x: ");
Serial.print(xCenterOrXOrigin);
Serial.print("   ");

Serial.print("y: ");
Serial.print(yCenterOrYOrigin);
Serial.print("   ");

Serial.print("width: ");
Serial.print(widthOrXTarget);
Serial.print("   ");

Serial.print("height: ");
Serial.print(heightOrYTarget);
Serial.print("   ");

Serial.print("learnedIndex: ");
Serial.print(learnedIndex);
Serial.print("   ");

Serial.println();
Serial.println("-----------------------------");
Serial.println();

這樣我們就把數據讀取出來啦。

幾個函數說明

checkSum()

checkSum() 函數的功能就是校驗讀取到的數據是否正確。只要簡單講讀到的數據相加,並最終取和的低 8 位,檢查是否和讀到的最後一個數據相等即可。相等的話,將標記變量 receiveflag 賦值爲 1 即可,否則賦值爲 0,並返回 receiveflag 的結果。

// check sum
char checkSum(unsigned char *buf, char leng)
{
  char receiveflag = 0;
  int sum = 0;
  int sumLow = 0;

  for (int i = 0; i < (leng - 1); i++)
  {
    sum = sum + buf[i];
  }
  sum = sum + 0x55;

  sumLow = sum & 0x00FF;
  if (sumLow == buf[leng - 1])
  {
    sum = 0;
    receiveflag = 1;
  }
  return receiveflag;
}

getX()

getY()

getWidthOrXTarget()

getHeightOrYTarget()

getLearnedIndex()

這 5 個函數的原理基本一致,其函數名基本可以自解釋含義,此處也不再贅述解釋每個函數的功能。

這裏以第 1 個函數 getX() 爲例,介紹這幾個函數內部的原理。

通過查看通信協議,我們可以知道,X 座標的數值,分別存儲在 buf 變量中的第 5 個和第 6 個(實際是完整數據的 6 個和第 7 個數據,但是 buf 變量中沒有存儲第 1 個數據 0x55),所以這裏取 buf[4]buf[5] 來進行計算即可。 由於是 16 進制的數據,所以可以通過移位進行計算,當然這裏的 << 8 等價爲 * 256。最後將計算出來的值返回即可。

// X Center of Block [Block mode] 
// or X Origin of Arrow [Arrow mode: Line tracking mode]
int getX(unsigned char *thebuf)
{
  int xCenterValue;

  // calculate X Center of Block value
  xCenterValue = ((thebuf[5] << 8) + thebuf[4]);

  return xCenterValue;
}

其他幾個數據,也是同樣道理,只要取對應的數據位進行計算即可。

總結

通過這一章節,我們根據 HuskyLens 的通信協議,把 HuskyLens 返回的數據都讀取出來了。那麼我們可以利用這些數據做一些什麼有趣好玩的事情呢?

物體追蹤,人臉識別,物體識別,巡線追蹤,顏色識別,標籤識別等功能,要怎麼用呢?

交互手勢控制、自主機器人、智能門禁、交互式玩具等又要怎麼實現呢?

我們下期見!

代碼下載

請到知識星球下載本教程對應的源代碼:https://t.zsxq.com/RB6EaYf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章