Convert indexed image into grayscale - image

How can I convert an RGB or indexed images into gray scale without using B=ind2gray(A,map) from MATLAB?

I don't understand why you just can't use ind2gray.... but if you really have to implement this from first principles, that's actually not too bad. What ind2gray does (IIRC) is that it takes an indexed image and with the colour map, it converts the image into a colour image. Once you're done that, you convert the resulting colour image to grayscale. The index image is actually a grid of lookup values that span from [1,N]. Also, the colour map is a N x 3 array where each row is a RGB tuple / colour. It should be noted that the colour map is double precision where each component spans between [0,1]. Therefore, for each location in the index image, it tells you which tuple from the lookup table is mapped to this location. For example, if we had an index image such that:
X =
[1 2
3 4]
... and we had a colour map that was 4 x 3, this means that the top left corner gets the first colour denoted by the first row of the map, the top right corner gets the second colour, bottom left corner gets the third colour and finally the bottom right corner gets the fourth colour.
The easiest way to do this would be to use X to index into each column of the input map, then concatenate all of the results together into a single 3D matrix. Once you're done, you can convert the image into its luminance / grayscale counterpart. Given that you have an index image called X and its corresponding colour map, do this:
colour_image = cat(3, map(X), map(X + size(map,1)), map(X + 2*size(map,1)));
gray = sum(bsxfun(#times, colour_image, permute([0.2126 0.7152 0.0722], [3 1 2])), 3);
The first statement is very simple. Take note that map is N x 3 and X can range between [1,N]. If we use X and index directly into map, you would only be grabbing the first column of values, or the first component of the colours / red. We need to access the same values in the right order for the second column, and because MATLAB accesses elements in column-major format, we simply have to add all of the offsets by N so that we can access the values in the second column to get the second component of colours / green. Finally, you'd offset by 2N to get the third component of colours / blue. We'd take each red, green and blue channel and concatenate them together to get a 3D image.
Once we get this 3D image, it's a matter of converting the colour image into luminance. I am using the SMPTE Rec. 709 standard to convert from a colour pixel to luminance. That relationship is:
Y = 0.2126 R + 0.7152 G + 0.0722 B
That's the purpose of the second statement. We will take each component, multiply them by their respective weight and sum all of the values together. You should have your luminance image as a result.
To check to see if this works, we can use the trees dataset from the image processing toolbox. This comes with an index image X, followed by a colour map map:
load trees;
%// Previous code
colour_image = cat(3, map(X), map(X + size(map,1)), map(X + 2*size(map,1)));
gray = sum(bsxfun(#times, colour_image, permute([0.2126 0.7152 0.0722], [3 1 2])), 3);
%// Show colour image as well as resulting gray image
figure; subplot(1,2,1);
imshow(colour_image);
subplot(1,2,2);
imshow(gray);
We get:
We can actually show that this is indeed the right output by converting the image to grayscale using ind2gray, then showing the difference between the two images. If the images are equal, that means that the resulting image should be black, which means that the outputs produced by the above procedure and ind2gray are exact.
Therefore:
gray2 = ind2gray(X, map);
figure;
imshow(abs(gray-gray2));
We get:
... yup... zilch, nothing, zero, notta.... so what I implemented in comparison to ind2gray is basically the same thing.

Related

Error while converting a 3d matrix into an animated gif in Matlab

I am attempting to make a movie from a 3d matrix, which is made multiple 2d matrices and the third dimension is time.
I have read the following question witch is pretty much the same and I have attempted to do the same.
How to make a video from a 3d matrix in matlab
The 3d matrix I want to play is stored in a object instanced A.
a.movie; % 3D matrix
X = permute(a.movie,[1 2 4 3]); % 4D matrix
movie = immovie(X,map); % map is the colormap you want to use
implay(movie);
I would like to know why should a.movie be permuted? And what is the map referred?
How can I define 0 as blue and 100 as red?
The post you linked us to exactly answers that. immovie expects a m x n x 1 x k matrix where m and n are the rows and columns of 1 slice from your 3D matrix, and k is the number of slices. You currently have your 3D matrix set up to be m x n x k. Therefore, by permuting, you are artificially creating a 4D matrix from your 3D original matrix. Simply put, you can think of your 3D matrix as having a singleton 4D dimension: m x n x k x 1. The job of permute here is to swap the 3rd and 4th dimension - that's why you see the [1 2 4 3] vector in the permute call. The first and second dimensions represent the rows and columns, and you leave those empty.
Now that answers the permute question. The map is defined as a colour map. This maps each value in your 3D matrix to a unique colour. Basically, the colour map is a M x 3 matrix where row in this matrix corresponds to a unique colour. Each column represents a colour channel. Therefore, the first column represents the proportion of red you want, the second channel is the proportion of green and the last is the proportion of blue. Keep in mind that these colours should be normalized between [0,1].
The goal of the colour map is to take each value in your 3D matrix, and figure out which colour that value maps to. The way to do this is to use each value in your 3D matrix exactly as it is and use this to access the row of the colour map. This row gives you the colours you want. Now, I'm assuming that your values in the 3D matrix span from 0 to 100.
If you want the colours to span between blue and red. The blue colour has an exact colour of RGB = (0,0,1) assuming normalized coordinates and similarly, the red represents the exact colour of RGB = (1,0,0). Therefore, start off with RGB = (0,0,1), then start linearly increasing the red component while linearly decreasing the blue component until the red is 1 and the blue is 0.
What we can do is figure out how many unique values there are in your matrix, then we can create our colour map that way so we can ensure that each value in your matrix gets assigned to one colour. However, this will require that a.movie be redefined to ensure that we can assign a value to a colour.
Therefore, I'd create your colour map like this:
[unq,~,id] = unique(a.movie);
movie_IDs = reshape(id, size(a.movie));
M = numel(unq);
map = [linspace(1,0,M).', zeros(M,1), linspace(0,1,M).'];
Now, go ahead and use map with the above code to create your movie.
X = permute(movie_IDs,[1 2 4 3]); % 4D matrix
movie = immovie(X,map); % map is the colormap you want to use
implay(movie);
However, the colour map you're looking at is the jet colour map. Therefore, you can simply just create a jet colour map:
map = jet(M);
However, you must make sure you run through each value in a.movie and assign a unique integer to each value to ensure that there are no gaps in your data and every value gets assigned to a new value that goes up from 1 to M in order for the movie to properly access the right colour.
MATLAB has a bunch of built-in colour maps for you to use if you don't feel like designing your own colour map. http://www.mathworks.com/help/matlab/ref/colormap.html#inputarg_map - However, from what I see in your post, making the colour map is what you want to do.

Draw a horizontal line on an image whose row corresponds to the largest count of dark pixels

I wish to draw a horizontal line on a binary image in MATLAB which has the maximum amount of black pixels. So in this word for example:
... I have to identify the horizontal line with most pixels.
I understand that I can open the binary image variable editor, and plot a row which has maximum zeroes however that doesn't seem to work.
I have to make a baseline of this word like this:
... as the output assuming that the maximum pixels lie where I drew the line.
Purely going by your definition, you want to figure out the row that has the largest amount of black pixels. Simply sum over all columns of each row and find the maximum. Then when you're done, locate the row with the largest count and set this line to red.
Something like this comes to mind. I'm going to read your image from StackOverflow directly and am going to use the image processing toolbox to help me with this analysis:
%// Read image from SO
im = imread('http://s15.postimg.org/cwg2sxnwr/mathworksss.png');
%// Keep a copy of a binary version
im_bw = im2bw(im);
%// Sum over all of the columns and look for dark pixels
row_sums = sum(~im_bw, 2);
%// Find row with max sum
[~,row_max] = max(row_sums);
%// Draw a red line through the original image as it's in RGB
im(row_max,:,1) = 255;
im(row_max,:,2:3) = 0;
%// Show the image
imshow(im);
The first image reads in the image directly from SO and puts it into the MATLAB workspace. The next line thresholds the image to binary to allow the analysis to be easier. We also keep a copy of the original image so we can mark the baseline with red. The next line after that uses sum and sums over every column of each row individually and adds up the black pixels. This is achieved by inverting the binary image so that dark pixels become bright to facilitate the summing. We then use max to figure out the row with the largest sum and that's done by looking at the second output of max. Once we find this location, we use this row and set all of the pixels in this row to red, or RGB = (255,0,0).
I get this image:
Now the above code draws a line from the left to the right. If you wanted to limit this and only draw a red line where there is text, perhaps find the left most and right most dark pixel and add a bit of breathing room to them, then draw a line through.... something like this comes to mind:
%// Read image from SO
im = imread('http://s15.postimg.org/cwg2sxnwr/mathworksss.png');
%// Keep a copy of a binary version
im_bw = im2bw(im);
%// Sum over all of the columns and look for dark pixels
row_sums = sum(~im_bw, 2);
%// Find row with max sum
[~,row_max] = max(row_sums);
%// Find left most and right most black columns
[~,left_most] = find(~im_bw,1,'first');
[~,right_most] = find(~im_bw,1,'last');
%// Buffer for drawing the line before the first and after the last column
buf = 20;
%// Draw the line
im(row_max,left_most-buf:right_most+buf,1) = 255;
im(row_max,left_most-buf:right_most+buf,2:3) = 0;
%// Show the image
imshow(im);
As you can see, most of the code remains the same (reading in the image, thresholding, column summing and max) but towards the end, I use find to find the first and last instance of a black pixel with respect to the columns. I then use these columns with the same maximum row found earlier, then subtracting the left most black column pixel location and adding the right most black column pixel location by a buffer amount (I chose 20 here), then setting the line of pixels within this region to red.
We get:
Now, it is your wish to find the row with the second highest sum and draw another line through that row. That's not bad to do. However, this will require some post-processing because the outer edges of each character is not a single pixel thick... so the second highest sum may actually still give you a row that is around the first maximum. As such, I would suggest shrinking the text slightly then applying the row sum logic again. You can do this with morphological binary erosion with a small structuring element... say, a 3 x 3 square. This can be done with imerode for the erosion and using strel to specify the square structuring element.
You'd apply the row sum logic to this new image, but then use these results and draw on the original image. You don't want to operate on this new image because it looks like there are some areas of the text that are a single pixel thick and these will be eliminated after erosion.
Something like this comes to mind:
%// Read image from SO
im = imread('http://s15.postimg.org/cwg2sxnwr/mathworksss.png');
%// Keep a copy of a binary version - also invert for ease
im_bw = ~im2bw(im);
%// Slightly erode the text
im_bw = imerode(im_bw, strel('square', 3));
%// Sum over all of the columns and look for dark pixels
row_sums = sum(im_bw, 2);
%// Sort the column sums in descending order and figure out the two highest sums
[~,ind_sort] = sort(row_sums,'descend');
%// First highest sum is the bottom - mark as red
red_row1 = ind_sort(1);
%// Second highest sum is the middle - mark as red too
red_row2 = ind_sort(2);
%// Find left most and right most black columns
[~,left_most] = find(im_bw,1,'first');
[~,right_most] = find(im_bw,1,'last');
%// Buffer for drawing the line before the first and after the last column
buf = 20;
%// Draw the two red lines
im(red_row1,left_most-buf:right_most+buf,1) = 255;
im(red_row1,left_most-buf:right_most+buf,2:3) = 0;
im(red_row2,left_most-buf:right_most+buf,1) = 255;
im(red_row2,left_most-buf:right_most+buf,2:3) = 0;
%// Show the image
imshow(im);
As you can see, most of the logic is the same. The only thing I really changed was that I eroded the image, sorted the row sums in descending order and extracted the first two locations that are the highest. I then repeated the logic to draw a line through the row, but instead of one row, we have two now.
We get:

Resize an image with bilinear interpolation without imresize

I've found some methods to enlarge an image but there is no solution to shrink an image. I'm currently using the nearest neighbor method. How could I do this with bilinear interpolation without using the imresize function in MATLAB?
In your comments, you mentioned you wanted to resize an image using bilinear interpolation. Bear in mind that the bilinear interpolation algorithm is size independent. You can very well use the same algorithm for enlarging an image as well as shrinking an image. The right scale factors to sample the pixel locations are dependent on the output dimensions you specify. This doesn't change the core algorithm by the way.
Before I start with any code, I'm going to refer you to Richard Alan Peters' II digital image processing slides on interpolation, specifically slide #59. It has a great illustration as well as pseudocode on how to do bilinear interpolation that is MATLAB friendly. To be self-contained, I'm going to include his slide here so we can follow along and code it:
Please be advised that this only resamples the image. If you actually want to match MATLAB's output, you need to disable anti-aliasing.
MATLAB by default will perform anti-aliasing on the images to ensure the output looks visually pleasing. If you'd like to compare apples with apples, make sure you disable anti-aliasing when comparing between this implementation and MATLAB's imresize function.
Let's write a function that will do this for us. This function will take in an image (that is read in through imread) which can be either colour or grayscale, as well as an array of two elements - The image you want to resize and the output dimensions in a two-element array of the final resized image you want. The first element of this array will be the rows and the second element of this array will be the columns. We will simply go through this algorithm and calculate the output pixel colours / grayscale values using this pseudocode:
function [out] = bilinearInterpolation(im, out_dims)
%// Get some necessary variables first
in_rows = size(im,1);
in_cols = size(im,2);
out_rows = out_dims(1);
out_cols = out_dims(2);
%// Let S_R = R / R'
S_R = in_rows / out_rows;
%// Let S_C = C / C'
S_C = in_cols / out_cols;
%// Define grid of co-ordinates in our image
%// Generate (x,y) pairs for each point in our image
[cf, rf] = meshgrid(1 : out_cols, 1 : out_rows);
%// Let r_f = r'*S_R for r = 1,...,R'
%// Let c_f = c'*S_C for c = 1,...,C'
rf = rf * S_R;
cf = cf * S_C;
%// Let r = floor(rf) and c = floor(cf)
r = floor(rf);
c = floor(cf);
%// Any values out of range, cap
r(r < 1) = 1;
c(c < 1) = 1;
r(r > in_rows - 1) = in_rows - 1;
c(c > in_cols - 1) = in_cols - 1;
%// Let delta_R = rf - r and delta_C = cf - c
delta_R = rf - r;
delta_C = cf - c;
%// Final line of algorithm
%// Get column major indices for each point we wish
%// to access
in1_ind = sub2ind([in_rows, in_cols], r, c);
in2_ind = sub2ind([in_rows, in_cols], r+1,c);
in3_ind = sub2ind([in_rows, in_cols], r, c+1);
in4_ind = sub2ind([in_rows, in_cols], r+1, c+1);
%// Now interpolate
%// Go through each channel for the case of colour
%// Create output image that is the same class as input
out = zeros(out_rows, out_cols, size(im, 3));
out = cast(out, class(im));
for idx = 1 : size(im, 3)
chan = double(im(:,:,idx)); %// Get i'th channel
%// Interpolate the channel
tmp = chan(in1_ind).*(1 - delta_R).*(1 - delta_C) + ...
chan(in2_ind).*(delta_R).*(1 - delta_C) + ...
chan(in3_ind).*(1 - delta_R).*(delta_C) + ...
chan(in4_ind).*(delta_R).*(delta_C);
out(:,:,idx) = cast(tmp, class(im));
end
Take the above code, copy and paste it into a file called bilinearInterpolation.m and save it. Make sure you change your working directory where you've saved this file.
Except for sub2ind and perhaps meshgrid, everything seems to be in accordance with the algorithm. meshgrid is very easy to explain. All you're doing is specifying a 2D grid of (x,y) co-ordinates, where each location in your image has a pair of (x,y) or column and row co-ordinates. Creating a grid through meshgrid avoids any for loops as we will have generated all of the right pixel locations from the algorithm that we need before we continue.
How sub2ind works is that it takes in a row and column location in a 2D matrix (well... it can really be any amount of dimensions you want), and it outputs a single linear index. If you're not aware of how MATLAB indexes into matrices, there are two ways you can access an element in a matrix. You can use the row and column to get what you want, or you can use a column-major index. Take a look at this matrix example I have below:
A =
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
If we want to access the number 9, we can do A(2,4) which is what most people tend to default to. There is another way to access the number 9 using a single number, which is A(11)... now how is that the case? MATLAB lays out the memory of its matrices in column-major format. This means that if you were to take this matrix and stack all of its columns together in a single array, it would look like this:
A =
1
6
11
2
7
12
3
8
13
4
9
14
5
10
15
Now, if you want to access element number 9, you would need to access the 11th element of this array. Going back to the interpolation bit, sub2ind is crucial if you want to vectorize accessing the elements in your image to do the interpolation without doing any for loops. As such, if you look at the last line of the pseudocode, we want to access elements at r, c, r+1 and c+1. Note that all of these are 2D arrays, where each element in each of the matching locations in all of these arrays tell us the four pixels we need to sample from in order to produce the final output pixel. The output of sub2ind will also be 2D arrays of the same size as the output image. The key here is that each element of the 2D arrays of r, c, r+1, and c+1 will give us the column-major indices into the image that we want to access, and by throwing this as input into the image for indexing, we will exactly get the pixel locations that we want.
There are some important subtleties I'd like to add when implementing the algorithm:
You need to make sure that any indices to access the image when interpolating outside of the image are either set to 1 or the number of rows or columns to ensure you don't go out of bounds. Actually, if you extend to the right or below the image, you need to set this to one below the maximum as the interpolation requires that you are accessing pixels to one over to the right or below. This will make sure that you're still within bounds.
You also need to make sure that the output image is cast to the same class as the input image.
I ran through a for loop to interpolate each channel on its own. You could do this intelligently using bsxfun, but I decided to use a for loop for simplicity, and so that you are able to follow along with the algorithm.
As an example to show this works, let's use the onion.png image that is part of MATLAB's system path. The original dimensions of this image are 135 x 198. Let's interpolate this image by making it larger, going to 270 x 396 which is twice the size of the original image:
im = imread('onion.png');
out = bilinearInterpolation(im, [270 396]);
figure;
imshow(im);
figure;
imshow(out);
The above code will interpolate the image by increasing each dimension by twice as much, then show a figure with the original image and another figure with the scaled up image. This is what I get for both:
Similarly, let's shrink the image down by half as much:
im = imread('onion.png');
out = bilinearInterpolation(im, [68 99]);
figure;
imshow(im);
figure;
imshow(out);
Note that half of 135 is 67.5 for the rows, but I rounded up to 68. This is what I get:
One thing I've noticed in practice is that upsampling with bilinear has decent performance in comparison to other schemes like bicubic... or even Lanczos. However, when you're shrinking an image, because you're removing detail, nearest neighbour is very much sufficient. I find bilinear or bicubic to be overkill. I'm not sure about what your application is, but play around with the different interpolation algorithms and see what you like out of the results. Bicubic is another story, and I'll leave that to you as an exercise. Those slides I referred you to does have material on bicubic interpolation if you're interested.
Good luck!

How can I "plot" an image on top of another image with a different colormap?

I've got two images, one 100x100 that I want to plot in grayscale and one 20x20 that I want to plot using another colormap. The latter should be superimposed on the former.
This is my current attempt:
A = randn(100);
B = ones(20);
imagesc(A);
colormap(gray);
hold on;
imagesc(B);
colormap(jet);
There are a couple of problems with this:
I can't change the offset of the smaller image. (They always share the upper-left pixel.)
They have the same colormap. (The second colormap changes the color of all pixels.)
The pixel values are normalised over the composite image, so that the first image changes if the second image introduces new extreme values. The scalings for the two images should be separate.
How can I fix this?
I want an effect similar to this, except that my coloured overlay is rectangular and not wibbly:
Just change it so that you pass in a full and proper color matrix for A (i.e. 100x100x3 matrix), rather than letting it decide:
A = rand(100); % Using rand not randn because image doesn't like numbers > 1
A = repmat(A, [1, 1, 3]);
B = rand(20); % Changed to rand to illustrate effect of colormap
imagesc(A);
hold on;
Bimg = imagesc(B);
colormap jet;
To set the position of B's image within its parent axes, you can use its XData and YData properties, which are both set to [1 20] when this code has completed. The first number specifies the coordinate of the leftmost/uppermost point in the image, and the second number the coordinate of the rightmost/lowest point in the image. It will stretch the image if it doesn't match the original size.
Example:
xpos = get(Bimg, 'XData');
xpos = xpos + 20; % shift right a bit
set(Bimg, 'XData', xpos);

image's sub-image find and replace

How can I detect a known image or pattern within an image so that I can replace it with my own text?
Input option 1 (detect aaa and bbb separately):
Input option 2 (detect red value and blue value separately):
Output:
Running code
I'll show you the code and results running in Mathematica using your option 2.
First we read the image
m = Import#"C:\\imagereplace.png"
Then we separate the channels RGB
ColorSeparate[m]
Obtaining
Next we correlate the red channel image (the one to the right) with a Box Matrix, retaining only the rectangular shape. And transform the result to a B&W image.
Binarize#ImageCorrelate[m1[[3]], BoxMatrix[3]] ;
Obtaining a full size image but containing only the black rectangle.
Now we find the edges of the rectangle (just a loop).
Having the size and coordinates of the rectangles, we create a raster image of the text, corresponding to the detected size, getting:
r1 = Binarize#Rasterize["My Text", RasterSize -> {jmax-jmin + 1, imax-imin + 1},
ImageSize -> {jmax-jmin + 1, imax- imin + 1}]
Now we replace the data block with the new one. Obtaining:
I'll not do the Blue channel, as it is the same thing.
HTH!!
Note: The Image correlation is the only trick used. The rest is code as usual. Here you can find the basics about correlation.

Resources