imwrite, what it does with fractions and negative values

Hello,
This is an odd question, but how does imwrite handle fractions or negative inputs?
I am interested in reproducing this effect.
For example If I randomly generate some data and save it as an image and then read the saved image, I will see that matlab (somehow) assigned a uint8 value for each pixel. What modiciations do I need to perform to 'a' in order to get it to be equal to 'b' ?
Thank you for taking time to read this.
-James
clc
a = rand(2,10)-.5;
imwrite(a,'cake.jpg');
b=imread('cake.jpg');
a
b

 Accepted Answer

Try this illustrative demo:
a = rand(2,5) - 0.5 % Range is between -0.5 and +0.5
fileName = 'Delete_me.jpg';
imwrite(a, fileName);
b=imread(fileName)
delete(fileName); % Delete temporary file.
fileName = 'Delete_me.png';
imwrite(a, fileName);
b=imread(fileName)
delete(fileName); % Delete temporary file.
a2 = a*255
imwrite(a2, fileName);
% If image is floating point, it expects it to be in the range 0-1.
% Then it will scale 0-1 to 0-255 and clip negative to 0 and more than 1 to 255.
b=imread(fileName)
delete(fileName); % Delete temporary file.
a3 = uint8(a*255) % Make uint8. This may clip values <0 or >255 to 0 and 255.
imwrite(a3, fileName);
% If image is floating point, it expects it to be in the range 0-1.
% Then it will scale 0-1 to 0-255 and clip negative to 0 and more than 1 to 255.
b=imread(fileName) % Comes back unchanged as it should with uint8 values.
delete(fileName); % Delete temporary file.
fileName = 'Delete_me.mat';
save(fileName, 'a');
s = load(fileName);
b = s.a
delete(fileName); % Delete temporary file.
As you can see if it's a floating point number, imwrite will expect the number to be in the range 0-1 and it will multiply it by 255 and then cast to uint8. Numbers below 0 get clipped to 0, and numbers above 1 get clipped to 1 (so they'll be recalled as 255).
Moreover, run it a few times and you will probably see that the JPG recalled numbers are slightly different than the PNG numbers because of the lossy compression. So that's why you should use PNG, which is pretty much now a de facto standard format that most people use, and not JPG (which image analysts hate because it changes the values).
If you want to save the fractional part of the numbers then you should use MATLAB's proprietary format .MAT which will recall the floating point image with all its original, unchanged values.
I'm not sure exactly what you mean when you say "I am interested in reproducing this effect." What effect? What does "reproduce" mean to you.

5 Comments

Thank you very much for your response. Yes, this does seem to approximate the conversion to jpg.
The reason why I am interested in this is that I am generating a lot of images for training a neural network. I noticed (like you mentioned) that TIF and PNG are much better at capturing the full range of input values. However, I noticed that the JPG images actually train better for some classes which means that the transformation from a double matrix to UINT8 JPG images is somehow creating desirable training images. This matrix transformation is producing a desirable effect which I am attempting to understand.
When I say reproduce this effect, I mean that I am interested in how imwrite is trasforming the fractional data and negative values to a 0 to 255 image. It does not appear to be clipping <0 values (as I would have suspected it to). Below, a is a matrix and b is the saved and then imread('b.jpg') JPG image. Noticed how not all of the negative elements in 'a' are positive in 'b' (also check out (2,2) and (2,10), opposite sign in 'a' same value in 'b').
a =
-0.3219 -0.4433 -0.1642 -0.2911 0.1754 0.4121 0.2455 0.0619 0.0972 -0.3659
-0.1404 0.0219 -0.3243 0.4052 -0.0315 -0.3960 0.2363 -0.3158 -0.2001 -0.2874
>> b
b =
2×10 uint8 matrix
0 0 0 19 30 99 74 13 15 9
2 4 11 80 12 12 53 10 8 4
I am guessing that imwrite is doing something with the eigenvetors in addition to scaling the data which is what I would like to reproduce.
Does this make sense?
Thank you,
-James
You're seeing the result of JPG compression artifacts. They're made worse by the small size your your image. Try this and I think you'll understand:
% Demo by Image Analyst
clc; % Clear the command window.
close all; % Close all figures (except those of imtool.)
clear; % Erase all existing variables. Or clearvars if you want.
workspace; % Make sure the workspace panel is showing.
format long g;
format compact;
fontSize = 18;
fprintf('Beginning to run %s.m ...\n', mfilename);
rows = 200;
columns = 400;
a = rand(rows, columns) - 0.5 % Range is between -0.5 and +0.5
fileName = 'Delete_me.jpg';
imwrite(a, fileName);
b=imread(fileName)
delete(fileName); % Delete temporary file.
% Scatter plot it.
subplot(2, 2, 1);
scatter(a(:), b(:), 5, 'filled');
grid on;
xlabel('Original Value', 'FontSize', fontSize);
ylabel('Recalled Value', 'FontSize', fontSize);
title('JPG Format', 'FontSize', fontSize);
fileName = 'Delete_me.png';
imwrite(a, fileName);
b=imread(fileName)
delete(fileName); % Delete temporary file.
% Scatter plot it.
subplot(2, 2, 2);
scatter(a(:), b(:), 5, 'filled');
grid on;
xlabel('Original Value', 'FontSize', fontSize);
ylabel('Recalled Value', 'FontSize', fontSize);
title('PNG Format', 'FontSize', fontSize);
fileName = 'Delete_me.mat';
save(fileName, 'a');
s = load(fileName);
b = s.a
delete(fileName); % Delete temporary file.
% Scatter plot it.
subplot(2, 2, 3);
scatter(a(:), b(:), 5, 'filled');
grid on;
xlabel('Original Value', 'FontSize', fontSize);
ylabel('Recalled Value', 'FontSize', fontSize);
title('MAT Format', 'FontSize', fontSize);
fprintf('Done running %s.m.\n', mfilename);
See how the PNG follows a definite clipping and linear scaling formula, and the JPG is all fuzzy, which means it's basically unpredictable.
I find it difficult to believe that reading in changed/altered values is going to train better than the original, accurate values. Perhaps the noise introduced by JPG lets it handle noise in your test set better, but I doubt it.
Thank you, I will look into this more.
If it is is a 'random or unpredicatable' then that will be difficult to replicate :)
I agree with you, the lossy images are probably helping the network handle noise better. But it looks like it is applying some type of low pass filter on my images. I was just trying to understand what type of filter this was simulating. It looks like it is some type of clip combined with a low pass which removes some of the higher frequency components.
a is the raw matrix on right normalized to a -255 to 255 scale. b is the saved jpg and then reloaded.
Thank you for your help,
James
Actually that doesn't agree with me. Note the last few words of that sentence "but I doubt it".
JPG convert the image to the spectral domain and then does a kind of low pass filter. Not exactly like a Fourier filter though because it does it block-by-block. That's what gives JPG images such lousy appearance with the notorious "JPG block artifacts" where you have huge rectangular blocks of uniform color. It's expecially bad in slowly varying areas, like the sky.
If your ground truth has positive and negative values, then you'd want to scale your images to 0-255 before saving to an image file. Use the same scaling factors for all images, or, to simulate possible changes in brightness (like you'd get if you did augmentation) you can scale each independently.
Thanks for all of your help and for your clarifiation. I selected yours as the answer.
Best Regards,
James

Sign in to comment.

More Answers (2)

You can change to using random integer function instead of rand function.
Also change the image format to png instead of jpg.
a = randi([0,255],2,10,'uint8');
imwrite(a,'cake.png');
b=imread('cake.png');
isequal(a,b)
ans = logical
1

1 Comment

Thank you for you reply. This is interesting but I am trying to reproduce the transformation that imwrite seems to be applying to matrix 'a'. See my above reply Image Analyst.
:)

Sign in to comment.

%approximately
function imwrite(data, filename)
if isfloat(data)
data = uint8(data * 255);
end
and so on
end
This is a simplification, as the 255 depends upon the bit depth options.
Notice that the range of the floating point data is not tested first. imwrite() does not check to see whether the data happens to be in the range 0.0 to 255.0 and if so then simply uint8() it: imwrite() multiplies all floating point data by 255. If the floating point data happened to have values greater than 1.0 then the result of the multiplication will be greater than 255.0, and no matter what range that turns out to be, it is then processed by uint8() [unless 16 bit output was requested.]
uint8() has its usual properties:
  • any negative value "underflows" to 0.
  • any value greater than 255 "saturates" to 255.
  • any non-integer value is rounded to the nearest integer before being changed to integer data type.

1 Comment

Thank you for your reply.
I agree that when using imwrite() that values <0 should be 0. But if that is the case then how do you explain the below?
a = rand(2,10)-.5;
imwrite(a,'cake.jpg');
b=imread('cake.jpg');
a =
-0.3219 -0.4433 -0.1642 -0.2911 0.1754 0.4121 0.2455 0.0619 0.0972 -0.3659
-0.1404 0.0219 -0.3243 0.4052 -0.0315 -0.3960 0.2363 -0.3158 -0.2001 -0.2874
>> b
b =
2×10 uint8 matrix
0 0 0 19 30 99 74 13 15 9
2 4 11 80 12 12 53 10 8 4
look at a(1,4) = negative value but b(1,4) = positive
Also, b(2,2)=4 and b(2,10)=4 but a(2,2) and a(2,10) are opposite sign. Opposite sign values are produce the same output value of 4.
I think something to do with the compression of the JPG images (using the eignevalues) is causing this effect. I am interested in exploring why this is happening becuse this 'transformation' is having a positive imact of my trianing data for a neural network. Just trying to undertand what is happening inside imwrite.
Thanks,
James

Sign in to comment.

Tags

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!