FREE Subscription to Dr. Dobb’s Digest: Same Great Content, New Digital Edition
Site Archive (Complete)
Email
Print
Reprint

add to:
Del.icio.us
Digg
Google
Furl
Slashdot
Y! MyWeb
Blink
August 01, 1996

Image Processing and Visual Basic

(Page 6 of 6)
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 parrish@HiWAAY.net.


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

Previous Page | 1 | 2 | 3 | 4 | 5 | 6
TOP 5 ARTICLES
No Top Articles.
DR. DOBB'S CAREER CENTER
Ready to take that job and shove it? open | close
Search jobs on Dr. Dobb's TechCareers
Function:

Keyword(s):

State:  
  • Post Your Resume
  • Employers Area
  • News & Features
  • Blogs & Forums
  • Career Resources

    Browse By:
    Location | Employer | City
  • Most Recent Posts:
    MEDIA CENTER  more
    NetSeminar
    Modernize your Development by Moving Build and Code Quality Upstream
    Moderated by Jon Erickson, Editor-in-Chief of Dr. Dobb's, this interactive panel discussion brings industry experts Anders Wallgren, CTO of Electric Cloud and Gwyn Fisher, CTO of Klocwork together for a candid discussion of the cost savings, productivity and quality benefits that can be achieved by stabilizing builds and code quality as early in the development cycle as possible.

    The reality of today's development environment - geographically distributed teams, the use of Agile development practices, increasing application complexity, etc. - is straining the viability of the traditional coding, build and release process. To stay ahead of the curve, development teams are modernizing their approach to dealing with these issues, and as a result are achieving new levels of development productivity. Register for the webcast.
    Date: Wednesday, July 15, 2009
    Time: 11 am PT/2 pm ET
    Modernize your Development by Moving Build and Code Quality Upstream
    Moderated by Jon Erickson, Editor-in-Chief of Dr. Dobb's, this interactive panel discussion brings industry experts Anders Wallgren, CTO of Electric Cloud and Gwyn Fisher, CTO of Klocwork together for a candid discussion of the cost savings, productivity and quality benefits that can be achieved by stabilizing builds and code quality as early in the development cycle as possible.

    The reality of today's development environment - geographically distributed teams, the use of Agile development practices, increasing application complexity, etc. - is straining the viability of the traditional coding, build and release process. To stay ahead of the curve, development teams are modernizing their approach to dealing with these issues, and as a result are achieving new levels of development productivity. Register for the webcast.
    Date: Wednesday, July 15, 2009
    Time: 11 am PT/2 pm ET
                                   
    INFO-LINK

    Resource Links: