Yet Another Ambilight Knockoff

An Ambilight knockoff focusing on performance, even if it has no tangible benefit :)

Public Chat
Similar projects worth following
As a wise man once said, the bigger, the better. Yet another Ambilight knockoff (YAAK) is an Ambilight software implementation focusing on squeezing out as big a (theoretical) framerate as possible. On the hardware end, an Arduino Pro Micro µC controls a WS2813 strip (Picked over the ubiquitous WS2812B because of the higher refresh rate, obviously).

I got bored one night and decided that some bias lighting for my monitor would be cool. I could've used static bias lighting like a pleb, but that's boring, so I decided that I would do dynamic bias lighting that "extends" the monitor by changing the color according to the edge of the screen. To do achieve that I had 3 main options:

  1. Buy a Phillips TV
  2. Use a pre-made, open-source solution
  3. Make my own

If option 1 would've been viable, I wouldn't be here now would I? 

Some (presumably) nice people already have gone through the trouble of creating an open-source knockoff of Phillips Ambilight and created Hyperion. But it requires a Raspberry Pi (something I didn't have on hand). So I thought: how hard could it be to make my own solution with hardware I had on hand? 

As it turns out, not that hard depending on how much shitfuckery you can tolerate.

  • 1 × Arduino Pro Micro Other microcontrollers should also work, but no guarantees.
  • 1 × WS2813 LED Strip A WS2812B strip could also work
  • 1 × 5V DC power supply With enough current to drive the LED strip

  • The first attempt

    Kristiāns04/14/2021 at 15:10 0 comments

    I decided to use a WS2813 strip and the ATMega32u4-based Arduino Pro Micro. The Pro Micro is good for a couple of reasons - the Chinese clones are cheap and, presumably, it has stable serial communication with OK throughput since the ATMega32u4 can talk to USB directly - although I mainly picked it just because I had it on hand. Since the ATMega32u4 is lucky if it manages to calculate the first 100 digits of pi until the end of next year, all the processing will have to be done on the PC, which meant it needed to not hog performance, while still being fast.

    The software

    For the prototype, I used Python with NumPy to handle all the calculations and mss to capture the screen. I divided the edge of the screen more or less evenly (rounding the couple pixels of division error) into cells with some pre-determined height.

    In the corners, in theory (if the LED strip is laid out evenly), 2 LEDs are responsible for the same cell, and I could've come up with a neat solution of how to split the corner evenly between the 2 LEDs, but I said fuck it and just let the top and bottom LEDs take the corner and assumed that the side LEDs are offset by a bit (which, in practice, they were because of the wires connecting the LED strip segments).

    (No, that isn't a short-circuit - that's just my terrible soldering)

    I have color data in cells, but now how do I process the data to get a single color? 

    First I experimented with clustering algorithms. I attempted to use K-Means from the SciPy module, which did work, but it was sooooooo slooooooooow. It was only managing to calculate 2 frames per second with 100 cells (All benchmarks are done on a 1920x1080 display). After some further research, I decided to not bother with anything fancy (at least for the first attempt) and go with the good ol' mean value. 

    Instant improvement. It was getting a solid 50 FPS with 100 cells. But then I thought - why stop there? After splitting the code into 2 parts, and rewriting the calculations using Cython, It was already doing around 100 FPS. Granted, it didn't include transmitting the data to the hardware, but it was a good sign. To verify that the code was actually doing its job, I wrote a quick and dirty function that rendered the output of each frame using Pygame. 

    The rendered cells are rotated by 90deg, but good enough for debug


    The hardware

    I put together the LED controller and wrote some simple code that would listen for data on the serial port in the format

    {Cell #} {R} {G} {B}

    There I ran into an issue - turns out I was changing a single LED, and then refreshing the entire strip of LEDs to make the updated color visible... approximately 80 times per frame, which, it turns out, is quite performance intensive. I ended up reserving LED address 255 and made the uC update the entire LED strip if it receives FF as the first parameter (Cell #). Now the PC client just send FF at the end of each frame. 

    At this point, I couldn't tell if it was updating any slower than the framerate my python code was outputting, so, as far as I could tell, problem solved?

    Making the software interact with the hardware

    After calculating the entire frame the code sent each cell's color value to the uC using pySerial. What surprised me is that it worked... first try (more or less). Simply godlike. But there were issues.

    The first problem - performance. When sending data to the uC, the performance decreased to a plebian 40 FPS. After doing a bit of optimization - if a cell is very dark, just assume that it is black to not bother with the rest of the calculations and if a cell has a similar value to which it had in the previous frame, don't bother sending over the new color. In the end, this increased the performance to an acceptable 60 FPS.

    The second problem - some cells were getting a weird color. It turned out I was getting (and silencing) a division by zero error when calculating the mean color in a very dark cell because the code...

    Read more »

View project log

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates