From ec305108a6523e53bab609927d5e2a27db50f10e Mon Sep 17 00:00:00 2001 From: Bharath Sudharsan Date: Sat, 23 Jul 2022 04:16:33 +0100 Subject: [PATCH] Updated code --- README.md | 8 +- ...Forest-classifier.h => [h]-HogClassifier.h | 0 [h]-HogPipeline.h | 220 ++++++++++++++++++ 3 files changed, 224 insertions(+), 4 deletions(-) rename [h]-HOG-plus-RandomForest-classifier.h => [h]-HogClassifier.h (100%) create mode 100644 [h]-HogPipeline.h diff --git a/README.md b/README.md index 8d533e2..f55399d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TinyML-CAM - Image Recognition System that Runs at 80 FPS in 1 Kb of RAM +## TinyML-CAM - Image Recognition System that Runs at 80 FPS in 1 Kb of RAM ### Image Recognition Demo - ESP32 ESP32 classifying Raspberry Pi Pico, Portenta H7, Wio Terminal from image frames @@ -17,6 +17,6 @@ Following can be observed from the video: ### Code -- [ipynb]-TinyML-CAM-full-code-with-markdown.ipynb -- [h]-HOG-plus-RandomForest-classifier.h -- [ino]-arduino-ESP32-code.ino - upload to +- [[ipynb]-TinyML-CAM-full-code-with-markdown.ipynb](https://github.com/bharathsudharsan/TinyML-CAM/blob/main/%5Bipynb%5D-TinyML-CAM-full-code-with-markdown.ipynb) +- [[h]-HOG-plus-RandomForest-classifier.h](https://github.com/bharathsudharsan/TinyML-CAM/blob/main/%5Bh%5D-HOG-plus-RandomForest-classifier.h) +- [[ino]-arduino-ESP32-code.ino](https://github.com/bharathsudharsan/TinyML-CAM/blob/main/%5Bino%5D-arduino-ESP32-code.ino) - upload to diff --git a/[h]-HOG-plus-RandomForest-classifier.h b/[h]-HogClassifier.h similarity index 100% rename from [h]-HOG-plus-RandomForest-classifier.h rename to [h]-HogClassifier.h diff --git a/[h]-HogPipeline.h b/[h]-HogPipeline.h new file mode 100644 index 0000000..c2fb578 --- /dev/null +++ b/[h]-HogPipeline.h @@ -0,0 +1,220 @@ +#ifndef UUID5853197456 +#define UUID5853197456 + + + #ifndef UUID5853199664 +#define UUID5853199664 + +/** + * HOG(block_size=8, bins=9, cell_size=3) + */ +class HOG { + public: + + /** + * Transform input image + */ + template + bool transform(T *input, U *output) { + + uint16_t f = 0; + uint16_t block = 0; + float hog[135] = {0}; + + // compute gradients + for (uint16_t blockY = 0; blockY < 3; blockY++) { + const uint16_t blockOffsetY = blockY * 320; + + for (uint16_t blockX = 0; blockX < 5; blockX++) { + const uint16_t blockOffsetX = blockX * 8; + float hist[9] = {0}; + + for (uint16_t _y = 1; _y < 7; _y += 1) { + const uint16_t rowOffset = blockOffsetY + _y * 40 + blockOffsetX; + const uint16_t rowOffsetBefore = rowOffset - 40; + const uint16_t rowOffsetAfter = rowOffset + 40; + + for (uint16_t _x = 1; _x < 7; _x += 1) { + const uint16_t offset = rowOffset + _x; + const uint16_t offsetBefore = rowOffsetBefore + _x; + const uint16_t offsetAfter = rowOffsetAfter + _x; + const float gy = input[offsetAfter] - input[offsetBefore]; + const float gx = input[offset + 1] - input[offset - 1]; + const float g = sqrt(gy * gy + gx * gx); + uint8_t angle = abs(this->arctan(gy, gx) * 180 / 3.141592653589793f / 20); + + if (angle >= 8) angle = 8; + hist[angle] += g; + } + } + + for (uint16_t i = 0; i < 9; i++) + hog[f++] = hist[i]; + + block += 1; + + // end of cell, normalize + if ((block % 3) == 0) { + const uint16_t offset = (block - 3) * 9; + float maxGradient = 0.0001; + + for (uint16_t i = 0; i < 27; i++) { + const float h = hog[offset + i]; + + if (h > maxGradient) + maxGradient = h; + } + + for (uint16_t i = 0; i < 27; i++) { + hog[offset + i] /= maxGradient; + } + + maxGradient = 0.0001; + } + } + } + + + // copy over + for (uint16_t i = 0; i < 135; i++) + output[i] = hog[i]; + + + return true; + } + + protected: + + + /** + * optional atan2 approximation for faster calculation + */ + float arctan(float y, float x) { + + float r = 0; + + if (abs(y) < 0.00000001) + return 0; + else if (abs(x) < 0.00000001) + return 3.14159274 * (y > 0 ? 1 : -1); + else { + float a = min(abs(x), abs(y)) / max(abs(x), abs(y)); + float s = a * a; + r = ((-0.0464964749 * s + 0.15931422) * s - 0.327622764) * s * a + a; + + if (abs(y) > abs(x)) + r = 1.57079637 - r; + } + + if (x < 0) + r = 3.14159274 - r; + if (y < 0) + r = -r; + + return r; + + } + + +}; + + + +#endif + + +/** + * ImagePipeline: HogPipeline + * --------- + * - Resize(from=((160, 120)), to=(40, 30), pixformat=gray) + * > HOG(block_size=8, bins=9, cell_size=3) + */ +class HogPipeline { + public: + static const size_t NUM_INPUTS = 1200; + static const size_t NUM_OUTPUTS = 135; + static const size_t WORKING_SIZE = 135; + float features[135]; + + /** + * Extract features from input image + */ + template + bool transform(T *input) { + time_t start = micros(); + ok = true; + + preprocess(input); + + + + ok = ok && hog.transform(input, features); + + + + latency = micros() - start; + + return ok; + } + + /** + * Debug output feature vector + */ + template + void debugTo(PrinterInterface &printer, uint8_t precision=5) { + printer.print(features[0], precision); + + for (uint16_t i = 1; i < 135; i++) { + printer.print(", "); + printer.print(features[i], precision); + } + + printer.print('\n'); + } + + /** + * Get latency in micros + */ +uint32_t latencyInMicros() { + return latency; +} + +/** + * Get latency in millis + */ +uint16_t latencyInMillis() { + return latency / 1000; +} + + protected: + bool ok; + time_t latency; + + HOG hog; + + + template + void preprocess(T *input) { + + + // grayscale rescaling + const float dy = 4.0f; + const float dx = 4.0f; + + for (uint16_t y = 0; y < 30; y++) { + const size_t sourceOffset = round(y * dy) * 160; + const size_t destOffset = y * 40; + + for (uint16_t x = 0; x < 40; x++) + input[destOffset + x] = input[sourceOffset + ((uint16_t) (x * dx))]; + } + + + } +}; + + +static HogPipeline hog; + + +#endif \ No newline at end of file