|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionLanguages 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. BackgroundThresholdingThresholding 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:
Gray Scale ConversionGray 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);
Connected Component AnalysisConnected 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 Pointer ArithmeticImage 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 CodeNow 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. You see three important issues here:
Notice that we have a loop from
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 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 AlgorithmThis 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)
ConclusionI 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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||