Teachable Machine 嵌入式神经网络的做视觉分类技术

人工智能

636人已加入

描述

Teachable Machine 嵌入式神经网络 – Arduino 也可以做视觉分类!

Google Teachable Machine 最近推出了新的神经网络导出方案,需要使用 Arduino Nano 33 BLE Sense 搭配 OV7670 相机模块,就可以让Arduino 透过汇出的 tensorflow lite 档案来做到边缘装置端的”实时”影像分类。

说是实时,但都在Arduino 上执行了,当然不可能快到哪里去,图片也是黑白的,这都是针对 Arduino 的运算能力来考虑,且 Arduino Nano 33 BLE Sense 与 OV7670 相机模块这两个买起来也快接近 Raspberry Pi 了。另外,ESP32cam 搭配 tensorflow lite 很早就能做到深度学习视觉分类应用,但用 teachable machine 可以自行训练所要目标,也是不错的选择。老话一句,看您的项目需求来决定使用哪些软硬件喔!

本文会带您完成相关的软硬件环境设定,并操作  Teachable Machine 透过相机模块来搜集照片、训练神经网络,最后导出档案给 Arduino 执行实时影像(灰阶)分类!

以下操作步骤根据 teachable Machine 网站说明

https://github.com/googlecreativelab/teachablemachine-community/blob/master/snippets/markdown/tiny_image/GettingStarted.md

硬件

Arduino Nano 33 BLE Sense / Nano 33 BLE

目前指定只能用这片板子,其他板子编译会有问题,看看之后有没有机会在别的板子上执行啰,详细规格请参考原厂网站。

https://store-usa.arduino.cc/products/arduino-nano-33-ble-sense

Arduino

以下是实物照片,板子都愈来愈小呢(视力挑战)

Arduino

重要信息有写在盒装背面,当然看原厂网站是最快的。

https://store-usa.arduino.cc/products/arduino-nano-33-ble-sense

Arduino

Ov7670 相机模块

由 OmniVision 推出的相机模块,本范例会把它接在Arduino上,并直接从 Teachable Machine 来撷取黑白影像作为训练数据集。

规格请看这里。

http://web.mit.edu/6.111/www/f2016/tools/OV7670_2006.pdf

实体照片如下

Arduino

Arduino

接下来是大工程,使用母母杜邦线并根据下表完成接线,请细心完成啰。

Arduino

完成如下图

Arduino

软件– Arduino IDE

请先取得 Arduino IDE,我使用 Arduino 1.8.5。OV7670 相机模块需要汇入一些函式库,请根据以下步骤操作:

1.安装Arduino_TensorFlowLite 函式库:Arduino IDE,请开启 Tools -> Manage Libraries,并搜寻Arduino_TensorFlowLite.,请选择 Version 2.4.0-ALPHA 之后的版本,点选安装。

Arduino

2.安装 Arduino_OV767X 函式库:搜寻Arduino_OV767X 并安装。

Arduino

软件– Processing

Processing 是用来连接 Arduino 与 TeachableMachine。请先下载 Processing IDE 3.X 版本。

https://processing.org/download/?PHPSESSID=8e6890fd30e3476408b69f203c217284

下载好 Processing IDE 之后,请开启 Sketch -> Add Library -> Manage Libraries,并搜寻ControlP5 与 Websockets,点选安装就完成了

Arduino

Arduino

软件– Teachable Machine

根据网站说明,embedded model 是标准影像分类神经网络模型的迷你版,因此可在微控制器上运行。

Arduino

这应该是最简单的地方啦,但在操作 TM 之前要先完成上述的软硬件设定。完成之后请根据以下步骤操作:

1.下载 TMUploader ArduinoSketch,解压缩之后于Arduino IDE 开启同名的 .ino 檔。板子类型要选择 Arduino Nano 33,COM port 也要正确设定否则将无法刻录。本程序负责把 Arduino 所拍摄的影像送往 Processing。

https://github.com/googlecreativelab/teachablemachine-community/tree/master/snippets/markdown/tiny_image/tiny_templates/TMUploader

2.下载 TMConnectorProcessing Sketch, 解压缩之后于 Arduino IDE 开启同名的 .pde 檔。点选左上角的执行(Play)键,会看到如下的画面,并列出可用的 COM port 与联机状态。

https://github.com/googlecreativelab/teachablemachine-community/tree/master/snippets/markdown/tiny_image/tiny_templates/TMConnector

Arduino

3.请由画面中来选择您的 Arduino,如果列出很多装置不知道怎么选的话,可由 Arduino IDE 中来交叉比对。顺利的话就会在 Processing 执行画面中看到相机的实时预览画面。如果画面停顿或是没有画面,请检查接线是否都接对了。如果画面有更新但是模糊,请转动相机模块前端圆环来调整焦距。

4.回到 Teachable Machine 网站,新增一个 ImageProject 专案。先点选 Device,再点选 [Attempt to connect to device] 选项,顺利的话应该就可以看到 OV7670的画面了。

Arduino

收集资料与训练

接下来的步骤就一样了,请用您的照相机来搜集想要训练的图片吧,图片格式为 96 x 96 灰阶。请用相机对准想要辨识的物体,从 [webcam] 选项来收集照片。请注意,即便用 [Upload] 选项去上传彩色照片,训练完的模型一样只能接受单色(灰阶)输入。请尽量让数据收集与后续测试时使用同一个相机模块 (原场考照的概念~)

Arduino

训练完成(很快)之后,于 Teachable Machine 右上角点选 [Export Model],于弹出画面中选择 Tensorflow Lite 并勾选下方的 Tensorflow Lite for Microcontrollers ,最后点选 [Download myModel] 就好了!转档需要稍等一下(有可能要几分钟),完成就会下载一个 converted_tinyml.zip,档名如果不对,就代表之前的选项选错了喔

Arduino

解压缩可以看到 converted_tinyml 相关内容

Arduino

执行于 Arduino

关闭所有 Processing app,因为我们暂时不需要收集照片了,且这样占住 COM port 而无法上传 Arduino 程序。上传完成,请开启 Arduino IDE 的 Serial Monitor,就会看到每一个画面的辨识结果与信心指数 (-128 to 127),请回顾本文一开始的执行影片就知道啰,happy making !

TMUploader Arduino 程序

#include

#include

#include"ImageProvider.h"

voidsetup() {

pinMode(LED_BUILTIN, OUTPUT);

digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltagelevel)

delay(400);                       // wait for a second

digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltageLOW

delay(400);                       // wait for a second

digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltagelevel)

delay(400);                       // wait for a second

digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltageLOW

delay(400);                       // wait for a second

Serial.begin(9600);

while (!Serial);

}

constint kNumCols = 96;

constint kNumRows = 96;

constint kNumChannels = 1;

constint bytesPerFrame = kNumCols * kNumRows;

// QVGA: 320x240 X 2 bytes per pixel (RGB565)

uint8_tdata[kNumCols * kNumRows * kNumChannels];

voidflushCap() {

for (int i = 0; i < kNumCols * kNumRows *kNumChannels; i++) {

data[i] = 0;

}

}

voidloop() {

//  Serial.println(000"creatingimage");

GetImage(kNumCols, kNumRows, kNumChannels,data);

//  Serial.println("got image");

Serial.write(data, bytesPerFrame);

//  flushCap();

}

TMConnectorProcessing 程序

importprocessing.serial.*;

importjava.nio.ByteBuffer;

importjava.nio.ByteOrder;

importwebsockets.*;

importjavax.xml.bind.DatatypeConverter;

importcontrolP5.*;

importjava.util.*;

SerialmyPort;

WebsocketServerws;

// mustmatch resolution used in the sketch

finalint cameraWidth = 96;

finalint cameraHeight = 96;

finalint cameraBytesPerPixel = 1;

finalint bytesPerFrame = cameraWidth * cameraHeight * cameraBytesPerPixel;

PImagemyImage;

byte[] frameBuffer= new byte[bytesPerFrame];

String[]portNames;

ControlP5cp5;

ScrollableListportsList;

booleanclientConnected = false;

voidsetup()

{

size(448, 224);

pixelDensity(displayDensity());

frameRate(30);

cp5 = new ControlP5(this);

portNames = Serial.list();

portNames = filteredPorts(portNames);

ws = new WebsocketServer(this, 8889,"/");

portsList =cp5.addScrollableList("portSelect")

.setPosition(235, 10)

.setSize(200, 220)

.setBarHeight(40)

.setItemHeight(40)

.addItems(portNames);

portsList.close();

// wait for full frame of bytes

//myPort.buffer(bytesPerFrame);  

//myPort = new Serial(this, "COM5",9600);

//myPort = new Serial(this,"/dev/ttyACM0", 9600);

//myPort = new Serial(this, "/dev/cu.usbmodem14201",9600);  

myImage = createImage(cameraWidth,cameraHeight, RGB);

noStroke();

}

voiddraw()

background(240);

image(myImage, 0, 0, 224, 224);

drawConnectionStatus();

}

voiddrawConnectionStatus() {

fill(0);

textAlign(RIGHT, CENTER);

if (!clientConnected) {

text("Not Connected to TM", 410,100);

fill(255, 0, 0);

} else {

text("Connected to TM", 410,100);

fill(0, 255, 0);

}

ellipse(430, 102, 10, 10);

}

voidportSelect(int n) {

String selectedPortName = (String) cp5.get(ScrollableList.class,"portSelect").getItem(n).get("text");

try {

myPort = new Serial(this, selectedPortName,9600);

myPort.buffer(bytesPerFrame);

}

catch (Exception e) {

println(e);

}

}

booleanstringFilter(String s) {

return (!s.startsWith("/dev/tty"));

}

intlastFrame = -1;

String[] filteredPorts(String[] ports) {

int n = 0;

for (String portName : ports) if(stringFilter(portName)) n++;

String[] retArray = new String[n];

n = 0;

for (String portName : ports) if(stringFilter(portName)) retArray[n++] = portName;

return retArray;

}

voidserialEvent(Serial myPort) {

// read the saw bytes in

myPort.readBytes(frameBuffer);

//println(frameBuffer);

// access raw bytes via byte buffer

ByteBuffer bb = ByteBuffer.wrap(frameBuffer);

bb.order(ByteOrder.BIG_ENDIAN);

int i = 0;

while (bb.hasRemaining()) {

//0xFF & to treat byte as unsigned.

int r = (int) (bb.get() & 0xFF);

myImage.pixels[i] = color(r, r, r);

i++;

//println("adding pixels");

}

if (lastFrame == -1) {

lastFrame = millis();

}

else {

int frameTime = millis() - lastFrame;

print("fps: ");

println(frameTime);

lastFrame = millis();

}

myImage.updatePixels();

myPort.clear();

String data = DatatypeConverter.printBase64Binary(frameBuffer);

ws.sendMessage(data);

}

voidwebSocketServerEvent(String msg) {

if (msg.equals("tm-connected"))clientConnected = true;

}

编辑:黄飞

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
评论(0)
发评论
jf_46681104 2023-03-20
0 回复 举报
这个为什么会花屏呀 ov7670 收起回复

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分