Showing posts with label Image Processing. Show all posts
Showing posts with label Image Processing. Show all posts

Friday, July 2, 2010

Axis Camera Control

For my live image processing, I tend to use Axis products. Their cameras provide the images as a motion JPEG stream via Ethernet, while their video servers can convert full PAL/NTSC video to motion JPEG streams via Ethernet. It's handy, no real video converters required. Just plug your computer onto the same network as cameras, and bam!

There's a few way to get the images. Individual images can be accessed using an HTTP request:
http:///axis-cgi/jpg/image.cgi?resolution=704x576&camera=1
If you wanted to do that in C#:
System.Net.HttpWebRequest myReq;
System.Net.HttpWebResponse myResp;
myReq = (System.Net.HttpWebRequest)(System.Net.WebRequest.Create("http://136.18.33.16/axis-cgi/jpg/image.cgi?resolution=704x576&camera=1");
myReq.Timeout = 500;
myResp = (System.Net.HttpWebResponse)(myReq.GetResponse());
Bitmap bmp = new Bitmap(Image.FromStream(myResp.GetResponseStream()));
Easy. For easy access of motion video, Axis has provided the handy AxAxisMediaControl as part of their Windows SDK.

I've been using this for years, but have recently noticed a few problems. The main one being that the camera control has to always be visible in your form, otherwise it will just loop the last received image. So no screensavers, the program has to always be on top, and don't even think of hiding the Axis control (which is what I did, I wanted to hide the live stream). Also, to get the image into C# required some memory manipulation and even then the images were coming in upside down.

Despite trolling through the forums and support, there's no fix for this. It seems like I'm not the only one. Now this was a problem for me. I had a safety-critical program that was analysing 15+ frames a second 24 hours a day and could not be turned off. However, we needed to lock the computer up so that no one else could access the computer. Once we locked up the computer, the whole thing stopped working.

I tried work around, I tried accessing the image frame by frame using the HTTP request, but it was too slow. So in frustration, I created my own custom AxisCameraControl (download the code here).

Although not as complete as the Axis one (there's no audio or PTZ) it's perfect for receiving just motion JPEG streams. It has support for
  • multiple camers for video servers
  • password protected devices
  • limiting of maximum frame rate
  • different video sizes
  • autoreconnect on stream loss
  • limiting JPEG compression
The control is not a visible control, unlike the Axis one. It is more of a background threaded class. It has a lot of the same options as the original Axis control, but the following are always required:
public string IP;
public int Width;
public int Height;
public int Framerate;
When running (by calling public void Start()), a worker thread runs in the background. It opens up a motion JPEG stream to the device and looks for the start of each image. At this point, I have to credit/thank http://www.codeproject.com/KB/audio-video/cameraviewer.aspx for providing me with the motion JPEG code.

Once an individual JPEG is decoded from the stream, it throws an event.
Bitmap bmp = (Bitmap)Bitmap.FromStream(new MemoryStream(buffer, start, stop - start));
AxisArgs ax = new AxisArgs(bmp);
OnNewImage(this, ax);
The argument for the event (AxisArgs) is a Bitmap, allowing the image to be passed by value, not by reference like the old Axis control. After all of this, the image is disposed and everything is started again.

Usage in a program is fairly easy:
AxisCameraControl.Camera axisCameraControl1 = new AxisCameraControl.Camera();
axisCameraControl1.IP = "136.18.33.16";

axisCameraControl1.Width = width;
axisCameraControl1.Height = height;
axisCameraControl1.Framerate = frameRate;
axisCameraControl1.OnNewImage += new AxisCameraControl.Camera.NewImage(axisCameraControl1_OnNewImage);
if (!axisCameraControl1.IsPlaying())
axisCameraControl1.Start();

void axisCameraControl1_OnNewImage(object sender, AxisCameraControl.AxisArgs args)
{
Bitmap bmp = new Bitmap(args.Image());
//do something with bmp
pictureBox1.Image = bmp;
}
Simple! And it doesn't have to be visible to work. I've been testing this for the last month, and so far there seems to be nothing wrong with it. I've got a lot more work to do before it has the same functionality as the original AxAxisCameraControl, so I will update as it progresses.

Download the C# camera control class

Bugs:
  • On some servers, can take a few seconds to get the image started
  • If updating to a pictureBox with a high frame rate, the maximum frame rate drops dramatically for large images. Better to update pictureBox with an image every few frames for a frame rate over 8 FPS at an image size greater then 640x480

Thursday, June 3, 2010

C# Bitmap manipulation

I tend to do a bit of image processing in C#. Using SetPixel() and GetPixel() is such a time consuming process, especially when you are image processing live video streams.

The solution? Do it in pointers. I could go into detail using pointers in C#, but that's everywhere else on the net. So just know that you do it in Unsafe mode. Don't forget to enable Unsafe mode. Go to Project>Properties>Build and check Allow Unsafe Code in Visual Studio.

The following block of code will then enable you to edit a single pixel:
        Bitmap theImage = new Bitmap("C:\\theimage.jpg");
        int stride; //stride of the image for processing (basically padding at the sides of images
        BitmapData bmData = theImage.LockBits(new Rectangle(0, 0, theImage.Width, theImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
        stride = bmData.Stride;
        System.IntPtr Scan0 = bmData.Scan0; //pointer to the pixels of the image
        int width = theImage.Width;
        int height = this.Height;
        unsafe
        {
            //code is "unsafe" as it has pointer operations
            //don't freak out too much, "unsafe" doesn't mean this will blow up in your face
            byte red, green, blue;
            byte* p = (byte*)(void*)Scan0;  //pointer to the first pixel of the bitmap
            byte* pOriginal = (byte*)(void*)Scan0;
            int nOffset = stride - width * 3;    //offeset increment for each line
            p = pOriginal + 3 * (x) + stride * y;
            blue = p[0];
            green = p[1];
            red = p[2];
            //do whatever
        }
        theImage.UnlockBits(bmData);
This will do one pixel, of a 24 bits per pixel bitmap, with the Byte* variables blue, green and red being that particular colour of the pixel from 0 to 255. The variables are:
  • theImage - a Bitmap file that you are manipulating
  • stride - how wide the image is, in padding terms
  • bmData - the raw bitmap data
  • Scan0 - pointer to the first bite of the pixel
  • p - the byte address of the individual pixel you want to work on
  • pOriginal - the byte address of the first pixel. If you are doing iterative work, it's easier just to have this as a pointer variable for the pointer math
  • x and y - integer values for the x and y co-ordinate of the pixel being set
I tend to do iterations only over regions of interest, so I do iterative loops which just assign a new value for p (based off of x and y) and change the individual blue, green and red colours. To change the values of the colours, use code such as:

blue = (byte)255;
green = (byte)0;
red = byte(0);
That will make the pixel blue.