changing bits per pixel in MATLAB - image

How does one change the bits per pixel of an image loaded into MATLAB? I use the file dialog and the imread functions to load the image into a matrix. i just need to change that image's bits per pixel. Giving the user that ability to choose anywhere from 1 bit to 8 bits. I know how to give the users the ability to choose one I just don't know who to change it. How does one change that? (By the way I'm in MATLAB R2012a)

The way I understand it, you want to do something like this:
imdata = rgb2gray(imread('ngc6543a.jpg') ); % Assuming that we have a grayscale uint8 image
figure('name', 'Before');
imagesc(imdata);
colormap('gray');
numberOfBits = input('Enter number of bits:\n');
maxValue = 2^numberOfBits - 1;
newImage = imdata * (maxValue / 256);
figure('name', 'After');
imagesc(newImage);
colormap('gray');
The image ngc6543a.jpg is a sample image, so you can run this code immediately as it is.

This documentation page contains lots of information about what you want to do: Reducing the Number of Colors in an Image.
A simple example is the following (pretty much taken straight from that page), which will dither the image and produce a colour map (slightly different to the OP's answer - not sure which one you want to do):
>> RGB = imread('peppers.png');
>> [x,map] = rgb2ind(RGB, 2); % Reduce to a 2-colour image
>> imagesc(x)
>> colormap(map)
You should choose the number of colours based on the maximum number that however many bits can hold.

Related

A proper way to convert 2D Array into RGB or GrayScale image for precision difference

I have a 2D CNN model where I perform a classification task. My images are all coming from a sensor data after conversion.
So, normally, my way is to convert them into images using the following approach
newsize = (9, 1000)
pic = acc_normalized[0]
img = Image.fromarray(np.uint8(pic*255), 'L')
img = img.resize(newsize)
image_path = "Images_Accel"
image_name = "D1." + str(2)
img.save(f"{image_path}/{image_name}.jpeg")
This is what I obtain:
However, their precision is sort of important. For instance, some of the numerical values are like:
117.79348187327987 or 117.76568758022673.
As you see in the above line, their difference is the digits, when I use uint8, it only takes 117 to when converting it into image pixels and it looks the same, right? But, I'd like to make them different. In some cases, the difference is even at the 8th or 10th digit.
So, when I try to use mode F and save them .jpeg in Image.fromarray line it gives me error and says that PIL cannot write mode F to jpeg.
Then, I tried to first convert them RGB like following;
img = Image.fromarray(pic, 'RGB')
I am not including np.float32 just before pic or not multiplying it by 255 as it is. Then, I convert this image to grayscale. This is what I got for RGB image;
After converting RGB into grayscale:
As you see, it seems that there is a critical different between the first pic and the last pic. So, what should be the proper way to use them in 2D CNN classification? or, should I convert them into RGB and choose grayscale in CNN implementation and a channel of 1? My image dimensions 1000x9. I can even change this dimension like 250x36 or 100x90. It doesn't matter too much. By the way, in the CNN network, I am able to get more than 90% test accuracy when I use the first-type of image.
The main problem here is using which image conversion method I'll be able to take into account those precision differences across the pixels. Would you give me some idea?
---- EDIT -----
Using .tiff format I made some quick comparisons.
First of all, my data looks like the following;
So, if I convert this first reading into an image using the following code where I use np.float64 and L gives me a grayscale image;
newsize = (9, 1000)
pic = acc_normalized[0]
img = Image.fromarray(np.float64(pic), 'L')
img = img.resize(newsize)
image_path = "Images_Accel"
image_name = "D1." + str(2)
img.save(f"{image_path}/{image_name}.tiff")
It gives me this image;
Then, the first 15x9 matrix seems like following image; The contradiction is that if you take a closer look at the numerical array, for instance (1,4) member, it's a complete black where the numerical array is equal to 0.4326132099074307. For grayscale images, black means that it's close to 0 cause it makes white if it's close to 1. However, if it's making a row operation, there is another value closer to 0 and I was expecting to see it black at (1,5) location. If it does a column operation, there is again something wrong. As I said, this data has been already normalized and varies within 0 and 1. So, what's the logic that it converts the array into an image? What kind of operation it does?
Secondly, if I first get an RGB image of the data and then convert it into a grayscale image, why I am not having exactly the same image as what I obtained first? Should the image coming from direct grayscale conversion (L method, np.float64) and the one coming from RGB-based (first I get RGB then convert it to grayscale) be the same? There is a difference in black-white pixels in those images. I do not know why we have it.
---- EDIT 2 ----
.tiff image with F mode and np.float32 gives the following;
I don't really understand your question, but you seem to want to store image differences that are less than 1, i.e. less than the resolution of integer values.
To do so, you need to use an image format that can store floats. JPEG, PNG, GIF, TGA and BMP cannot store floats. Instead, use TIFF, EXR or PFM formats which can handle floats.
Alternatively, you can create 16-bit PNG images wherein each pixel can store values in range 0..65535. So, say the maximum difference you wanted to store was 60 you could calculate the difference and multiply it by 1000 and round it to make an integer in range 0..60000 and store as 16-bit PNG.
You could record the scale factor as a comment within the image if it is variable.

Read RGB image into binary and display it as RGB in Matlab

This question is based on the one asked earlier Understanding image steganography by LSB substitution method
In order to make the code efficient and reduce the mean square error (MSE) the suggestion was: "read the file as is with and convert it to bits with de2bi(fread(fopen(filename)), 8). Embed these bits to your cover image with the minimum k factor required, probably 1 or 2. When you extract your secret, you'll be able to reconstruct the original file." This is what I have been trying but somewhere I am doing wrong as I am not getting any display. However, the MSE has indeed reduced. Basically, I am confused as to how to convert the image to binary, perform the algorithm on that data and display the image after extraction.
Can somebody please help?
I've made some modifications to your code to get this to work regardless of what the actual image is. However, they both need to be either colour or grayscale. There are also some errors your code that would not allow me to run it on my version of MATLAB.
Firstly, you aren't reading in the images properly. You're opening up a byte stream for the images, then using imread on the byte stream to read in the image. That's wrong - just provide a path to the actual file.
Secondly, the images are already in uint8, so you can perform the permuting and shifting of bits natively on this.
The rest of your code is the same as before, except for the image resizing. You don't need to specify the number of channels. Also, there was a syntax error with bitcmp. I used 'uint8' instead of the value 8 as my version of MATLAB requires that you specify a string of the expected data type. The value 8 here I'm assuming you mean 8 bits, so it makes sense to put 'uint8' here.
I'll also read your images directly from Stack Overflow. I'll assume the dinosaur image is the cover while the flower is the message:
%%% Change
x = imread('https://i.stack.imgur.com/iod2d.png'); % cover message
y = imread('https://i.stack.imgur.com/Sg5mr.png'); % message image
n = input('Enter the no of LSB bits to be subsituted- ');
%%% Change
S = uint8(bitor(bitand(x,bitcmp(2^n-1,'uint8')),bitshift(y,n-8))); %Stego
E = uint8(bitand(255,bitshift(S,8-n))); %Extracted
origImg = double(y); %message image
distImg = double(E); %extracted image
[M N d] = size(origImg);
distImg1=imresize(distImg,[M N]); % Change
figure(1),imshow(x);title('1.Cover image')
figure(2),imshow(y);title('2.Message to be hide')
figure(3),imshow((abs(S)),[]);title('3.Stegnographic image')
figure(4),imshow(real(E),[]); title('4.Extracted image');
This runs for me and I manage to reconstruct the message image. Choosing the number of bits to be about 4 gives you a good compromise between the cover and message image.
Loading the byte stream instead of the pixel array of the secret will result to a smaller payload. How smaller it'll be depends on the image format and how repetitive the colours are.
imread() requires a filename and loads a pixel array if said filename is a valid image file. Loading the byte stream of the file and passing that to imread() makes no sense. What you want is this
% read in the byte stream of a file
fileID = fopen(filename);
secretBytes = fread(fileID);
fclose(fileID);
% write it back to a file
fileID = fopen(filename);
fwrite(fileID, secretBytes);
fclose(fileID);
Note that the cover image is loaded as a pixel array, because you'll need to modify it.
The size of your payload is length(secretBytes) * 8 and this must fit in your cover image. If you decide to embed k bits per pixel, for all your colour planes, the following requirement must be met
secretBytes * 8 <= prod(size(coverImage)) * k
If you want to embed in only one colour plane, regardless of whether your cover medium is an RGB or greyscale, you need to modify that to
secretBytes * 8 <= size(coverImage,1) * size(coverImage,2) * k
If this requirement isn't met, you can choose to
stop the process
ask the user for a smaller file to embed
increase k
include more colour planes, if available
The following is a prototype for embedding in one colour plane in the least significant bit only (k = 1).
HEADER_LEN = 24;
coverImage = imread('lena.png');
secretBytes = uint8('Hello world'); % this could be any byte stream
%% EMBEDDING
coverPlane = coverImage(:,:,1); % this assumes an RGB image
bits = de2bi(secretBytes,8)';
bits = [de2bi(numel(bits), HEADER_LEN) bits(:)'];
nBits = length(bits);
coverPlane(1:nBits) = bitset(coverPlane(1:nBits),1,bits);
coverImage(:,:,1) = coverPlane;
%% EXTRACTION
nBits = bi2de(bitget(coverPlane(1:HEADER_LEN),1));
extBits = bitget(coverPlane(HEADER_LEN+1:HEADER_LEN+nBits),1);
extractedBytes = bi2de(reshape(extBits',8,length(extBits)/8)')';
Along with your message bytes you have to embed the length of the secret, so the extractor knows how many bits to extract.
If you embed with k > 1 or in more than one colour planes, the logic becomes more complicated and you have to be careful how you implement the changes.
For example, you can choose to embed in each colour plane at a time until you run out of bits to hide, or you can flatten the whole pixel array with coverImage(:), which will embed in the RGB of each pixel, one pixel at a time until you run out of bits.
If you embed with k > 1, you have to pad your bits vector with 0s until its length is divisible by k. Then you can combine your bits in groups of k with
bits = bi2de(reshape(a',k,length(bits)/k)')';
And to embed them, you want to resort back to using bitand() and bitor().
coverPlane(1:nBits) = bitor(bitand(coverPlane(1:nBits), bitcmp(2^k-1,'uint8')), bits);
There are more details, like extracting exactly 24 bits for the message length and I can't stress enough you have to think very carefully how you implement all of those things. You can't just stitch parts from different code snippets and expect everything to do what you want it to do.

how can I get good binary image using Otsu method for this image?

here is my image
a.png
for binarization I try this code.
im=rgb2gray(I);
maxp=uint16(max(max(im)));
minp=uint16(min(min(im)));
bw=im2bw(im,(double(minp+maxp))/(1.42*255));
bw=~bw;
imm=bw;
but I need binarization by otsu.how can I get good binary output using otsu method?
plz help
thanks
MATLAB has its own implementation of Otsu thresholding called multithresh. In your case the code to obtain the segmented image should be something like this:
im=rgb2gray(I); % convert image to grayscale
thresh = multithresh(im); % find one threshold (using Otsu method)
segmented_im = imquantize(im, thresh); % segment image
imagesc(segmented_im); % show segmented image
I haven't tested it so I don't know how well it would perform on your image.
EDIT:
I tested it, and it doesn't work as expected. One of the problems is that Otsu's method works well when there is a clear bimodal distribution of the pixel intensities. This bimodality is lacking in your image. A call to imhist(im) after the grayscale conversion leads to this (comments added by me):
As you can see, the distribution is almost trimodal, and the threshold selected by multithresh is the first one, while you want the second one. The first workaround that comes to my mind (especially if all the images in your dataset are similar to the one you posted, i.e. have a similar intensity distribution) is to make multithresh output two thresholds, and then selecting the last (highest) one:
thresholds = multithresh(im, 2);
thresh = thresholds(end);
Then proceed with the segmentation of the image as stated above. This second method leads to this segmentation:
EDIT 2 (putting it all together):
Indeed the output segmented_im is not a binary image, but a label image. It's easy enough to convert it to a binary image. I will include directly all the code in this next snippet:
im=rgb2gray(I); % convert image to grayscale
thresholds = multithresh(im, 2); % find two thresholds using Otsu
thresh = thresholds(end); % select larger one
segmented_im = imquantize(im, thresh); % segment image
segmented_im(segmented_im == 1) = 0; % make background black (0)
segmented_im(segmented_im == 2) = 255; % make foreground white (255)
binary_im = im2bw(segmented_im); % make binary (logical) image
imshow(binary_im); % show binary image
binary_im il a logical matrix with false (0) for background, and true (1) for foreground. segmented_im is a double matrix with 0 for background and 255 for foreground. I hope this serves your purposes!

Detecting black spots on image - Image Segmentation

I'm trying to segment an image with Color-Based Segmentation Using K-Means Clustering. I already created 3 clusters, and the cluster number 3 is like this image:
This cluster has 3 different colors. And I want to only display the black spots of this image. How can I do that?
The image is 500x500x3 uint8.
Those "holes" look like they are well defined with the RGB values all being set to 0. To make things easy, convert the image to grayscale, then threshold the image so that any intensities less than 5 set the output to white. I use a threshold of 5 instead to ensure that we capture object pixels in their entirety taking variations into account.
Once that's done, you can use the function bwlabel from the image processing toolbox (I'm assuming you have it as you're dealing with images) where the second output tells you how many distinct white objects there are.
Something like this could work:
im = imread('http://i.stack.imgur.com/buW8C.png');
im_gray = rgb2gray(im);
holes = im_gray < 5;
[~,count] = bwlabel(holes);
I read in the image directly from StackOverflow, convert the image to grayscale, then determine a binary mask where any intensity that is less than 5, set the output to white or true. Once we have this image, we can use bwlabel's second output to determine how many objects there are.
I get this:
>> count
count =
78
As an illustration, if we show the image where the holes appear, I get this:
>> imshow(holes);
The amount of "holes" is a bit misleading though. If you specifically take a look at the bottom right of the image, there are some noisy pixels that don't belong to any of the "holes" so we should probably filter that out. As such, a simple morphological opening filter with a suitable sized structure will help remove spurious noisy islands. As such, use imopen combined with strel to define the structuring element (I'll choose a square) as well as a suitable size of the structuring element. After, use the structuring element and filter the resulting image and you can use this image to count the number of objects.
Something like this:
%// Code the same as before
im = imread('http://i.stack.imgur.com/buW8C.png');
im_gray = rgb2gray(im);
holes = im_gray < 5;
%// Further processing
se = strel('square', 5);
holes_process = imopen(holes, se);
%// Back to where we started
[~,count] = bwlabel(holes_process);
We get the following count of objects:
>> count
count =
62
This seems a bit more realistic. I get this image now instead:
>> imshow(holes_process);

Find and crop defined image areas automatically

I want to process an image in matlab
The image consists out of a solid back ground and two specimens (top and bottom side). I already have a code that separate the top and bottom and make it two images. But the part what I don't get working is to crop the image to the glued area only (red box in the image, I've only marked the top one). However, the cropped image should be a rectangle just like the red box (the yellow background, can be discarded afterwards).
I know this can be done with imcrop, but this requires manual input from the user. The code needs to be automated such that it is possible to process more images without user input. All image will have the same colors (red for glue, black for material).
Can someone help me with this?
edit: Thanks for the help. I used the following code to solve the problem. However, I couldn't get rid of the black part right of the red box. This can be fix by taping that part off before making pictures. The code which I used looks a bit weird, but it succeeds in counting the black region in the picture and getting a percentage.
a=imread('testim0.png');
level = graythresh(a);
bw2=im2bw(a, level);
rgb2=bw2rgb(bw2);
IM2 = imclearborder(rgb2,4);
pic_negative = ait_imgneg(IM2);
%% figures
% figure()
% image(rgb2)
%
% figure()
% imshow(pic_negative)
%% Counting percentage
g=0;
for j=1:size(rgb2,2)
for i=1:size(rgb2,1)
if rgb2(i,j,1) <= 0 ...
& rgb2(i,j,2) <= 0 ...
& rgb2(i,j,3) <= 0
g=g+1;
end
end
end
h=0;
for j=1:size(pic_negative,2)
for i=1:size(pic_negative,1)
if pic_negative(i,j)== 0
h=h+1;
end
end
end
per=g/(g+h)
If anyone has some suggestions to improve the code, I'm happy to hear it.
For a straight-forward image segmentation into 2 regions (background, foreground) based on color (yellow, black are prominent in your case), an option can be clustering image color values using kmeans algorithm. For additional robustness you can transform the image from RGB to Lab* colorspace.
The example for your case follows the MATLAB Imape Processing example here.
% read and transform to L*a*b space
im_rgb = double(imread('testim0.png'))./256;
im_lab = applycform(im_rgb, makecform('srgb2lab'));
% keep only a,b-channels and form feature vector
ab = double(lab_I(:,:,2:3));
[nRows, nCols, ~] = size(ab);
ab = reshape(ab,nRows * nCols,2);
% apply k-means for 2 regions, repeat c times, e.g. c = 5
nRegions = 2;
[cluster_idx cluster_center] = kmeans(ab,nRegions, 'Replicates', 5);
% get foreground-background mask
im_regions = reshape(cluster_idx, nRows, nCols);
You can use the resulting binary image to index the regions of interest (or find the boundary) in the original reference image.
Images are saved as matrices. If you know the bounds in pixels of the crop box want to crop you can execute the crop using indexing.
M = rand(100); % create a 100x100 matrix or load it from an image
left = 50;
right = 75;
top = 80;
bottom = 10;
croppedM = M(bottom:top, left:right);
%save croppedm
You can easily get the unknown bounded crop by
1) Contour plotting the image,
2) find() on the result for the max/min X/ys,
3) use #slayton's method to perform the actual crop.
EDIT: Just looked at your actual image - it won't be so easy. But color enhance/threshold your image first, and the contours should work with reasonable accuracy. Needless to say, this requires tweaking to your specific situation.
since you've already been able to seperate the top and bottom, and also able to segment the region you want (including the small part of the right side that you don't want), I propose you just add a fix at the end of the code via the following.
after segmentation, sum each column Blue intensity value, so that you are compressing the image from 2d to 1d. so if original region is width=683 height=59, the new matrix/image will be simply width=683 height=1.
now, you can apply a small threshold to determine where the edge should lie, and apply a crop to the image at that location. Now you get your stats.

Resources