convert 16 bit image to 8 bit image

124 views (last 30 days)
mohd
mohd on 5 Mar 2012
Edited: DGM on 7 Jul 2024
how to convert 16 bit image to 8 bit image

Accepted Answer

Jan
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
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
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.

Sign in to comment.

More Answers (3)

Rasmus Herlo
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
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.

Sign in to comment.


Wayne King
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)

DGM
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.

Community Treasure Hunt

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

Start Hunting!