convert 16 bit image to 8 bit image
124 views (last 30 days)
Show older comments
how to convert 16 bit image to 8 bit image
0 Comments
Accepted Answer
Jan
on 5 Mar 2012
The uint16 image used values from 0 to 2^16-1, while the uint8 images use a range from, 0 to 2^8-1 only. If you just cast the original values to uint8, the saturation will destroy a lot of information. Better use:
img8 = uint8(img16 / 256);
3 Comments
dror yemini
on 10 Sep 2020
yes but you loase somthing better before take bits with information so maybe do equalization of histogram before conversion ?
DGM
on 7 Jul 2024
Edited: DGM
on 7 Jul 2024
I know this is old, but I have to disagree. Let's consider the uint16-uint8 downconversion that's given. The obvious transformation seems fairly simple. Just map the endpoints of the two intervals to each other. Since this is unsigned, we really only have one endpoint pair to deal with.
% a full-scale ramp
x = uint16(0:65535);
% simple linear transformation between interval endpoints
ir = [0 65535];
or = [0 255];
y0 = double(x)/(ir(2)/or(2));
The catch is picking the right scaling factor and rounding method. This is how the IPT tools do it.
% default IPT-style rescaling
% Y = round(X/((2^bitsin - 1)/(2^bitsout - 1)))
%y1 = im2uint8(x);
y1 = uint8(double(x)/257); % 257, not 256
% plot everything
plotthings(x,y0,y1,ir)
A lot of code just does this sort of conversion by bitshifting instead. I suppose there are reasons to do it this way just the same.
% this is equivalent to bitshifting
% Y = fix(X/2^(bitsin - bitsout))
y1 = uint8(fix(double(x)/256));
% plot everything
plotthings(x,y0,y1,ir)
We have to remember though that cast() and uint8(), etc use round() on the input, not fix().
% i don't know what this is
% Y = round(X/2^(bitsin - bitsout))
y1 = uint8(double(x)/256);
% plot everything
plotthings(x,y0,y1,ir)
function plotthings(x,y0,y1,ir)
figure
% compare the error between the integer approximations
% and an ideal linear transformation
w = 2000;
labels = {'IPT-style','ideal'};
subplot(2,2,1)
thisx = x(1:w);
plot(thisx,y1(1:w)); hold on; plot(thisx,y0(1:w));
xlim(imrange(thisx))
legend('string',labels,'location','northwest')
xlabel('uint16 values'); ylabel('uint8 values')
title('bottom of scale')
subplot(2,2,2)
thisx = x(end-w:end);
plot(thisx,y1(end-w:end)); hold on; plot(thisx,y0(end-w:end));
xlim(imrange(thisx))
title('top of scale')
% the IPT method has a more uniformly distributed error,
% with a smaller peak magnitude
% the significance of this error depends on the width of the output class
% i.e. the relative significance of 1LSB
subplot(2,2,3:4)
plot(x,double(y0)-double(y1)); hold on
plot(x,y0-y0)
xlim(ir)
legend('string',labels)
xlabel('uint16 values'); ylabel('uint8 values')
title('error WRT float conversion')
ylim([-1.5 1.5])
end
As the comments mention, this error is still relatively small. It manifests as a slight change in contrast. Especially for wider output classes, it's probably arguable that it doesn't matter. If we want to generalize these conversions, we should also consider narrower outputs, where the error would be more significant. Not too long ago, I was using imcast() to do a bunch of downconversion to uint4-scale. At that point, it's noticeable.
More Answers (3)
Rasmus Herlo
on 11 Feb 2021
OBS: Jan's answer will do absolutely fine, if your image is optimized to use the entire scale. However, if your pixel values are only occupying a fraction of the available bits (common in much imaging), the direct conversion will lead to a significant loss of information. In addition, you might want to get your image on 1:2^16 (instead of 0:2^16-1) before normalizing.
Image conversion in software like ImageJ therefore uses linear scaling, and one would typically apply a similar approach here, by:
Input: Im16 'uint16' (any 16bit image)
dbIm16 = double(Im16)+1
dbIm16 = min(dbIm16(:)); db16max = max(dbIm16(:));
TgBit = 8; % or any other lower bit scale
% example with 16bit to 8bit
Norm_wOffSet = dbIm16/db16max; % maintaining putative offset from 0 in the data
Im8_wOffSet = uint8(Norm_wOffSet*2^TgBit-1); % back to 0:2^8-1
Norm_woOffSet = (dbIm16-db16min)/(db16max-db16min); % Scales linearly to full range
Im8_woOffSet = uint8(Norm_woOffSet*2^TgBit-1); % back to 0:2^8-1
1 Comment
Tiziana
on 21 Nov 2022
Thank so much for your answer. Could I ask you the reasons of this "you might want to get your image on 1:2^16 (instead of 0:2^16-1) before normalizing"? Thank you very much.
Wayne King
on 5 Mar 2012
You can use uint8() to cast the image into unsigned 8-bit integers. Or int8() for signed 8-bit integers.
X = uint8(ones(10,10,'uint16'));
class(X)
0 Comments
DGM
on 30 Dec 2023
Edited: DGM
on 30 Dec 2023
I don't know why nobody brought it up in 10+ years, but:
outpict = im2uint8(inpict); % recast with appropriate scaling
This will preserve the relative scale of the image regardless of the input class. Everything within the expected range of the input class (i.e. [-32768 32767] for int16) will fit within [0 255]. This works regardless of whether the input is int16 or uint16 or double, etc.
@Rasmus Herlo has a good point, though. A lot of technical and medical images in 16b seem to be unscaled raw values that occupy a small portion of the available dynamic range. Depending on the goals, it might be prudent to rescale them. That's easy enough.
outpict = im2uint8(mat2gray(inpict)); % normalize to extrema, then recast/scale
... though if the image is some sort of raw sensor data, a contextually-relevant nonlinear scaling might be more appropriate.
0 Comments
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!