Skip to content
This repository was archived by the owner on Aug 11, 2021. It is now read-only.

Commit 4ad75b5

Browse files
Add Getting Started tutorial for Vision Shield (Arduino) (#88)
* Added install-core-image * Created the Content.md file * Added the metadata.json file * Updated content for step-4 * Revision of the Vision Shield Getting Started Tutorial (#83) * Change title * Fix broken image path * Clean up formatting * Fix typos and rephrase content up to chapter 4 * Add explanations for updated sketch * Add correct info to metadata file * Added attach_boards image * Added Images to Instruction * Updated Reviewd-by Section * Added Images to Assets * Update vs_ard_gs_cover.png * Update metadata.json * Added image description * Change Cover format * Replace screenshot with correct file format * Fix incorrect title case * Allow for java syntax code blocks * Migrate metadata to frontmatter * Make cover SVG format * Update cover image * Remove mention of Pro IDE Co-authored-by: Sebastian Romero <s.romero@arduino.cc> Co-authored-by: Sebastian Romero <s.hunkeler@arduino.cc>
1 parent 96036de commit 4ad75b5

File tree

7 files changed

+1915
-0
lines changed

7 files changed

+1915
-0
lines changed
Loading

content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_attach_boards.svg

Lines changed: 922 additions & 0 deletions
Loading
Loading

content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_cover.svg

Lines changed: 610 additions & 0 deletions
Loading
Loading
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
---
2+
title: Getting Started With The Vision Shield Camera
3+
coverImage: assets/vs_ard_gs_cover.svg
4+
tags: [Getting Started, Camera, Processing, Serial]
5+
description: This tutorial shows you how to capture frames from the Vision Shield Camera module and visualise the video output through a Processing sketch.
6+
---
7+
8+
# Getting Started With The Vision Shield Camera
9+
## Overview
10+
This tutorial shows you how to capture frames from the Vision Shield Camera module and visualise the video output through a Processing sketch.
11+
12+
### You Will Learn
13+
- Capturing the frames from the camera.
14+
- Sending the frames as a byte stream through a Serial connection.
15+
- Visualising the frames in Processing.
16+
17+
### Required Hardware and Software
18+
- [Portenta H7 board](https://store.arduino.cc/portenta-h7)
19+
- Portenta Vision Shield ( [LoRa](https://store.arduino.cc/portenta-vision-shield-lora) or [Ethernet](https://store.arduino.cc/portenta-vision-shield) )
20+
- USB C cable (either USB A to USB C or USB C to USB C)
21+
- Arduino IDE 1.8.10+
22+
- Processing 3.5.4+
23+
24+
## Instructions
25+
Accessing the Vision Shield's camera data is done with the help of both Arduino and the Processing IDE. The Arduino sketch handles the capture of image data by the on-board camera while the java applet created with Processing helps to visualise this data with the help of a serial connection. The following steps will run you through how to capture, package the data through the serial port and visualise the output in Processing.
26+
27+
### 1. The Basic Setup
28+
Connect the Vision Shield to your Portenta H7 as shown in the figure. The top and Bottom high density connecters the aligned with and are connected to the concurrent ones on the Portenta board. Plug in the H7 to your computer using the USB C cable.
29+
30+
![Connecting the Vision Shield to Portenta](assets/vs_ard_gs_attach_boards.svg)
31+
32+
Open the board manager in the Arduino IDE and install the latest version of the Portenta Core which is [v1.3.2](https://github.com/arduino/ArduinoCore-mbed/releases/tag/1.3.2)
33+
34+
![Download the mbed core](assets/vs_ard_gs_core.png)
35+
36+
### 2. Capturing the Frames
37+
38+
Create a new Arduino sketch called `CameraCaptureRawBytes.ino`.
39+
40+
To capture the frames you will need to use the functions contained in `camera.h` which comes with the Portenta core. This library contains all APIs related to frame capturing, motion detection and pattern recognition. Include the header file in your sketch.
41+
42+
```cpp
43+
#include "camera.h"
44+
```
45+
46+
Next, let's intialise a camera object and a frame buffer of the size 320*240 (76'800 bytes).
47+
48+
```cpp
49+
CameraClass cam;
50+
uint8_t fb[320*240];
51+
```
52+
53+
In the `setup()` function, let's start the Serial communication at `921600` baud rate and iniitialise the camera using `cam.begin()`.
54+
55+
```cpp
56+
void setup() {
57+
Serial.begin(921600);
58+
//Init the cam QVGA, 30FPS
59+
cam.begin(CAMERA_R320x240, 30);
60+
}
61+
```
62+
63+
In the loop we need to capture each Frame and send it over a serial connection to the Processing sketch that will display the frames. We will use the `grab(uint8_t *buffer, uint32_t timeout=5000);` function to fetch the frame from the frame buffer and save it into our custom data buffer.
64+
65+
```cpp
66+
void loop() {
67+
// put your main code here, to run repeatedly:
68+
69+
// Wait until the receiver acknowledges
70+
// that they are ready to receive new data
71+
while(Serial.read() != 1){};
72+
73+
// Grab frame and write to serial
74+
if (cam.grab(fb) == 0) {
75+
Serial.write(fb, 320*240);
76+
}
77+
78+
}
79+
```
80+
81+
### 3. Create the Processing Sketch
82+
Open a new processing sketch file and name it `CameraCapture.pde`.
83+
84+
![Create a processing sketch](assets/vs_ard_open_pde_sketch.png)
85+
86+
Let's start by importing the libraries and initialising the variables you will need to process the captured data. To process the data sent by the Vision Shield you will need to import the following libraries:
87+
88+
- `processing.serial.*` : a [Serial Library](https://processing.org/reference/libraries/serial/index.html) that is used to read and write data to external devices over the serial line.
89+
- `java.nio.ByteBuffer` : a java class that provides access to operations on byte buffers
90+
91+
```java
92+
import processing.serial.*;
93+
import java.nio.ByteBuffer;
94+
```
95+
96+
Next we initialise the following variables to process the received pixels from the serial port. We set the dimensions, pixel count, and bytes required per frame.
97+
98+
```java
99+
// must match resolution used in the sketch
100+
final int cameraWidth = 320;
101+
final int cameraHeight = 240;
102+
final int cameraBytesPerPixel = 1;
103+
final int cameraPixelCount = cameraWidth * cameraHeight;
104+
final int bytesPerFrame = cameraWidth * cameraHeight * cameraBytesPerPixel;
105+
```
106+
107+
To recieve the frames you will need a Serial port, a PImage object and an array to store the pixel values of the frame. Add the following variables to the code.
108+
109+
```java
110+
Serial myPort;
111+
PImage myImage;
112+
byte[] frameBuffer = new byte[bytesPerFrame];
113+
int pixelPosition = 0;
114+
int lastUpdate = 0;
115+
boolean shouldRedraw = false;
116+
```
117+
118+
Here we will establish a connection to the serial port and prepare the buffer to store the frame pixels. Additionally we send a byte to the Arduino sketch from Processing to let it know that it's ready to receive data.
119+
120+
```java
121+
void setup() {
122+
size(640, 480);
123+
124+
// if you know the serial port name
125+
//myPort = new Serial(this, "COM5", 921600); // Windows
126+
//myPort = new Serial(this, "/dev/ttyACM0", 921600); // Linux
127+
myPort = new Serial(this, "/dev/cu.usbmodem14101", 921600); // Mac
128+
129+
// Set the number of bytes to buffer
130+
myPort.buffer(bytesPerFrame)
131+
132+
// Create an image based on the camera's dimensions and format
133+
myImage = createImage(cameraWidth, cameraHeight, ALPHA);
134+
135+
// Let the Arduino sketch know we're ready to receive data
136+
myPort.write(1);
137+
}
138+
```
139+
140+
The draw function checks if the connection is still alive and if there is any new data that can be drawn as an image. In that case the original image gets copied into a new image object so that it can be scaled up.
141+
142+
```java
143+
void draw() {
144+
// Time out after 1.5 seconds and ask for new data
145+
if(millis() - lastUpdate > 1500) {
146+
println("Connection timed out.");
147+
myPort.clear();
148+
myPort.write(1);
149+
}
150+
151+
if(shouldRedraw){
152+
PImage img = myImage.copy();
153+
img.resize(640, 480);
154+
image(img, 0, 0);
155+
shouldRedraw = false;
156+
}
157+
}
158+
```
159+
160+
### 4. Visualing the Frames
161+
For this step, you will use the `serialEvent()` callback function to update the `myImage` when a new data is received on the serial port.
162+
163+
```java
164+
void serialEvent(Serial myPort) {
165+
lastUpdate = millis();
166+
167+
// read the received bytes
168+
myPort.readBytes(frameBuffer);
169+
170+
// Access raw bytes via byte buffer
171+
ByteBuffer bb = ByteBuffer.wrap(frameBuffer);
172+
173+
int i = 0;
174+
175+
while (bb.hasRemaining()) {
176+
// read 8-bit pixel
177+
byte pixelValue = bb.get();
178+
179+
// set pixel color
180+
myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue));
181+
}
182+
183+
myImage.updatePixels();
184+
185+
// Ensures that the new image data is drawn in the next draw loop
186+
shouldRedraw = true;
187+
188+
// Let the Arduino sketch know we received all pixels
189+
// and are ready for the next frame
190+
myPort.write(1);
191+
}
192+
```
193+
194+
The first thing we do inside this method is to update the timestamp for when the last data was read. This is to detect and recover from a connection timeout. Then read the bytes from the `frameBuffer` array which you can do with the help of the [`readBytes()`](https://processing.org/reference/libraries/serial/Serial_readBytes_.html) method that returns the number of bytes read.
195+
196+
```java
197+
lastUpdate = millis();
198+
199+
// read the received bytes
200+
myPort.readBytes(frameBuffer);
201+
```
202+
203+
Then the frame buffer is translated into a ByteBuffer that allows for easy and safe access to the underlying bytes without having to worry about the array indices.
204+
205+
```cpp
206+
// Access raw bytes via byte buffer
207+
ByteBuffer bb = ByteBuffer.wrap(frameBuffer);
208+
```
209+
210+
Next we read the frame buffer and convert the bytes into pixel color values. The image gets constructed by sequentially filling the pixels array of the image. The conversion of the raw data is done wih [`color()`](https://processing.org/reference/color_.html) and [`Byte.toUnsignedInt()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Byte.html).
211+
212+
```java
213+
int i = 0;
214+
215+
while (bb.hasRemaining()) {
216+
// read 8-bit pixel
217+
byte pixelValue = bb.get();
218+
219+
// set pixel color
220+
myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue));
221+
}
222+
```
223+
224+
Once all the pixels have been updated, you need to tell the sketch to redraw the image. Additionally we send an acknowledgement back to the arduino sketch to ask it to send the pixels for the next frame. We update the image with `updatePixels()` and write `1` to the serial port for the acknowledgement.
225+
226+
``` cpp
227+
myImage.updatePixels();
228+
229+
// Ensures that the new image data is drawn in the next draw loop
230+
shouldRedraw = true;
231+
232+
// Let the Arduino sketch know we received all pixels
233+
// and are ready for the next frame
234+
myPort.write(1);
235+
```
236+
237+
### 5. Upload the Sketch
238+
239+
Select the right serial port on your IDE and upload the Arduino sketch to your H7. After a successful upload, run the `CameraViewer.pde` sketch in Processing. You should be able to see the rendered camera output on the Processing canvas.
240+
241+
![Camera output on Processing](assets/vs_ard_frames_captured.png)
242+
243+
## Conclusion
244+
245+
In this tutorial you learnt how to capture the frames from your Vision Shield's Camera and to visualise the frames throught Processing. This knowledge can be useful for you to build and experiment simple computer vision applications for both outdoor and indoor environments.
246+
247+
### Complete Sketch
248+
The `CaptureRawBytes.ino` Sketch.
249+
250+
```cpp
251+
#include "camera.h"
252+
253+
CameraClass cam;
254+
uint8_t fb[320*240];
255+
256+
void setup() {
257+
Serial.begin(921600);
258+
259+
// Init the cam QVGA, 30FPS
260+
cam.begin(CAMERA_R320x240, 30);
261+
}
262+
263+
void loop() {
264+
// put your main code here, to run repeatedly:
265+
266+
// Wait until the receiver acknowledges
267+
// that they are ready to receive new data
268+
while(Serial.read() != 1){};
269+
270+
// Grab frame and write to serial
271+
if (cam.grab(fb) == 0) {
272+
Serial.write(fb, 320*240);
273+
}
274+
275+
}
276+
```
277+
278+
The `CameraViewer.pde` Sketch.
279+
280+
```java
281+
/*
282+
This sketch reads a raw Stream of RGB565 pixels
283+
from the Serial port and displays the frame on
284+
the window.
285+
Use with the Examples -> CameraCaptureRawBytes Arduino sketch.
286+
This example code is in the public domain.
287+
*/
288+
289+
import processing.serial.*;
290+
import java.nio.ByteBuffer;
291+
import java.nio.ByteOrder;
292+
293+
Serial myPort;
294+
295+
// must match resolution used in the sketch
296+
final int cameraWidth = 320;
297+
final int cameraHeight = 240;
298+
final int cameraBytesPerPixel = 1;
299+
final int cameraPixelCount = cameraWidth * cameraHeight;
300+
final int bytesPerFrame = cameraPixelCount * cameraBytesPerPixel;
301+
302+
PImage myImage;
303+
byte[] frameBuffer = new byte[bytesPerFrame];
304+
int lastUpdate = 0;
305+
boolean shouldRedraw = false;
306+
307+
void setup() {
308+
size(640, 480);
309+
310+
// if you have only ONE serial port active
311+
//myPort = new Serial(this, Serial.list()[0], 921600); // if you have only ONE serial port active
312+
313+
// if you know the serial port name
314+
//myPort = new Serial(this, "COM5", 921600); // Windows
315+
//myPort = new Serial(this, "/dev/ttyACM0", 921600); // Linux
316+
myPort = new Serial(this, "/dev/cu.usbmodem14401", 921600); // Mac
317+
318+
// wait for full frame of bytes
319+
myPort.buffer(bytesPerFrame);
320+
321+
myImage = createImage(cameraWidth, cameraHeight, ALPHA);
322+
323+
// Let the Arduino sketch know we're ready to receive data
324+
myPort.write(1);
325+
}
326+
327+
void draw() {
328+
// Time out after 1.5 seconds and ask for new data
329+
if(millis() - lastUpdate > 1500) {
330+
println("Connection timed out.");
331+
myPort.clear();
332+
myPort.write(1);
333+
}
334+
335+
if(shouldRedraw){
336+
PImage img = myImage.copy();
337+
img.resize(640, 480);
338+
image(img, 0, 0);
339+
shouldRedraw = false;
340+
}
341+
}
342+
343+
void serialEvent(Serial myPort) {
344+
lastUpdate = millis();
345+
346+
// read the received bytes
347+
myPort.readBytes(frameBuffer);
348+
349+
// Access raw bytes via byte buffer
350+
ByteBuffer bb = ByteBuffer.wrap(frameBuffer);
351+
352+
/*
353+
Ensure proper endianness of the data for > 8 bit values.
354+
When using > 8bit values uncomment the following line and
355+
adjust the translation to the pixel color.
356+
*/
357+
//bb.order(ByteOrder.BIG_ENDIAN);
358+
359+
int i = 0;
360+
361+
while (bb.hasRemaining()) {
362+
// read 8-bit pixel
363+
byte pixelValue = bb.get();
364+
365+
// set pixel color
366+
myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue));
367+
}
368+
369+
myImage.updatePixels();
370+
371+
// Ensures that the new image data is drawn in the next draw loop
372+
shouldRedraw = true;
373+
374+
// Let the Arduino sketch know we received all pixels
375+
// and are ready for the next frame
376+
myPort.write(1);
377+
}
378+
```
379+
380+
**Authors:** Lenard George, Sebastian Romero
381+
**Reviewed by:** Sebastian Romero [2021-04-26]
382+
**Last revision:** Sebastian Romero [2021-04-26]

scripts/validation/config/config-tutorials.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ allowedSyntaxSpecifiers:
99
- cpp
1010
- py
1111
- text
12+
- java
1213

1314
allowNestedLists: false
1415
metadataSchema: scripts/validation/config/metadata-schema.json

0 commit comments

Comments
 (0)