The previous parts of this series (part 1, part 2) have delved into an investigation into the new PHOTO functions included in the PV-WAVE 2016.1. A number of helper functions and utilities have been presented thus far, and this edition moves into applying other existing PV-WAVE functions to images as a continued exploration. Specifically, edge detection is discussed, which leads into the concept of sharpening an image using the unsharp mask technique.
PV-WAVE includes the SOBEL function for edge detection of two-dimensional image arrays. This topic is discussed in detail on the Sobel operator page of Wikipedia, so readers unfamiliar should peruse that page first. Two example images provided in that discussion (a 24-bit color image of a steam engine and a grayscale image of a brick wall with a bike and bike rack) are used here as well for easy comparison. The differences between a 24-bit color image and an 8-bit grayscale image are highlighted as well.
The grayscale image is a bit easier to work with, so I will start there. The source image, Bikesgray.jpg, is loaded into PV-WAVE and displayed as a sanity check first:
The built-in SOBEL function cannot work directly on the PHOTO associative array data type, so its pixels are extracted first. Following this, it’s a direct call to the function:
To display the result, we could use the TVSCL procedure or go through the steps of creating a new PHOTO from the pixel array. Both methods are shown next for reference:
Working with a 24-bit color image requires slightly more effort. One could isolate each of the three RGB channels and run the algorithm against each channel. However, gradients across each single-color sub-image are not necessarily aligned with what is perceived in a color photograph. Further, since the goal is to find and highlight gradients in the image, color information is not really required.
To simplify the process, the source image is flattened into grayscale based on luminance. This point was discussed in part 2 regarding computing a histogram, and so details of that discussion are not repeated here. Instead, it will suffice to repeat that the grayscale image is based on luminosity, computed using a standard function based on RGB input values for each pixel as:
The SOBEL function is then called with this luminance array as input. For this example, the Valve_original_(1).PNG image from the Wikipedia page is used as the source. As before, it’s loaded and displayed as the first step:
The image can be flattened in a couple lines of code without creating several intermediary variables with:
Then we can run the edge detection algorithm as before and show the results:
PV-WAVE also includes the ROBERTS function as another option for edge detection. To continue my quest of building wrapper functions, I wrote PHOTO_EDGE to wrap both SOBEL and ROBERTS with the ability to work with both grayscale images and colored images as input. In all cases, a grayscale image containing highlighted edges is the output. With this helper function in hand, the above steps can be condensed to:
Edge detection can produce some interesting images, and mathematically, finding two dimensional gradients can be of use, but these beg the question of the real value to edge detection. Turns out finding areas of steep color gradients helps to create a mask array when wanting to sharpen an image.
Sharpening a digital image is a technique to bring out apparent detail in a “soft” photograph, where the basic idea is to enhance contrast for edges in the image. The basic outline of steps is to identify edges, typically by blurring the image then subtracting off the base image to create an “unsharp mask” image. Then scale this mask by some amount after applying a threshold and add it to the base image. This is a deep subject that cannot be treated with justice in this short form article, so I’ll necessarily gloss over many details.
Most tutorials and discussions center on generating the mask. Typically, a Gaussian blur is applied to the original image. Care must be taken working across color channels, so often luminance is used, or the RGB image is converted to HSV and work is done with the value channel. The SMOOTH function could be used for this method using PV-WAVE, or, if a true Gaussian kernel is desired, use CONVOL. However, since we have the powerful SOBEL function, this step can be bypassed.
What to do with the mask is something of a trade secret and an art. Depending on the sources, there are various techniques to scale specific pixels by some amount, dictated by the mask threshold value. The math in PV-WAVE can get tricky because BYTE values wrap around, so addition and multiplication have to be done after converting to integers. Since values greater than 255 or less than 0 aren’t allowed in the final version, scaling is critical at each step, or else you lose brightness and detail in the result, which is the opposite of the goal.
A simpler alternative, yet still effective, technique is to generate a high contrast version of the source image, then wherever the mask is greater than the threshold, use pixels from the high contrast image. To generate a high contrast image, I wrote PHOTO_CONTRAST based on a formula discovered online. From there, this technique is nearly trivial in PV-WAVE using the helper functions. There are a few more steps required for color images, but the general idea can be shown with the grayscale bikes image used above:
These steps result in the image shown below.
The function PHOTO_SHARPEN follows this logic, accepting values for amount and threshold, and includes support for color images as well. The results of this example will not map to professional image editing tools for a few reasons. First, edges from SOBEL have a different character than a Gaussian blur. Second, the value for “amount” here maps to the contrast algorithm rather than a vague percentage typical of those tools. And finally, there’s no behind-the-scenes magic here, with all the math verifiable in the code, so any trade secret type adjustments aren’t included.
This post concludes the series of my investigation of the new PHOTO functions included in PV-WAVE 2016.1. Part 1 explored the basics of displaying and resizing techniques, while Part 2 dove into some quantitative aspects. In Part 3, edge detection and sharpening are briefly discussed. Throughout this series, PV-WAVE’s standard library of functions have been used wherever possible, along with the power and flexibility of the array-based language. Several helper functions have been produced to help others exploring the richness of the PHOTO routines.
If you have any questions or requests for additional topics, please let us know in the comments. If you want to try these features out for yourself, contact us for an evaluation.