Minimalistic arduino communication: Led graph bar and image processing - PART 1
I have been using the esp8266 for a while and exploring the use of least possible add-ons(to minimize costs) while interacting with it. The esp8266 has different modes of operation, the most common being access point and client. A typical usage scenario while using the esp8266 would be to set it in access point mode, no password set, connect to it and submit a form with the ssid and password for the network it should join. In my scenario, the esp8266 works only in access point mode and the user connects to it. I could use a fixed password, but I want a randomly generated password each time the esp8266 turns on. I could opt to use a display, but this will probably be more expensive.
I started to think about different ways to communicate and came up with a couple of ideas. The first of these is to use a blinking led graph bar to send the password to a phone via the camera. In theory, this seems reasonable, most phones have cameras capable of recording video at 30 fps. There are 2 parts required to get this to work, image processing to identify the state of the leds and a microcontroller with blinking leds. Since encoding characters into on/off leds is simple, I decided to get started with the image processing. Having an android phone, I’ll be using Java.
My first implementation was very basic. Loop through the image, have an additional array of visited points and when finding a color match from the rgb colors array do a graph like traveral. In this traversal I go in all directions iteratively by using a queue, checking if the point has been visited or not. I also added a local visited array when doing the traversal to avoid repeated traversals without modifying the global visited array, although this may not be necessary. I tested two ways of calculating the color distance; Manhattan distance and Euclidean distance. By avoiding Math.pow and Math.sqrt I thought I would be able to improve the performance of my blob detector, but my results when running on my machine showed me otherwise. The cold runtime for one color was around 40ms, while that for 3 colors was around 80ms. At 80ms the best I can do is 12.5 FPS, so this one is not good enough.
Sample image:
Result (Matching colors are [0,255,195],[0,0,255],[255,0,0]):
I have helper class ImageHandler, which has some code to draw the red rectangles on the found blobs.
My first implementation of blob detection is the following:
Having this take 80ms for three colors led me into trying something alternative at the time. It has been a long time since I have used Java more than the usual basics and I found out about RecursiveTask. I decided to give it a try. The idea was to split the image into four parts as subtaks until I got to a pixel level. Then start joining the results based on certain conditions. If at least three of the parts were a match, I would continue to return 1. Otherwise I would build pieces of blobs that I would then stick together at the end. This did not go well. I ended up with multiple java.util.concurrent.CancellationException. I went for the traditional fork/join and I doubt using Future would have helped in any way. This test was in the end a terrible failure.
Afterwards I decided to try splitting the image processing into a more reasonable number of threads, four. Although this did improve the runtime a bit, it wasn’t enough. By this time I had already dug into getting something running on Android. I had already tested the camera2 API and I decided to make a few more changes for my new class. I decided to receive the data as a one dimensional array (I decded to not care about the padding and other image stuff, for the moment), I added splitting into multiple threads on a row level (processing in order in the array could potentially help reduce the number of cache faults and thus increase performance), I added a minimum size for width and height of a blob, I process rows first, encoding 64 columns into one long to save memory, I added counters for row level and column level matches to allow skipping rows/columns without any possible blobs when I process the columns, I skip one row and one column at a time to only process number_of_pixels/4 (I later realized that with the introduction of the minimum size I could have skipped many more columns if there was no match) and I keep track of the matched color to avoid looping colors when I am checking for continuity of my last match. For a cold run, the runtime with 3 colors is 20ms on a single thread. When I ran my code with two threads, cold runtime was down to 15ms, but I realized I have some bugs. Since the single threaded performance would allow me 50 FPS I decided to leave it as is.
Result (Matching colors are [0,255,195],[255,0,192],[0,24,255]):
The code:
I went on to modify the Camera2 API example to test on my phone. I did not think that my phone’s camera could disappoint me more (bad photo quality), but my code would give me an exception on my Honor 10. Searching on Google, wasn’t very helpful. The issue was that even though I was requesting the image as ARGB_8888( from the Config enum in the Bitmap class), when reading from the ImageReader I was being told the format I got didn’t match what I requested. Upon investigating a bit more, it seems that they are using some private format, but this is only conjecture. I decided to test on a Moto G5 Plus and everything seemed to work, more or less. Images from my camera weren’t as perfect as the test images I quickly made using Krita. I haven’t tested with leds yet and set my matching colors to perfect red, green and blue, but I may test changing my code to compare with the last match instead of the color from my list of colors.
I did a number of changes to the sample code, but roughly the important pieces are:
This is it for part one. For part two I’ll move on to using an Arduino and testing this code in the conditions it is intended to be used.