{ "version": "https://jsonfeed.org/version/1.1", "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL -- https://eloquentarduino.github.io/category/electronics/feed/json/ -- and add it your reader.", "home_page_url": "https://eloquentarduino.github.io/category/electronics/", "feed_url": "https://eloquentarduino.github.io/category/electronics/feed/json/", "language": "en-US", "title": "Electronics – Eloquent Arduino Blog", "description": "Machine learning on Arduino, programming & electronics", "items": [ { "id": "https://eloquentarduino.github.io/?p=956", "url": "https://eloquentarduino.github.io/2020/02/easy-arduino-thermal-camera-with-ascii-video-streaming/", "title": "Easy Arduino thermal camera with (ASCII) video streaming", "content_html": "

Ever wanted to use your thermal camera with Arduino but found it difficult to go beyond the tutorials code? Let's see the easiest possible way to view your thermal camera streaming without an LCD display!

\n

\"Arduino

\n

\n

MLX90640 thermal camera

\n

For Arduino there are essentially two thermal camera available: the AMG8833 and the MLX90640.

\n

The AMG8833 is 8x8 and the MLX90640 is 32x24.

\n

They're not cheap, it is true.

\n

But if you have to spend money, I strongly advise you to buy the MLX90640: I have one and it's not that accurate. I can't imagine how low definition would be the AMG8833.

\n

If you want to actually get something meaningful from the camera, the AMG8833 won't give you any good results.

\n

Sure, you can do interpolation: interpolation would give you the impression you have a better definition, but you're just "inventing" values you don't actually have.

\n

For demo projects it could be enough. But for any serious application, spend 20$ more and buy an MLX90640.

\n

MLX90640 eloquent library

\n

As you may know if you read my previous posts, I strongly believe in "eloquent" code, that is code that's as easy as possible to read.

\n

How many lines do you think you need to read a MLX90640 camera? Well, not that much in fact.

\n
#include "EloquentMLX90640.h"\n\nusing namespace Eloquent::Sensors;\n\nfloat buffer[768];\nMLX90640 camera;\n\nvoid setup() {\n  Serial.begin(115200);\n\n  if (!camera.begin()) {\n    Serial.println("Init error");\n    delay(50000);\n  }\n}\n\nvoid loop() {\n  camera.read(buffer);\n  delay(3000);\n}
\n

If you skip the declaration lines, you only need a begin() and read() call.

\n

That's it.

\n

What begin() does is to run all of the boilerplate code I mentioned earlier (checking the connection and initializing the parameters).

\n

read() populates the buffer you pass as argument with the temperature readings.

\n

From now on, you're free to handle that array as you may like: this is the most flexible way for the library to handle any use-case. It simply does not pose any restriction.

\n

You can find the camera code at the end of the page or on Github.

\n

Printing as ASCII Art

\n

Now that you have this data, you may want to actually "view" it. Well, that's not an easy task as one may hope.

\n

You will need an LCD if you want to create a standalone product. If you have one, it'll be the best, it's a really cute project to build.

\n

Here's a video from Adafruit that showcases even a 3D-printed case.

\n

\n

If you don't have an LCD, though, it is less practical to access your image.

\n

I did this in the past, and it meant creating a Python script reading the serial port every second and updating a plot.
\nIt works, sure, but it's not the most convenient way to handle it.

\n

This is the reason I thought about ASCII art: it is used to draw images in plain text, so you can view them directly in the serial monitor.

\n

Of course they will not be as accurate or representative as RGB images, but can give you an idea of what you're framing in realtime.

\n

I wrote a class to do this. Once imported in your sketch, it is super easy to get it working.

\n
#include "EloquentAsciiArt.h"\n\nusing namespace Eloquent::ImageProcessing;\n\nfloat buffer[768];\nuint8_t bufferBytes[768];\nMLX90640 camera;\n// we need to specify width and height of the image\nAsciiArt<32, 24> art(bufferBytes);\n\nvoid loop() {\n  camera.read(buffer);\n\n  // convert float image to uint8\n  for (size_t i = 0; i < 768; i++) {\n    // assumes readings are in the range 0-40 degrees\n    // change as per your need\n    bufferBytes[i] = map(buffer[i], 0, 40, 0, 255);\n  }\n\n  // print to Serial with a border of 2 characters, to distinguish one image from the next\n  art.print(&Serial, 2);\n  delay(2000);\n}
\n

As you can see, you need to create an AsciiArt object, map the image pixels in the range 0-255 and call the print() method: easy peasy!

\n

You can find the ASCII art generator code at the end of the page or on Github.

\n

Here's the result of the sketch. It's a video of me putting my arms at the top of my head, once at a time, then standing up.

\n
Resize the Serial Monitor as only a single frame at a time is visble to have a \"video streaming\" effect
\n
\n
\n

Of course the visual effect won't be as impressive as an RGB image, but you can clearly see my figure moving.

\n

The real bad part is the "glitch" you see between each frame when the scrolling happens: this is something I don't know if it's possible to mitigate.

\n
\r\n

Check the full project code on Github

\n
\n
\n
#pragma once\n\n#include "Wire.h"\n#include "MLX90640_API.h"\n#include "MLX90640_I2C_Driver.h"\n\n#ifndef TA_SHIFT\n//Default shift for MLX90640 in open air\n#define TA_SHIFT 8\n#endif\n\nnamespace Eloquent {\n    namespace Sensors {\n\n        enum class MLX90640Status {\n            OK,\n            NOT_CONNECTED,\n            DUMP_ERROR,\n            PARAMETER_ERROR,\n            FRAME_ERROR\n        };\n\n        class MLX90640 {\n        public:\n            /**\n             *\n             * @param address\n             */\n            MLX90640(uint8_t address = 0x33) :\n                _address(address),\n                _status(MLX90640Status::OK) {\n\n            }\n\n            /**\n             *\n             * @return\n             */\n            bool begin() {\n                Wire.begin();\n                Wire.setClock(400000);\n\n                return isConnected() && loadParams();\n            }\n\n            /**\n             *\n             * @return\n             */\n            bool read(float result[768]) {\n                for (byte x = 0 ; x < 2 ; x++) {\n                    uint16_t frame[834];\n                    int status = MLX90640_GetFrameData(_address, frame);\n\n                    if (status < 0)\n                        return fail(MLX90640Status::FRAME_ERROR);\n\n                    float vdd = MLX90640_GetVdd(frame, &_params);\n                    float Ta = MLX90640_GetTa(frame, &_params);\n                    float tr = Ta - TA_SHIFT;\n                    float emissivity = 0.95;\n\n                    MLX90640_CalculateTo(frame, &_params, emissivity, tr, result);\n                }\n            }\n\n        protected:\n            uint8_t _address;\n            paramsMLX90640 _params;\n            MLX90640Status _status;\n\n            /**\n             * Test if device is connected\n             * @return\n             */\n            bool isConnected() {\n                Wire.beginTransmission(_address);\n\n                if (Wire.endTransmission() == 0) {\n                    return true;\n                }\n\n                return fail(MLX90640Status::NOT_CONNECTED);\n            }\n\n            /**\n             *\n             * @return\n             */\n            bool loadParams() {\n                uint16_t ee[832];\n                int status = MLX90640_DumpEE(_address, ee);\n\n                if (status != 0)\n                    return fail(MLX90640Status::DUMP_ERROR);\n\n                status = MLX90640_ExtractParameters(ee, &_params);\n\n                if (status != 0)\n                    return fail(MLX90640Status::PARAMETER_ERROR);\n\n                return true;\n            }\n\n            /**\n             * Mark a failure\n             * @param status\n             * @return\n             */\n            bool fail(MLX90640Status status) {\n                _status = status;\n\n                return false;\n            }\n        };\n    }\n}
\n
\n
#pragma once\n\n#include "Stream.h"\n\nnamespace Eloquent {\n    namespace ImageProcessing {\n\n        /**\n         *\n         * @tparam width\n         * @tparam height\n         */\n        template<size_t width, size_t height>\n        class AsciiArt {\n        public:\n            AsciiArt(const uint8_t *data) {\n                _data = data;\n            }\n\n            /**\n             * Get pixel at given coordinates\n             * @param x\n             * @param y\n             * @return\n             */\n            uint8_t at(size_t x, size_t y) {\n                return _data[y * width + x];\n            }\n\n            /**\n             * Print as ASCII art picture\n             * @param stream\n             */\n            void print(Stream *stream, uint8_t frameSize = 0) {\n                const char glyphs[] = " .,:;xyYX";\n                const uint8_t glyphsCount = 9;\n\n                printAsciiArtHorizontalFrame(stream, frameSize);\n\n                for (size_t y = 0; y < height; y++) {\n                    // vertical frame\n                    for (uint8_t k = 0; k < frameSize; k++)\n                        Serial.print('|');\n\n                    for (size_t x = 0; x < width; x++) {\n                        const uint8_t glyph = floor(((uint16_t) at(x, y)) * glyphsCount / 256);\n\n                        stream->print(glyphs[glyph]);\n                    }\n\n                    // vertical frame\n                    for (uint8_t k = 0; k < frameSize; k++)\n                        Serial.print('|');\n\n                    stream->print('\\n');\n                }\n\n                printAsciiArtHorizontalFrame(stream, frameSize);\n                stream->flush();\n            }\n\n        protected:\n            const uint8_t *_data;\n\n            /**\n             *\n             * @param stream\n             * @param frameSize\n             */\n            void printAsciiArtHorizontalFrame(Stream *stream, uint8_t frameSize) {\n                for (uint8_t i = 0; i < frameSize; i++) {\n                    for (size_t j = 0; j < width + 2 * frameSize; j++)\n                        stream->print('-');\n                    stream->print('\\n');\n                }\n            }\n        };\n    }\n}
\n

L'articolo Easy Arduino thermal camera with (ASCII) video streaming proviene da Eloquent Arduino Blog.

\n", "content_text": "Ever wanted to use your thermal camera with Arduino but found it difficult to go beyond the tutorials code? Let's see the easiest possible way to view your thermal camera streaming without an LCD display!\n\n\nMLX90640 thermal camera\nFor Arduino there are essentially two thermal camera available: the AMG8833 and the MLX90640.\nThe AMG8833 is 8x8 and the MLX90640 is 32x24.\nThey're not cheap, it is true.\nBut if you have to spend money, I strongly advise you to buy the MLX90640: I have one and it's not that accurate. I can't imagine how low definition would be the AMG8833.\nIf you want to actually get something meaningful from the camera, the AMG8833 won't give you any good results.\nSure, you can do interpolation: interpolation would give you the impression you have a better definition, but you're just "inventing" values you don't actually have.\nFor demo projects it could be enough. But for any serious application, spend 20$ more and buy an MLX90640.\nMLX90640 eloquent library\nAs you may know if you read my previous posts, I strongly believe in "eloquent" code, that is code that's as easy as possible to read.\nHow many lines do you think you need to read a MLX90640 camera? Well, not that much in fact.\n#include "EloquentMLX90640.h"\n\nusing namespace Eloquent::Sensors;\n\nfloat buffer[768];\nMLX90640 camera;\n\nvoid setup() {\n Serial.begin(115200);\n\n if (!camera.begin()) {\n Serial.println("Init error");\n delay(50000);\n }\n}\n\nvoid loop() {\n camera.read(buffer);\n delay(3000);\n}\nIf you skip the declaration lines, you only need a begin() and read() call.\nThat's it.\nWhat begin() does is to run all of the boilerplate code I mentioned earlier (checking the connection and initializing the parameters).\nread() populates the buffer you pass as argument with the temperature readings.\nFrom now on, you're free to handle that array as you may like: this is the most flexible way for the library to handle any use-case. It simply does not pose any restriction.\nYou can find the camera code at the end of the page or on Github.\nPrinting as ASCII Art\nNow that you have this data, you may want to actually "view" it. Well, that's not an easy task as one may hope.\nYou will need an LCD if you want to create a standalone product. If you have one, it'll be the best, it's a really cute project to build.\nHere's a video from Adafruit that showcases even a 3D-printed case.\n\nIf you don't have an LCD, though, it is less practical to access your image.\nI did this in the past, and it meant creating a Python script reading the serial port every second and updating a plot.\nIt works, sure, but it's not the most convenient way to handle it.\nThis is the reason I thought about ASCII art: it is used to draw images in plain text, so you can view them directly in the serial monitor.\nOf course they will not be as accurate or representative as RGB images, but can give you an idea of what you're framing in realtime.\nI wrote a class to do this. Once imported in your sketch, it is super easy to get it working.\n#include "EloquentAsciiArt.h"\n\nusing namespace Eloquent::ImageProcessing;\n\nfloat buffer[768];\nuint8_t bufferBytes[768];\nMLX90640 camera;\n// we need to specify width and height of the image\nAsciiArt<32, 24> art(bufferBytes);\n\nvoid loop() {\n camera.read(buffer);\n\n // convert float image to uint8\n for (size_t i = 0; i < 768; i++) {\n // assumes readings are in the range 0-40 degrees\n // change as per your need\n bufferBytes[i] = map(buffer[i], 0, 40, 0, 255);\n }\n\n // print to Serial with a border of 2 characters, to distinguish one image from the next\n art.print(&Serial, 2);\n delay(2000);\n}\nAs you can see, you need to create an AsciiArt object, map the image pixels in the range 0-255 and call the print() method: easy peasy!\nYou can find the ASCII art generator code at the end of the page or on Github.\nHere's the result of the sketch. It's a video of me putting my arms at the top of my head, once at a time, then standing up.\nResize the Serial Monitor as only a single frame at a time is visble to have a \"video streaming\" effect\n\nhttps://eloquentarduino.github.io/wp-content/uploads/2020/02/Thermal-ascii-speedup.mp4\nOf course the visual effect won't be as impressive as an RGB image, but you can clearly see my figure moving.\nThe real bad part is the "glitch" you see between each frame when the scrolling happens: this is something I don't know if it's possible to mitigate.\n\r\nCheck the full project code on Github\n\n\n#pragma once\n\n#include "Wire.h"\n#include "MLX90640_API.h"\n#include "MLX90640_I2C_Driver.h"\n\n#ifndef TA_SHIFT\n//Default shift for MLX90640 in open air\n#define TA_SHIFT 8\n#endif\n\nnamespace Eloquent {\n namespace Sensors {\n\n enum class MLX90640Status {\n OK,\n NOT_CONNECTED,\n DUMP_ERROR,\n PARAMETER_ERROR,\n FRAME_ERROR\n };\n\n class MLX90640 {\n public:\n /**\n *\n * @param address\n */\n MLX90640(uint8_t address = 0x33) :\n _address(address),\n _status(MLX90640Status::OK) {\n\n }\n\n /**\n *\n * @return\n */\n bool begin() {\n Wire.begin();\n Wire.setClock(400000);\n\n return isConnected() && loadParams();\n }\n\n /**\n *\n * @return\n */\n bool read(float result[768]) {\n for (byte x = 0 ; x < 2 ; x++) {\n uint16_t frame[834];\n int status = MLX90640_GetFrameData(_address, frame);\n\n if (status < 0)\n return fail(MLX90640Status::FRAME_ERROR);\n\n float vdd = MLX90640_GetVdd(frame, &_params);\n float Ta = MLX90640_GetTa(frame, &_params);\n float tr = Ta - TA_SHIFT;\n float emissivity = 0.95;\n\n MLX90640_CalculateTo(frame, &_params, emissivity, tr, result);\n }\n }\n\n protected:\n uint8_t _address;\n paramsMLX90640 _params;\n MLX90640Status _status;\n\n /**\n * Test if device is connected\n * @return\n */\n bool isConnected() {\n Wire.beginTransmission(_address);\n\n if (Wire.endTransmission() == 0) {\n return true;\n }\n\n return fail(MLX90640Status::NOT_CONNECTED);\n }\n\n /**\n *\n * @return\n */\n bool loadParams() {\n uint16_t ee[832];\n int status = MLX90640_DumpEE(_address, ee);\n\n if (status != 0)\n return fail(MLX90640Status::DUMP_ERROR);\n\n status = MLX90640_ExtractParameters(ee, &_params);\n\n if (status != 0)\n return fail(MLX90640Status::PARAMETER_ERROR);\n\n return true;\n }\n\n /**\n * Mark a failure\n * @param status\n * @return\n */\n bool fail(MLX90640Status status) {\n _status = status;\n\n return false;\n }\n };\n }\n}\n\n#pragma once\n\n#include "Stream.h"\n\nnamespace Eloquent {\n namespace ImageProcessing {\n\n /**\n *\n * @tparam width\n * @tparam height\n */\n template<size_t width, size_t height>\n class AsciiArt {\n public:\n AsciiArt(const uint8_t *data) {\n _data = data;\n }\n\n /**\n * Get pixel at given coordinates\n * @param x\n * @param y\n * @return\n */\n uint8_t at(size_t x, size_t y) {\n return _data[y * width + x];\n }\n\n /**\n * Print as ASCII art picture\n * @param stream\n */\n void print(Stream *stream, uint8_t frameSize = 0) {\n const char glyphs[] = " .,:;xyYX";\n const uint8_t glyphsCount = 9;\n\n printAsciiArtHorizontalFrame(stream, frameSize);\n\n for (size_t y = 0; y < height; y++) {\n // vertical frame\n for (uint8_t k = 0; k < frameSize; k++)\n Serial.print('|');\n\n for (size_t x = 0; x < width; x++) {\n const uint8_t glyph = floor(((uint16_t) at(x, y)) * glyphsCount / 256);\n\n stream->print(glyphs[glyph]);\n }\n\n // vertical frame\n for (uint8_t k = 0; k < frameSize; k++)\n Serial.print('|');\n\n stream->print('\\n');\n }\n\n printAsciiArtHorizontalFrame(stream, frameSize);\n stream->flush();\n }\n\n protected:\n const uint8_t *_data;\n\n /**\n *\n * @param stream\n * @param frameSize\n */\n void printAsciiArtHorizontalFrame(Stream *stream, uint8_t frameSize) {\n for (uint8_t i = 0; i < frameSize; i++) {\n for (size_t j = 0; j < width + 2 * frameSize; j++)\n stream->print('-');\n stream->print('\\n');\n }\n }\n };\n }\n}\nL'articolo Easy Arduino thermal camera with (ASCII) video streaming proviene da Eloquent Arduino Blog.", "date_published": "2020-02-29T17:20:15+01:00", "date_modified": "2020-03-02T20:19:00+01:00", "authors": [ { "name": "simone", "url": "https://eloquentarduino.github.io/author/simone/", "avatar": "http://1.gravatar.com/avatar/d670eb91ca3b1135f213ffad83cb8de4?s=512&d=mm&r=g" } ], "author": { "name": "simone", "url": "https://eloquentarduino.github.io/author/simone/", "avatar": "http://1.gravatar.com/avatar/d670eb91ca3b1135f213ffad83cb8de4?s=512&d=mm&r=g" }, "tags": [ "Computer vision", "Electronics", "Eloquent library" ], "attachments": [ { "url": "https://eloquentarduino.github.io/wp-content/uploads/2020/02/Thermal-ascii-speedup.mp4", "mime_type": "video/mp4", "size_in_bytes": 479591 } ] } ] }