# How to perform an image normalization after for example adding a constant to it?

I had some magical tasks to do on my lessons. I had to write an app which performs some operations: basic (adding, subtracting, multiplying a constant and a second image), geometric, filtering, histograms etc. There were ~50 tasks.

I've made them all, even with Prewitt filters and other down/upbandwith and gradient things.

Now, my teacher sent me an info, that I did my normalization wrong in basic operations, for example in adding a constant.

As he wrote in his document there are 3 ways of handling the pixel overflow >255 or <0:

• cutting the overflow - which means a simple clamp
• scaling images before processing
• normalization of the final image - scaling and moving the image function with a specified range that the final image fills the given range.

I've left first two and got the third one, because he said it's the correct way. I've read somewhere that I still need to combine it with a clamp (0,255).

Now, I got his formula:

$$\large f_{norm}=Z_{rep}[(f-f_{min})\div(f_{max}-f_{min})]$$

And I thought it's easy to understand, but I have some problems with it.

As I understand:

• $f_{norm}$ - final pixel (normalized)
• $f$ - given pixel
• $f_{min}$ - minimum
• $f_{max}$ - extremum
• $Z_{rep}$ - clamp 0-255?

I have the following questions:

1. Are $f_{min}$ and $f_{max}$ taken from the given image, or from the image after the operation (for ex. adding a constant)? I know what extremes are, but I can't figure it out which two should I use in this formula.

2. Is $Z_{rep}$ really a clamp method as I thought which just cuts to 0-255? I've read somewhere that even with normalization there can be an overflow.

3. Does normalization work like: no matter what value you'll add or subtract from the image, I'll make for you a beautiful 0-255 values? Or am I wrong?

I've even made some calculations, made some images in photoshop with extremes 30,200 and even 0,255, put them into the formula and got some results, but I still don't know if it's done well.

I'm sitting here for 3 weeks, really and can't get which extremes should I use and if I should clamp or not.

I believe that Zrep is the output range. In 8-bit it would be 255. Let's say fmin was 25, fmax was 132. You want to scale that range to 0-255. The part of the equation in brackets: [(f-fmin) / (fmax-fmin)] will give you a percentage - a value between 0 and 1. If f is 25, you'd get 0%. If f is 89, you'd get ~60%. If it's 132, you'd get 100%

So once you have the percentage, you then need to multiply that by the output range, Zrep. That converts from a percentage to whatever range you need it in, and ensures that the largest value in the input is mapped to the largest output value, and the smallest input is mapped to the smallest output value.

Coming from an audio perspective dealing with dynamics processing (which is also digital signal processing of course!), here's the answers I would believe to be true.

1. fmin and fmax are the global min and max values of the image before it's normalized (but after the adding a constant or whatever other processing has happened). Since in an RGB image you have three channels of data, the max would be the maximum value seen in any channel, and min would be the minimum value seen in any channel.
2. I have no idea what Zrep is unfortunately. If you are truly normalizing the data, the only cause of overflow I can think of is due to rounding issues which can definitely happen. Yes, I would clamp, but also I would do my operations in a larger non integer intermediary data format (such as floating point, or maybe fixed point) before clamping and then converting to the final data format (eg. from float to uint8).
3. Normalization will modify the signal such that it will span the entire range possible (0-255) however the result may not be "beautiful". Basically, you might find banding in the results where instead of a smooth gradient from one color to the next you might have very distinct bands of color steps. You will see this being especially true if you don't use a larger non integer intermediary format like i mentioned in the last point, but can come up even if you do.

By the way, there are other ways to deal with this situation of going out of range. In the audio world there is such thing as limiters and compressors which deal with adjusting dynamics in a non linear way.

A simplistic look at how they would apply to graphics would be that maybe you leave the color values that are between 32 and 223 alone so that most colors are unaffected, but then, colors less than 32 and colors greater than 223 get a non linear transformation to "squish" them into the valid range of values.

If you are dealing with a high dynamic range image (HDR), you may have very large values in the thousands or ten thousands or higher. As you can imagine, trying to squish all of that into the 223-255 range with a non linear transformation might be a little rediculous, so you may just squish in a certain amount (maybe make it so 512 input = 255 output in your remapping function), and then clamp the result.

In case you are interested, games sometimes deal with this situation as you move from an indoors darker area to an outdoors brighter area. Some games will make it so that the brighter outside is far too bright at first, and then over time, their colors will scale more towards being "normal". This is to mimic how it takes time for your eyes to adjust to bright light after being in the dark. The reverse effect is also valid of course, of going from a bright area to a dark one, and having things be too dark until your eyes adjust.

Lastly, in case you are interested, there is a lot of work being done in games and graphics on mapping HDR color values to LDR values, and applying color grading and tone mapping. The work is focused not only on quality, but on speed, so there are different trade offs for real time processing vs off line processing.

• If you are truly normalizing the data, the only cause of overflow I can think of is due to rounding issues which can definitely happen. Yeah, but If I put value 400 to this formula (which can happen when I add a high number to the image) I get a normalized value of 555 which is not in the expected range :D There's something wrong with these extremes O.o Commented Jan 23, 2017 at 18:52
• No problem. Let's say that after adding 400 to each channel of each pixel, the minimum value seen in any channel is 400 and the maximum is 555. You would then run through every channel value of every pixel and apply the normalization formula. Output = (Input - 400) / (555-400) If you plug an input value of 400 into that equation you get an output of 0. If you plug an input value of 555 into that equation you get an output value of 1. For values between 400 and 555 you'll get values between 0 and 1. This result should then be multiplied by 255 to make it [0,255]. Commented Jan 23, 2017 at 18:56
• Now you've said something complately opposite to your answer, cause you've said that I should use the old image's extremes and now, that new ones :D But according to your explanation - it works, I just tested it (swapped so the app takes extremes from the new image). Strange results, cause the image looks a little bit brighter, even if I added 1000 to it. Will test some and give some time for the question cause I really need to be sure that this is the correct way. There's no talking with my stubborn teacher, he can't even explain what he meant. Commented Jan 23, 2017 at 19:04
• I think the confusion is coming from the addition and the normalization being seen as a single operation. it's really two operations. 1) Add 400 to each channel in a pixel. You will need the pixel values to be in some storage type which can handle values outside of the 0-255 range. Floats could be a good quick and easy choice. Doubles if you need precision. Fixed point if you need speed. 2) Now do the normalization step. Find min and max values, normalize all values (all channels of all pixels) muliply by 255. 3) A final third pass does a clamp and conversion from float back to uint8. Commented Jan 23, 2017 at 19:13
• That's what I'm doing right now. I read the first image, store it in the array A, now, I add a value 100 to each pixel, now I copy the array to array B and save array A to see results (before normalization). I find extremes on array B, normalize array B, clamp it and save it as the "after" image. Here's a link to all three images: drive.google.com/open?id=0B9DujHxzmzAySl9SU2VKNjhsNDA I have them in PCX format cause I chose it as a working for lessons (it's the least complicated one :D). You'll need a photoshop or pcx viewer to view it, but you probably know that ;D Commented Jan 23, 2017 at 19:29