arduinocc
Published © MIT

AI on the Edge with Arduino

Here we show the end to end process to create a custom Object Detection System, all running on the Edge using a Google Coral Dev Board Micro

IntermediateFull instructions provided2 hours109
AI on the Edge with Arduino

Things used in this project

Hardware components

Coral Dev Board Micro
Google Coral Dev Board Micro
×1

Software apps and online services

visual micro
Microsoft Visual Studio 2022
LabelImg
Google Colaboratory

Story

Read more

Schematics

Coral Dev Board Micro

Just the board...

Code

CoralCustomObjectDetection.ino

C/C++
Main INO Sketch for this Example
/**
* Adapted from Shawn Hymel's sketch to add WiFi capability, and complete webpage with bounding box drawing
* Example Model created with Colab Notebook:
* https://colab.research.google.com/drive/163JTsZU_bthrhiIh5QncyuzGeHCC6e_r?usp=sharing
* (Clone Of:  https://colab.research.google.com/github/ShawnHymel/google-coral-micro-object-detection/blob/master/notebooks/mediapipe-object-detection-learning.ipynb)
*
* This code is additions to the below article:
* https://github.com/ShawnHymel/google-coral-micro-object-detection/tree/master/firmware/object-detection-http
* Which uses tasks though is still a single threaded infinitely looped execution.
*
* MLModels can be reviewed using Netron Website, which helps visualise the inputs and outputs
* https://netron.app/
*
* TODO: Does being able to fit the tensors etc help us to use Teachable Machine?
* This could be the in depth version, and that the nice click and go version for classification....
*
* -----------------------------------------------------------------------------------------
 * Object Detection with HTTP server over WiFi (via USB does not work on Windows Clients)
 *
 * Perform object detection with the provided TFLite file running on the TPU.
 * Image and object detection data is streamed to an HTTP server that can be
 * accessed via Ethernet-over-USB connection.
 *
 * Based on the following code examples from Google LLC:
 *  - https://github.com/google-coral/coralmicro/tree/main/examples/camera_streaming_http
 *  - https://github.com/google-coral/coralmicro/tree/main/examples/detect_objects
 *
 * WARNING: There is a bug in HTTP handler part of this code that seems to
 * corrupt the bounding box info when requested by the client. I suspect this is
 * due to the server not being able to handle multiple requests at the same time
 * or there is a race condition with the bounding box info being updated. If you
 * solve this, please let me know. You will get a metaphorical gold star.
 *
 * Author: Shawn Hymel
 * Date: November 25, 2023
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


 // WiFi Credentials for the Wifi HTTP Server
#define WIFI_SSID "xxxxxxxxxxx"
#define WIFI_PASS "xxxxxxxxxxx"

// Wifi HTTP Server - 0: Disable, 1: Enable
#define ENABLE_HTTP_SERVER 1
// Ethernet Over USB (Linux Only) - 0: Disable, 1: Enable
#define USEETHERNETOVERUSB 0
// Show Files on-chip on startup - 0: Disable, 1: Enable
#define PRINTFSTOSERIAL_ONBOOT 1
// Show Detection Information via Serial (Bounding Box, Confidence etc) - 0: Disable, 1: Enable
#define DEBUG 1


#include <cstdio>
#include <vector>
#include <cmath>

#include "libs/base/filesystem.h"
#include "libs/base/http_server.h"
#include "libs/base/led.h"
#include "libs/base/strings.h"
#include "libs/base/utils.h"
#include "libs/camera/camera.h"
#include "libs/libjpeg/jpeg.h"
#include "libs/tensorflow/detection.h"
#include "libs/tensorflow/utils.h"
#include "libs/tpu/edgetpu_manager.h"
#include "libs/tpu/edgetpu_op.h"
#include "third_party/freertos_kernel/include/FreeRTOS.h"
#include "third_party/freertos_kernel/include/task.h"
#include "third_party/freertos_kernel/include/semphr.h"
#include "third_party/tflite-micro/tensorflow/lite/micro/micro_error_reporter.h"
#include "third_party/tflite-micro/tensorflow/lite/micro/micro_interpreter.h"
#include "third_party/tflite-micro/tensorflow/lite/micro/micro_mutable_op_resolver.h"

#include "metadata.hpp"
#include <WiFi.h>

 // NOTE: These MUST Be present to ensure floats can be printed and used in strings!!
asm(".global _printf_float"); // This should be defined in cxxflags.txt
asm(".global _scanf_float"); // this is missing altogether!

namespace coralmicro {
    namespace {

        // Image result struct
        typedef struct {
            std::string info;
            std::vector<uint8_t>* jpeg;
        } ImgResult;

        // Bounding box struct
        typedef struct {
            float id;
            float score;
            float ymin;
            float xmin;
            float ymax;
            float xmax;
        } BBox;

        // Camera settings
        constexpr auto camRotation = CameraRotation::k270; // Default: CameraRotation::k0

        // Globals
        constexpr char kIndexFileName[] = "/index.html";
        constexpr char kCameraStreamUrlPrefix[] = "/camera_stream";
        constexpr char kBoundingBoxPrefix[] = "/bboxes";
        constexpr char kModelPath[] =
            "/model_int8_edgetpu.tflite";
        constexpr int kTensorArenaSize = 8 * 1024 * 1024;
        STATIC_TENSOR_ARENA_IN_SDRAM(tensor_arena, kTensorArenaSize);
        static std::vector<uint8_t>* img_ptr;
        static SemaphoreHandle_t img_mutex;
        static SemaphoreHandle_t bbox_muchtex;
        static SemaphoreHandle_t websrv_muchtex;
        static int img_width;
        static int img_height;
        static constexpr float score_threshold = 0.5f;
        static constexpr float iou_threshold = 0.3f;
        static constexpr size_t max_bboxes = 5;
        static constexpr unsigned int bbox_buf_size = 100 + (max_bboxes * 200) + 1;
        static char bbox_buf[bbox_buf_size];

        // Copy of image data for HTTP server
#if ENABLE_HTTP_SERVER
        static std::vector<uint8_t>* img_copy;
#endif

        /*******************************************************************************
         * Functions
         */

        void Blink(unsigned int num, unsigned int delay_ms);
        bool CalculateAnchorBox(unsigned int idx, float* anchor);
        float CalculateIOU(BBox* bbox1, BBox* bbox2);

#if ENABLE_HTTP_SERVER
        /**
         * Handle HTTP requests
         */
        HttpServer::Content UriHandler(const char* uri) {

            // Give client main page
            if (StrEndsWith(uri, "index.shtml") ||
                StrEndsWith(uri, "coral_micro_camera_something.html")) {
                return std::string(kIndexFileName);
            }
            // Deliver our file from the FileSystem
            else if (StrEndsWith(uri, "coral_micro_camera.html")) {
                printf("Loading Camera HTML From LittleFS...\r\n");
                lfs_dir_t root;
                CHECK(lfs_dir_open(Lfs(), &root, "/") >= 0);
                std::string readstr;
                printf("Reading File...\r\n");
                CHECK(LfsReadFile("/web/coral_micro_camera.html", &readstr));
                CHECK(lfs_dir_close(Lfs(), &root) >= 0);
                printf("Parsing to Vector...\r\n");
                // If we return a string its treated as a file.... which would be right really! (if it would serve the file by default)
                std::vector<uint8_t> response;
                response.reserve(5000);
                StrAppend(&response, readstr.c_str());
                printf("Parsing to Vector...\r\n");
                return response;

            }
            // Give client compressed image data
            else if (StrEndsWith(uri, kCameraStreamUrlPrefix)) {

                // Read image from shared memory and compress to JPG
                std::vector<uint8_t> jpeg;
                if (xSemaphoreTake(img_mutex, portMAX_DELAY) == pdTRUE) {
                    JpegCompressRgb(
                        img_copy->data(),
                        img_width,
                        img_height,
                        75,         // Quality
                        &jpeg
                    );
                    xSemaphoreGive(img_mutex);
                }

                return jpeg;

                // Give client bounding box info
            }
            else if (StrEndsWith(uri, kBoundingBoxPrefix)) {

                // Read bounding box info from shared memory and convert to vector of bytes
                char bbox_info_copy[bbox_buf_size];
                std::vector<uint8_t> bbox_info_bytes;
                if (xSemaphoreTake(bbox_muchtex, portMAX_DELAY) == pdTRUE) {
                    std::strcpy(bbox_info_copy, bbox_buf);
                    bbox_info_bytes.assign(
                        bbox_info_copy,
                        bbox_info_copy + std::strlen(bbox_info_copy)
                    );

                    if (bbox_info_copy[25] == ']' && bbox_info_copy[24] == '[') {
                        printf("No Boxes");
                    }
                    else {
                        printf("BBoxes:%s\r\n", bbox_info_copy);
                    }
                    
                    xSemaphoreGive(bbox_muchtex);
                }

                // TODO: Figure out the multi-request or race condition bug that is causing
                // the bbox_info_bytes to be corrupted. The workaround is to have the
                // client timeout if it doesn't get a response in some amount of time.
                // vMicro: We haven't seen corruption per-se but the float objects broke the JSON string
                // as soon as one was included so it was never fully formed.
                // This was due to the missing ASM instructions at the top (or additional cxxflags.txt entries required)
                return bbox_info_bytes;
            }

            return {};
        }
#endif

        void PrintDirectory(lfs_dir_t* dir, const char* path, int num_tabs) {
            constexpr int kMaxDepth = 3;
            if (num_tabs > kMaxDepth) {
                return;
            }

            lfs_info info;
            while (lfs_dir_read(Lfs(), dir, &info) > 0) {
                if (info.name[0] == '.') {
                    continue;
                }

                for (int i = 0; i < num_tabs; ++i) {
                    printf("\t");
                }

                printf("%s", info.name);

                if (info.type == LFS_TYPE_DIR) {
                    char subpath[LFS_NAME_MAX];
                    printf("/\r\n");
                    lfs_dir_t subdir;
                    snprintf(subpath, LFS_NAME_MAX, "%s/%s", path, info.name);
                    CHECK(lfs_dir_open(Lfs(), &subdir, subpath) >= 0);
                    PrintDirectory(&subdir, subpath, num_tabs + 1);
                    CHECK(lfs_dir_close(Lfs(), &subdir) >= 0);
                }
                else {
                    printf("\t\t%ld\r\n", info.size);
                }
            }
        }

        void PrintFilesystemContents() {
            lfs_dir_t root;
            CHECK(lfs_dir_open(Lfs(), &root, "/") >= 0);
            printf("Printing filesystem:\r\n");
            PrintDirectory(&root, "", 0);
            printf("Finished printing filesystem.\r\n");
            CHECK(lfs_dir_close(Lfs(), &root) >= 0);
        }

        /**
         * Loop forever taking images from the camera and performing inference
         */
        [[noreturn]] void InferenceTask(void* param) {
#if !USEETHERNETOVERUSB
            // Wait for the Webserver to startup
            while (xSemaphoreTake(websrv_muchtex, configTICK_RATE_HZ * 1000) != pdTRUE) {
                delay(500);
            }
#endif
            

            // Used for calculating FPS
            unsigned long dtime;
            unsigned long timestamp;
            unsigned long timestamp_prev = xTaskGetTickCount() *
                (1000 / configTICK_RATE_HZ);

            // x_center, y_center, w, h
            float anchor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };

            // Load model
            std::vector<uint8_t> model;
            if (!LfsReadFile(kModelPath, &model)) {
                printf("ERROR: Failed to load %s\r\n", kModelPath);
                vTaskSuspend(nullptr);
            }

            // Initialize TPU
            auto tpu_context = EdgeTpuManager::GetSingleton()->OpenDevice();
            if (!tpu_context) {
                printf("ERROR: Failed to get EdgeTpu context\r\n");
                vTaskSuspend(nullptr);
            }

            // Initialize ops
            tflite::MicroErrorReporter error_reporter;
            tflite::MicroMutableOpResolver<3> resolver;
            resolver.AddDequantize();
            resolver.AddDetectionPostprocess();
            resolver.AddCustom(kCustomOp, RegisterCustomOp());

            // Initialize TFLM interpreter for inference
            tflite::MicroInterpreter interpreter(
                tflite::GetModel(model.data()),
                resolver,
                tensor_arena,
                kTensorArenaSize,
                &error_reporter
            );
            if (interpreter.AllocateTensors() != kTfLiteOk) {
                printf("ERROR: AllocateTensors() failed\r\n");
                vTaskSuspend(nullptr);
            }

            // Check model input tensor size
            if (interpreter.inputs().size() != 1) {
                printf("ERROR: Model must have only one input tensor\r\n");
                vTaskSuspend(nullptr);
            }

            // Configure model inputs and outputs
            auto* input_tensor = interpreter.input_tensor(0);
            img_height = input_tensor->dims->data[1];
            img_width = input_tensor->dims->data[2];
            img_ptr = new std::vector<uint8>(img_height * img_width *
                CameraFormatBpp(CameraFormat::kRgb));
            std::vector<tensorflow::Object> results;

#if ENABLE_HTTP_SERVER
            // Copy image to shared memory for HTTP Server to send on request
            img_copy = new std::vector<uint8_t>(img_ptr->size());
#endif

            // Get output tensor shapes
            TfLiteTensor* tensor_bboxes = interpreter.output_tensor(0);
            TfLiteTensor* tensor_scores = interpreter.output_tensor(1);
            unsigned int num_boxes = tensor_bboxes->dims->data[1];
            unsigned int num_coords = tensor_bboxes->dims->data[2];
            unsigned int num_classes = tensor_scores->dims->data[2];

            // Get quantization parameters
            const float input_scale = input_tensor->params.scale;
            const int input_zero_point = input_tensor->params.zero_point;
            const float locs_scale = tensor_bboxes->params.scale;
            const int locs_zero_point = tensor_bboxes->params.zero_point;
            const float scores_scale = tensor_scores->params.scale;
            const int scores_zero_point = tensor_scores->params.zero_point;

            // Convert threshold to fixed point
            uint8_t score_threshold_quantized =
                static_cast<uint8_t>(score_threshold * 256);

            // Print input/output details
#if DEBUG
            printf("num_boxes: %d\r\n", num_boxes);
            printf("num_coords: %d\r\n", num_coords);
            printf("num_classes: %d\r\n", num_classes);
            printf("bytes in tensor_bboxes: %d\r\n", tensor_bboxes->bytes);
            if (tensor_scores->data.data == nullptr) {
                printf("tensor_scores.data is empty!\r\n");
            }
            printf("input_scale: %f\r\n", input_scale);
            printf("input_zero_point: %d\r\n", input_zero_point);
            printf("locs_scale: %f\r\n", locs_scale);
            printf("locs_zero_point: %d\r\n", locs_zero_point);
            printf("scores_scale: %f\r\n", scores_scale);
            printf("scores_zero_point: %d\r\n", scores_zero_point);
            printf("score_threshold_quantized: %d\r\n", score_threshold_quantized);
#endif

            // Do forever
            while (true) {

                std::vector<std::vector<float>> bbox_list;

                // Calculate time between inferences
                timestamp = xTaskGetTickCount() * (1000 / configTICK_RATE_HZ);
                dtime = timestamp - timestamp_prev;
                timestamp_prev = timestamp;

                // Turn status LED on to let the user know we're taking a photo
                LedSet(Led::kUser, true);

                // Get frame from camera using the configuration we set (~38 ms)
                if (xSemaphoreTake(img_mutex, portMAX_DELAY) == pdTRUE) {

                    // Configure camera image
                    CameraFrameFormat fmt{
                      CameraFormat::kRgb,
                      CameraFilterMethod::kBilinear,
                      camRotation,
                      img_height,
                      img_width,
                      false,            // Preserve ratio
                      img_ptr->data(),  // Where the image is saved
                      true              // Auto white balance
                    };

                    // Take a photo
                    if (!CameraTask::GetSingleton()->GetFrame({ fmt })) {
                        printf("ERROR: Could not capture frame from camera\r\n");
                        continue;
                    }

                    // Turn status LED off to let the user know we're done taking a photo
                    LedSet(Led::kUser, false);

                    // Copy image to input tensor (~6 ms)
                    std::memcpy(
                        tflite::GetTensorData<uint8_t>(input_tensor),
                        img_ptr->data(),
                        img_ptr->size()
                    );

                    // Perform inference (~65 ms)
                    if (interpreter.Invoke() != kTfLiteOk) {
                        printf("ERROR: Inference failed\r\n");
                        continue;
                    }

                    // Get data
                    uint8_t* scores = tensor_scores->data.uint8;
                    uint8_t* raw_boxes = tensor_bboxes->data.uint8;

                    // Find bounding boxes with scores above threshold
                    for (unsigned int i = 0; i < metadata::num_anchors; ++i) {
                        for (unsigned int c = 0; c < num_classes; ++c) {

                            // Only keep boxes above a particular score threshold
                            if (scores[i * num_classes + c] > score_threshold_quantized) {

                                // Calculate anchor box coordinates based on index
                                if (!CalculateAnchorBox(i, anchor)) {
                                    printf("ERROR: Could not calculate anchor box\r\n");
                                    continue;
                                }

                                // Assume raw box output tensor is given as YXHW format
                                float y_center = raw_boxes[(i * num_coords) + 0];
                                float x_center = raw_boxes[(i * num_coords) + 1];
                                float h = raw_boxes[(i * num_coords) + 2];
                                float w = raw_boxes[(i * num_coords) + 3];

                                // De-quantize the output boxes (move to x, y, w, h format)
                                x_center = (x_center - locs_zero_point) * locs_scale;
                                y_center = (y_center - locs_zero_point) * locs_scale;
                                w = (w - locs_zero_point) * locs_scale;
                                h = (h - locs_zero_point) * locs_scale;

                                // Scale the output boxes from anchor coordinates
                                x_center = x_center / metadata::x_scale * anchor[2] + anchor[0];
                                y_center = y_center / metadata::y_scale * anchor[3] + anchor[1];
                                if (metadata::apply_exp_scaling) {
                                    w = exp(w / metadata::w_scale) * anchor[2];
                                    h = exp(h / metadata::h_scale) * anchor[3];
                                }
                                else {
                                    w = w / metadata::w_scale * anchor[2];
                                    h = h / metadata::h_scale * anchor[3];
                                }

                                // Convert box coordinates to top left and bottom right
                                float x_min = x_center - w / 2.0f;
                                float y_min = y_center - h / 2.0f;
                                float x_max = x_center + w / 2.0f;
                                float y_max = y_center + h / 2.0f;

                                // Clamp values to between 0 and 1
                                x_min = std::max(std::min(x_min, 1.0f), 0.0f);
                                y_min = std::max(std::min(y_min, 1.0f), 0.0f);
                                x_max = std::max(std::min(x_max, 1.0f), 0.0f);
                                y_max = std::max(std::min(y_max, 1.0f), 0.0f);

                                // De-quantize the score
                                float score = (scores[(i * num_classes) + c] - scores_zero_point) *
                                    scores_scale;

                                // Add to list of bboxes
                                bbox_list.push_back({ (float)c, score,
                                  y_min, x_min, y_max, x_max });
                            }
                        }
                    }

                    // Copy image to separate buffer for HTTP server
#if ENABLE_HTTP_SERVER
                    std::memcpy(
                        img_copy->data(),
                        img_ptr->data(),
                        img_ptr->size()
                    );
#endif

                    // Unlock critical section
                    xSemaphoreGive(img_mutex);
                }

                // Sort bboxes by score
                std::sort(bbox_list.begin(), bbox_list.end(),
                    [](const std::vector<float>& a, const std::vector<float>& b) {
                        return a[1] > b[1];
                    }
                );

                // Perform non-maximum suppression
                for (unsigned int i = 0; i < bbox_list.size(); ++i) {
                    for (unsigned int j = i + 1; j < bbox_list.size(); ++j) {
                        BBox bbox1 = {
                          bbox_list[i][0],
                          bbox_list[i][1],
                          bbox_list[i][2],
                          bbox_list[i][3],
                          bbox_list[i][4],
                          bbox_list[i][5]
                        };
                        BBox bbox2 = {
                          bbox_list[j][0],
                          bbox_list[j][1],
                          bbox_list[j][2],
                          bbox_list[j][3],
                          bbox_list[j][4],
                          bbox_list[j][5]
                        };
                        float iou = CalculateIOU(&bbox1, &bbox2);
                        if (iou > iou_threshold) {
                            bbox_list.erase(bbox_list.begin() + j);
                            --j;
                        }
                    }
                }

                // Determine number of bboxes to send
                size_t num_bboxes_output = (bbox_list.size() < max_bboxes) ?
                    bbox_list.size() : max_bboxes;



                // Convert top k bboxes to JSON string
                std::string bbox_string = "{\"dtime\": " + std::to_string(dtime) + ", ";
                bbox_string.append("\"bboxes\": [");
                for (unsigned int i = 0; i < num_bboxes_output; ++i) {
                    int class_id = static_cast<int>(bbox_list[i][0]);
                    bbox_string.append("{\"id\": " + std::to_string(class_id) + ", ");
                    bbox_string += "\"score\": " + std::to_string(bbox_list[i][1]) + ", ";
                    bbox_string.append("\"xmin\": " + std::to_string(bbox_list[i][3]) + ", ");
                    bbox_string.append("\"ymin\": " + std::to_string(bbox_list[i][2]) + ", ");
                    bbox_string.append("\"xmax\": " + std::to_string(bbox_list[i][5]) + ", ");
                    bbox_string.append("\"ymax\": " + std::to_string(bbox_list[i][4]) + "}");
                    if (i != num_bboxes_output - 1) {
                        bbox_string.append(", ");
                    }
                }
                bbox_string.append("]}");

                // Check length of JSON string
                if (int(bbox_string.length()) > bbox_buf_size) {
                    printf("ERROR: Bounding box JSON string too long\r\n");
                    continue;
                }

                // Convert global char array
                if (xSemaphoreTake(bbox_muchtex, portMAX_DELAY) == pdTRUE) {
                    std::strcpy(bbox_buf, bbox_string.c_str());
                    xSemaphoreGive(bbox_muchtex);
                }

                // Print bounding box JSON string
                printf("%s\r\n", bbox_buf);

                // Sleep to let other tasks run (Webserver)
                vTaskDelay(pdMS_TO_TICKS(10));
            }
        }

        /**
        * Blink error codes on the status LED
        */
        void Blink(unsigned int num, unsigned int delay_ms) {
            static bool on = false;
            for (unsigned int i = 0; i < num * 2; i++) {
                on = !on;
                coralmicro::LedSet(Led::kStatus, on);
                vTaskDelay(pdMS_TO_TICKS(delay_ms));
            }
        }

        /**
         * Calculate anchor box coordinates based on index and metadata
         */
        bool CalculateAnchorBox(unsigned int idx, float* anchor) {

            unsigned int sector = 0;
            float x_idx;
            float x_center;
            float y_idx;
            float y_center;
            float w;
            float h;

            // Check index
            if (idx >= metadata::num_anchors) {
                return false;
            }

            // Find the sector that the index belongs in
            for (unsigned int s = 0; s < metadata::num_sectors; ++s) {
                if (idx >= metadata::reset_idxs[s]) {
                    sector = s;
                }
            }

            // Find the X centert
            x_idx = (idx % metadata::num_xs_per_y[sector]) /
                metadata::num_anchors_per_coord;
            x_center = (metadata::x_strides[sector] / 2.0f) +
                (x_idx * metadata::x_strides[sector]);

            // Find the Y center
            y_idx = (idx - metadata::reset_idxs[sector]) /
                metadata::num_xs_per_y[sector];
            y_center = (metadata::y_strides[sector] / 2.0f) +
                (y_idx * metadata::y_strides[sector]);

            // Find the width and height
            w = metadata::widths[sector][idx % metadata::num_anchors_per_coord];
            h = metadata::heights[sector][idx % metadata::num_anchors_per_coord];

            // Save anchor box coordinates
            anchor[0] = x_center;
            anchor[1] = y_center;
            anchor[2] = w;
            anchor[3] = h;

            return true;
        }

        /**
         * Calculate intersection over union (IOU) between two bounding boxes
         */
        float CalculateIOU(BBox* bbox1, BBox* bbox2) {

            // Calculate intersection
            float x_min = std::max(bbox1->xmin, bbox2->xmin);
            float y_min = std::max(bbox1->ymin, bbox2->ymin);
            float x_max = std::min(bbox1->xmax, bbox2->xmax);
            float y_max = std::min(bbox1->ymax, bbox2->ymax);
            float intersection = std::max(0.0f, x_max - x_min) *
                std::max(0.0f, y_max - y_min);

            // Calculate union
            float bbox1_area = (bbox1->xmax - bbox1->xmin) *
                (bbox1->ymax - bbox1->ymin);
            float bbox2_area = (bbox2->xmax - bbox2->xmin) *
                (bbox2->ymax - bbox2->ymin);
            float union_area = bbox1_area + bbox2_area - intersection;

            // Calculate IOU
            float iou = 0.0f;
            if (union_area > 0.0f) {
                iou = intersection / union_area;
            }

            return iou;
        }

        /*******************************************************************************
         * Main
         */

        void Main() {

            // Say hello
            Blink(3, 500);
#if DEBUG
            printf("Object detection inference and HTTP server over USB\r\n");
#endif

            // Initialize image mutex
            img_mutex = xSemaphoreCreateMutex();
            if (img_mutex == NULL) {
                printf("Error creating image mutex\r\n");
                while (true) {
                    Blink(2, 100);
                }
            }

            // Initialize bounding box mutex
            bbox_muchtex = xSemaphoreCreateMutex();
            if (bbox_muchtex == NULL) {
                printf("Error creating bbox mutex\r\n");
                while (true) {
                    Blink(2, 100);
                }
            }

            // Initialize WebServer mutex
            websrv_muchtex = xSemaphoreCreateBinary();
            if (websrv_muchtex == NULL) {
                printf("Error creating websrv mutex\r\n");
                while (true) {
                    Blink(2, 100);
                }
            }

            // Initialize camera
            CameraTask::GetSingleton()->SetPower(true);
            CameraTask::GetSingleton()->Enable(CameraMode::kStreaming);

            // Start capture and inference task
#if DEBUG
            printf("Starting inference task\r\n");
#endif

            xTaskCreate(
                &InferenceTask,
                "InferenceTask",
                configMINIMAL_STACK_SIZE * 30,
                nullptr,
                kAppTaskPriority - 1, // Make inference lower than console/server
                nullptr
            );

#if PRINTFSTOSERIAL_ONBOOT
            // Show Files onboard as part of startup
            PrintFilesystemContents();
#endif
#if ENABLE_HTTP_SERVER

#if USEETHERNETOVERUSB
            // Initialize IP over USB
            std::string usb_ip;
            if (GetUsbIpAddress(&usb_ip)) {
#if DEBUG
                printf("Serving on: http://%s/coral_micro_camera.html\r\n", usb_ip.c_str());
#endif
            }
#else 
            // Initialize WIFI 
            WiFiConnect(WIFI_SSID, WIFI_PASS, 20);

            while (!WiFiIsConnected()) {
                delay(1000);
            }
#if DEBUG
            printf("Serving on: http://%s/coral_micro_camera.html\r\n", WiFiGetIp().value().c_str());
#endif
#endif
            // Initialize HTTP server (attach request handler)
            HttpServer http_server;
            http_server.AddUriHandler(UriHandler);
            UseHttpServer(&http_server);
            xSemaphoreGive(websrv_muchtex);
#endif

            // Main will go to sleep
            vTaskSuspend(nullptr);
        }
    }  // namespace

}  // namespace coralmicro



void setup() {
    coralmicro::Main();
}

void loop() {

}
///**
// * Entrypoint
// */
//extern "C" void app_main(void* param) {
//    (void)param;
//    coralmicro::Main();
//}

Local Board.txt Overrides

Arduino
Additions to Local Board.txt file to ensure Coral Micro Uploads Files correctly
recipe.hooks.sketch.prebuild.3.pattern.windows=cmd.exe /c if exist "{data.dir}" ( xcopy /Y /E "{data.dir}\*" "{build.path}\data\" )
coral_micro_wifi.upload.pattern={cmd.path} --elfloader_path {elfloader.file} --flashloader_path {flashloader.file} --build_dir . --elf_path {build.path}/{build.project_name}.elf --toolchain {build.compiler_path} --arduino --data_dir={build.path}/data {upload.extra_flags}
recipe.hooks.sketch.prebuild.2.pattern.windows=cmd.exe /c if exist "{build.source.path}\data" ( xcopy /Y /E "{build.source.path}\data\*" "{build.path}\data\" )

Credits

arduinocc
5 projects • 13 followers
Arduino compatible IDE for Microsoft Visual Studio and Atmel Studio 7 with unique USB Debugger, Trace, Pin Viewer and Plotter, GDB Debugging
Contact

Comments

Please log in or sign up to comment.