Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Computer Vision - Decoding a Morse Code Flashing LED

0.00/5 (No votes)
4 Dec 2009 1  
Using webcam and image processing to decode a Morse code flashing LED.

Source_Code

Introduction

I hope you all know what Morse code is; for those who are not aware of it, Morse code is a standardized sequence of dots and dashes to represent any character and number. Well, in this article, I'm demonstrating a program that can control an LED connected to the parallel port of my computer and make it flash Morse code. For dots, the LED remains "on" for lesser time than dashes, thus giving an impression of Morse code. Not to forget the most interesting part: the program can use the webcam and a little bit of image processing to make the computer understand the Morse code flashing LED and convert it back to English.

Perhaps you are asking, "What's the use?" Well, no practical use.. but sometimes, coding is just fun. Ever since I made the webcam enabled Tic-Tac-Toe, I was thinking of some unique ways to communicate with my computer using something real and physical.. and ended up writing this program!

Before we begin, I would recommend you to go through I/O Ports Uncensored - 1 - Controlling LEDs (Light Emitting Diodes) with Parallel Port by Levent Saltuklaroglu, and be sure to read the sections on Parallel Ports and Hexadecimal / Decimal / Binary if you haven't already done so, as we will use the same procedure to reach the parallel port in our application.

One really cool thing with this program would be using two computers, one having a parallel port (LED attached) and the other with a USB webcam. Watch this YouTube video and check out this program in action ..

Generating the Morse code (by flashing an LED)

Source_Code

As, you can see in the picture above, the program can control an LED connected to the parallel port of my computer and make it flash Morse code. All I have to do is enter a phrase in English and press the button. It also has an option for putting out the audio depicting Morse code.

First of all, a written text is converted to Morse code by a string extension, and finally, the generated Morse code is used to control the LED and audio part. Check the code snippet below:

static class StringToMorse
{
    //extension to string
    public static string GetMorseCode(this string str)
    {
        string morse="";
        foreach (char ch in str)
        {
            if (ch == 'a' || ch == 'A')
            {
                morse += ".- ";
            }
            else if (ch == 'b' || ch == 'B')
            {
                morse += "-... ";
            }
            else if (ch == 'c' || ch == 'C')
            {
                morse += "-.-. ";
            }
            // All alphabets not included
            // It'd have made article unnecessarily big..
        }
    }

Now, once the Morse code is generated, the program calls a function asynchronously in a different thread to make the LED flash the Morse without hanging the application. I'm using inpout32.dll to control the parallel port. You can find the complete details about importing and using this DLL in the article I recommended above. Below is a code snippet that uses the generated Morse code to flash the LED:

private void stringToLed(string str)//generated morse code is argument
{
    foreach (char ch in str)
    {
        int mul_fac = Convert.ToInt16(comboBox1.Text);
        richTextBox1.Text += ch;
        int sleep = Convert.ToInt16(some vaue);//pause between dot and dash
        if (ch == '.')
        {
            PortInterop.Output(888, 255); // set all data pins to 1
            System.Threading.Thread.Sleep(on time of dot);
            PortInterop.Output(888, 0);
            System.Threading.Thread.Sleep(sleep);
        }
        else if (ch == '-')
        {
            PortInterop.Output(888, 255); 
            System.Threading.Thread.Sleep(on time for dash);
            PortInterop.Output(888, 0);
            System.Threading.Thread.Sleep(sleep);
        }
        else if (ch == '/')
        {
            PortInterop.Output(888, 0);// set all data pins to 0
            System.Threading.Thread.Sleep(character pause);
        }
        else if (ch == ' ')
        {
            PortInterop.Output(888, 0); 
            System.Threading.Thread.Sleep(word pause);
        }

    }
}

Webcam and image processing...

To add more fun, I added another feature of decoding this Morse code. The program watches the on/off sequence of the LED and converts it into English!

Earlier, I was thinking of processing the whole webcam frame and finding the on/ off state of the LED, but this technique made the application work too slow that it couldn't even differentiate between a dot and a dash. So, I made an assumption that the camera source will be stationery, and the user will have to define the light source by a mouse click within the webcam window (see the image below: the point of interception of the two yellow lines is the marker which defines the light source).

Once the light source is defined, the program can go through the pixels near the defined light source and calculate the average brightness of each pixel.

using System.Drawing;
Color c = someBitmap.GetPixel(x,y);
float b = c.GetBrightness();

Wow, that's easy! This code was simple to write, and easy to understand. However, unfortunately, it is very slow. If you use this code, it might take several milliseconds to process, because the GetPixel()/SetPixel() methods are too slow for iterating through bitmaps. So, in this project, we'll make use of the BitmapData class in GDI+ to access the information we want. BitmapData only allows us to access the data it stores through a pointer. This means that we'll have to use the unsafe keyword to scope the block of code which accesses the data. Based on an article by Eric Gunnerson, here's a class which will perform a very quick unsafe image processing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
public unsafe class UnsafeBitmap
{
    Bitmap bitmap;
    int width;
    BitmapData bitmapData = null;
    Byte* pBase = null;

    public UnsafeBitmap(Bitmap bitmap)
    {
        this.bitmap = new Bitmap(bitmap);
    }

    public UnsafeBitmap(int width, int height)
    {
        this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
    }

    public void Dispose()
    {
        bitmap.Dispose();
    }

    public Bitmap Bitmap
    {
        get
        {
            return (bitmap);
        }
    }

    public struct PixelData
    {
        public byte blue;
        public byte green;
        public byte red;
    }

    private Point PixelSize
    {
        get
        {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF bounds = bitmap.GetBounds(ref unit);

            return new Point((int)bounds.Width, (int)bounds.Height);
        }
    }

    public void LockBitmap()
    {
        GraphicsUnit unit = GraphicsUnit.Pixel;

        RectangleF boundsF = bitmap.GetBounds(ref unit);
        Rectangle bounds = new Rectangle((int)boundsF.X, (int)boundsF.Y, 
                           (int)boundsF.Width, (int)boundsF.Height);

        width = (int)boundsF.Width * sizeof(PixelData);

        if (width % 4 != 0)
        {
            width = 4 * (width / 4 + 1);
        }

        bitmapData = bitmap.LockBits(bounds, ImageLockMode.ReadWrite, 
                                     PixelFormat.Format24bppRgb);
        pBase = (Byte*)bitmapData.Scan0.ToPointer();
    }

    public PixelData GetPixel(int x, int y)
    {
        PixelData returnValue = *PixelAt(x, y);
        return returnValue;
    }

    public void SetPixel(int x, int y, PixelData colour)
    {
        PixelData* pixel = PixelAt(x, y);
        *pixel = colour;
    }

    public void UnlockBitmap()
    {
        bitmap.UnlockBits(bitmapData);
        bitmapData = null;
        pBase = null;
    }

    public PixelData* PixelAt(int x, int y)
    {
        return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
    }
}

Be sure to check Eric's article on unsafe image processing. This class can be used for retrieving the red, green, and blue values of any pixel, as shown below:

private void GetBritnessOnce(ref Bitmap image)
{
    // This code is for getting brighness only once !!
    // pt is point defining light source
    Rectangle rect = new Rectangle(pt.X - 3, pt.Y - 3, 6, 6);
    //cropping image withing boundaries of this rectangle
    Bitmap img = image.Clone(rect, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
    UnsafeBitmap uBitmap = new UnsafeBitmap(img);//unsafe bitmap class
    uBitmap.LockBitmap();
    float avgBritness = 0;
    for (int x = 0; x < 6; x++)
    {
         for (int y = 0; y < 6; y++)
         {
              byte red, green, blue;
              red = uBitmap.GetPixel(x, y).red;
              green = uBitmap.GetPixel(x, y).green;
              blue = uBitmap.GetPixel(x, y).blue;
              avgBritness += (299 * red + 587 * green + 114 * blue) / 1000;
              // brightness function
         }
     }
     avgBritness /= 36 ;
     uBitmap.UnlockBitmap();
     label19.Text = Convert.ToString(avgBritness);
}

With the brightness value, the program can find whether the light source is "on" or "off", and with a stop watch, the timings of on/ off sequences could be calculated...

The program provides all the stats below the webcam view, and with these stats, it also predicts the Morse code! Make sure to watch the video above.

Some notes on using the software

Well, the most tricky part is adjusting the settings within the program to make it work perfectly.. Let's start with the settings one by one..

Here, "dot" defines the time span for which the LED will remain on for every dot within the Morse code, and "DMF", by default, is 3, which means the time span for every dash in the Morse code will be "dot" * 3.

Let's suppose we need to define " ._ " by flashing LEDs. How will we do that..?

LED on for "LESS time" --> LED off for "SOME time" --> LED on for "MORE time"

This LED off for "SOME time" is what "Imm" is in the above settings.

Now, let's come to the settings for the decoding part. I'll soon add some AI so that the program will adapt itself after collecting some on/off data. For now, let me tell you the significance of each setting:

For brightness less than the "Brightness Threshold", the light source will be considered "off". For best results, keep this setting only a little less than the brightness of the light source in "on" state. Similarly, you can play with other settings to get the best results. The program will provide all the statistics below the webcam window.

Conclusion

We have reached the end of this article, and I hope you enjoyed reading it. Now, here's some homework for you: try implementing features like AI for the program, and make this program self-adaptive according to its environment. Use your ideas, and if you end up doing something cool, I'd love to hear about it. :) Have fun!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here