Usando Grove Vision AI V2 con Comandos AT
Introducción
Grove Vision AI V2 |
---|
![]() |
El Grove Vision AI V2 es un potente módulo de cámara AI que permite a los usuarios desplegar modelos listos para usar en aplicaciones de visión. Además de sus capacidades de AI, el Grove Vision AI V2 ofrece varias características de hardware y opciones de uso para mejorar su funcionalidad y facilidad de uso.
En esta página, nos enfocaremos en guiar a los usuarios sobre cómo utilizar efectivamente la biblioteca de Arduino específicamente diseñada para el Grove Vision AI V2. Cubriremos el proceso de instalación, características clave, y proporcionaremos ejemplos prácticos que demuestran cómo usar las funciones de la biblioteca para controlar y configurar el módulo, permitiendo a los usuarios crear aplicaciones innovadoras basadas en visión con facilidad.
Firmware y Conjunto de Comandos AT
El Grove Vision AI V2 viene con firmware preinstalado que soporta un conjunto de comandos AT. Este conjunto de comandos permite a los usuarios controlar y configurar la funcionalidad del módulo sin la necesidad de entornos de programación complejos.
La especificación del conjunto de comandos AT se puede encontrar en SSCMA-Micro. Es importante tener en cuenta que el conjunto de comandos AT puede evolucionar con el tiempo, y las versiones más nuevas del firmware pueden incluir comandos adicionales. Se recomienda mantener el firmware actualizado para acceder a las últimas características.
Si necesitas flashear un nuevo firmware o actualizar el firmware, puedes navegar al sitio web de SenseCraft AI.
Biblioteca de Arduino
Para simplificar el uso del conjunto de comandos AT y proporcionar una interfaz amigable para el usuario, está disponible una biblioteca de Arduino llamada Seeed_Arduino_SSCMA. Esta biblioteca envuelve los comandos AT en funciones fáciles de usar, permitiendo a los usuarios integrar rápidamente el Grove Vision AI V2 en sus proyectos de Arduino.
La biblioteca de Arduino mantiene compatibilidad con el conjunto de comandos AT más reciente, asegurando un enfoque consistente y unificado para interactuar con el módulo. Al utilizar esta biblioteca, los usuarios pueden enfocarse en desarrollar sus aplicaciones sin preocuparse por los detalles de bajo nivel de los comandos AT.
MCU | Versión de Placa | Grove(I2C) | Pin(Uart) |
SAMD21 | 1.8.5 | ✅ | ⚠️ |
RP2040 | 3.9.1 | ✅ | ✅ |
nRF52840 - nRF52 Boards | 1.1.8 | ✅ | ⚠️ |
nRF52840 - mbed-enabled | 2.9.2 | ✅ | ⚠️ |
ESP32C3 | 2.0.17 | ✅ | ✅ |
ESP32S3 | 2.0.17 | ✅ | ✅ |
*⚠️: podría no funcionar bien
Conectar el Grove Vision AI V2
- Grove(I2C)
- Compatible con XIAO
Usando un cable grove para conectar el Grove Vision AI V2 a cualquier placa mcu.
De esta manera puedes usar no solo UART sino también I2C para establecer una conexión.
Para alta capacidad de respuesta, la velocidad de baudios serial del Grove vision v2 es 921600
por defecto.
Así que necesitarás usar serial por hardware en lugar de serial por software para conectar.
Ejemplo 1: Capturar una imagen
Consulta invoke - FAQ
// invoke once, no filter, contain image
if (!AI.invoke(1, false, true)){
if (AI.last_image().length() > 0){
Serial.print("Last image:");
Serial.println(AI.last_image());
}
}
AI.last_image()
es una cadena, que contiene los datos de imagen (JPEG) codificados en base64. 🖱️¿aprender cómo analizarla?
Código Completo
- Grove(I2C)
- Compatible con XIAO (UART)
#include <Seeed_Arduino_SSCMA.h>
SSCMA AI;
void setup()
{
AI.begin();
Serial.begin(9600);
}
void loop()
{
// invoke once, no filter, get image
if (!AI.invoke(1, false, true)){
if (AI.last_image().length() > 0){
Serial.print("Last image:");
Serial.println(AI.last_image());
}
}
}
#include <Seeed_Arduino_SSCMA.h>
#ifdef ESP32
#include <HardwareSerial.h>
// Define two Serial devices mapped to the two internal UARTs
HardwareSerial atSerial(0);
#else
#define atSerial Serial1
#endif
SSCMA AI;
void setup()
{
AI.begin(&atSerial);
Serial.begin(9600);
}
void loop()
{
// invoke once, no filter, get image
if (!AI.invoke(1, false, true)){
if (AI.last_image().length() > 0){
Serial.print("Last image:");
Serial.println(AI.last_image());
}
}
}
Conectar dispositivos | Subir firmware | Monitorear |
---|---|---|
![]() | ![]() | ![]() |
Ejemplo 2: Obtener resultado de inferencia
Código Completo
- Grove(I2C)
- Compatible con XIAO (UART)
#include <Seeed_Arduino_SSCMA.h>
SSCMA AI;
void setup() {
AI.begin();
Serial.begin(9600);
}
void loop() {
if (!AI.invoke(1, false, false)) { // invoke once, no filter, not contain image
Serial.println("invoke success");
Serial.print("perf: prepocess=");
Serial.print(AI.perf().prepocess);
Serial.print(", inference=");
Serial.print(AI.perf().inference);
Serial.print(", postpocess=");
Serial.println(AI.perf().postprocess);
for (int i = 0; i < AI.boxes().size(); i++) {
Serial.print("Box[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.boxes()[i].target);
Serial.print(", score=");
Serial.print(AI.boxes()[i].score);
Serial.print(", x=");
Serial.print(AI.boxes()[i].x);
Serial.print(", y=");
Serial.print(AI.boxes()[i].y);
Serial.print(", w=");
Serial.print(AI.boxes()[i].w);
Serial.print(", h=");
Serial.println(AI.boxes()[i].h);
}
for (int i = 0; i < AI.classes().size(); i++) {
Serial.print("Class[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.classes()[i].target);
Serial.print(", score=");
Serial.println(AI.classes()[i].score);
}
for (int i = 0; i < AI.points().size(); i++) {
Serial.print("Point[");
Serial.print(i);
Serial.print("]: target=");
Serial.print(AI.points()[i].target);
Serial.print(", score=");
Serial.print(AI.points()[i].score);
Serial.print(", x=");
Serial.print(AI.points()[i].x);
Serial.print(", y=");
Serial.println(AI.points()[i].y);
}
for (int i = 0; i < AI.keypoints().size(); i++) {
Serial.print("keypoint[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.keypoints()[i].box.target);
Serial.print(", score=");
Serial.print(AI.keypoints()[i].box.score);
Serial.print(", box:[x=");
Serial.print(AI.keypoints()[i].box.x);
Serial.print(", y=");
Serial.print(AI.keypoints()[i].box.y);
Serial.print(", w=");
Serial.print(AI.keypoints()[i].box.w);
Serial.print(", h=");
Serial.print(AI.keypoints()[i].box.h);
Serial.print("], points:[");
for (int j = 0; j < AI.keypoints()[i].points.size(); j++) {
Serial.print("[");
Serial.print(AI.keypoints()[i].points[j].x);
Serial.print(",");
Serial.print(AI.keypoints()[i].points[j].y);
Serial.print("],");
}
Serial.println("]");
}
}
}
#include <Seeed_Arduino_SSCMA.h>
#ifdef ESP32
#include <HardwareSerial.h>
// Define two Serial devices mapped to the two internal UARTs
HardwareSerial atSerial(0);
#else
#define atSerial Serial1
#endif
SSCMA AI;
void setup() {
AI.begin( & atSerial);
Serial.begin(9600);
}
void loop() {
if (!AI.invoke(1, false, false)) { // invoke once, no filter, not contain image
Serial.println("invoke success");
Serial.print("perf: prepocess=");
Serial.print(AI.perf().prepocess);
Serial.print(", inference=");
Serial.print(AI.perf().inference);
Serial.print(", postpocess=");
Serial.println(AI.perf().postprocess);
for (int i = 0; i < AI.boxes().size(); i++) {
Serial.print("Box[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.boxes()[i].target);
Serial.print(", score=");
Serial.print(AI.boxes()[i].score);
Serial.print(", x=");
Serial.print(AI.boxes()[i].x);
Serial.print(", y=");
Serial.print(AI.boxes()[i].y);
Serial.print(", w=");
Serial.print(AI.boxes()[i].w);
Serial.print(", h=");
Serial.println(AI.boxes()[i].h);
}
for (int i = 0; i < AI.classes().size(); i++) {
Serial.print("Class[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.classes()[i].target);
Serial.print(", score=");
Serial.println(AI.classes()[i].score);
}
for (int i = 0; i < AI.points().size(); i++) {
Serial.print("Point[");
Serial.print(i);
Serial.print("]: target=");
Serial.print(AI.points()[i].target);
Serial.print(", score=");
Serial.print(AI.points()[i].score);
Serial.print(", x=");
Serial.print(AI.points()[i].x);
Serial.print(", y=");
Serial.println(AI.points()[i].y);
}
for (int i = 0; i < AI.keypoints().size(); i++) {
Serial.print("keypoint[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.keypoints()[i].box.target);
Serial.print(", score=");
Serial.print(AI.keypoints()[i].box.score);
Serial.print(", box:[x=");
Serial.print(AI.keypoints()[i].box.x);
Serial.print(", y=");
Serial.print(AI.keypoints()[i].box.y);
Serial.print(", w=");
Serial.print(AI.keypoints()[i].box.w);
Serial.print(", h=");
Serial.print(AI.keypoints()[i].box.h);
Serial.print("], points:[");
for (int j = 0; j < AI.keypoints()[i].points.size(); j++) {
Serial.print("[");
Serial.print(AI.keypoints()[i].points[j].x);
Serial.print(",");
Serial.print(AI.keypoints()[i].points[j].y);
Serial.print("],");
}
Serial.println("]");
}
}
}
Conectar dispositivos | Cargar firmware | Monitorear |
---|---|---|
![]() | ![]() | ![]() |
Ejemplo 3: Guardar Imágenes JPEG en Tarjeta SD
Más detalles en la sección Set action trigger - AT protocol.
El módulo Grove Vision AI V2 permite guardar imágenes JPEG directamente en una tarjeta SD externa. Para garantizar la compatibilidad, se recomienda formatear la tarjeta SD como FAT32 con un tamaño de clúster de 8192
o usar el sistema de archivos exFAT. Al guardar imágenes, el módulo crea automáticamente una ruta de guardado predeterminada llamada Grove Vision AI (V2) Export
si no existe ya.
- Asegúrate de que la versión del firmware sea posterior al 18/4/2024.
Dentro de esta ruta de guardado, se crea una nueva carpeta para cada sesión de arranque cuando se activa la acción de guardado, y el nombre de la carpeta es un número incrementado. El nombre de la carpeta más reciente se almacena en un archivo oculto llamado .sscma
en la ruta de guardado, que no debe ser modificado por el usuario para evitar posibles errores.
Para previsualizar imágenes como streams usando Python
import os
import platform
import tkinter as tk
from PIL import Image, ImageTk
import time
class ImagePlayer:
def __init__(self, parent_directory, switch_time=1):
self.parent_directory = parent_directory
self.image_files = []
self.current_index = 0
self.switch_time = switch_time
self.root = tk.Tk()
self.label = tk.Label(self.root)
self.label.pack()
self.load_image_files()
def load_image_files(self):
# Get all subdirectories in the parent directory
directories = [os.path.join(self.parent_directory, folder) for folder in os.listdir(self.parent_directory) if os.path.isdir(os.path.join(self.parent_directory, folder))]
# Traverse subdirectories and get image file paths
for directory in directories:
image_files = [os.path.join(directory, file) for file in sorted(os.listdir(directory)) if file.endswith(('.jpg', '.jpeg', '.png'))]
self.image_files.extend(image_files)
def play_images(self):
if self.current_index < len(self.image_files):
image_file = self.image_files[self.current_index]
image = Image.open(image_file)
self.display_image(image)
self.current_index += 1
self.root.after(int(self.switch_time * 1000), self.play_images)
else:
self.root.destroy()
def display_image(self, image):
# Adjust the image size to fit the window
width, height = self.root.winfo_screenwidth(), self.root.winfo_screenheight()
image.thumbnail((width, height))
# Convert the image to a format usable by Tkinter
photo = ImageTk.PhotoImage(image)
# Display the image in the label
self.label.config(image=photo)
self.label.image = photo
def start(self):
self.root.geometry("240x240") # Set the window size
self.root.title("Image Player") # Set the window title
self.root.after(0, self.play_images) # Start playing the images
self.root.mainloop()
# Create an ImagePlayer object and provide the parent directory path
parent_directory = r"E:\Grove Vision AI (V2) Export"
player = ImagePlayer(parent_directory, switch_time=0.3) # Customize the image switch time here (in seconds)
# Start the image player
player.start()
Cuando llamas a save_jpeg()
, significa que envías un comando AT AT+ACTION="save_jpeg()"
al módulo Grove Vision AI V2.
Y debes llamarlo solo una vez.
Si ya no quieres almacenar la imagen JPEG, tendrás que limpiar los Action Sets, incluso si reinicias tu módulo Grove Vision AI V2.
void setup()
{
atSerial.println("AT+ACTION=\"\""); // Same as `AI.clean_actions()`
AI.clean_actions();
}
Código Completo
- Grove(I2C)
- Compatible con XIAO (UART)
- Verificar acciones AT
#include <Seeed_Arduino_SSCMA.h>
SSCMA AI;
void setup()
{
Serial.begin(9600); // Initialize serial port
AI.begin();
AI.save_jpeg();
}
void loop()
{
static int cnt = 0;
// every invoke it will save jpeg
if (!AI.invoke(1, false, false)){
Serial.printf("Record image %d\n", ++cnt);
}
}
#include <Seeed_Arduino_SSCMA.h>
#ifdef ESP32
#include <HardwareSerial.h>
// Define two Serial devices mapped to the two internal UARTs
HardwareSerial atSerial(0);
#else
#define atSerial Serial1
#endif
SSCMA AI;
void setup()
{
Serial.begin(9600); // Initialize serial port
AI.begin(&atSerial);
AI.save_jpeg();
}
void loop()
{
static int cnt = 0;
// every invoke it will save jpeg
if (!AI.invoke(1, false, true)){
Serial.printf("Record image %d\n", ++cnt);
}
}
#include <Seeed_Arduino_SSCMA.h>
#ifdef ESP32
#include <HardwareSerial.h>
// Define two Serial devices mapped to the two internal UARTs
HardwareSerial atSerial(0);
#else
#define atSerial Serial1
#endif
SSCMA AI;
void setup()
{
Serial.begin(9600); // Initialize serial port
AI.begin(&atSerial);
AI.save_jpeg();
}
void loop()
{
atSerial.println("AT+ACTION?"); // request to get action information
String str_action = atSerial.readString(); // read the response
if(str_action.indexOf("save_jpeg") > 0){ // check if the action exists
Serial.println("save_jpeg exists");
Serial.println("trigger action: clean_actions");
AI.clean_actions(); // clean the action
}else{ // if null, trigger action to save_jpeg again
Serial.println("save_jpeg doesn't exist");
Serial.println("trigger action: save_jpeg");
AI.save_jpeg();
}
delay(5000);
}
si no funciona, por favor verifica si la tarjeta SD está formateada e insertada correctamente.
Conectar dispositivos | Subir firmware | Monitor |
---|---|---|
![]() | ![]() | ![]() |
Imagen Base64 a JPEG
Hay dos formas de obtener la imagen decodificada cuando intentas obtener la imagen JPEG en programación:
- ESP32
- Arduino
Si tu mcu es ESP32, puedes usar el siguiente código para obtener la imagen:
#include <string.h>
#include "mbedtls/base64.h"
#include <Seeed_Arduino_SSCMA.h>
#define DECODED_IMAGE_MAX_SIZE(15 * 1024) // choose your own max size_t
static unsigned char jpegImage[DECODED_IMAGE_MAX_SIZE + 1];
size_t decode_base64_image(const unsigned char * p_data, unsigned char * decoded_str) {
if (!p_data || !decoded_str)
return 0;
size_t str_len = strlen((const char * ) p_data);
size_t output_len = 0;
// Obtain the decoded length
int decode_ret = mbedtls_base64_decode(NULL, 0, & output_len, p_data, str_len);
if (decode_ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) {
Serial.println(TAG, "Invalid character in Base64 string");
return 0;
}
// Check if the decoded length is reasonable
if (output_len == 0 || output_len > DECODED_IMAGE_MAX_SIZE) {
Serial.println("Base64 decode output size is too large or zero.");
return 0;
}
// Actual decoding operation
decode_ret = mbedtls_base64_decode(decoded_str, DECODED_IMAGE_MAX_SIZE, & output_len, p_data, str_len);
if (decode_ret != 0) {
Serial.println(TAG, "Failed to decode Base64 string, error: %d", decode_ret);
return 0;
}
Serial.println("str_len: %d, output_len: %d", str_len, output_len);
return output_len;
}
SSCMA AI;
void setup() {
AI.begin();
Serial.begin(115200);
}
void loop() {
if (!AI.invoke(1, false, true)) {
if (AI.last_image().length() > 0) {
Serial.print("Last image:");
Serial.println(AI.last_image());
size_t jpegImageSize = decode_base64_image(AI.last_image().c_str(), jpegImage);
if (jpegImageSize > 0) {
// your function to display the jpeg image
display_jpeg_image(jpegImage, jpegImageSize);
}
}
}
}
#include <base64.h>
#include <Seeed_Arduino_SSCMA.h>
#define atSerial Serial1 /* Define your Serial interface */
SSCMA AI;
void setup() {
AI.begin( & atSerial);
Serial.begin(115200);
}
void loop() {
// invoke once, no filter, get image
if (!AI.invoke(1, false, true)) {
if (AI.last_image().length() > 0) {
String toEncode = AI.last_image();
Serial.print("Image Code:");
Serial.println(toEncode);
String encoded_jpeg = base64::encode(toEncode);
// your function to display the jpeg image
func_display_jpeg(encoded_jpeg);
}
}
}
Base64 es una forma de codificar datos binarios (como imágenes) en una cadena de caracteres ASCII. Esto permite que los datos binarios se incluyan en formatos que no admiten binarios de forma nativa, como JSON.
La codificación Base64 se utiliza para evitar transmitir datos binarios directamente a través de protocolos basados en texto (como HTTP, JSON, XML). También evita la necesidad de bibliotecas de análisis adicionales, ya que la mayoría de los lenguajes de programación tienen funciones integradas de codificación/decodificación Base64. Base64 permite que los datos binarios se transmitan de forma segura como texto ASCII estándar y se decodifiquen fácilmente de vuelta a su forma binaria original.
Hay muchas herramientas en línea que facilitan la decodificación de Base64 a una imagen, como: Decodificador en línea Base64 Simplemente pega la cadena Base64 en la herramienta y mostrará la imagen decodificada.
Personalización y desarrollo de SDK
Para usuarios que requieren más personalización y funcionalidad avanzada, el Grove Vision AI V2 también admite el desarrollo de SDK. El chip controlador principal Himax del módulo se puede programar directamente usando el SDK proporcionado, permitiendo proyectos de grado industrial con características altamente personalizadas.
Los usuarios interesados en el desarrollo de SDK pueden consultar los ejemplos existentes, como sdio_app, que demuestra operaciones SDIO directas. Estos ejemplos sirven como punto de partida para modificar e implementar proyectos personalizados.
Al aprovechar el poder del SDK, los usuarios pueden desbloquear todo el potencial del Grove Vision AI V2 y crear soluciones adaptadas a sus necesidades específicas. Sigue la página wiki anterior para tener más orientación.
FAQ y solución de problemas
¿Cuál es la función de invoke?
Cada vez que se obtienen los valores de datos del Grove Vision V2, se supone que debe llamar a la función invoke
:
/**
* @brief Invoke the algorithm for a specified number of times
*
* This function invokes the algorithm for a specified number of times and waits for the response and event.
* The result can be filtered based on the difference from the previous result, and the event reply can be
* configured to contain only the result data or include the image data as well.
*
* @param times The number of times to invoke the algorithm
* @param filter If true, the event reply will only be sent if the last result is different from the previous result
* (compared by geometry and score)
* @param show If true, the event reply will also contain the image data; if false, the event reply will *only contain* the result data
* @return int Returns CMD_OK if the invocation is successful and the response and event are received within the timeout;
* otherwise, returns CMD_ETIMEDOUT
*
* Pattern: AT+INVOKE=<N_TIMES,DIFFERED,RESULT_ONLY>\r
* Request: AT+INVOKE=1,0,1\r
*/
int SSCMA::invoke(int times, bool filter, bool show);
¿Qué hace AI.begin()
?
Si usas SSCMA.begin()
, por defecto utiliza I2C (Wire) para la comunicación, como se define en el encabezado de la función:
bool begin(TwoWire *wire = &Wire, int32_t rst = -1, uint16_t address = I2C_ADDRESS,
uint32_t wait_delay = 2, uint32_t clock = SSCMA_IIC_CLOCK);
Recursos
Referencias
Contribuir
¡Damos la bienvenida a contribuciones en cualquier forma! Si deseas contribuir a esta página, puedes:
- Reportar errores o sugerir nuevas características abriendo un issue
- Enviar mejoras o correcciones de página creando un pull request
- Mejorar la documentación sugiriendo ediciones o adiciones
- Ayudar a responder preguntas o proporcionar soporte a otros usuarios
- Compartir el producto con otros que puedan encontrarlo útil
Para comenzar, por favor lee nuestras Pautas de Contribución para más información sobre cómo contribuir y el proceso para enviar pull requests.
¡Apreciamos todas las contribuciones y te agradecemos por ayudar a mejorarlo!
Soporte Técnico y Discusión de Productos
¡Gracias por elegir nuestros productos! Estamos aquí para proporcionarte diferentes tipos de soporte para asegurar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para atender diferentes preferencias y necesidades.