@ -1,9 +1,13 @@
|
|||||||
#
|
# Run other services on the edge
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
|
It's not just image classifiers that can be run on the edge, anything that can be packaged up into a container can be deployed to an IoT Edge device. Serverless code running as Azure Functions, such as the triggers you've created in earlier lessons can be run in containers, and therefor on IoT Edge.
|
||||||
|
|
||||||
|
Pick one of the previous lessons and try to run the Azure Functions app in an IoT Edge container. You can find a guide that shows how to do this using a different Functions app project in the [Tutorial: Deploy Azure Functions as IoT Edge modules on Microsoft docs](https://docs.microsoft.com/azure/iot-edge/tutorial-deploy-function?view=iotedge-2020-11&WT.mc_id=academic-17441-jabenn).
|
||||||
|
|
||||||
## Rubric
|
## Rubric
|
||||||
|
|
||||||
| Criteria | Exemplary | Adequate | Needs Improvement |
|
| Criteria | Exemplary | Adequate | Needs Improvement |
|
||||||
| -------- | --------- | -------- | ----------------- |
|
| -------- | --------- | -------- | ----------------- |
|
||||||
| | | | |
|
| Deploy an Azure Functions app to IoT Edge | Was able to deploy an Azure Functions app to IoT Edge and use it with an IoT device to run a trigger from IoT data | Was able to deploy a Functions App to IoT Edge, but was unable to get the trigger to fire | Was unable to deploy a Functions App to IoT Edge |
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import io
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from picamera import PiCamera
|
||||||
|
|
||||||
|
camera = PiCamera()
|
||||||
|
camera.resolution = (640, 480)
|
||||||
|
camera.rotation = 0
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
image = io.BytesIO()
|
||||||
|
camera.capture(image, 'jpeg')
|
||||||
|
image.seek(0)
|
||||||
|
|
||||||
|
with open('image.jpg', 'wb') as image_file:
|
||||||
|
image_file.write(image.read())
|
||||||
|
|
||||||
|
prediction_url = '<URL>'
|
||||||
|
headers = {
|
||||||
|
'Content-Type' : 'application/octet-stream'
|
||||||
|
}
|
||||||
|
image.seek(0)
|
||||||
|
response = requests.post(prediction_url, headers=headers, data=image)
|
||||||
|
results = response.json()
|
||||||
|
|
||||||
|
for prediction in results['predictions']:
|
||||||
|
print(f'{prediction["tagName"]}:\t{prediction["probability"] * 100:.2f}%')
|
@ -0,0 +1,28 @@
|
|||||||
|
from counterfit_connection import CounterFitConnection
|
||||||
|
CounterFitConnection.init('127.0.0.1', 5000)
|
||||||
|
|
||||||
|
import io
|
||||||
|
import requests
|
||||||
|
from counterfit_shims_picamera import PiCamera
|
||||||
|
|
||||||
|
camera = PiCamera()
|
||||||
|
camera.resolution = (640, 480)
|
||||||
|
camera.rotation = 0
|
||||||
|
|
||||||
|
image = io.BytesIO()
|
||||||
|
camera.capture(image, 'jpeg')
|
||||||
|
image.seek(0)
|
||||||
|
|
||||||
|
with open('image.jpg', 'wb') as image_file:
|
||||||
|
image_file.write(image.read())
|
||||||
|
|
||||||
|
prediction_url = '<URL>'
|
||||||
|
headers = {
|
||||||
|
'Content-Type' : 'application/octet-stream'
|
||||||
|
}
|
||||||
|
image.seek(0)
|
||||||
|
response = requests.post(prediction_url, headers=headers, data=image)
|
||||||
|
results = response.json()
|
||||||
|
|
||||||
|
for prediction in results['predictions']:
|
||||||
|
print(f'{prediction["tagName"]}:\t{prediction["probability"] * 100:.2f}%')
|
@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
@ -0,0 +1,26 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:seeed_wio_terminal]
|
||||||
|
platform = atmelsam
|
||||||
|
board = seeed_wio_terminal
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
|
||||||
|
seeed-studio/Seeed Arduino FS @ 2.0.3
|
||||||
|
seeed-studio/Seeed Arduino SFUD @ 2.0.1
|
||||||
|
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
|
||||||
|
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
|
||||||
|
seeed-studio/Seeed Arduino RTC @ 2.0.0
|
||||||
|
bblanchon/ArduinoJson @ 6.17.3
|
||||||
|
build_flags =
|
||||||
|
-w
|
||||||
|
-DARDUCAM_SHIELD_V2
|
||||||
|
-DOV2640_CAM
|
@ -0,0 +1,160 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduCAM.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
class Camera
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Camera(int format, int image_size) : _arducam(OV2640, PIN_SPI_SS)
|
||||||
|
{
|
||||||
|
_format = format;
|
||||||
|
_image_size = image_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init()
|
||||||
|
{
|
||||||
|
// Reset the CPLD
|
||||||
|
_arducam.write_reg(0x07, 0x80);
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
_arducam.write_reg(0x07, 0x00);
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
// Check if the ArduCAM SPI bus is OK
|
||||||
|
_arducam.write_reg(ARDUCHIP_TEST1, 0x55);
|
||||||
|
if (_arducam.read_reg(ARDUCHIP_TEST1) != 0x55)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change MCU mode
|
||||||
|
_arducam.set_mode(MCU2LCD_MODE);
|
||||||
|
|
||||||
|
uint8_t vid, pid;
|
||||||
|
|
||||||
|
// Check if the camera module type is OV2640
|
||||||
|
_arducam.wrSensorReg8_8(0xff, 0x01);
|
||||||
|
_arducam.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
|
||||||
|
_arducam.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
|
||||||
|
if ((vid != 0x26) && ((pid != 0x41) || (pid != 0x42)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_arducam.set_format(_format);
|
||||||
|
_arducam.InitCAM();
|
||||||
|
_arducam.OV2640_set_JPEG_size(_image_size);
|
||||||
|
_arducam.OV2640_set_Light_Mode(Auto);
|
||||||
|
_arducam.OV2640_set_Special_effects(Normal);
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startCapture()
|
||||||
|
{
|
||||||
|
_arducam.flush_fifo();
|
||||||
|
_arducam.clear_fifo_flag();
|
||||||
|
_arducam.start_capture();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool captureReady()
|
||||||
|
{
|
||||||
|
return _arducam.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readImageToBuffer(byte **buffer, uint32_t &buffer_length)
|
||||||
|
{
|
||||||
|
if (!captureReady()) return false;
|
||||||
|
|
||||||
|
// Get the image file length
|
||||||
|
uint32_t length = _arducam.read_fifo_length();
|
||||||
|
buffer_length = length;
|
||||||
|
|
||||||
|
if (length >= MAX_FIFO_SIZE)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the buffer
|
||||||
|
byte *buf = new byte[length];
|
||||||
|
|
||||||
|
uint8_t temp = 0, temp_last = 0;
|
||||||
|
int i = 0;
|
||||||
|
uint32_t buffer_pos = 0;
|
||||||
|
bool is_header = false;
|
||||||
|
|
||||||
|
_arducam.CS_LOW();
|
||||||
|
_arducam.set_fifo_burst();
|
||||||
|
|
||||||
|
while (length--)
|
||||||
|
{
|
||||||
|
temp_last = temp;
|
||||||
|
temp = SPI.transfer(0x00);
|
||||||
|
//Read JPEG data from FIFO
|
||||||
|
if ((temp == 0xD9) && (temp_last == 0xFF)) //If find the end ,break while,
|
||||||
|
{
|
||||||
|
buf[buffer_pos] = temp;
|
||||||
|
|
||||||
|
buffer_pos++;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
_arducam.CS_HIGH();
|
||||||
|
}
|
||||||
|
if (is_header == true)
|
||||||
|
{
|
||||||
|
//Write image data to buffer if not full
|
||||||
|
if (i < 256)
|
||||||
|
{
|
||||||
|
buf[buffer_pos] = temp;
|
||||||
|
buffer_pos++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_arducam.CS_HIGH();
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
buf[buffer_pos] = temp;
|
||||||
|
|
||||||
|
buffer_pos++;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
_arducam.CS_LOW();
|
||||||
|
_arducam.set_fifo_burst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((temp == 0xD8) & (temp_last == 0xFF))
|
||||||
|
{
|
||||||
|
is_header = true;
|
||||||
|
|
||||||
|
buf[buffer_pos] = temp_last;
|
||||||
|
buffer_pos++;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
buf[buffer_pos] = temp;
|
||||||
|
buffer_pos++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_arducam.clear_fifo_flag();
|
||||||
|
|
||||||
|
_arducam.set_format(_format);
|
||||||
|
_arducam.InitCAM();
|
||||||
|
_arducam.OV2640_set_JPEG_size(_image_size);
|
||||||
|
|
||||||
|
// return the buffer
|
||||||
|
*buffer = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ArduCAM _arducam;
|
||||||
|
int _format;
|
||||||
|
int _image_size;
|
||||||
|
};
|
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// WiFi credentials
|
||||||
|
const char *SSID = "<SSID>";
|
||||||
|
const char *PASSWORD = "<PASSWORD>";
|
||||||
|
|
||||||
|
const char *PREDICTION_URL = "<PREDICTION_URL>";
|
@ -0,0 +1,123 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <rpcWiFi.h>
|
||||||
|
#include "SD/Seeed_SD.h"
|
||||||
|
#include <Seeed_FS.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "camera.h"
|
||||||
|
|
||||||
|
Camera camera = Camera(JPEG, OV2640_640x480);
|
||||||
|
|
||||||
|
WiFiClient client;
|
||||||
|
|
||||||
|
void setupCamera()
|
||||||
|
{
|
||||||
|
pinMode(PIN_SPI_SS, OUTPUT);
|
||||||
|
digitalWrite(PIN_SPI_SS, HIGH);
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
SPI.begin();
|
||||||
|
|
||||||
|
if (!camera.init())
|
||||||
|
{
|
||||||
|
Serial.println("Error setting up the camera!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectWiFi()
|
||||||
|
{
|
||||||
|
while (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.println("Connecting to WiFi..");
|
||||||
|
WiFi.begin(SSID, PASSWORD);
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Connected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
while (!Serial)
|
||||||
|
; // Wait for Serial to be ready
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
connectWiFi();
|
||||||
|
|
||||||
|
setupCamera();
|
||||||
|
|
||||||
|
pinMode(WIO_KEY_C, INPUT_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void classifyImage(byte *buffer, uint32_t length)
|
||||||
|
{
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(client, PREDICTION_URL);
|
||||||
|
httpClient.addHeader("Content-Type", "application/octet-stream");
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(buffer, length);
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
JsonArray predictions = obj["predictions"].as<JsonArray>();
|
||||||
|
|
||||||
|
for(JsonVariant prediction : predictions)
|
||||||
|
{
|
||||||
|
String tag = prediction["tagName"].as<String>();
|
||||||
|
float probability = prediction["probability"].as<float>();
|
||||||
|
|
||||||
|
char buff[32];
|
||||||
|
sprintf(buff, "%s:\t%.2f%%", tag.c_str(), probability * 100.0);
|
||||||
|
Serial.println(buff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonPressed()
|
||||||
|
{
|
||||||
|
camera.startCapture();
|
||||||
|
|
||||||
|
while (!camera.captureReady())
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
Serial.println("Image captured");
|
||||||
|
|
||||||
|
byte *buffer;
|
||||||
|
uint32_t length;
|
||||||
|
|
||||||
|
if (camera.readImageToBuffer(&buffer, length))
|
||||||
|
{
|
||||||
|
Serial.print("Image read to buffer with length ");
|
||||||
|
Serial.println(length);
|
||||||
|
|
||||||
|
classifyImage(buffer, length);
|
||||||
|
|
||||||
|
delete (buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (digitalRead(WIO_KEY_C) == LOW)
|
||||||
|
{
|
||||||
|
buttonPressed();
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(200);
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/page/plus/unit-testing.html
|
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"modulesContent": {
|
||||||
|
"$edgeAgent": {
|
||||||
|
"properties.desired": {
|
||||||
|
"schemaVersion": "1.1",
|
||||||
|
"runtime": {
|
||||||
|
"type": "docker",
|
||||||
|
"settings": {
|
||||||
|
"minDockerVersion": "v1.25",
|
||||||
|
"loggingOptions": "",
|
||||||
|
"registryCredentials": {
|
||||||
|
"ClassifierRegistry": {
|
||||||
|
"username": "<Container registry name>",
|
||||||
|
"password": "<Container Password>",
|
||||||
|
"address": "<Container registry name>.azurecr.io"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systemModules": {
|
||||||
|
"edgeAgent": {
|
||||||
|
"type": "docker",
|
||||||
|
"settings": {
|
||||||
|
"image": "mcr.microsoft.com/azureiotedge-agent:1.1",
|
||||||
|
"createOptions": "{}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edgeHub": {
|
||||||
|
"type": "docker",
|
||||||
|
"status": "running",
|
||||||
|
"restartPolicy": "always",
|
||||||
|
"settings": {
|
||||||
|
"image": "mcr.microsoft.com/azureiotedge-hub:1.1",
|
||||||
|
"createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"ImageClassifier": {
|
||||||
|
"version": "1.0",
|
||||||
|
"type": "docker",
|
||||||
|
"status": "running",
|
||||||
|
"restartPolicy": "always",
|
||||||
|
"settings": {
|
||||||
|
"image": "<Container registry name>.azurecr.io/classifier:v1",
|
||||||
|
"createOptions": "{\"ExposedPorts\": {\"80/tcp\": {}},\"HostConfig\": {\"PortBindings\": {\"80/tcp\": [{\"HostPort\": \"80\"}]}}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$edgeHub": {
|
||||||
|
"properties.desired": {
|
||||||
|
"schemaVersion": "1.1",
|
||||||
|
"routes": {
|
||||||
|
"upstream": "FROM /messages/* INTO $upstream"
|
||||||
|
},
|
||||||
|
"storeAndForwardConfiguration": {
|
||||||
|
"timeToLiveSecs": 7200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
# Classify an image using an IoT Edge based image classifier - Virtual IoT Hardware and Raspberry Pi
|
||||||
|
|
||||||
|
In this part of the lesson, you will use the Image Classifier running on the IoT Edge device.
|
||||||
|
|
||||||
|
## Use the IoT Edge classifier
|
||||||
|
|
||||||
|
The IoT device can be re-directed to use the IoT Edge image classifier. The URL for the Image Classifier is `http://<IP address or name>/image`, replacing `<IP address or name>` with the IP address or host name of the computer running IoT Edge.
|
||||||
|
|
||||||
|
The Python library for Custom Vision only works with cloud-hosted models, not models hosted on IoT Edge. This means you will need to use the REST API to call the classifier.
|
||||||
|
|
||||||
|
### Task - use the IoT Edge classifier
|
||||||
|
|
||||||
|
1. Open the `fruit-quality-detector` project in VS Code if it is not already open. If you are using a virtual IoT device, then make sure the virtual environment is activated.
|
||||||
|
|
||||||
|
1. Open the `app.py` file, and remove the import statements from `azure.cognitiveservices.vision.customvision.prediction` and `msrest.authentication`.
|
||||||
|
|
||||||
|
1. Add the following import at the top of the file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Delete all the code after the image is saved to a file, from `image_file.write(image.read())` to the end of the file.
|
||||||
|
|
||||||
|
1. Add the following code to the end of the file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
prediction_url = '<URL>'
|
||||||
|
headers = {
|
||||||
|
'Content-Type' : 'application/octet-stream'
|
||||||
|
}
|
||||||
|
image.seek(0)
|
||||||
|
response = requests.post(prediction_url, headers=headers, data=image)
|
||||||
|
results = response.json()
|
||||||
|
|
||||||
|
for prediction in results['predictions']:
|
||||||
|
print(f'{prediction["tagName"]}:\t{prediction["probability"] * 100:.2f}%')
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<URL>` with the URL for your classifier.
|
||||||
|
|
||||||
|
This code makes a REST POST request to the classifier, sending the image as the body of the request. The results come back as JSON, and this is decoded to print out the probabilities.
|
||||||
|
|
||||||
|
1. Run your code, with your camera pointing at some fruit, or an appropriate image set, or fruit visible on your webcam if using virtual IoT hardware. You will see the output in the console:
|
||||||
|
|
||||||
|
```output
|
||||||
|
(.venv) ➜ fruit-quality-detector python app.py
|
||||||
|
ripe: 56.84%
|
||||||
|
unripe: 43.16%
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 You can find this code in the [code-classify/pi](code-classify/pi) or [code-classify/virtual-iot-device](code-classify/virtual-iot-device) folder.
|
||||||
|
|
||||||
|
😀 Your fruit quality classifier program was a success!
|
@ -0,0 +1,52 @@
|
|||||||
|
# Classify an image using an IoT Edge based image classifier - Wio Terminal
|
||||||
|
|
||||||
|
In this part of the lesson, you will use the Image Classifier running on the IoT Edge device.
|
||||||
|
|
||||||
|
## Use the IoT Edge classifier
|
||||||
|
|
||||||
|
The IoT device can be re-directed to use the IoT Edge image classifier. The URL for the Image Classifier is `http://<IP address or name>/image`, replacing `<IP address or name>` with the IP address or host name of the computer running IoT Edge.
|
||||||
|
|
||||||
|
### Task - use the IoT Edge classifier
|
||||||
|
|
||||||
|
1. Open the `fruit-quality-detector` app project if it's not already open.
|
||||||
|
|
||||||
|
1. The image classifier is running as a REST API using HTTP, not HTTPS, so the call needs to use a WiFi client that works with HTTP calls only. This means the certificate is not needed. Delete the `CERTIFICATE` from the `config.h` file.
|
||||||
|
|
||||||
|
1. The prediction URL in the `config.h` file needs to be updated to the new URL. You can also delete the `PREDICTION_KEY` as this is not needed.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const char *PREDICTION_URL = "<URL>";
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<URL>` with the URL for your classifier.
|
||||||
|
|
||||||
|
1. In `main.cpp`, change the include directive for the WiFi Client Secure to import the standard HTTP version:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Change the declaration of `WiFiClient` to be the HTTP version:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
WiFiClient client;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Select the line that sets the certificate on the WiFi client. Remove the line `client.setCACert(CERTIFICATE);` from the `connectWiFi` function.
|
||||||
|
|
||||||
|
1. In the `classifyImage` function, remove the `httpClient.addHeader("Prediction-Key", PREDICTION_KEY);` line that sets the prediction key in the header.
|
||||||
|
|
||||||
|
1. Upload and run your code. Point the camera at some fruit and press the C button. You will see the output in the serial monitor:
|
||||||
|
|
||||||
|
```output
|
||||||
|
Connecting to WiFi..
|
||||||
|
Connected!
|
||||||
|
Image captured
|
||||||
|
Image read to buffer with length 8200
|
||||||
|
ripe: 56.84%
|
||||||
|
unripe: 43.16%
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 You can find this code in the [code-classify/wio-terminal](code-classify/wio-terminal) folder.
|
||||||
|
|
||||||
|
😀 Your fruit quality classifier program was a success!
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 263 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |