Click here to Skip to main content
Click here to Skip to main content
Go to top

Image Processing Basics in C#

, 16 Nov 2008
Rate this:
Please Sign up or sign in to vote.
This article demonstrates the utilization of C# for basic image processing algorithms
lenaCC.jpg

Introduction  

Languages like C# and Java are very easy to use, and they present a lot of features for Application Layer development. This means that they could easily access peripheral devices and at the same time, make GUI (Graphical user interface) development much easier. However it is always said that they are not good for signal processing applications because of the overhead and limited possibility of optimization. In such applications, the code usually runs much more slowly when compared to C/C++ code. We know this is a natural result. However, there are certain applications which are not very speed-demanding. Some examples may include educational software, algorithm test software, or integration with existing applications. For these reasons, signal, or in this case image processing in C# may be a good idea. In this article I won't describe complicated image processing algorithms but I will describe how one can implement these algorithms in C# in an efficient way, using simple examples such as thresholding, gray scale conversion and connected component analysis. This will sort of be like an introduction to people who like signal processing and at the same time want to write test codes in C# to make things easier.

Background

Thresholding

Thresholding is binarizing the image in such a way that the values bigger than a threshold will be 255 (maximum pixel value in bytes) and pixels with smaller intensities will be set to 0 (black). It is a very important operation that is often used to prepare images for vectorization, further segmentation or use as guide layers in the creation of drawings. It can be used with raster data images to set off ranges of values that may then be used for subsequent analysis or as selection masks. Some people refer to it as foreground and background separation. Here is a result of Lena thresholded:

lenaThresh.jpg

Gray Scale Conversion

Gray scale conversion is setting of all pixels of an image to a weighted average of the values in different color channels. The conversion is done through the mapping:

GRAY= (byte)(.299 * R + .587 * G + .114 * B);

lenaGRAY.jpg

Connected Component Analysis

Connected component labeling is used in computer vision to detect unconnected regions in binary digital images, although color images and data with higher-dimensionality can also be processed. When integrated into an image recognition system or human-computer interaction interface, connected component labeling can operate on a variety of information. (Wikipedia definition).

Connected component labeling works by scanning an image, pixel-by-pixel (from top to bottom and left to right) in order to identify connected pixel regions, i.e. regions of adjacent pixels which share the same set of intensity values V. (For a binary image V={1}; however, in a graylevel image V will take on a range of values, for example: V={51, 52, 53, ..., 77, 78, 79, 80}.)

Pointer Arithmetic

Image pixels are represented in a 1D array in memory. We access it through pointers. While reading the rest of the code, one should keep in mind that pointers are actually only addresses. Even though the image is a 2D structure, for convenience and ease of processing & speed, it is a common technique to represent it as a 1D structure. In such a case, the image can be indexed as:

[y*ImageWidth*ChannelCount+x*ChannelCount]

Because of the linear structure of the memory, a pointer is used linearly to index the image using the given formula. Languages like C/C++/C# and even assembler allow us to use arithmetic operation on pointers. For example:

int x[5];
int* p =&x[0]; // Get address of x
p=p+2;         // This line increments the pointer by 2 memory addresses and hence 
	      //we could access x[2]

Using the Code  

Now we will go step by step showing how we could write a simple image processing routine in C#: This code is very easy and yet undesired of writing an image processing routine. GetPixel and SetPixel functions have several drawbacks. First of all they are functions. We have the function overhead of accessing and modifying a pixel value. However, in many cases we would want to modify the image through a pointer operation not a function call. The next example utilizes the “unsafe” block in C#. Inside unsafe blocks, we have access to pointers from C#. But don't mix it with the idea of pointers that you are familiar from C/C++. These pointers don't address the unmanaged code directly. What they address is related to intermediate language. You can read about it more in many articles. It has its own instructions (soft instructions of course) and own language. The conclusion is that pointers in unsafe blocks are not as fast as native pointers. Here is how one can implement it:

You see three important issues here:

  1. Lockbits and UnlockBits functions
  2. byte* access
  3. p[0] operation

Notice that we have a loop from 0 to Width*ChannelCount*Height which is the total amount of bytes that are stored in the memory. Then we increment the pointer by ptr+=3 so that we can process each color pixel. When using GDI, one should be careful about two important facts:

  1. Pixels are ordered as BGRBGRBGRBGRBGRBGR… instead of RBGRBGRBGRBG… This is one of the strange things Microsoft has come up with. 
  2. The image is flipped. This is not so crucial when using techniques described in this article, but it is important when one needs to go more native.

Most of the people who do some work on image processing with C# use the coding convention that I described above. Unfortunately, this style has some unoptimized operations. For example, we have a y value counting from 0 to imageSize, but it is not used inside the loop. Also, the pointer is incremented by 3 each time while y is also incremented. To overcome this problem, we could go ahead and use further pointer arithmetic. Here is an example:

private void Convert2GrayScaleFast(Bitmap bmp)
{
    BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 
			ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    unsafe
    {
        byte* p = (byte*)(void*)bmData.Scan0.ToPointer();
        int stopAddress = (int)p + bmData.Stride * bmData.Height;
        while ((int)p != stopAddress)
        {
            p[0] = (byte)(.299 * p[2] + .587 * p[1] + .114 * p[0]);
            p[1] = p[0];
            p[2] = p[0];
            p += 3;
        }
    }
    bmp.UnlockBits(bmData);
}

In this example, we calculate the start and end addresses of the image structure, thinking that it is a 1D linear array. We increment the pointer from start to end addresses and get values in between. If one measures the computation time of these three algorithms (slow, well known and new), he/she will see that there is a huge difference between each of them. If you download the code, you will see the measured times in messageboxes.

Connected Component Labeling Algorithm

This algorithm is the stack implementation of general recursive connected component labeling algorithm. You can use it for many applications and get good results. I am not pretty sure that it's the best implementation but the results seem satisfactory. The implementation is very similar to Trajan's strongly connected component algorithm. However it has been generalized for images. The pseudo-code is as follows:

Input: Graph G = (V, E), Start node v0

index = 0                       // DFS node number counter 
S = empty                       // An empty stack of nodes
tarjan(v0)                      // Start a DFS at the start node

procedure tarjan(v)
  v.index = index               // Set the depth index for v
  v.lowlink = index
  index = index + 1
  S.push(v)                     // Push v on the stack
  forall (v, v') in E do        // Consider successors of v 
    if (v'.index is undefined)  // Was successor v' visited? 
      tarjan(v')                // Recurse
      v.lowlink = min(v.lowlink, v'.lowlink)
    elseif (v' in S)            // Is v' on the stack?
      v.lowlink = min(v.lowlink, v'.lowlink)
  if (v.lowlink == v.index)     // Is v the root of an SCC?
    print "SCC:"
    repeat
      v' = S.pop
      print v'
    until (v' == v)

Conclusion

I hope this article will help anyone to speed up their image processing algorithms. I don't know if this will happen or not but if Microsoft can integrate inline assembler (Not intermediate language assembler but native assembler) in C#, we will be able to write algorithms almost as fast as C++ code if not faster.

History

  • 16th November, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Tolga Birdal
CEO Gravi Information Technologies and Consultancy Ltd
Turkey Turkey
Currently, also an MSc. student in Technical University of Munich, I develop practical application in computer vision for more than 5 years. I design real-time solutions to industrial and practical vision problems, both 3D and 2D. Very interested in developing algorithms in C relating math and vision.
 
Please visit Gravi's web page (http://www.gravi.com.tr) and my page (http://www.tbirdal.me) to learn more about what we develop.
 
I admire performance in algorithms.
 
"Great minds never die, they just tend to infinity..."
Follow on   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinmemberÇağrı Daşkın2-Apr-14 1:39 
GeneralMy vote of 5 Pinmembermanoj kumar choubey20-Feb-12 19:15 
Questionit can be posible to Comparing Images using that algorithm? Pinmemberchieto28-Nov-11 4:27 
AnswerRe: it can be posible to Comparing Images using that algorithm? PinmemberTolga Birdal28-Nov-11 4:29 
GeneralRe: it can be posible to Comparing Images using that algorithm? Pinmemberchieto28-Nov-11 23:01 
GeneralRe: it can be posible to Comparing Images using that algorithm? PinmemberTolga Birdal28-Nov-11 23:05 
GeneralRe: it can be posible to Comparing Images using that algorithm? Pinmemberchieto29-Nov-11 12:49 
Questionhelp, thank you Pinmembermostrodf12-Oct-11 15:56 
AnswerRe: help, thank you PinmemberTolga Birdal12-Oct-11 21:57 
GeneralRe: help, thank you Pinmembermostrodf13-Oct-11 14:28 
Generalpregunta sobre Imagenes tiff aplicado al codigo PinmemberMember 332055927-Feb-10 10:15 
GeneralRe: pregunta sobre Imagenes tiff aplicado al codigo PinmemberTolga Birdal28-Feb-10 5:50 
GeneralIt's important! Pinmemberslovica4-Feb-10 17:18 
Generaltypo in discussion PinmemberMember 445497117-Nov-08 7:39 
GeneralRe: typo in discussion PinmemberTolga Birdal17-Nov-08 13:09 
GeneralIs it really "almost as fast as a C++ code if not faster" PinmemberDmitri Nesteruk17-Nov-08 5:38 
GeneralRe: Is it really "almost as fast as a C++ code if not faster" PinmemberTolga Birdal17-Nov-08 13:14 
GeneralI remember that image PinmemberJoel Ivory Johnson17-Nov-08 5:17 
GeneralRe: I remember that image PinmemberTolga Birdal17-Nov-08 13:14 
GeneralJust what I was looking for. Pinmembernordhaus17-Nov-08 4:12 
AnswerRe: Just what I was looking for. PinmemberPung17-Nov-08 5:15 
GeneralGood article Pinmemberabombardier17-Nov-08 3:25 
GeneralRe: Good article PinmemberTolga Birdal17-Nov-08 13:15 
GeneralRe: Good article Pinmemberabombardier17-Nov-08 16:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 16 Nov 2008
Article Copyright 2008 by Tolga Birdal
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid