Matlab range - gamma correction - image

Could you tell me how to take a range of an image? I want to do a gamma correction of "cameraman.tif" for pixels in range 0:120 gamma = 1.8, for pixels in range 120:255 gamma = 0.5
But all pixels go to first if statement so I am unable to apply second gamma.
a = imread('cameraman.tif');
gamma1 = 2;
gamma2 = 0.5;
N =size(a);
out = zeros(N);
for i=1:N
for j=1:N
temp=a(i,j);
if temp>0&temp<120
out(i,j)=temp.^2;
end
if temp>120&temp<=255
out(kx,ky)=temp.^0.5;
end
end
end
imshow(out)

Your second if statement uses access variables kx and ky.... I'm assuming you wanted to use i and j:
out(i,j)=temp.^0.5;
You also must make sure that the intensity is double precision for the square root to work. Therefore make sure that the intensity read in per location is cast to double, then convert back to uint8 when you're done. Actually, do the conversion after you sweep through the whole image.
for i=1:N
for j=1:N
temp=double(a(i,j)); % Change
if temp>0&temp<120
out(i,j)=temp.^2;
end
if temp>120&temp<=255
out(i,j)=temp.^0.5; % Change
end
end
end
out = uint8(out); % Change
kx and ky were set somewhere else in your code and are never changing, so this means that if and when the second if statement does happen, the setting of the gamma only happens at one spot only defined at kx and ky. My advice to you would be to write an actual function so that you aren't cross-contaminating variables in different workspaces. Encompassing this in a function would have given you an error immediately telling you that kx and ky are not defined.
BTW, I would rather you do this more efficiently without loops. You can very easily perform the same operations vectorized. However, this requires that you convert the image to double as the default type is uint8 for the Cameraman image. Therefore, use double to convert an image to double, do the gamma correction, then convert back using uint8:
a = double(imread('cameraman.tif'));
out = zeros(size(a));
out(a > 0 & a < 120) = a(a > 0 & a < 120).^2;
out(a >= 120 & a <= 255) = a((a >= 120 & a <= 255).^0.5;
out = uint8(out);
The first and second line of code are of course familiar. The third line of code finds a logical mask where we search for intensities between 0 and 120 exclusive. Once we find those values, we use the same logical mask to index into the original image and only access those values, square each value and set them in the same spatial locations at the output. The same can be said for the last line of code where you're searching between 120 and 255 but you are taking the square root instead. We finally convert to uint8 for display.

Related

How to create a mask or detect image section based on the intensity value?

I have a matrix named figmat from which I obtain the following pcolor plot (Matlab-Version R 2016b).
Basically I only want to extract the bottom red high intensity line from this plot.
I thought of doing it in some way of extracting the maximum values from the matrix and creating some sort of mask on the main matrix. But I'm not understanding a possible way to achieve this. Can it be accomplished with the help of any edge/image detection algorithms?
I was trying something like this with the following code to create a mask
A=max(figmat);
figmat(figmat~=A)=0;
imagesc(figmat);
But this gives only the boundary of maximum values. I also need the entire red color band.
Okay, I assume that the red line is linear and its values can uniquely be separated from the rest of the picture. Let's generate some test data...
[x,y] = meshgrid(-5:.2:5, -5:.2:5);
n = size(x,1)*size(x,2);
z = -0.2*(y-(0.2*x+1)).^2 + 5 + randn(size(x))*0.1;
figure
surf(x,y,z);
This script generates a surface function. Its set of maximum values (x,y) can be described by a linear function y = 0.2*x+1. I added a bit of noise to it to make it a bit more realistic.
We now select all points where z is smaller than, let's say, 95 % of the maximum value. Therefore find can be used. Later, we want to use one-dimensional data, so we reshape everything.
thresh = min(min(z)) + (max(max(z))-min(min(z)))*0.95;
mask = reshape(z > thresh,1,n);
idx = find(mask>0);
xvec = reshape(x,1,n);
yvec = reshape(y,1,n);
xvec and yvec now contain the coordinates of all values > thresh.
The last step is to do some linear polynomial over all points.
pp = polyfit(xvec(idx),yvec(idx),1)
pp =
0.1946 1.0134
Obviously these are roughly the coefficients of y = 0.2*x+1 as it should be.
I do not know, if this also works with your data, since I made some assumptions. The threshold level must be chosen carefully. Maybe some preprocessing must be done to dynamically detect this level if you really want to process your images automatically. There might also be a simpler way to do it... but for me this one was straight forward without the need of any toolboxes.
By assuming:
There is only one band to extract.
It always has the maximum values.
It is linear.
I can adopt my previous answer to this case as well, with few minor changes:
First, we get the distribution of the values in the matrix and look for a population in the top values, that can be distinguished from the smaller values. This is done by finding the maximum value x(i) on the histogram that:
Is a local maximum (its bin is higher than that of x(i+1) and x(i-1))
Has more values above it than within it (the sum of the height of bins x(i+1) to x(end) < the height of bin x):
This is how it is done:
[h,x] = histcounts(figmat); % get the distribution of intesities
d = diff(fliplr(h)); % The diffrence in bin height from large x to small x
band_min_ind = find(cumsum(d)>size(figmat,2) & d<0, 1); % 1st bin that fit the conditions
flp_val = fliplr(x); % the value of x from large to small
band_min = flp_val(band_min_ind); % the value of x that fit the conditions
Now we continue as before. Mask all the unwanted values, interpolate the linear line:
mA = figmat>band_min; % mask all values below the top value mode
[y1,x1] = find(mA,1); % find the first nonzero row
[y2,x2] = find(mA,1,'last'); % find the last nonzero row
m = (y1-y2)/(x1-x2); % the line slope
n = y1-m*x1; % the intercept
f_line = #(x) m.*x+n; % the line function
And if we plot it we can see the red line where the band for detection was:
Next, we can make this line thicker for a better representation of this line:
thick = max(sum(mA)); % mode thickness of the line
tmp = (1:thick)-ceil(thick/2); % helper vector for expanding
rows = bsxfun(#plus,tmp.',floor(f_line(1:size(A,2)))); % all the rows for each column
rows(rows<1) = 1; % make sure to not get out of range
rows(rows>size(A,1)) = size(A,1); % make sure to not get out of range
inds = sub2ind(size(A),rows,repmat(1:size(A,2),thick,1)); % convert to linear indecies
mA(inds) = true; % add the interpolation to the mask
result = figmat.*mA; % apply the mask on figmat
Finally, we can plot that result after masking, excluding the unwanted areas:
imagesc(result(any(result,2),:))

How to add a Gaussian shaped object to an image?

I am interested in adding a single Gaussian shaped object to an existing image, something like in the attached image. The base image that I would like to add the object to is 8-bit unsigned with values ranging from 0-255. The bright object in the attached image is actually a tree represented by normalized difference vegetation index (NDVI) data. The attached script is what I have have so far. How can I add a a Gaussian shaped abject (i.e. a tree) with values ranging from 110-155 to an existing NDVI image?
Sample data available here which can be used with this script to calculate NDVI
file = 'F:\path\to\fourband\image.tif';
[I R] = geotiffread(file);
outputdir = 'F:\path\to\output\directory\'
%% Make NDVI calculations
NIR = im2single(I(:,:,4));
red = im2single(I(:,:,1));
ndvi = (NIR - red) ./ (NIR + red);
ndvi = double(ndvi);
%% Stretch NDVI to 0-255 and convert to 8-bit unsigned integer
ndvi = floor((ndvi + 1) * 128); % [-1 1] -> [0 256]
ndvi(ndvi < 0) = 0; % not really necessary, just in case & for symmetry
ndvi(ndvi > 255) = 255; % in case the original value was exactly 1
ndvi = uint8(ndvi); % change data type from double to uint8
%% Need to add a random tree in the image here
%% Write to geotiff
tiffdata = geotiffinfo(file);
outfilename = [outputdir 'ndvi_' '.tif'];
geotiffwrite(outfilename, ndvi, R, 'GeoKeyDirectoryTag', tiffdata.GeoTIFFTags.GeoKeyDirectoryTag)
Your post is asking how to do three things:
How do we generate a Gaussian shaped object?
How can we do this so that the values range between 110 - 155?
How do we place this in our image?
Let's answer each one separately, where the order of each question builds on the knowledge from the previous questions.
How do we generate a Gaussian shaped object?
You can use fspecial from the Image Processing Toolbox to generate a Gaussian for you:
mask = fspecial('gaussian', hsize, sigma);
hsize specifies the size of your Gaussian. You have not specified it here in your question, so I'm assuming you will want to play around with this yourself. This will produce a hsize x hsize Gaussian matrix. sigma is the standard deviation of your Gaussian distribution. Again, you have also not specified what this is. sigma and hsize go hand-in-hand. Referring to my previous post on how to determine sigma, it is generally a good rule to set the standard deviation of your mask to be set to the 3-sigma rule. As such, once you set hsize, you can calculate sigma to be:
sigma = (hsize-1) / 6;
As such, figure out what hsize is, then calculate your sigma. After, invoke fspecial like I did above. It's generally a good idea to make hsize an odd integer. The reason why is because when we finally place this in your image, the syntax to do this will allow your mask to be symmetrically placed. I'll talk about this when we get to the last question.
How can we do this so that the values range between 110 - 155?
We can do this by adjusting the values within mask so that the minimum is 110 while the maximum is 155. This can be done by:
%// Adjust so that values are between 0 and 1
maskAdjust = (mask - min(mask(:))) / (max(mask(:)) - min(mask(:)));
%//Scale by 45 so the range goes between 0 and 45
%//Cast to uint8 to make this compatible for your image
maskAdjust = uint8(45*maskAdjust);
%// Add 110 to every value to range goes between 110 - 155
maskAdjust = maskAdjust + 110;
In general, if you want to adjust the values within your Gaussian mask so that it goes from [a,b], you would normalize between 0 and 1 first, then do:
maskAdjust = uint8((b-a)*maskAdjust) + a;
You'll notice that we cast this mask to uint8. The reason we do this is to make the mask compatible to be placed in your image.
How do we place this in our image?
All you have to do is figure out the row and column you would like the centre of the Gaussian mask to be placed. Let's assume these variables are stored in row and col. As such, assuming you want to place this in ndvi, all you have to do is the following:
hsizeHalf = floor(hsize/2); %// hsize being odd is important
%// Place Gaussian shape in our image
ndvi(row - hsizeHalf : row + hsizeHalf, col - hsizeHalf : col + hsizeHalf) = maskAdjust;
The reason why hsize should be odd is to allow an even placement of the shape in the image. For example, if the mask size is 5 x 5, then the above syntax for ndvi simplifies to:
ndvi(row-2:row+2, col-2:col+2) = maskAdjust;
From the centre of the mask, it stretches 2 rows above and 2 rows below. The columns stretch from 2 columns to the left to 2 columns to the right. If the mask size was even, then we would have an ambiguous choice on how we should place the mask. If the mask size was 4 x 4 as an example, should we choose the second row, or third row as the centre axis? As such, to simplify things, make sure that the size of your mask is odd, or mod(hsize,2) == 1.
This should hopefully and adequately answer your questions. Good luck!

once added,how to get back the original images using matlab?

A = imread('ab.jpg');
A = imresize(A,[255 255]); subplot(2,2,1), imshow(A);
B = imread('cd.jpg');
B = imresize(B,[255 255]);subplot(2,2,2), imshow(B);
C = imadd(A,B);subplot(2,2,3),imshow(C);
This is the program i have used to add two images and it is working fine. Now i want to get back both the images. Am not able to figure out how to get it back without taking one of the images as reference (ie subtracting image A from the sum to get image B). Can anyone please help?
Let's not worry about images. Say I have two real integers, x and y. I add them to get z = x+y. Now if I tell someone the integer z is there any way for him or her to get back x and/or y?
Potentially you can retrieve one of the numbers out of the summation, by having the other. But the issue you're facing with is the fact that your image matrices cannot exceed the value 255 since MATLAB uses uint8 type by default. Here is an example for two corresponding pixels in image A and B:
% A(1,1) == 130 is ture
% B(1,1) == 180 is ture
C(1,1) = A(1,1) + B(1,1); %C(1,1) == 310 is false! c(1,1) == 255 is true
expectedB = C(1,1) - A(1,1); % expectedB == 180 is false! expectedB == 125 is true
You can cast the type of your matrices to int16. Whereas by doing so MATLAB's functions sich as imshow do not work as you want them to. Because they assume that your

speed up matlab code for eliminating white pixels

I have rgb images from a camera which contain white pixels. I wrote the following code to eliminate them. It works but takes forever.
% elliminate white pixel
while 1
maxValue = max(imageRGB(:));
[maxY maxX] = maxPosition(squeeze(imageRGB(:,:,c)));
surr = 2;
x_l = maxX - surr; if x_l < 1, x_l = 1; end
x_r = maxX + surr; if x_r > size(imageRGB,2), x_r = size(imageRGB,2); end
y_u = maxY - surr; if y_u < 1, y_u = 1; end
y_b = maxY + surr; if y_b > size(imageRGB,1), y_b = size(imageRGB,1); end
meanArea = ((y_b-y_u)+1) * ((x_r-x_l)+1) - 1;
mean = (sum(sum(imageRGB(y_u:y_b, x_l:x_r,c))) - maxValue)/meanArea;
if (maxValue/mean > 1.5)
imageRGB(maxY,maxX,c) = mean;
else
break;
end
end
Any ideas how to speed up this codeĀ“?
Correct me if I'm wrong, or ignore this 'answer' entirely, but the code posted appears to:
Find the most white pixel in the image (I'm guessing here, imageRGB isn't a Matlab built-in).
Find the position in the image of the most white pixel (another guess, another unknown function maxPosition).
Do some sort of averaging to replace the most-white pixel with an average over its immediate neighbourhood.
Repeat the process until the stopping criterion is satisfied.
If you have the Image Processing Toolbox, you will find that it has all sorts of functions for adjusting pixel intensity which is, I think, what you are trying to do, so you can stop reading this answer now. If you don't have the toolbox, read on.
If you can, you should amend your entire approach and decide, from one read of the image, what the threshold for averaging should be. This would lift the computation of maxValue out of the loop, maybe replace it by a single computation of thresholdValue. Then you could lift the calculation of [maxY maxX] out of the loop too.
If you can't do this, there are still some options for increasing the speed of your operations. You could either:
Pad the image with a 2-pixel halo all round before starting operations. Then apply your operation to all the white pixels in the original image. Obviously you'll have to set the halo pixels to the right value to leave your operation unchanged.
or
Operate only on the pixels in the image which are not within 2 pixels of the edge. This will produce an output image which is 4 pixels smaller in each dimension, but on large images this is often not a problem.
Either of these eliminates a whole slew of if statements and the repeated calculation of meanArea (since it becomes a constant).
If you can calculate a threshold once, at the start of processing, rather than recalculating it iteratively you might find that you can write a function to implement the averaging which you can apply to all the pixels in the image, and eliminate the need to find the white pixels. The function would have to leave the un-white pixels unchanged of course. Applying an operation to every pixel, ensuring that it is a null-operation for the pixels which should not be changed (or an identity operation for those pixels) is sometimes faster than first finding the pixels that need to be changed and then applying the operation only to those pixels.
From what I know if perform poorly.
you could replace
x_l = maxX - surr; if x_l < 1, x_l = 1; end
with
x_l = max(maxX - surr,1);
and the others analogous.
Also you could put the (maxValue/mean > 1.5) in the condition for the while loop.
in the lines
maxValue = max(imageRGB(:));
[maxY maxX] = maxPosition(squeeze(imageRGB(:,:,c)));
you search for max twice. I suppose you could save some time if you write it like:
[maxY maxX] = maxPosition(squeeze(imageRGB(:,:,c)));
maxValue = imageRGB(maxY,maxX,c);
Another possibility would be to remove the sorting and just calculate the average on the whole image. This is easily done with conv2 which is a built-in and therefor very fast compared to anything anyone of us could cook up.
Assuming you are working with double gray-scale images:
% generate an averageing filter
filterMat=ones(2*filterSize+1);
filterMat=filterMat/sum(filterMat(:));
% convolve with image
meanComplete=conv2(picture,filterMat,'same');
% calculate the decision criterion
changeIndices=picture./meanComplete>relThreshold & picture>absThreshold;
% use logical indexing to replace white pixels with the mean
newPicture=picture;
newPicture(changeIndices)=mean(changeIndices);
I need 50ms for one Full-HD image.

dohist on a double - matlab

how do i convert an image represented as double into an image that i can use to produce a histogram?
(with dohist:)
% computes the histogram of a given image into num bins.
% values less than 0 go into bin 1, values bigger than 255
% go into bin 255
% if show=0, then do not show. Otherwise show in figure(show)
function thehist = dohist(theimage,show)
% set up bin edges for histogram
edges = zeros(256,1);
for i = 1 : 256;
edges(i) = i-1;
end
[R,C] = size(theimage);
imagevec = reshape(theimage,1,R*C); % turn image into long array
thehist = histc(imagevec,edges)'; % do histogram
if show > 0
figure(show)
clf
pause(0.1)
plot(thehist)
axis([0, 256, 0, 1.1*max(thehist)])
end
I am guessing that you just need to normalize your image first, to do this you can use:
255*(theimage./(max(theimage(:)));
Your code seems fine, you could make sure the bounds get treated correctly with:
theimage(theimage<0) = 0;
theimage(theimage>255) = 255;
But this shouldnt be necessary, usually you either get a double image ranging [0,1] or uint8 [0,255] when you read an image with imread(). Just rescale to [0,255] in this case if needed.
Some other tips:
You can make the edges-vector like this:
edges = 0:255;
And theimage(:) is the same as reshape(theimage,1,R*C) in this case since you want one long vector.
The built-in function hist can be applied directly to images of class double.
Matlab documentation link
If you have an image which you suspect to have N bits of resolution on the interval [A,B], you can call hist directly on the image (without conversion) like:
[H,bins] = hist(IM,linspace(A,B,2^N));
to retrieve the histogram and bins or
hist(IM,linspace(A,B,2^N));
to simply plot the histogram.

Resources