Pages

Tuesday 18 February 2014

Creating "Mood lights" animation with OpenCV

The other day I went on a typical London walk near Thames, and as always loved the lights, reflections and the view. It was amazing! One thing I really liked was the RGB mood lights on the bridge that transformed from every possible color in a way that it made the whole experience amazing!! Here is a glimpse from my instagram.



Since there was a sequence of colors involved, I thought I would at least try to replicate these mood lights using OpenCV. Turns out its not very difficult to make this animation at all. I wrote an algorithm for doing this using some clever tricks that did make it simple and interesting. Here is a gif showing how cool the animation looks when you execute the code.





The code for this is using the HSV colorspace and by fixing the saturation (S) and brightness/value (V) to a maximum value and changing only the hue (H) from minimum to maximum, I was able to replicate the color changing lights.

To play with HSV colorspace in OpenCV, I first found out the range of possible values for each of the three channels (H, S and V). Below is a summary taken from here.

H -> [0 -- 179]
S -> [0 -- 255]
V -> [0 -- 255]

For the above gif, I kept S and V fixed to 255 and kept H oscillating from 0 to 179 and back, since the hue (H) channel is circular, meaning that the maximum value represents color which is same as the one at minimum value for H..

By default cv::imshow and cv::imwrite functions in OpenCV assume the input image to be RGB. So in order to get the correct colors I had to convert the HSV image to RGB using a cv::cvtColor call.

Finally here is the code, if anyone might be interested to actually see the implementation details:


#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <math.h>
#define C_COLOR_STEP 255
#define C_ROWS 300
#define C_COLS 700

//int counterForWrite = 0;
//char writeBuffer[100];
int colorCount = 0;
cv::Scalar getNextColor()
{
//return cv::Scalar(rand()%256, rand()%256, rand()%256);
if(colorCount == 0)
{
colorCount++;
return cv::Scalar(179, 255 , 255);

}
else if(colorCount == 1)
{
colorCount = 0;
return cv::Scalar(0, 255, 255);

}

}

char animateColor(cv::Mat &inImage, cv::Scalar &curColor, const cv::Scalar &finalColor)
{
cv::Scalar stepColor = (finalColor - curColor)/C_COLOR_STEP;

char ch = NULL;

while(std::floor(curColor[0] + 0.5) != std::floor(finalColor[0] + 0.5)  &&

ch != 27)
{
curColor += stepColor;
inImage = curColor;
cv::Mat dst;
cv::cvtColor(inImage, dst,CV_HSV2RGB);
inImage = dst;
cv::imshow("AnimateImage", inImage);
ch = cv::waitKey(1);
//std::cout << "In loop" << std::endl;
//std::cout << "curColor: " << curColor << " FinalColor: " << finalColor << std::endl;
//std::cout << int(curColor[0]) << " " << int(finalColor[0]) << std::endl;

//sprintf(writeBuffer, "moodAnimate_%03d.png", counterForWrite++);
//cv::imwrite(writeBuffer, inImage);

}

return ch;
}



int main()
{
cv::Mat displayImg = cv::Mat(C_ROWS, C_COLS, CV_8UC3);
char ch = NULL;
cv::Scalar curC = getNextColor();
displayImg = curC;
cv::Scalar finalC = getNextColor();
while(animateColor(displayImg, curC, finalC) != 27 )
{
finalC = getNextColor();
}
return 1;
}