Pixel Data Types
We talked before about different data types in particular this uint8 or unsigned integer eight bit quantity. Let’s just have a little bit of a look at numeric values and I am going to do a really trivial example here.
I am going to create a variable called A which is equal to 100, variable called B which is equal to 200. You can do some really simple arithmetic and you can work out what the answers are going to be.
A plus B is 300. A minus B is -100. A divided by B is equal to 0.5, all exactly as we would expect. These numeric quantities within MATLAB are double precision numbers.
Let’s remove these variables from the workspace. They are all gone, and I am going to create variables now of uint8 type. So this variable A; we can see it is of the uint8 type and it has got a value of 100 just like before, but the type is different. We create the variable B, it has got a value of 200 and it is of the uint8 type. So now let’s do some arithmetic. Lets add A and B together. It has got a value of 255.
Subtract them and we see that the value is 0. Now let’s do a division. So these answers are a little bit surprising and the reason for this is that the unsigned integer eight bit numbers have got a very limited dynamic range.
So the results that we see here on our screen make perfect sense. I add the two numbers together and although the results should be 300, the maximum possible value that it can have as a uint8 type is 255 and that is what MATLAB has set it to.
Similarly, the smallest possible value it can have is 0, so although the correct result was -100, MATLAB has set the answer to 0. Well we call it clipping. So the results have been clipped. They can not go below 0 and they cannot go above 255.
The division result is also a little bit surprising. What has happened here is that A divided by B, the result is 0.5 and MATLAB has rounded that up to the value of 1. In many programming languages, if I divide a small integer by a larger integer the result will be 0. MATLAB has done a rounding operation and given a value of 1.
In terms of the actual computer hardware, the uint8 type is stored in a single byte of the computer’s memory. The smallest value is when all of the eight bits have got a value of 0, equivalent to a decimal value of 10. And the largest value is when all of the bits have got a value of 1, which is the equivalent to the decimal value of 255.
For an arbitrary n bit integer, the maximum value is 2^n-1. MATLAB supports a number of additional unsigned integer types; it supports uint16 which has got 16 bits, and uint32 which has got 32 bits, and these are able to represent numbers with much larger maximum values.
Now we have displayed this image before and as previously discussed, the pixels in this image are of a type unsigned integer. Click around and here we have the integer values of the pixels. The brightest one will be 255; the smallest one will be 0.
Now there is another way that we can represent the pixels in an image and I am going to create a new variable, I am going to call it imd, image double. And what we are going to do is read the image again, but into a different variable this time. We are going to pass the option double, and what this will do is convert all of the pixels into double precision numbers, instead of unsigned integer numbers.
Now let’s display this new image and I will create a new figure. And I will display my new image in this new figure. Now we see it looks exactly the same as the other one, but if I click around on the pixel values here we will see that they are floating point numbers now. And in this convention, 0 represents black and 1 represents white. So this is a reasonably bright number in the dark of this door way, the pixel values are only 0.03.
So this is another way of representing an image, each pixel, each element in this matrix is a double precision number. So we have a lot of precision with each of the pixel values in this representation of the image. The disadvantage is that such an image requires much, much, more memory.
The double precision numbers occupy eight bytes of storage; the uint8 occupies only one byte of storage, so this image on the right-hand side occupies eight times more memory than the image on the left. And we can see that quite easily, using the about function; tell me about the variable im. We can see it occupies 1.1 megabytes of memory in the computer. Let’s have a look at the other image and you can see that it occupies 8.7 megabytes.
But sometimes there are advantages in having the pixels represented by double precision numbers.
In terms of the actual computer hardware, the double precision number is organised in memory like this. It occupies 8 bytes of computer memory or a total of 64 bits.
And the first bit is the sign bit and that has got a 0 for a positive number, 1 for a negative number. That is followed by 11 exponent bits and 52 fraction bits and I’ll talk about those in a moment.
This particular layout is defined by the IEEE 754 standard, so floating point numbers on almost all computers today are represented in this particular fashion.
Now if we denote the fraction part by F and the exponent part by E, the actual value that is represented is Fx2^E. So the E is the exponent part and F is what we call the fraction part—sometimes also called the mantissa.
Now, the exponent has got a range of -1022 to 1021. If we do the conversion from binary into decimal, we see that this gives us a range of this multiplication factor which goes from 10^-308, to almost 10^-307. So this kind of number has got an enormous dynamic range.
Now the exponent is represented by 11 bits that has got two 2048 possible different values.
Four of those values are reserved, they are special and one of them is used to represent an infinite number, another is used to represent a negative infinite number. One is used to represent what is called not a number, so that is something that is numerically invalid. And another value is used to represent the precise value of zero.
The fraction is organised so that it is always in the range from 0.5 to 1. So the most significant bit in a fraction represents 2^-2 or 1/4.
The least significant bit has got an equivalent value of about 10^-16, so that means that this double precision representation has got effectively 16 significant digits. So a lot of numeric precision and a very, very, large dynamic range; that is the advantage of this double precision floating point representation of numbers within a computer.
Now there’s some limits to the precision and if we consider a number like this, 0.1, that is a tenth in decimal, it doesn’t have a precise representation as a binary fraction. It is a repeating fraction in binary, so we can’t represent it precisely.
An example of this is, for instance, I want to look at the decimal value 90.1. And I tell MATLAB to display this in its long format, so that is showing a lot of decimal digits and what we see is that 90.1 is actually represented internally by 90.09999... and the last digit is shown as 4. So it is wrong in the seventeenth significant digit, but as we just talked about a moment ago, double precision guarantees you around sixteen significant figures, so this is to be expected.
So, to recap the numeric ranges of these different representations that we use for pixels.
We can represent pixels as either unsigned eight bit integers which would occupy a single byte of computer memory each, and they have a numeric range of 0 and 255. Or we can represent them as double precision floating point numbers—they occupy eight bytes of computer storage each. And we typically map pixel intensities into the range 0 to 1, 0 being dark, 1 being bright.
There are also some difficulties when we use integer arithmetic. One of the problems is saturation, so if the result of our arithmetic operation exceeds the maximum possible value for that representation, it is clipped or limited to the maximum possible value for the representation.
So in this case, the result of the arithmetic should be 300. But it is clipped to 255, which is the maximum possible value of a uint8 type. Another problem is quantisation, so if we look at these two divisions—49 divided by 100 gives a result of 0—it is effectively rounded down to 0, whereas 50 divided by 100 is rounded up to 1.
MATLAB normally deals with matrices of floating point numbers. An image is typically represented by an array of small integer values, pixel value or greyscale values, which have a limited dynamic range and special rules for arithmetic.
High school mathematics
This content assumes an understanding of high school-level mathematics, e.g. trigonometry, algebra, calculus, physics (optics) and some knowledge/experience of programming (any language).