BookNotesSlidesP-sets Syllabusdraft · v0.2.49.1
◀▲⌂▶
💬Comments welcome. To leave a note, select any text and click the note / highlight button that pops up — or open the panel with the tab at the top-right (‹). Notes are visible only inside our private review group.

45.7 Problem Set 4 — High dynamic range⧉

Problem Set 4: HDR and Tonemapping. Due Wednesday October 14, 9:00pm.

45.7.1 Summary⧉

Important.

To merge images to HDR, we will work with images encoded linearly. Most of the digital images you may encounter are encoded with a gamma of $2.2$ (in particular those of the handout). Therefore, in this problem set, you will have to undo the gamma $2.2$ from your inputs before merging them to HDR. You will also have to apply a gamma $2.2$ to the images you output in the linear domain (e.g. after tone-mapping). You can use the gamma_code function from problem set 1 that can be found in basicImageManipulation.cpp to do this. We assume all the inputs and outputs of the functions in this problem set are in the linear space (i.e. you will need to undo the gamma before you call the function and you will need to apply gamma after you tone map).

In the whole assignment, we will only work with images of static subjects and the camera hasn't moved between shots in the same sequence.

45.7.2 HDR merging⧉

Consult the course slides for the overall HDR merging approach. We will first compute weights for each pixel and each channel to eliminate values that are too high or too low. We will then compute the scale factor between the values of two images, to determine their relative exposures.

Finally, we will merge a sequence of images using a weighted combination of the individual images leveraging the weights and scale factors. The slide “Assembling HDR” in lecture 6 might be helpful here.

Problem set 4, ante2-w1
Problem set 4, ante2-w1
Problem set 4, ante2-w2
Problem set 4, ante2-w2

Figure 1. The weights computed from the ante2 image sequence using the default parameters provided in computeWeight. You can see this output after running testComputeFactor.

With these two methods, we know which pixels are usable in each image and what the relative exposures between the images is. With this information we are ready to merge a sequence of images to a single HDR image.

For the rest of the problem, you can assume that the first image in the sequence is the darkest, and that images are given in order of increasing exposure.

For each image in the given sequence, you need to compute its multiplicative scale factor ($k_i$ in the equation on the “Assembling HDR” slide from lecture 6). This requires computing the scale factor between adjacent images in the sequence using computeFactor, then chaining them together. For example, if you have the factor between image 3 and image 2 and image 2 and image 1, you can compute the factor between image 3 and image 1 by chaining the factors together:

$$ \frac{k_3}{k_1} = \frac{k_3}{k_2}\frac{k_2}{k_1} $$

You should should pick one of the images as the reference' image (e.g. the darkest image), and compute the factors with respect to it. This means that each image's $k_i$ value will be relative to your reference' image i.e. you will compute it only up to a global scale factor.

Pixels badly exposed in every image.

See slide “Special Cases” in lecture 6. A pixel that is too bright even in the darkest image is clipped in all of them. The darkest image's value, possibly exactly 1.0, is then the best you have: it is a lower bound on the true value, but no exposure does better. Likewise, a pixel that is too dark even in the brightest image is best measured by the brightest image, even at exactly 0.0. So compute each image's weights with computeWeight and these ranges, both ends included, with the images ordered from darkest to brightest:

$$ \begin{array}{lll} image & lower bound & upper bound \\ \hline darkest (first) & {\tt epsilonMini} & 1.0 \\ every other one & {\tt epsilonMini} & {\tt epsilonMaxi} \\ brightest (last) & 0.0 & {\tt epsilonMaxi} \\\end{array} $$

With two images there is no middle one: the first takes the first row and the second the last. The weights are per channel, so one pixel's red may come from a different exposure than its green, for example when red is clipped in one image and green is not. That is intended.

Use these same weights when you call computeFactor on each pair of adjacent images. The extra pixels they keep never count in a pair: a pixel at 1.0 in the darker image of a pair is also above epsilonMaxi in the brighter one, and a pixel at 0.0 in the brighter image is also below epsilonMini in the darker one, so the median is unchanged.

If a pixel still has weight 0 in every image, use the first image's value, so that you never divide by 0. With the ranges above this only happens when two consecutive exposures are more than epsilonMaxi/epsilonMini, about 500 times, apart.

As you merge pixels, do not forget to scale them with the factors computed above: computeFactor returns the median of im2/im1, so each image's values are brought to the reference exposure by dividing them by that image's $k_i$ relative to the reference (with the darkest image as reference, $k_1 = 1$ and the brighter images have $k_i > 1$).

To test your method, you can write out the output image scaled by different scaling values. testMakeHDR in a4_main.cpp illustrates the process for the design image sequence.

Your test images.

Put the images you make in Input/debug/, lowercase, at most $256\times256$ pixels. An exposure sequence is named hdr_<n>_<i>_<what>.png, with <n> from 1 to 3 for the sequence and <i> from 1 for its darkest exposure, in order of increasing exposure, like the provided sequences. Later in the set you also make tone_1_… png to tone_3_… png. make prepare copies Input/debug/ into your submission, so they travel with your code. Keep the code that makes them, and the code that runs your functions on them, in a4_main.cpp, and write the results into Output/ under the same name with _out added.

Problem set 4, scaledHDR design 0
Problem set 4, scaledHDR design 0
Problem set 4, scaledHDR design 2
Problem set 4, scaledHDR design 2
Problem set 4, scaledHDR design 4
Problem set 4, scaledHDR design 4
Problem set 4, scaledHDR design 6
Problem set 4, scaledHDR design 6
Problem set 4, scaledHDR design 8
Problem set 4, scaledHDR design 8
Problem set 4, scaledHDR design 10
Problem set 4, scaledHDR design 10

Figure 2. The HDR image created from the design image sequence clipped to different ranges. You can see this output after running testMakeHDR.

45.7.3 Tone mapping⧉

We have assembled our first HDR image, but this image still cannot be displayed properly on our low dynamic range screen. Let's implement tone mapping to remap the HDR information to a displayable range. Make sure you understand the slides “Contrast reduction in log domain” in order to combine the base luminance, detail and brightness scaling factor.

Your tone mapper will follow the method studied in class. The function is called toneMap, declared in hdr.h as Image toneMap(const Image &im, float targetBase=100, float detailAmp=3, bool useBila=false, float sigmaRange=0.1). It takes as input an HDR image, a target contrast for the base (lowpass) layer, an amplification factor for the detail, a Boolean to switch the lowpass/highpass separation between the bilateral filter or Gaussian blur, and the bilateral filter's range standard deviation, used when useBila is true.

As described in the lecture, our goal is to reduce the contrast from the HDR image (say, 1:10000) to what the display can show (say 1:100). Although gamma correction might seem like the first thing to consider, this results in washed out images, as shown on the gamma compression slide in lecture 7. The colors are actually okay (they're all there) but the high frequencies are washed out. We therefore want to work on the luminance, and increase the high frequencies. We also want to work in the log-domain since the human eye is sensitive to multiplicative contrast (recall lecture 1).

We want to modify only on the log-luminance:

We recommend you do this by first implementing the helper function float image_minnonzero(const Image &im), followed by Image log10Image(const Image &im), for which we have written function signatures and comments for you. Then, call log10Image with the luminance image as the argument.

Next, we want to extract the detail of the (log) luminance channel. We do this by blurring the log luminance, and subtracting this from the original log luminance. If you recall problem set 2, this gives you the details (high frequencies).

You are welcome to use your own implementation of filtering methods, but you can also use our versions in filtering.cpp.

At this point we have a detail image and a base image. Our goal is to reduce the contrast on the base image while also preserving (or even amplifying) the details. The slides “Contrast Reduction in log domain” might be helpful here.

Make sure to add an offset that ensures that the largest base value will be mapped to 1 once the image is put back into the linear domain

We have provided two new functions for you that you might find useful: float Image::min() const and float Image::max() const to get the minimum (respectively maximum) value of an image.

Enjoy your results and compare the bilateral version with the Gaussian one. Use the functions testToneMapping_ante2, testToneMapping_ante3, testToneMapping_design, testToneMapping_boston in a4_main.cpp to help test your tone mapping function. Feel free to try them on your own images!

Note: The bilateral filter on the design image sequence takes a very long time. It is not necessary to test bilateral filter tone mapping for this image sequence. If you do test it, you likely won't get exactly the same image as given in the slides because it was generated with different parameters.

Problem set 4, ante2-tonedHDRsimple-gauss
Problem set 4, ante2-tonedHDRsimple-gauss
Problem set 4, ante2-tonedHDRsimple-bilateral
Problem set 4, ante2-tonedHDRsimple-bilateral

Figure 3. Tone mapping of the ante2 image sequence using the parameters provided in testToneMapping_ante2

Problem set 4, boston-tonedHDRsimple-gauss
Problem set 4, boston-tonedHDRsimple-gauss
Problem set 4, boston-tonedHDRsimple-bilateral
Problem set 4, boston-tonedHDRsimple-bilateral

Figure 4. Tone mapping of the boston image sequence using the parameters provided in testToneMapping_boston

45.7.4 Extra credit (10% max)⧉

45.7.5 Submission⧉

Turn in your files to the online submission system and make sure all your files are in the asst directory under the root of the zip file. If your code compiles on the submission system, it is organized correctly. The submission system will run code in your main function, but we will not use this code for grading. The submission system should also show you the image your code writes to the ./Output directory

In the submission system, there will be a form in which you should answer the following questions: