In the art of photography, light can be considered as either your friend or enemy. A friend as it serves as the illumination of your test subjects which brings your images to life while sadly an enemy as it’s high dependence on light can be a continuous burden to you. Based from my experiences thought, it is far better to have a highly intense light source as the settings in a camera can easily be tweaked to reduce the amount of light entering your optical system. On the other side, there are limited solutions when there is a lack of light. Sometimes even making use of a ‘flash’ still gives us dark images.

So how are we to solve this? Through post-processing of course!

This activity concentrates on the technique in manipulating the histogram in order to adjust the brightness of your image. Take for example the image below:

Image of my brother walking towards a moving car on one fine foggy evening in California.

In order to manipulate the pixels of this image, we first have to convert the image in grayscale as not doing so will further complicate the process. As learned from the previous techniques, the conversion to grayscale as well as displaying the histogram is done by the code snippet below:

Which gives rise to the images below:

It is important to take note here that the grayscale plot of an image is actually called as the graylevel probability distribution function (PDF) once the plot is normalized to the total number of pixels [1]. One can clearly observe that the image is indeed dark as most of the pixels are located on the left portion to the graph which is close to 0 (signifying the darkest pixel).

A more interesting quantity we would be using for this activity is called the cumulative distribution function or CDF. From Wikipedia, a CDF is the area of the the PDF after some time. Simply put, the CDF shows the evolution of the total area of your PDF. This can be taken in Scilab through the use of the cumsum() function as already shown in the code snippet above. With our PDF illustrated above, its corresponding CDF is as seen in the image beside this paragraph.

The manipulation of the histogram is highly dependent on the shape of our CDF. Hence, we would investigate on the effect of the shape of the CDF to our histogram and eventually the output image.

The new CDFs we hope to implement on our image are as illustrated below:

These CDFs were produced through the use of the code snippet below.

Both the linear and quadratic cases were done through simply defining the x2 to be a list starting from 1 to 255. On the other hand, the Gaussian CDF was produced by first plotting a Gaussian curve followed by the cumsum() function. It is also important that since our original CDF is normalized (ranging only from 0 to 1), the last line of the code normalizes our ideal CDFs.

Now having our ideal CDF and the original CDF, the implementation of the ideal CDF is next. This is done through a process called “backprojection”.  The steps through this method is listed as follows:

Illustrative step for the backprojection method [1]

  1. For each x-values of the original  CDF (take note that this ranges from 1 to 255 signifying your pixel intensities),
  2. It’s corresponding y-value was noted.
  3. The y-value noted for a specific x-value of the original CDF was then traced to the ideal CDF. We try to find the nearest y-value present in our original CDF
  4. then we take note of its corresponding x-value in our ideal CDF.
The backprojection code that I came up with was composed of for loops as I was unable to fully understand the interp() function which was suggested by Mam Jing Soriano. Thank you to Mr. Mark Tarcelo for suggesting a solution for my problems during the coding process. The snippet of the  code is as displayed below:
The main problem that I encountered was the fact that the y-values for the original and ideal CDFs, even though both have the same range, are not exactly similar. This is troublesome when we try to trace back our original CDFs y-value. Simply making use of the find() function will most of the time won’t trace it back properly. As a solution, instead of using the find( y2==CDF(i) line, we split our y-values at the point of our CDF(i) as seen in the code above. Through splitting, we have a faint idea on the location of our traced y-value.   The next step in finally choosing which point is closest to our original CDFs y-value. This is where the if and else functions goes in. This instructs the program that if the value for the edge A is smaller than that of the value of edge B, we make use of the value for the edge A and vice versa.
However, this will not yet give our desired image. We have to implement this new backprojected pixels to the image This was done by replacing our pixels (x-value of the new CDF) in our image matrix with its new intensity (y-value of the new CDF). The code below does the trick:
The output images are as follows:
It is clear that there are evident changes to our output images. The images appear to be brighter as compared to our original grayscale image. As proof, the tree branch located at the upper right corner of the image is now visible whereas it was not at the original image. It was also evident that there are different effects for the different shapes of our CDFs. This is of course expected as we are varying the CDF which is related to our histogram which is also related to the intensities and pixels of our image.
To check whether or not we have properly backprojected the pixels, the histograms and CDFs of the output images was taken (in theory, the CDFs should resemble our ideal CDFs):
It does resemble our ideal CDFs except that for the earlier portions of each CDF, it is noticeable that the curves are not smooth. In fact, taking a look at the histogram gives us an idea as to why we ended up with these kinds of CDF.
Some values for the low-valued pixels actually don’t have corresponding pixel value. The histogram of a linear CDF should show a properly distributed) plot. However, it was observed that since some low-valued pixels don’t have any values in it… it all added up to the other pixel values hence giving us high number of pixels. This problem is brought by our backproject method. Recall that we  just chose the value of y that gave out the smallest difference from our ideal CDF plot to the original CDF plot. A solution would be to figure out a way in determining the x-values for the exactly traced y-values of our ideal CDF.
In Photoshop however (and also other image editing softwares), this process is made easy for users. The CDF of an image can be easily observed and manipulated by the use of the curves option in Photoshop (Images — > Adjustments –> Curves). The manipulation is done by simply changing the CDF through clicking and dragging your cursor. Images below show some snapshots of the process:
Far easier right?
Also, compared to our output images from my own cold.. the image still seems to be smooth. The transition from one shade to the other of gray is better as compared to our output images wherein at times it looks too pixelated already. This could have been brought by the problem discussed earlier.
On a personal note, this activity made me think for a long time. I was trying to figure out how to properly trace back the y-values to m ideal plot. I also tried to studied the interp() function and make use of it however I never got around into having a proper output image from it. Overall, I think I deserve a grade of 9/10 as I was still able to fully accomplish the requirements. However, the limitations of my code brought by the backprojection script gave errors to my output images.
Oh well… so much for wanting to retrieve my extra grades from my earlier activities.. 😦

References:

[1] Soriano, Jing. Activity 5 – Enhancement by Histrogram Manipulation manual. Applied Physics 186. National Institute of Physics, UPD.