# Image levels: how to alter 'exposure' of dark and light areas?

I was working with some less than ideal photographs, and wanted to adjust them before continuing. I wanted to raise the levels of the very dark areas and lower the levels of the very light areas. I couldn't find a function in Mathematica 8 that would allow me to do this. As a quick work-round, I wrote a function that quickly adjusted the levels. It looks terrible in this simplified example (I had more levels going, for one thing), but you get the idea (I hope!).

image1 = Image[
ReliefPlot[
Table[i - 3 Sin[i^2 + j^2],
{i, -4, 4, .03},
{j, -4, 4, .03}]]];
tweakC = Compile[{pixel}, Module[{ p = pixel},
m = Mean[p];
Which[
m < 0.3, p = pixel * 1.5,
m > 0.85, p = pixel * 0.8 ,
m > 0, p = p ]]];
ImageAssemble[{image1, image2}]


:

What's the best way to do this? I don't think ImageAdjust or ImageClip do what I want.

Two things.

First, a minor point: if you rewrite your compiled function as

tweakC = Compile[{{pixel, _Real, 1}},
Module[{m},
m = Mean[pixel];
Which[
m <= 0.3, pixel*1.5,
m >= 0.85, pixel*0.8,
True, pixel]
]
]


then the ImageApply bit is 20 times faster (due to not having to use external calls). It's also a bit cleaner.

If you have v8 and a C compiler, you can speed it up by another factor of 2 by using CompilationTarget->"C".

Second, and more important, is that your tone curve looks like this:

the jumps at .35 and .8 lead to harsh transitions. So I thought I'd use a smoother curve which you can interactively manipulate (horrible code, but seems to do the job):

image1 = Image[
ReliefPlot[
Table[i - 3 Sin[i^2 + j^2], {i, -4, 4, .03}, {j, -4, 4, .03}]]];
DynamicModule[
{pts = {{0, 0}, {.25, .25}, {.5, .5}, {.75, .75}, {1, 1}}}, Dynamic[];
LocatorPane[
Dynamic[pts],
Dynamic[
curve = InterpolatingPolynomial[pts, x];
ImageApply[Function[{x}, Evaluate@curve], image1,
Interleaving -> False]];
Dynamic[
Plot[curve, {x, 0, 3}, PlotRange -> {{0, 1}, {0, 1}}]]
],
LocatorAutoCreate -> True
]
]
GraphicsGrid[
{{image1, Dynamic[image2]}}
]


it looks like this:

The idea is, you define a curve by moving the locators, and the image on the right bottom reflects that transformation. The whole thing is interactive. You may add more locators by alt-clicking on windows and linux, cmd-clicking on OS X.

Note that I have little understanding of Dynamic etc, so this is probably badly written in terms of dynamic interactivity.

• snap! I used Erf though. – Verbeia Jan 18 '12 at 21:29
• Basically, this works similarly to the curves tool in any image editor, see eg cambridgeincolour.com/tutorials/photoshop-curves.htm – acl Jan 18 '12 at 22:23
• This is amazingly cool. It's cute how you implemented curves functionality using Locator and Dynamic. – Mike Bailey Jan 19 '12 at 3:20
• @Mike much cheaper than photoshop! (and I lifted much of the code wholesale from the docs) – acl Jan 19 '12 at 3:24
• Neat - this is basically "Image>> Adjustments>>Curves" menu in Photoshop. The only suggestion is to add LocatorAutoCreate -> True option to LocatorPane[...] function so by pressing ALT+CLICK you can add or remove locators. For precision-crafting of the curve. – Vitaliy Kaurov Jan 19 '12 at 14:59

The fact that you are using a discrete function can't be helping. It will inherently create 'edges' in the graphic, even with more levels.

I'm also a bit confused by the fact that the output of your function is non-monotonic.

Plot[Which[m < 0.3, m*1.5, m > 0.85, m*0.8, m > 0, m], {m, 0, 1}]


EDIT In fact my experimentation suggests that any function with kinks, let alone discontinuities such as the one you have used, will have problems with colour shifting.

I would suggest using a function that looks more like this, if you want to make dark areas darker and light areas lighter. You can tweak the parameter 5 to taste.

Plot[0.5 Erf[5. (m - 0.5)] + 0.5, {m, 0, 1}]


tweakL = Compile[{pixel},
Module[{p = pixel, m}, m = Mean[p];
p = pixel *0.5 Erf[5. (m - 0.5)] + 0.5]];

ImageApply[tweakL, image1]]; ImageAssemble[{image1, image2}]


To reduce the contrast, you need to define a similar function that is smooth and has the desired slopes, much as acl has done

• Thanks. I'll play with this and see if I can make the dark areas lighter and the light areas darker. But it looks like I didn't miss an obvious 'ImageExposure' function, which is something. – cormullion Jan 18 '12 at 22:09

I liked @acl implementation of "curves". Here is a note on simple and effective image improvement. These is built in functionality in ImageAdjust[] :

Manipulate[
Row[{image, ImageAdjust[image, {x, y, z}]}], {{x, 0, "contrast"}, 0,
1}, {{y, 0, "brightness"}, 0, 1}, {{z, 1, "gamma correction"}, .1,
2}]


Also this interactive interface is built in in M. Right click on the image, in context menu choose "Adjust Image" - and you can do it quickly via popped up interface, good for workflow.

• I've got a couple more hours before I can upvote again, but +1! – Verbeia Jan 18 '12 at 22:12
• @vitaliy that looks promising. It might do the trick, but I'll have to se if it modifies the mid-range of the photos... – cormullion Jan 18 '12 at 22:14
• @vitaliy But isn't this changing the whole image? Not just the badly exposed areas? – cormullion Jan 18 '12 at 22:37
• Yes but mostly in such a way that whole image improves. You cannot sharply distinguish between "bad" and "good" areas, in a typical meaningful image even "bad" areas are correlated with their "better" neighborhood, so to have no abrupt lines you have to modify the neighborhood too a bit. For more advanced techniques you can see M8 function Inpaint[...]. Tone curve is also something to look at - bu this demonstration does not use Image Processing really, just shows general algorithm with pixels. I am sure it can be done better in M8: demonstrations.wolfram.com/ToneCurve – Vitaliy Kaurov Jan 18 '12 at 22:57