Subtract background from image - matlab

I am trying to to subtract the background from a picture of an object to leave only the foreground object. I have found the RGB values of the background as 218 220 219 using imshow(). How can I use the RGB values with imsubtract()?
y = [218 220 219];
z = imsubtract(img,y);
Error using imsubtract (line 55)
X and Y must have the same size and class, or Y must be a scalar double.

You can use bsxfun to do that
z = bsxfun( #minus, img, permute( [218 220 219], [1 3 2] ) );
You need to pay attention to data type and range. If img is of type uint8 pixel values will be in range 0..255 but it will be difficult to subtract values as you'll see results underflowing at 0: uint8(4) - uint8(10) is 0...
Thus, you might want to convert img to double using im2double having pixel values in range 0..1. In that case you'll have to convert the "gray" vector [2218 220 219] to 0..1 range by dividing it by 255.
So, a more complete solution would be
z = bsxfun( #minus, im2double(img), permute( [218 220 219]/255, [1 3 2] ) );

The following ended up getting me closer to the answer I was looking for, although not without your guidance!
img = imread('IMG_0792.jpg');
img = im2double(img);
rows = numel(img(:,1,1));
columns = numel(img(1,:,1));
for i = 1:rows
for j = 1:columns
if ( ( img(i,j,1) > 0.75) && ( img(i,j,2) > 0.7) && ( img(i,j,3) > 0.7) )
img(i,j,1) = 1;
img(i,j,2) = 1;
img(i,j,3) = 1;
end
end
end
imshow(img);

Related

Frames of type double must be in the range of 0 to 1: MATLAB

I have a video and I have made a Sobel mask for it on MATLAB. Now I have to apply that Sobel mask on each frame of the video by reading each frame through for loop. The process is something like:
Step 1: Reading frame.
step 2: Converting it to grayscale using rgb2gray.
Step 3: Converting it to double.
Here, after applying the mask when I try to write the frame on the resultant video.avi file, I get the following error:
"Frames of type double must be in the range of 0 to 1"
What is wrong with my code? The code I wrote is shown below:
vid = VideoReader('me.mp4');
frames = read(vid);
total = get(vid, 'NumberOfFrames');
write = VideoWriter('me.avi');
open(write);
mask1 = [-1 -2 -1; 0 0 0; 1 2 1]; % Horizontal mask
mask2 = [-1 0 1; -2 0 2; -1 0 1]; %Vertical Mask
for k = 1 : 125
image = frames(:,:,:,k);
obj = image;
obj1 = rgb2gray(obj);
obj2=double(obj1);
for row = 2 : size(obj2, 1) - 1
for col = 2 : size(obj2, 2) - 1
c1 = obj2(row - 1, col - 1) * mask1(1 ,1);
c2 = obj2(row - 1, col) * mask1(1 ,2);
c3 = obj2(row - 1, col + 1) * mask1(1 ,3);
c4 = obj2(row, col - 1)*mask1(2, 1);
c5 = obj2(row, col)*mask1(2, 2);
c6 = obj2(row, col + 1)*mask1(2, 3);
c7 = obj2(row + 1, col - 1)*mask1(3,1);
c8 = obj2(row + 1, col)*mask1(3,2);
c9 = obj2(row + 1, col + 1)*mask1(3,3);
c11 = obj2(row - 1, col - 1)*mask2(1 , 1);
c22 = obj2(row, col - 1)*mask2(2, 1);
c33 = obj2(row + 1, col - 1)*mask2(3, 1);
c44 = obj2(row -1, col)*mask2(1, 2);
c55 = obj2(row, col)*mask2(2 , 2);
c66 = obj2(row +1, col)*mask2(2 , 3);
c77 = obj2(row - 1, col + 1)*mask2(1 , 3);
c88 = obj2(row, col +1)*mask2(2 , 3);
c99 = obj2(row + 1, col + 1)*mask2(3 , 3);
result = c1 + c2 + c3 +c4 +c5+ c6+ c7+ c8 +c9;
result2 = c11 + c22 + c33 + c44 + c55 + c66 + c77 + c88 + c99;
%result = double(result);
%result2 = double(result2);
rim1(row, col) = ((result^2+result2^2) *1/2);
rim2(row, col) = atan(result/result2);
end
end
writeVideo(write, rim2); %This line has the problem with rim2 as rim2 is the frame i'm trying to write on the video file.
end
close(write);
rim2 has range [-pi/2, pi/2] at the end, which is not compatible with the write function which expects [0,1] range.
Convert it to [0,1] range using the mat2gray function, i.e.
writeVideo(write, mat2gray(rim2));
Your code will then work as expected (confirmed on my machine).
By the way, this doesn't affect your code, but presumably you meant to do im2double(A) rather than double(A). The former produces a "proper" grayscale image in the range [0,1], whereas the latter simply converts your uint8 image in the range [0,255] to double format (i.e. [0.0, 255.0]).
The line of rim2 inside your double for loop is using atan, which will generate values that are both positive and negative - from -pi/2 to +pi/2 exactly. rim2 is expected to have values that are only between [0,1]. I can't figure out what exactly you're doing, but it looks like you're calculating the magnitude and gradient angle at each pixel location. If you want to calculate the magnitude, you have to take the square root of the result, not simply multiply by 1/2. The calculation of the gradient (... or even the whole Sobel filter calculation...) is very funny.
I'll just assume this is working for your purposes so I'm not sure how to change the output of rim2for suitable display but perhaps you could scale it to the range of [0,1] before you write the video so that it's within this range.
Something like this would work before you write the frame:
rim2 = (rim2 - min(rim2(:))) / (max(rim2(:)) - min(rim2(:)));
writeVideo(write, rim2);
The above is your typical min-max normalization that is seen in practice. Specifically, the above will ensure that the smallest value is 0 while the largest value is 1 per frame. If you want to be consistent over all frames, simply add pi/2 then divide by pi. This assumes that the minimum is -1 and the maximum is +1 over all frames however.
rim2 = (rim2 + pi/2) / pi;
writeVideo(write, rim2);
However, I suspect you want to write the magnitude to file, not the angle. Therefore, replace the video writing with rim1 as the frame to write instead of rim2, then normalize after. Make sure your gradient calculation is correct though:
rim1(row, col) = ((result^2+result2^2)^(1/2));
% or use sqrt:
% rim1(row, col) = sqrt(result^2 + result2^2);
Now write to file:
rim1 = (rim1 - min(rim1(:))) / (max(rim1(:)) - min(rim1(:)));
writeVideo(write, rim1);
However, if I can provide a method of efficiency, don't use for loops to compute the gradient and angle. Use conv2 and ensure you use the 'same' flag or imfilter from the image processing toolbox to perform the filtering for you, then calculate the gradient and angle vectorized. Also, convert to grayscale and cast your frame in one go in the main loop. I'll assume you have the image processing toolbox as having the computer vision toolbox (you have this as you're using a VideoWriter object) together with the image processing toolbox is what most people have:
vid = VideoReader('me.mp4');
frames = read(vid);
total = get(vid, 'NumberOfFrames');
write = VideoWriter('me.avi');
open(write);
mask1 = [-1 -2 -1; 0 0 0; 1 2 1]; % Horizontal mask
mask2 = [-1 0 1; -2 0 2; -1 0 1]; %Vertical Mask
for k = 1 : 125
obj2 = double(rgb2gray(frames(:,:,:,k))); % New
grad1 = imfilter(obj2, mask1); % New
grad2 = imfilter(obj2, mask2); % New
rim1 = sqrt(grad1.^2 + grad2.^2); % New
rim2 = atan2(grad1, grad2); % New
% Normalize
rim2 = (rim2 - min(rim2(:))) / (max(rim2(:)) - min(rim2(:)));
writeVideo(write, rim2);
end
close(write);

Image normalization: Issue with arithmetic operation

I'm trying to normalize an image, I applied the formula to do so, however I encounter a weird arithmetic operation problem in doing so.
This is my code:
IM3=imread('mdb022.pgm');
i = IM3(:,:,1);
rtemp = min(i); % find the min. value of pixels in all the columns (row vector)
rmin=min(rtemp) % find the min. value of pixel in the image
rtemp = max(i); % find the max. value of pixels in all the columns (row vector)
rmax=max(rtemp) % find the max. value of pixel in the image
a=255;
b=rmax-rmin
m=a/b % find the slope of line joining point (0,255) to (rmin,rmax)
c = 255 - m*rmax; % find the intercept of the straight line with the axis
i_new = m*i + c; % transform the image according to new slope
On the command window:
>> contrast_stretching_1
rmin =
0
rmax =
221
b =
221
m =
1
For the step m=a/b, the division should be 255 divided by 221, which is equal to 1.1538...., but why does matlab shows 1?
Can somebody enlighten me on this and help me solve this issue?
Thanks!
Change your image to double (or single)
IM3 = imread('mdb022.pgm');
I = double(IM3(:,:,1));
rmin = min(I(:));
rmax = max(I(:));
m = 255.0 ./ (rmax - rmin);
I_new = m.*(I - rmax) + 255.0;
% I_new = uint8(I_new); if you want the image as uint8

Matlab convolutions (Dimension Mismatch Error)

I have to use matlab to find the convolution over the range 0 <= n <= 20.
x[n] = δ[n] + δ[n-2] and h[n] = 2*(3^n)u[n]
I have tried to do this and i was met with a "X is not the same length as Y" when trying to plot it and have tried to correct it. Could someone let me know if this is correct?
n = [0:20];
x =[1 0 1];
h= 2*3.^n;
y = conv(x,h);
ysize = size(y,2)
z = [0:(ysize-1)];
ysize = size (y,2);
p = stem(z ,y ,'r' ,'filled');
set (p, 'LineWidth', 2, 'MarkerSize', 4);
title ('y[n] = x[n] * h[n]');
xlabel ('n');
ylabel ('y[n]');
I have tested your code. And it is giving the following output (no error of size) Code is perfect.
I calculated the convolution online that results the same. Your code is perfect.

How to find neighbors in 4D array in MATLAB?

I am a bit confused and would greatly appreciate some help.
I have read many posts about finding neighboring pixels, with this being extremely helpful:
http://blogs.mathworks.com/steve/2008/02/25/neighbor-indexing-2/
However I have trouble applying it on a 4D matrix (A) with size(A)=[8 340 340 15]. It represents 8 groups of 3D images (15 slices each) of which I want to get the neighbors.
I am not sure which size to use in order to calculate the offsets. This is the code I tried, but I think it is not working because the offsets should be adapted for 4 dimensions? How can I do it without a loop?
%A is a 4D matrix with 0 or 1 values
Aidx = find(A);
% loop here?
[~,M,~,~] =size(A);
neighbor_offsets = [-1, M, 1, -M]';
neighbors_idx = bsxfun(#plus, Aidx', neighbor_offsets(:));
neighbors = B(neighbors_idx);
Thanks,
ziggy
Have you considered using convn?
msk = [0 1 0; 1 0 1; 0 1 0];
msk4d = permute( msk, [3 1 2 4] ); % make it 1-3-3-1 mask
neighbors_idx = find( convn( A, msk4d, 'same' ) > 0 );
You might find conndef useful for defining the basic msk in a general way.
Not sure if I've understood your question but what about this sort of approach:
if you matrix is 1D:
M = rand(10,1);
N = M(k-1:k+1); %//immediate neighbours of k
However this could error if k is at the boundary. This is easy to fix using max and min:
N = M(max(k-1,1):min(k+1,size(M,1))
Now lets add a dimenion:
M = rand(10,10);
N = M(max(k1-1,1):min(k1+1,size(M,1), max(k2-1,1):min(k2+1,size(M,2))
That was easy, all you had to do was repeat the same index making the minor change of using size(M,2) for the boundary (and also I changed k to k1 and k2, you might find using an array for k instead of separate k1 and k2 variables works better i.e. k(1) and k(2))
OK so now lets skip to 4 dimensions:
M = rand(10,10,10,10);
N = M(max(k(1)-1,1):min(k(1)+1,size(M,1)), ...
max(k(2)-1,1):min(k(2)+1,size(M,2)), ...
max(k(3)-1,1):min(k(3)+1,size(M,3)), ...
max(k(4)-1,1):min(k(4)+1,size(M,4))); %// Also you can replace all the `size(M,i)` with `end` if you like
I know you said you didn't want a loop, but what about a really short loop just to refactor a bit and also make it generalized:
n=ndims(M);
ind{n} = 0;
for dim = 1:n
ind{dim} = max(k(dim)-1,1):min(k(dim)+1,size(M,dim));
end
N = M(ind{:});
Here's how to get the neighbors along the second dimension
sz = size( A );
ndims = numel(sz); % number of dimensions
[d{1:ndims}] = ind2sub( sz, find( A ) );
alongD = 2; % work along this dim
np = d{alongD} + 1;
sel = np <= sz( alongD ); % discard neighbors that fall outside image boundary
nm = d{alongD} - 1;
sel = sel & nm > 0; % discard neighbors that fall outside image boundary
d = cellfun( #(x) x(sel), d, 'uni', 0 );
neighbors = cat( 1, ...
ind2sub( sz, d{1:alongD-1}, np(sel), d{alongD+1:end} ),...
ind2sub( sz, d{1:alongD-1}, nm(sel), d{alongD+1:end} ) );

What's wrong with this simple matlab code?

I have a segmented image 'a' of a signature made with a colored pen. The background is pure white. I need to compute the sum of r g b components of the foreground pixels, and the total pixels that constitute the foreground. Here is my code-
r=a(:,:,1);
g=a(:,:,2);
b=a(:,:,3);
rsum=0;
gsum=0;
bsum=0;
count=0;
for i=1:h
for j=1:w
if r(i,j)~=255 || g(i,j)~=255 || b(i,j)~=255
rsum=rsum + r(i,j);
gsum=gsum + g(i,j);
bsum=bsum + b(i,j);
count=count+1;
end
end
end
It computes the value of count correctly but rsum,gsum,bsum are all set to 255 which is clearly wrong. The matrix r,g,b is correct(shows pixels other than 255). Why does is not work?
It seems like the type of rsum, gsum and bsum is uint8 and it is saturated at 255.
Try explicitly cast the sum to a different type.
msk = r < 255 | g < 255 | b < 255;
rsum = sum( double( r(msk) ) );
gsum = sum( double( g(msk) ) );
bsum = sum( double( b(msk) ) );
count = sum(msk(:));
PS,
It is best not to use i and j as variable names in Matlab.