Image processing operations work by manipulating the pixels of an input image, to result in an output image. Such operations may be of many types - two of which are point operations and region operations. Point operations are perhaps the simplest of image processing operations. A pixel value in the output image depends solely on its corresponding value in the input image. In other words, if the output value of the pixel at location (x, y) depends only on the value at the pixel location (x, y) in the input image, then this is a point operation. Point operations map each pixel in the input image to their corresponding pixel in the output image. In contrast, region operations use a group of pixels surrounding an input pixel to yield a single output pixel. Filters are examples of region operations.
Point operations are performed to enhance an image. Details not clearly visible in the original image may become visible upon application of the point operator. For example, a dark region in an image may become brighter after the operation. Perhaps, the simplest point operations are increasing and decreasing of brightness. If an operator takes each pixel value and adds a constant number to it, then this point operation increases the brightness of the image. A similar subtraction operator reduces the brightness.
In this article, we present simple implementations of four kinds of point operations on 8-bit grayscale images. An 8-bit grayscale image has pixels in the range 0 - 255, for both the input and output images. We chose grayscale images because of the simplicity they provide in understanding these operations. The four point operations we demonstrate here are:
- Linear Operations,
- Log Operations,
- Exponential Operations, and
- Gamma Correction Operations.
There are a number of image processing tutorials and articles available in the public domain, and many of these perform complex operations. However, articles which present simple operations, with easy-to-understand implementations, are somewhat rare to come by. This article intends to fill that gap. The implementation presented here is simple, intended to teach and illustrate to beginners, rather than to optimize performance.
One possible formula for a simple point operation is:
where a and b are constants, p = p(x,y) is the grayscale intensity at location (x,y) in the input image, and q = q(x,y) is the corresponding grayscale intensity in the output image. The four different operations mentioned above differ in the structure of function f(p).
For a linear operation, ; and therefore, the formula becomes .
For a log operation, , and therefore, .
For an exponential operation, , with the resulting formula .
For a gamma correction operation, , where is a real number; and the formula becomes .
In reality, some of these formulas may need subtle adjustments to make them work satisfactorily. The exponential operation needs one such subtle adjustment, as can be seen in the code.
Given a point operation, it becomes necessary to solve for the values of a and b, and hence compute the resulting value of each pixel in the image. Once the values of a and b are known, it is a simple task to compute the output pixel value for any input pixel. Since there are only 256 different grayscale values to consider in an 8-bit image, it becomes computationally efficient to construct a lookup table, and assign output pixel values from that table. Use of lookup tables avoids needless repetition of the calculations. Whatever be the size of an image, the lookup table has just 256 entries (given 8-bit grayscale input and output images). Two numbers low and high, set by the user using track bars, are also used in the computation of a and b, and these form the lower and upper ranges for the point operation.
The language used for this application is C#, and the development platform is Visual Studio 2008 Express Edition.
The software functionality required is:
- Open an image and display it. Since 8-bit grayscale images may not be easily available, an additional functionality is to convert 24-bit or 32-bit colour images to grayscale. The formula for such a conversion is GrayscaleValue = 0.3 * red + 0.59 * green + 0.11 * blue.
- Display the histogram of the grayscale image.
- Perform the point operation - Linear, Log, Exponential, or Gamma - create the lookup table, and apply it to the grayscale image.
- Update the image using the selected point operation, based on the position of the upper and lower track bars, by re-creating the lookup table and updating the pixels accordingly.
The software is designed to use reusable graphical controls. Two reusable graphical controls are developed here:
ImagePanelControl - to display an 8-bit grayscale image. This control is designed to not just display an image, but facilitate scrolling of the image. Horizontal and vertical scrollbars are also provided as a part of this control; and the movement of these scrollbars is linked to the image position.
GraphControl - to display the image histogram. This control is designed to compute the histogram of a grayscale image, and also to draw the vertical and horizontal tick marks corresponding to the axes.
An additional feature in the design is the use of inheritance. The different point operations like linear, log, etc., are made to inherit from a parent class called
GrayscaleOp, which declares the common members and methods. It then becomes a case for instantiating the appropriate child class and using it.
Software Implementation and Code Extracts
The different classes in the application are:
GammaOp. The main form includes the two graphical controls mentioned above, in addition to the other visual controls (like buttons, radio buttons, trackbars, and labels).
ImagePanel control has a
List<byte> pix8 data member, which contains the image pixels, in addition to the image width and height. Given these parameters, it creates an image on the fly, using the following code:
private void CreateImage()
bmp = new Bitmap(imgWidth, imgHeight,
BitmapData bmd = bmp.LockBits(new Rectangle(0, 0,
int pixelSize = 3;
int i, j, j1, i1;
for (i = 0; i < bmd.Height; ++i)
byte* row = (byte*)bmd.Scan0 + (i * bmd.Stride);
i1 = i * bmd.Width;
for (j = 0; j < bmd.Width; ++j)
b = (byte)(pix8[i1+j]);
j1 = j * pixelSize;
row[j1] = b; row[j1 + 1] = b; row[j1 + 2] = b; }
The use of the
BitmapData class is to make the process of image creation faster, using C-style pointer arithmetic, rather than through the use of the slower
Scan0 is the start of the pixel data buffer, and
Stride is the number of bytes in a row (including any padding necessary). The other important method in this class is
g.DrawImage() within the
ImagePanelControl class, with the responsibility of properly displaying an image, hooks up the scrollbars with the image, so that the image scrolls properly when the scrollbars are moved.
GraphControl class, as mentioned above, creates the histogram of the image. It uses GDI+ to draw the histogram. The histogram is first created. After this, the maximum entry in this histogram is determined, and stored as
histMax. The histogram is then displayed using GDI+ commands. The histogram is composed of a set of vertical lines, placed adjacent to each other, giving the appearance of a continuous histogram.
pt1 = new Point();
pt2 = new Point();
Pen p = new Pen(Color.Blue);
Pen p1 = new Pen(Color.Tomato);
Graphics g = Graphics.FromHwnd(this.Handle);
for (i = 0; i < 256; ++i)
pt1.X = Convert.ToInt32(xLength * i / 255.0) + margin;
pt1.Y = Height - margin;
pt2.X = pt1.X;
pt2.Y = pt1.Y - Convert.ToInt32(histogram[i] * yLength / histMax);
g.DrawLine(p, pt1, pt2);
GammaOp, each contain their implementations of the
ComputeLookUpTable method, an example of which is given below (for
public override void ComputeLookUpTable(int low, int high, double gamma)
double a, b;
double lHigh = Math.Log10(high + 1.0);
double lLow = Math.Log10(low + 1.0);
double range = lHigh - lLow;
a = 255.0 / range;
b = -a * lLow;
for (int i = 0; i < 256; ++i)
if (i <= low) lookUpTable[i] = 0;
else if (i > high) lookUpTable[i] = 255;
intVal = Convert.ToInt32(a * Math.Log10(i + 1) + b);
if (intVal > 255) intVal = 255;
if (intVal < 0) intVal = 0;
lookUpTable[i] = (byte)intVal;
The rest of the code is for integrating the application into one - via the main form.
A point to note is that the Visual Studio project needs to be compiled once before opening the application's main form. This is required because the two controls
GraphControl are part of the main form, and the binaries corresponding to these controls must be available for proper display of the main form. Binaries have not been included as a part of the zipped project file supplied here.
Results and Conclusion
A simple application to demonstrate point processing of grayscale images was described above. This application enables the user to apply four point operations - linear, logarithmic, exponential, and gamma correction operations. A snapshot of the application is given at the top of this page. Selecting a radio button applies the corresponding point operation to the image. For the gamma operation, it is possible to select the gamma value to be applied, from a range of values. This application was inspired by the book "Digital Image Processing" by Nick Efford. Further work on this application includes addition of more such point processing operations and optimizing the performance of the application. A simple optimization is to apply the LUT (lookup table) only to the displayed part of the image, invalidate that part, and then apply the LUT to the rest of the image. Other kinds of optimizations are also possible.