Home ~ Back to cs6650 ~ Back to Code Pit ~ Class Web Site
Create an RGB image from the spectral image: L(x,y,lambda) = (y/n_y) delta(lambda - (420(x/n_x) + 380)) use n_x = n_y = 512.
I have already worked on this problem in a previous assignment. That was basically an assignment of gamut mapping. I wanted to try two new things this year. First, I wanted to make it, so that I could feed an arbitrary spectral image into some code that would do tone mapping and then spit out an RGB image. Second, I wanted to play around with the tone mapping parameters. I developed a technique for doing the first, but I have not yet implemented it yet. I had problems deciding how I wanted to match the spectral bins with the XYZ response data. The second was interesting, but I noticed that I still had to play the gamut mapping game. Talking with Pete, this spectral image is just a bad one to try tone mapping on. I guess I should try it on more sophisticated images. That will be project for the future.
So this is a copy of an email that I sent concerning how to take spectral data that is collected in bins and then tone mapping it.
The equations are :X = 683 * integral( xbar(lambda)*L(lambda) dlambda) Y = " ybar(lambda) " Z = " zbar(lambda) "
Sense these three are very similar except for the bar equation I will only discuss the X equation.
So what do we have here. X is an equation in terms of lambda. xbar is the response curve based on the frequency of the light. Some light has more of a response than others. In XYZ, Y is the luminance and encodes how "bright" a particular frequency is. I believe X and Z encode the hue, but I'm not entirely sure. In practice you use Y for tone mapping, and X and Z to retain hue (i.e. you do operations on Y, but not XZ. You don't want to change the hue when you do tone mapping, just the intensity of it).
L is the luminance. This is what you get if you rendered your scene using spectral color information. For the color assignment L(x,y,lambda) = y/yres*delta(lambda - (420 * (x/xres) + 380)). You can get X in terms of this function, but I'll save that for later. The point is L is some luminance that you have recorded.
So, X = integral( xbar(lambda) * L(lambda) dlambda). So how on earth do we compute this integral. Well, that all depend on L. If we have L as in our assignment we have a special thing called a dirac delta function to deal with.
Dirac Delta:
This function is a special one in terms
of signals (the impulse signal). In fact this isn't really a signal,
but it treated this way in the literature and more specially the
math. :)
delta(0) = 0 if t != 0 integral(-infinity, infinity, delta(t) dt) = 1 integral(a, b, delta(t-c)f(t) dt) = f(t) if c in [a,b]. I think of this as the delta function being nonzero at c, and that f(t) acts like a scaling on the integral which would be one otherwise.
For a more in depth look at this, check out Glassner vol. 1 pg. 148-152.
Given this we can do some substitution.
X = 683 * integral(xbar(lambda)*y/yres*delta(lambda - (420*(x/xres) + 380)) dlambda X = 683*y/yres * integral(xbar(lambda)*delta(...) dlambda) using the direct delta formulation: X = 683*y/yres * xbar(420*(x/xres) + 380)
You can now evaluate xbar using what ever kind of interpolation method you want from the xbar data (use linear, it is easy to implement, and I doubt you will be able to see the difference anyway).
This is similar for Y and Z. So now you can compute XYZ without having to compute L.
OK, obviously you can't do this with a real image. All you have is L in discrete bins. I like to think of my ray tracer outputting integral(a,b,L(lambda) dlambda) where a and b are the min and max frequencies of that particular bin. Taking this into consideration you can do some calculus mumbo jumbo to get something implementable.
X equals the integral over xbar() * L() dlambda. Now we have a function for integral of L() dlambda. This is what our ray tracer produced. The problem is now how do we evaluate this in respect to being multiplied by xbar(). We remember that integrals can be broken up into pieces based on the limits.
integral(a,c, f(t) dt) = integral(a,b, f(t) dt) + integral(b,c, f(t) dt) where a <= b <= c.
So the idea here is to break up the integral used to compute X into smaller integrals with the same limits as our L from our ray tracer. This way L() becomes constant with respect to lambda and we can pull it out of the integral. :)
L() with discrete bins:
__
__ | |
| | __ | |
| |__| | | |
___| |__| |___
a b c d e f g h
So, now we have X =
L[0] * integral(a,b, xbar(lambda) dlambda) +
L[1] * integral(b,c, xbar(lambda) dlambda) +
L[2] * integral(c,d, xbar(lambda) dlambda) +
L[3] * integral(d,e, xbar(lambda) dlambda) +
L[4] * integral(e,f, xbar(lambda) dlambda) +
L[5] * integral(f,g, xbar(lambda) dlambda) +
L[6] * integral(g,h, xbar(lambda) dlambda)
The only thing left is to compute the integral of xbar. This can be done however you want. You can get the discrete points of the curve in terms of lambda from the web site and then compute the integral numerically. You just must make sure that the limits match those of your spectral bins, otherwise this trick won't work (L will then not be constant, and you can't pull it out).
Here we have to take our data and move it from format to format. We must convert our spectral colors to XYZ in order to do tone mapping. From here we can supply a single variable, k, to affect the degree of tonemapping. From here we can map XYZ into RGB with a matrix multiply. Once you get RGB, you must do some gamut mapping to take into consideration the negative RGB values.
Spectral -> XYZ via response curves.
Compute tone mapping component by component:
X0 = k * X / (1 + k * Y);
Y0 = k * Y / (1 + k * Y);
Z0 = k * Y / (1 + k * Y);
Convert XYZ values to RGB via this matrix:
double XYZ2RGB_MATRIX[9] = { 2.5623, -1.0215, 0.0752,
-1.1661, 1.9778, -0.2562,
-0.3962, 0.0437, 1.1810};
Essentially what tonemapping is doing is compressing the really high values down into a smaller range. The smaller the value of k, the more compress the values became. All images are clamped to 0 and 1, so that I didn't have to mess with the gamut mapping, but still see the effects of the tone mapping.
| k | jpg | png |
| 100 | ![]() |
![]() |
| 10 | ![]() |
![]() |
| 1 | ![]() |
![]() |
| 0.1 | ![]() |
![]() |
| 0.01 | ![]() |
![]() |
| 0.005 | ![]() |
![]() |
| 0.001 | ![]() |
![]() |