Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Image Processing and Visual Basic


Visual Tools 96: Image Processing and Visual Basic

Image Processing and Visual Basic

Improving I/O and enhancing quality

Don Parrish

Don has more than 20 years of hardware/software engineering experience with digital communications systems. He can be contacted at [email protected].


Image processing is the capability of software to manipulate images that are represented by a sequence of numeric values. Capabilities supported by most image-processing programs fall into three general categories:

  • Service commands, which manage the image data for computer I/O. This includes the retrieval and storage of image files, printing images, and file management.
  • Display commands, which support how you choose to visualize the image data. This includes displaying an image as a positive or negative picture, color palette selection, and 2-D or 3-D types of image representations.
  • Image-processing commands, which process the data to obtain some type of measurement from the data or to modify the data by filtering. In this case, filtering refers to the modification of data through mathematical techniques.

Unfortunately, Visual Basic, the visual-development environment I prefer to work in, often falls short in supporting these requirements, particularly when it comes to fast image plotting. However, it is possible to achieve efficient image processing by improving disk access and image plotting speeds using the techniques I'll describe in this article. I implemented these techniques to tackle deep-sky imaging and other astronomy-related applications in which my original problem was to display an image in near-real time to aid in focusing a CCD camera when mounted to a telescope. My approach to reading and saving a file is a time-tested solution for efficient disk access (I first learned it over 15 years ago). The plot routine I present uses the same display technique that is used for camera focusingdisplay an image from a computer memory location. To enhance quality, I then bring out details using histograms during post processing.

Reading the Image File

Reading a file into the program is straightforward. Many programmers read each and every byte of data, one byte at a time, and store that data into an array. However, since I sometimes grow impatient waiting on data loops, I speed things up using strings to store the data file. With either method, a memory area is defined for data storage.

First, create a module, naming it whatever you like (mine is named IMAGE.BAS), then declare string arrays in that module under the general attribute. The string declarations in my program are Global Datarray As String and Workarray As String, which define Datarray and Workarray as globally recognized string arrays.

I use strings because Basic-type programs use them efficiently. Note that a specific size is not needed for the array definition. This provides some programming flexibility if you want to enter files with different formats and lengths. This is a necessity for today's nonstandard astro-imaging formats.

To enter the data, there are several commands common to Basic and Visual Basic. I use INPUT to load data into a string. In Introduction to Astronomical Image Processing (Willmann-Bell, 1991), Richard Berry uses this command in a for-next loop in his excellent AstroIP software (written in Basic and included with his book) to load data.

The disadvantage of INPUT in a for-next loop is speed, especially when running the program on a 386SX-based PC running Windows. Speed is lost under Windows 3.x because Windows is just another layer of overhead associated with getting the data into the computer.

Like any other program, Windows 3.x communicates with the computer and its peripherals through the operating system. That means more processing for the computer, so it takes longer than executing the program directly with DOS. This isn't a terrible problem, but it becomes a nuisance.

I gained speed by reducing the number of lines of computer instructions. Example 1, the code to input raw 8-bit data files, will load the data file within a few seconds, even on a slow machine. It works fast because only four statements are executed. The first statement instructs the computer where the file is located, the second and third store the entire file into strings. The fourth statement closes the activity.

The reason for the difference in speed is obvious when you consider that this code is executed just once, whereas the INPUT statement would be executed 31,680 times in a for-next loop. For a comparison, see Example 2, which reads a raw 8-bit data file with for-next loops.

Displaying the Data

Once the image data has been loaded into the computer and filtered, the next step is to display it. Many Basic programmers use the PLOT command to do this. While PLOT's syntax is common to Basic and Visual Basic, its execution isn't the same. The key difference between each language implementation is how you tell the program where to plot.

In Visual Basic, you don't need to write specific code to define a plot area, as with Basic. The plot area is defined by a viewing area within a form. The form is created when a new program is started, by selecting New Form from the File menu, or by selecting its icon from the tool bar. When this command is executed, you simply drag to the size of the window that you want. The viewing area can be either the form itself or a graphics box placed on the form. I'll stick with the form itself.

The plot routine next needs to be associated with the form. This is almost as easy as creating the form. Simply double-click on the form, select the appropriate event to initiate execution of the code, and enter the plot routine or its subroutine call.

At this point, it's convenient to choose a 256-color palette to associate with the form. Three such palettes come bundled with the Visual Basic language set. They're okay for demonstrations, so use pastel.dib with this program. A palette can be made part of the program by selecting it through the form's Property menu. If you don't do this, you'll be stuck with the Windows default 20-color palette and your pictures won't be recognizable.

To display the image almost instantly, I rely upon the Windows API. Look at the LOAD event of Listing One and you'll see a call to the plot routine in the IMAGE1 module (Listing Two). The plot routine isn't much code, consisting only of Visual Basic declarations and Windows API function calls.

The Windows API plot code listing consists of six lines of code and is in the PLOT_IMAGE procedure of Listing Two. The last line of code, StretchBlt, is used to double the size of the original image.

Using the Windows API also makes it easy to do a 2xzoom. This can be done with only one line to call the StretchBlt API function. I use this function to double the size of the original image multiple times. Each time you want the image size doubled, select Zoom In from the Display menu. The code for the 2xzoom is found in the Zoom_Image_In procedure of Listing One.

To zoom out, you just have to call the image-plotting routine again. This simply replaces the zoomed image. See the Zoom_

Image_Out procedure in Listing One.

Image Histograms

Histograms are mathematical techniques that keep a running count of how frequently a set of values will occur. For the image data we're using, these values fall within a range of 0 to 255.

From the image file chosen, you can plot a histogram chart. This is useful for identifying the general distribution pattern of the image data.

From the histogram, you will notice that deep-sky astronomy images have similar distributions: a large number of low values (sky background) that decreases rapidly toward the higher data values (astronomical objects). Likewise, planetary and lunar images exhibit distinct histograms.

By examining the histogram chart, you can decide how to select filters to improve an image. For example, histogram operations can "amplify" a narrow range of numeric values to bring out subtle image details overpowered by bright objects or to reduce a noisy, narrowband sky background.

The technique I use to develop a histogram is conventional: I just read each byte of data and increment the appropriate cell of a data array. Each cell of the data array represents a specific numeric value from 0 to 255.

The algorithm to establish a histogram is based on a simple loop:

1. Get a byte of data.

2. Increment the count for this value.

3. If there's data remaining, go to step 1.

4. Display histogram.

To start writing code to implement this algorithm, specify an array for the histogram data. First, for ease of expansion, reserve use of the histogram array for any routine in the program. To do this, name and dimension the array in the general attributes section of the image module; I used Global Dim HistoLevel(255) As Integer. Note that the array is sized for 8-bit data. If you have 12-bit data, the array would be sized to 4095.

The core of the histogram routine is now ready. My routine is located in the Normal_Histo object in Listing One. The routine simply reads each byte of the Datarray string with a for-next loop.

The for-next loop is used as a counter. The value statement returns the numeric value of a string value. The numeric value is then used to identify and increment the appropriate cell of the histogram array.

Plotting the histogram is accomplished by calling a routine called HistoPlot, located in Listing Two. HistoPlot uses the Visual Basic graphics LINE command to draw 256 vertical lines in a picture box on FORM1. The picture box is scaled to show the first line (representing value 0) on the far left and the last line on the far right (representing value 255). The vertical length of each line corresponds to the number stored in each cell of the histogram array. This number is the count of how often a particular value occurred (the frequency). Figure 1 shows a histogram of the galaxy M51 with the corresponding image.

Since large histogram values can cause a program error with Visual Basic, I've included an error-handling routine in the histogram subroutine.

Stretching Histograms

Stretching the histogram means that pixel values above or below a user-specified threshold will be adjusted toward an upper or lower boundary, respectively. This is useful for bringing out detail in an image.

For example, Figure 2 was created from Figure 1 by stretching only the upper boundary limit from 150. In Figure 2, background stars pop out and more structure appears in the galaxy. To improve the picture more, increase image contrast by also stretching from the low end value. Using stretch values 27 (lower) and 150 (upper) works well for this image. The improvement is readily apparent in Figure 3.

The Visual Basic code to stretch an image is located in the Stretch_Histo object of Listing One. It is the only for-next loop in this subroutine. This loop implements an algorithm that produces "equalization." Simply put, the lower and upper threshold values entered (for example, 27 and 150) are multiplied by a factor to set them to 0 and 255, respectively. The data between these threshold values is then adjusted (stretched) to cover the full range of the x-axis scale (equalization). (See the code segment beginning 'Calculate the histogram in Listing One.) Data in this code is stretched by multiplying each pixel value based on a factor from the threshold values, and limiting the minimum and maximum values to 0 and 255.

The line that finds a value for Brightness stretches the data point. This line takes the lower threshold value (LoRange) and the upper threshold value (HiRange) to determine a ratio. That ratio is multiplied by 256 to get the stretched value for the data point, Level. The If statements keep the stretched values within limits. The other code lines either extract data from a string or stuff it back into a string and a data array.

Saving Images

Saving an image is just as easy as reading an image:

1. Open a file.

2. PUT a string into the file.

3. Properly close the file.

See the Save_File object in Listing One (the code segment starting 'Place data into the file). This technique will store any data format; however, I have not manipulated the string to be presented as anything other than raw 8-bit data. To store the data in another format, the string may need headers and trailers, and the data may need to be rearranged.

Conclusion

Of course, you may want more control over how data is displayed, and you may want to change or select different color palettes. A simple display feature you can add is to show the negative of an image. This is done by changing a parameter in the BitBlt Windows API function. Another addition would be the capability to read and save data in other file formats.

When using Visual Basic it is important to limit the length of for-next and do loops for displaying data and performing data I/O. Long loops for I/O are real speed killers.

Example 1: Code to input raw 8-bit data files.

Open Path$ For Binary As #1
     Datarray$ = Input$(31680, #1)
     Workarray$=Datarray$
Close

Example 2: Code to read a raw 8-bit data file with for-next loops.

Open Path$ For Binary As #1
For Line = 1 To 165
For Point = 1 To 192
     Position = Point + (Line - 1)
 * 192
     Datarray$(Position) = 
INPUT(1,#1)
Next Point
Next Line
Close

Figure 1: Histogram of galaxy M51 with the corresponding image.

Figure 2: Image created from Figure 1 by stretching only the upper boundary limit from 150.

Figure 3: Stretch values 27 (lower) and 150 (upper) work well for this image.

Listing One

Private Sub Form_Load()
    Form1.WindowState = 2
    Open_File
End Sub

Private Sub Form_Paint()
    'Call the plot routine
        Plot_Image
End Sub

Private Sub Normal_Histo_Click()
    'If an error occurs, jump to error handling routine
    On Error GoTo Normal_Histogram_ErrorHandler

    'Erase old histogram
    Erase Histo_Array
    Workarray$ = Datarray$

    'Change mouse pointer's shape
    Screen.MousePointer = 11
    'Calculate a histogram
    For Datum = 1 To 31680
        Number = Asc(Mid$(Datarray$, Datum, 1))
        Histo_Array(Number) = Histo_Array(Number) + 1
    Next Datum
    'Enable the Stretch Histogram menu item
     Stretch_Histo.Enabled = -1
     Plot_Histogram
     Plot_Image
     Screen.MousePointer = 1
     Exit Sub

Normal_Histogram_ErrorHandler:

    ' Define MSGBOX variables
    Msg = "Enter a larger value!"
    DgDef = MB_OK + MB_ICONSTOP
    Title = "Process Error"
    ' Put together a message box with all the proper components
    MsgBox Msg, DgDef, Title
    Save_BMP_File.Enabled = 0
    Resume Normal_Histo_Ended

Normal_Histo_Ended:
      Screen.MousePointer = 1
       Plot_Image
End Sub

Private Sub OpenFile_Click()
    'Call the open file routine
    Open_File
End Sub
 Private Sub Quit_Program_Click()
    End
End Sub

Private Sub Save_File_Click()
    'If an error occurs, jump to error handling routine
    On Error GoTo ErrorHandler
    'Get path and name of file from the user
    Path$ = InputBox$("Enter path and file name." & Chr$(10) & Chr$(10) &
                   Chr$(10) & "If you want to exit," & Chr$(10) &
                   Chr$(10) & "press Escape" &
                   Chr$(10) & "or click on Cancel.", "SAVE FILE", "C:\VB\")
    'Place data into the file
    Open Path$ For Binary As #1
    Put #1, , Workarray$
    Close #1
    'This task is completed
    Exit Sub
'Error handling routine reports an error happened
ErrorHandler:
    ' Define MSGBOX variables
    Msg = "Did NOT save!"
    DgDef = MB_OK + MB_ICONSTOP
    Title = "I/O Error"
    ' Put together a message box with all the proper components
    MsgBox Msg, DgDef, Title
    Resume Normal_Histo_Completed
Normal_Histo_Completed:
End Sub

Private Sub Stretch_Histo_Click()
    ' Declare variables
    Dim Answer, DefVal, Msg, Title
    'Prompt user to input lower boundary
    Msg = "Enter a lower boundary value from 0 to 253."  ' Set prompt.
    Title = "Stretch Hitsogram Lower Limit" ' Set title.
    DefVal = "0"    ' Set default return value.
    'Insure user answers within limits
    Do
        Answer1 = InputBox(Msg, Title, DefVal)   ' Get user input.
    Loop Until Answer1 >= 0 And Answer1 <= 253
    'Tell user what was entered
    MsgBox "You entered " & Chr$(10) & Chr$(10) & Answer1 ' Display message.
    Lower_Limit = Answer1
    'Prompt user to input upper boundary
    Msg = "Enter an upper boundary value greater than" & Chr$(10) &
                                    Chr$(10) & Lower_Limit ' Set prompt.
    Title = "Stretch Hitsogram Upper Limit" ' Set title.
    DefVal = "255"    ' Set default return value.
    'Insure user answers within limits
    Do
        Answer2 = InputBox(Msg, Title, DefVal)   ' Get user input.
    Loop Until Answer2 >= Val(Answer1) And Answer2 <= 256
    'Tell user what was entered
    MsgBox "You entered " & Chr$(10) & Chr$(10) & Answer2 ' Display message.     Upper_Limit = Answer2
    'Setup for the histogram plot
    Dim DisplayLevel, HistoPlotLevel
    LoRange = Val(Lower_Limit)
    HiRange = Val(Upper_Limit)
    Screen.MousePointer = 11
    'Clear the old histogram plot
    Erase Histo_Array
    'Calculate the histogram
      For Lines = 1 To 165
        For Points = 1 To 192
            Level = Asc(Mid$(Datarray$, Points + (Lines - 1) * 192, 1))
            Brightness = CInt((Level - LoRange) / (HiRange - LoRange) * 256)
            If Brightness < 0 Then Brightness = 0
            If Brightness > 255 Then Brightness = 255
            Mid$(Workarray$, Points + (Lines - 1) * 192, 1) = Chr$(Brightness)
            Histo_Array(Brightness) = Histo_Array(Brightness) + 1
        Next Points
      Next Lines
    'Call plot routines for the histogram and image
    Plot_Histogram
    Plot_Image
    'Change the mouse pointer shape
    Screen.MousePointer = 0
    'Inhibit selection of STRETCH from the drop-down menu
    Normal_Histo.Enabled = -1
    Stretch_Histo.Enabled = 0
  Exit Sub

    'Setup error routine
ERROR_HistoStretch_MESSAGE:
    MsgBox "Values Out of Range.  Enter New Values.", 0, "ERROR MESSAGE"
    Resume ERROR_HistoStretch_HANDLER

ERROR_HistoStretch_HANDLER:
    Screen.MousePointer = 0
End Sub

Private Sub Zoom_Image_In_Click()
    'Declare variable
    Dim SuccessFlag
    'Use raster operations to double the size of the image
    'located in the left hand corner of the window
    If Zoom_Check = 0 Then
        'Double image size beginning from upper left corner of the original
        SuccessFlag = StretchBlt(Form1.hDC, 0, 0, 384, 330, Form1.hDC, 0, 0,
                                                           192, 165, &HCC0020)
        'Set a flag if this is done first
        Zoom_Check = 1
        Else
        'Double image size beginning from an offset equal in size to original
        SuccessFlag = StretchBlt(Form1.hDC, 0, 0, 384, 330, Form1.hDC, 48,
                                                        41, 240, 206, &HCC0020)
    End If
End Sub 
Private Sub Zoom_Image_Out_Click()
    'A safe way to downsize the image is to redraw it
    Plot_Image
End Sub

Listing Two
'Declarations of variables that may be accessed by any program function
Global Datarray As String, Workarray As String
Global Histo_Array(255) As Integer
Global Zoom_Check As Integer

'Windows API function calls
Declare Function CreateBitmapByString% Lib "GDI" Alias "CreateBitmap" (ByVal
      nWidth%, ByVal nHeight%, ByVal nPlanes%, ByVal nBitCount%, ByVal lpBits$)
Declare Function SelectObject% Lib "GDI" (ByVal hDC%, ByVal hObject%)
Declare Function CreateCompatibleBitmap% Lib "GDI" (ByVal hDC%,
                                                 ByVal nWidth%, ByVal nHeight%)
Declare Function CreateCompatibleDC% Lib "GDI" (ByVal hDC%)
Declare Function BitBlt% Lib "GDI" (ByVal hDestDC%,ByVal x%,ByVal y%,ByVal
     nWidth%,ByVal nHeight%,ByVal hSrcDC%,ByVal XSrc%,ByVal YSrc%,ByVal dwRop&)
Declare Function StretchBlt% Lib "GDI" (ByVal hDC%,ByVal x%,ByVal y%,ByVal
    nWidth%,ByVal nHeight%,ByVal hSrcDC%,ByVal XSrc%,ByVal YSrc%,
    ByVal nSrcWidth%,ByVal nSrcHeight%,ByVal dwRop&)
'Constants and variables used in error handlers
Global Const MB_OK = 0       ' Define button
Global Const MB_ICONSTOP = 16      ' Define Icon
Global Msg, DgDef, Title    ' Declare variables

'Routine used to open files
    'Initialize error handler
    On Error GoTo ErrorHandler_Input
        'Get file path from the user
        Path$ = InputBox$("Enter path and file name" & Chr$(10) & Chr$(10) &
           Chr$(10) & "If you want to exit,"& Chr$(10) & Chr$(10) & "Press
           Escape" & Chr$(10) & "or click on Cancel.", "OPEN FILE", "C:\VB\")
        'Load data from a file and put into strings
        Open Path$ For Binary As #1
            Datarray$ = Input$(31680, #1)
            Workarray$ = Datarray$
        Close
        'Clean up the form before plotting the image
        Form1.Picture1.Cls
        Form1.Cls
        'Set flags
        Form1.Save_BMP_File.Enabled = -1
        Form1.Stretch_Histo.Enabled = 0
        Form1.Normal_Histo.Enabled = -1
        'Plot the image
        Plot_Image
    Exit Sub
ErrorHandler_Input:
    ' Define MSGBOX variables
    Msg = "File NOT entered!"     DgDef = MB_OK + MB_ICONSTOP
    Title = "I/O Error"
    ' Put together a message box with all the proper components
    MsgBox Msg, DgDef, Title
    Resume Complete_the_Input
Complete_the_Input:
End Sub

Sub Plot_Histogram()
    'If an error occurs, jump to error handling routine
    On Error GoTo Plot_Histogram_ErrorHandler
    'Clear the picture box and initialize max display value
    Form1.Picture1.Cls
    HiLevel = 0
    'Declare variables
    Dim Answer, DefVal, Msg, Title
    Msg = "Enter a PEAK histogram value from 1 to 1000."  ' Set prompt.
    Title = "Hitsogram PEAK Limit" ' Set title.
    DefVal = "1000"    ' Set default return value.

Plot_Histo_Again:
    'Ask user for desired display maximum within limits
    Do
        Answer1 = InputBox(Msg, Title, DefVal)   ' Get user input.
    Loop Until Answer1 >= 1 And Answer1 <= 1000
    'Tell user what was entered
    MsgBox "You entered " & Chr$(10) & Chr$(10) & Answer1 ' Display message.
    PEAK_Number = Val(Answer1)
    'Initialize max histogram display value
    For Level = 0 To 255
        If Histo_Array(Level) > HiLevel Then HiLevel = Histo_Array(Level)
        If HiLevel > PEAK_Number Then HiLevel = PEAK_Number
    Next Level
    'Declare a scale for the picture box
     Form1.Picture1.Scale (0, HiLevel)-(255, 0)
    'Plot the histogram
    For Level = 0 To 255
        Form1.Picture1.Line (Level,0)-(Level,Histo_Array(Level)),RGB(0,0,0),B
    Next Level
    Exit Sub

Plot_Histogram_ErrorHandler:
    ' Define MSGBOX variables
    Msg = "Enter a larger value!"
    DgDef = MB_OK + MB_ICONSTOP
    Title = "Process Error"
    ' Put together a message box with all the proper components
    MsgBox Msg, DgDef, Title
    Resume Plot_Histo_Again
End Sub

Sub Plot_Image()
    'declare local variables to be used in this module
    Dim hMemDC, hMemBitMap, hMemDCBitmap, hBitmap, hPicmap
    Dim hBrush, Color, MaskMemDC, hSystemPal, hCurrentPal     Dim SuccessFlag
    'Initialize the zoom flag each time the plot routine is called
    Zoom_Check = 0
    'Clear the plot area
    Form1.Cls
    'create a compatible handle for the memory area from the picture source
    hMemDC = CreateCompatibleDC(Form1.hDC)
    'create a compatible bitmap area in memory for the picture source
    hMemBitMap = CreateCompatibleBitmap(Form1.hDC, 192, 165)
    'associate the handle with the bitmap definition
    hMemDCBitmap = SelectObject(hMemDC, hMemBitMap)
    'load string information into the memory area
    hBitmap = CreateBitmapByString(192, 165, 1, 8, Workarray$)
    'associate the handle with the memory area
    hPicmap = SelectObject(hMemDC, hBitmap)
    'plot the data
    SuccessFlag = BitBlt(Form1.hDC, 0, 0, 192, 165, hMemDC, 0, 0, &HCC0020)
    'Double the image size beginning from the upper left-hand corner of the
    original SuccessFlag = StretchBlt (Forml.hDC, 0 0, 384, 330, Forml.hDC, 0,
    0, 192, 165, &HCC0020)
End Sub

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.