Image Processing
Image Enhancement Using Palettes  Lab Report


Image Enhancement and Thresholding Using Palettes
contributed by
  Dean Inglis, PhD

Current Address:
Clinical Research Unit
25 Charlton Avenue East, Unit 610
Hamilton, Ontario

Perform on the fly, flicker free gray-scale image enhancement
 and thresholding using a palette with pf8bit bitmaps

Purpose
The purpose of this program is to demonstrate how to use a palette with a pf8bit bitmap to achieve brightness and contrast enhancement as well as achieving binary and full spectrum thresholding on the fly and in a flicker free manner. Using a pair of scroll bars, the user can adjust the contents of a 256 gray scale palette. The program quickly reassigns the palette to the bitmap giving the appearance of on the fly image processing.

Background
While developing a medical image processing application, I was interested in providing flicker free, on the fly contrast and brightness enhancement. Such images can have pixel values ranging from -1000 to 20000. My original solution was to create a lookup table that mapped the image values stored in a Width * Height buffer array down to a 256 gray scale pf8bit Bitmap. This can be done by calculating the coefficients in the equation

x' = C * x + B ; 0 <= x' <= 255

where x' is the modified pixel value, x is the original pixel value, C is the contrast, and B is the brightness. Suppose we have an image and we find the Minimum and Maximum pixel values. C and B are

C = 255 / (Max - Min)

B = -C * Min

It is easy to show that if x = Max, x' = 255 and similarly when x = Min, x' = 0. The Lookup table is a one dimensional integer array having length Max Min, with values defined such that

x' = Lookup( x - Min ) = C * x + B ; Min <= x <= Max

Then the image Buffer can be mapped to the display Bitmap using

Bitmap( i , j ) = Lookup[ Buffer( i , j ) - Min ] ; 0 <= i <= W , 0 <= j <= H

Now consider the case of full spectrum thresholding, which can be used to segment regions of an image having similar intensity values. A simple modification to the linear mapping function is accomplished by replacing x with f(x):

x' = C * f(x) + B

where f(x) is a range checking function between the desired limits Lower to Upper:

f(x) = x ; L <= x <= U , otherwise f(x) = Min

In order to achieve binary thresholding, change B to 0, C to 1 and f(x) to

f(x) = 255 ; L <= x <= U , otherwise f(x) = 0

In addition to thresholding, several non-linear mapping functions are provided in the program as alternatives to the original linear transformation described above. In the following definitions, assume that the values of x are already scaled between 0 and 255 (i.e. Min = 0, Max = 255). The Gaussian map is defined such that C = 255, B = 0 and

f(x) = Exp[ ( c - x )/(2 * s * s) ] ; 0 <= x , c <= 255 , 0 <= f(x) <= 1 , 0 < s < 255/2

Here, Exp is the exponential function, c is the center of the symmetric Gaussian curve, and s is a measure of the width of the curve. The Cosine map is defined with

f(x) = Cos[ Pi * ( x - c )/s ] ; c - s/2 < x < c + s/2 , otherwise f(x) = 0

0 <= c <= 255 , 0 < s <= 2 * 255

Again, c is the center of the symmetric positive half of a cosine curve, and s is a measure of the width of the curve (C = 255, B = 0). The Triangle map is a piecewise linear mapping function defined over two ranges of x as

f(x) = 2 * ( x - c - s/2 )/s ; c - s/2 < x <= c

f(x) = 2 * ( c + s/2 - x )/s ; c < x < c + s/2 , otherwise f(x) = 0

0 <= c <= 255 , 0 < s <= 2 * 255

with analogous definitions for c and s (C = 255, B = 0). The preceding definitions are provided as a demonstration of mapping in general and do not necessarily produce useful or practical results for a particular image. However, they can provide aesthetically interesting effects. Try toggling back and forth between the standard brightness/contrast transformation and the other functions set at their initial, default positions. In particular, observe the image appearance and note that the Triangle map gives the same results as the brightness/contrast operation. Change the location of the 'center' slider to position 0. This produces an inverted or negative image.

In any event, every time we change C or B for a given f(x), the process involves a two-step procedure of calculating a lookup table and then mapping the image values to a bitmap. This is time consuming, since we must loop through Max - Min + W *H addresses. Thus, for large, broad spectrum images, it can become difficult to achieve image modifications in apparent real time.

Materials and Equipment

Software Requirements
Windows 95/98/2000/NT
Borland C++ Builder 4 (to recompile)
PalettePrj.EXE

Hardware Requirements
800 by 600 display in 256 colors, 16, 24 or 32 bit color mode

Procedure

  1. Double click on the PalettePrj.exe file from the zipped Builder project.
  2. Click the Open button to load in a bitmap of your choice. The program will convert color bitmaps to pf8bit format by averaging the R, G, B color planes to a gray value.
  3. Move the left Brightness slider or the right Contrast slider on the panel at the left of the form and observe the changes in both the image and the gray scale bar to the right of the sliders. Labels at the bottom of the sliders are updated with the current values of B and C.
  4. Toggle between the radio group items to observe various thresholding and transformation effects. The sliders now work as upper and lower range selectors (thresholding) or as centering and scaling selectors.

Discussion
A solution to image modifications in apparent real time is to use a pf8bit bitmap for display purposes and a logical color palette (LOGPALETTE) structure (solution from Patrick M. Martin, posted 7/17/00 on borland.public.cppbuilder.graphics). This structure contains the number and an array of PALETTEENTRY structures which define the color and usage of each entry in the logical palette (ref. Win32 Programmer's Reference). The PALETTEENTRY structures contain 4 Byte fields, the most important of which are peRed, peBlue, and peGreen. Define a 256 gray scale palette and assign it to a temporary bitmap:

Graphics::Bitmap* aBitmap = new Graphics::Bitmap();
aBitmap->PixelFormat = pf8bit;
aBitmap->Width  =  10;
aBitmap->Height = 256;
LOGPALETTE* pal = NULL;
try
{
   pal = (LOGPALETTE*) malloc( sizeof(LOGPALETTE) + \
          sizeof(PALETTEENTRY) * 256);
   pal->palVersion = 0x300;
   pal->palNumEntries = 256;
   for(short i = 0 ; i < 256 ; i++)
   {
     pal->palPalEntry[i].peRed = (Byte) i;
     pal->palPalEntry[i].peGreen = pal->palPalEntry[i].peRed;
     pal->palPalEntry[i].peBlue = pal->palPalEntry[i].peRed;
   }
   HPALETTE hpal = CreatePalette(pal);
   if(hpal)
   {
     aBitmap?>Palette = hpal;
     //save a backup of the palette for later
     GetDIBColorTable(aBitmap->Canvas->Handle, 0, 256, OldPalette);
   }
}
  __finally
{
  delete pal;
}

It is a simple matter to retrieve or set the palette structure array in a pf8bit Bitmap with the Windows API functions: GetDIBColorTable(Bitmap->Canvas->Handle, 0, 256, OldPalette) and SetDIBColorTable(Bitmap->Canvas->Handle, 0, 256, NewPalette). OldPalette and NewPalette are 256 element arrays of RBGQUAD structures (similar to a PALETTEENTRY structure). Now, create a gray scale bar using a TImage component which displays the contents of the current palette:

Byte* ptr;
for(short y = 0; y < 256; y++)
{
  ptr = (Byte*)aBitmap->ScanLine[y];
  for(int x = 0; x < aBitmap->Width; x++)
  ptr[x] = y;
}
GrayScale->Picture->Assign(aBitmap);
delete aBitmap;

Whenever B or C are modified, as with a scroll bar, create a new color palette:

void __fastcall TForm1::ScrollBarChange(TObject *Sender)
{
  short i = 256;
  while(i--){
    short Val = (short)(i*Contrast+Brightness);
    NewPalette[i].rgbBlue = (Byte)(Val > 255 ? 255 : (Val < 0 ? 0 : Val));
    NewPalette[i].rgbGreen = NewPalette[i].rgbBlue;
    NewPalette[i].rgbRed = NewPalette[i].rgbBlue;
  }
  SetDIBColorTable(GrayScale->Picture->Bitmap->Canvas->Handle, 0, 256, NewPalette);
  GrayScale->Invalidate(); //repaint the image immediately
}

Now there are only 256 addresses to calculate. Note that x represents the default gray scale palette value, i, while x' is the new palette value, Val. Flicker is easily removed from the image by setting the form's ControlStyle in its constructor with ControlStyle << csOpaque.

The attached zipped project shows how to read in a general color bitmap and display it as a 256 gray scale image. The coding of the various effects is handled in a modified version of the last code example.

Conclusions
Modifications to gray scale images can be achieved on the fly without flicker by changing color table entries, assigning them to the handle of a bitmap's canvas, and then invalidating the image. The technique can easily be extended to 8 bit color bitmaps by modifying individual color planes independently.

References
Discussions on thresholding and image transformations in general can be found in:
Ramesh Jain, Rangachar Kasturi, Brian G. Schunck, Machine Vision, McGraw-Hill, 1995.
Additionally, implementation details and code examples can be found in:
Douglas A. Lyon, Image Processing in Java, Prentice Hall PTR, 1999


Keywords
256 gray scale, pf8bit Bitmap, Palettes, Brightness, Contrast, Thresholding, GetDIBColorTable, SetDIBColorTable, ScanLine, PixelFormat, LOGPALETTE, PALETTEENTRY, RBGQUAD, Builder

Download
Borland C++ Builder source and EXE (230 KB):  ImageEnhancementUsingPalettes.ZIP


Copyright (C) 2000 by  Dean Inglis.  All Rights Reserved.
Reproduced here with permission.
Updated 18 Feb 2002


since 29 Aug 2000