Outputting Regionprops to csv in MATLAB - image

I'm currently running some data analysis on a lot of pictures and the code i have running is the following:
close all
clear all
clc
A=imread('Ring_1_frame_120.jpg'); %Load picture
%A01-A010 = xmin ymin width height
%for all vials
A001=imcrop(A,[65 159 95 332]);
A002=imcrop(A,[182 161 95 332]);
A003=imcrop(A,[297 164 95 332]);
A004=imcrop(A,[402 165 90 332]);
A005=imcrop(A,[495 168 90 332]);
A006=imcrop(A,[606 166 90 332]);
A007=imcrop(A,[705 171 90 332]);
A008=imcrop(A,[808 175 90 332]);
A009=imcrop(A,[922 175 90 332]);
A0010=imcrop(A,[1031 175 90 332]);
w = who; % returns the names of all your current variables in a cell.
for i = 1:numel(w)
% A00 is unique to all the variables you want to process.
if ~isempty(strfind(w{i}, 'A00'))
% hard coding greenChannel and extracting the second plane.
eval(['greenChannel = ',w{i},'(:,:,2)']);
BW = edge(greenChannel,'Prewitt');
%figure, imshow(BW);
%Dialate Lines
se90 = strel('line', 3, 90);
se0 = strel('line', 3, 0);
BWsdil = imdilate(BW, [se90 se0]);
%figure, imshow(BWsdil), title('dilated gradient mask');
%Fill Lines
BWdfill = imfill(BWsdil, 'holes');
%figure, imshow(BWdfill), title('binary image with filled holes');
%Clean up borders
BWnobord = imclearborder(BWdfill, 4);
%figure, imshow(BWnobord), title('cleared border image');
%Final cleanup
seD = strel('diamond',1);
BWfinal = imerode(BWnobord,seD);
BWfinal = imerode(BWfinal,seD);
figure, imshow(BWfinal), title('segmented image');
L = bwlabel(BWfinal);
s = regionprops(L,'centroid');
data(:,:,i) = s; %save the xy coords as data matrix
end
end
The goal I'm trying to achieve is getting the variable s into a csv file, but I'm stuck at the last line since it's not working. It keeps overwriting itself. s is a structure ranging from 3x1 to 5x1 and I have also tried to use struct2cell and mat2cell but that was unsuccessful.

s is a structure, so what you need to do is unpack the structure so that it becomes a matrix, then you can save the matrix to file. s contains a field called Centroid, so you need to access that field.
However before I address that point, checking to see how many variables are in your workspace so you can determine how many times your loop has to iterate.... is very bad practice. Especially if you are using each variable name as a separate occurrence for processing. I highly recommend you use a structure to encapsulate this or some sort of cell array.
If I can provide a canonical post, please consult user Adriaan's excellent post on how to avoid dynamic variable names and sheds light on what I'm about to talk about here.
Something like this would work instead. I'll use a cell array because (at least to me) it is easier. Place your desired coordinates in a 2D matrix where each row is the top-left corner of the location in the image you want to process as well as the width and height (basically suitable for imcrop), then loop over each set of coordinates and place the cropped image as an element in a cell array. Cell array use is important because the dimensions per cropped image are different and so you can't use a normal matrix here:
A=imread('Ring_1_frame_120.jpg'); %Load picture
%A01-A010 = xmin ymin width height
coords = [65 159 95 332; 182 161 95 332; 297 164 95 332; 402 165 90 332;...
495 168 90 332; 606 166 90 332; 705 171 90 332; 808 175 90 332;...
922 175 90 332; 1031 175 90 332];
numImages = size(coords,1);
images = cell(1,numImages);
for ii = 1 : numImages
images{ii} = imcrop(A,coords(ii,:));
end
images is now a cell array of cropped images that belong to the image A. To access the right image, you can use images to do that like so:
img = images{ii};
ii is the image number you wish to access. Another comment I'd like to make is your use of eval. It is really not recommended in your loop either... which is why I decided to change the logic.
Do this instead:
for ii = 1 : numImages
% hard coding greenChannel and extracting the second plane.
greenChannel = images{ii}(:,:,2); %// Change for green channel
%// Now code is the same as before
BW = edge(greenChannel,'Prewitt');
%figure, imshow(BW);
%Dilate Lines
se90 = strel('line', 3, 90);
se0 = strel('line', 3, 0);
BWsdil = imdilate(BW, [se90 se0]);
%figure, imshow(BWsdil), title('dilated gradient mask');
%Fill Lines
BWdfill = imfill(BWsdil, 'holes');
%figure, imshow(BWdfill), title('binary image with filled holes');
%Clean up borders
Wnobord = imclearborder(BWdfill, 4);
%figure, imshow(BWnobord), title('cleared border image');
%Final cleanup
seD = strel('diamond',1);
BWfinal = imerode(BWnobord,seD);
BWfinal = imerode(BWfinal,seD);
figure, imshow(BWfinal), title('segmented image');
...
end
Alright, so now how do we get the coordinates of the centroid and save them to file? You simply need to unpack the structure and get the centroid coordinates. Make sure data is declared at the top is now a cell array:
data = cell(1, numImages);
The reason why you need a cell array (again) is because you don't know how many segmented components there are per cropped image you're looking at. Now finally at the end of your loop:
for ii = 1 : numImages
%// Your code...
%//...
L = bwlabel(BWfinal);
s = regionprops(L,'centroid');
%// New code
data{ii} = reshape([s.Centroid],2,[]).';
end
Now that you have the centroid coordinates stored in a cell array per cropped image, you can either create multiple CSVs where each CSV contains the centroids of each detected object for each cropped image, or you can concatenate all of the centroids together in a single matrix.
So, do either:
for ii = 1 : numImages
csvwrite(sprintf('data%d.csv', ii), data{ii});
end
... or
out = cat(1, data{:});
csvwrite('data.csv', out);
I'm not sure which method you want to use to write to file, but either of those should work.

You need to access struct elements using s(i).Centroid, as a minimal example,
a =imread('circlesBrightDark.png');
bw = a < 100;
s = regionprops(bw,'centroid');
for i =1:size(s)
data(:,:,i) = s(i).Centroid
end

Related

Object Displacement between frames using cross correlation in matlab

I would like to estimate the displacement of on object ( This object is a curve along the image) and i want to know how much it moves using cross correlation between successive frames:
I have a problem to determine the right size of the template ,when i change the template size , it give different results , can you help me
a = imread('001.jpg');
a1 = imread('002.jpg');
im1 = imcrop(a,[500 400 200 100]); % Template
im2 = imcrop(a1,[300 300 820 360]);
c = normxcorr2(im1,im2);
figure, surf(c), shading flat
%offset found by correlation
[max_c, imax] = max(abs(c(:)));
[ypeak, xpeak] = ind2sub(size(c),imax(1));
corr_offset = [(xpeak-size(im1,2))
(ypeak-size(im1,1))];
% relative offset of position of subimages
rect_offset = [(rect_a1(1)-rect_a(1))
(rect_a1(2)-rect_a(2))];
% total offset
offset = corr_offset + rect_offset;
xoffset = offset(1);
yoffset = offset(2);

Mid line through a set of dicom images in matlab

I have a set of Dicom images on matlab and i would like to add a midline going through all the images
I am outputting the images via imshow3d function
thanks
Edit: here's what i have, the random points are not in the middle they just run through the image
>> clc;
>>clear;
>>%imports dicom images
>>run DicomImport.m;
>>%random points for shortest distance test
>>a = [1 10 200];
>>b = [500 512 300];
>>ab = b - a;
>>n = max(abs(ab)) + 1;
>>s = repmat(linspace(0, 1, n)', 1, 3);
>>for d = 1:3
>> s(:, d) = s(:, d) * ab(d) + a(d);
>>end
>>s = round(s);
>>Z = 593;
>>N = 512;
>>X = zeros(N, N, Z);
>>X(sub2ind(size(X), s(:, 1), s(:, 2), s(:, 3))) = 1;
>>C = find(X);
>>ans.Img(C) = 5000;
>> %shows image
>>imshow3D(ans.Img);
So it looks like ans.Img contains the 3D matrix consisting of your image stack. It looks like you've got something going, but allow me to do this a bit differently. Basically, you need to generate a set of coordinates where we can access the image stack and draw a vertical line in the middle of the each image in the image stack. Do something like this. First get the dimensions of the stack, then determine the halfway point for the columns. Next, generate a set of coordinates that will draw a line down the middle for one image. After you do this, repeat this for the rest of the slices and get the column major indices for these:
%// Get dimensions
[rows,cols,slices] = size(ans.Img);
%// Get halfway point for columns
col_half = floor(cols/2);
%// Generate coordinates for vertical line for one slice
coords_middle_row = (1:rows).';
coords_middle_col = repmat(col_half, rows, 1);
%// Generate column major indices for the rest of the slices:
ind = sub2ind(size(ans.Img), repmat(coords_middle_row, slices, 1), ...
repmat(coords_middle_col, slices, 1), ...
reshape(kron(1:slices, ones(rows, 1)), [], 1));
%// Set the pixels accordingly
ans.Img(ind) = 5000;
This code is quite similar to the answer I provided to one of your earlier question; i.e. I don't use imshow3D but the framework is similar and simpler to modify in order to suit your need. In this case, upon pressing a pushbutton a line appears at the middle of the stack and you can scroll through it with the slider. I hope this can be of help.
function LineDicom(~)
clc
clear
close all
%// Load demo data
S = load('mri');
%// Get dimensions and number of slices.
ImageHeight = S.siz(1); %// Not used here
ImageWidth = S.siz(2); %// Not used here
NumSlices = S.siz(3);
S.D = squeeze(S.D);
%// Create GUI
hFig = figure('Position',[100 100 400 400],'Units','normalized');
%// create axes with handle
handles.axes1 = axes('Position', [0.2 0.2 0.6 0.6]);
%// create y slider with handle
handles.y_slider = uicontrol('style', 'Slider', 'Min', 1, 'Max', NumSlices, 'Value',1, 'Units','normalized','position', [0.08 0.2 0.08 0.6], 'callback', #(s,e) UpdateY);
handles.SlideryListener = addlistener(handles.y_slider,'Value','PostSet',#(s,e) YListenerCallBack);
%// Create pusbutton to draw line
handles.DrawLineButton= uicontrol('style', 'push','position', [40 40 100 30],'String','Draw line', 'callback', {#DrawLine,handles});
%// Flag to know whether pushbutton has been pushed
handles.LineDrawn = false;
%// Show 1st slice
imshow(S.D(:,:,1))
guidata(hFig,handles);
%// Listeners callbacks followed by sliders callbacks. Used to display each
%// slice smoothly.
function YListenerCallBack
handles = guidata(hFig);
%// Get current slice
CurrentSlice = round(get(handles.y_slider,'value'));
hold on
imshow(S.D(:,:,CurrentSlice));
%// If button was button, draw line
if handles.LineDrawn
line([round(ImageWidth/2) round(ImageWidth/2)],[1 ImageHeight],'Color','r','LineWidth',2);
end
drawnow
guidata(hFig,handles);
end
function UpdateY(~)
handles = guidata(hFig); %// Get handles.
CurrentSlice = round(get(handles.y_slider,'value'));
hold on
imshow(S.D(:,:,CurrentSlice));
if handles.LineDrawn
line([round(ImageWidth/2) round(ImageWidth/2)],[1 ImageHeight],'Color','r','LineWidth',2);
end
drawnow
guidata(hFig,handles);
end
%// Pushbutton callback to draw line.
function DrawLine(~,~,handles)
line([round(ImageWidth/2) round(ImageWidth/2)],[1 ImageHeight],'Color','r','LineWidth',2);
handles.LineDrawn = true;
guidata(hFig,handles);
end
end
Sample output:
and after moving the slider up:
Is this what you meant? If not I'll remove that answer haha and sorry.

Superimpose images and create a new image MATLAB

im1 = grayscale images
im2 = im2bw(im1, graythresh)
Can I superimpose im1 and im2 to get a 3rd image with the features from im1 and im2 according to a specified interval of graylevel ?
For example, I choose an interval of gray level between 110 and 120. All the pixel from im1 which have a value between 110 and 120 will keep their initial gray value and all the other one will keep their binary value (from im2).
I'm new to matlab.
Thank you.
Do you really need to threshold using im2bw?
If not, I would recommend something like this (using your example threshold values):
im1(im1<110) = 0;
im1(im1>120) = 255;
Every value below 110 will be set to zero, every value above 120 will be set to 255 (maximum gray level) and the rest remains uneffected.
I hope this is your wanted result. You do not need to superimpose images.
If you really want to use im2bw (uses a more advanced threshold method) and superimpose the images im1 and im2 you can go with this:
im1(im1<110 | im1>120) = 0;
im2(im1>=110 & im1<=120) = 0;
im3 = im1 + im2uint8(im2);
This isn't tough to do in Matlab, see this example:
range = [90 150];
grayThresh = 115;
im1 = imread('rice.png');
im2 = (im1 > grayThresh) * 255;
im3 = im1;
binaryPlaces = im1 < range(1) | im1 > range(2);
im3(binaryPlaces) = im2(binaryPlaces);
imshow(im3);
The hardest part is keeping track of what data type each of your images are. If the image is a double, then 0 is black and 1 is white, but if the image is a uint8, then 0 is black and 255 is white. Because the test image rice.png (included with Matlab) loads as a uint8, we need multiply to im2 by 255.

Separating Background and Foreground

I am new to Matlab and to Image Processing as well. I am working on separating background and foreground in images like this
I have hundreds of images like this, found here. By trial and error I found out a threshold (in RGB space): the red layer is always less than 150 and the green and blue layers are greater than 150 where the background is.
so if my RGB image is I and my r,g and b layers are
redMatrix = I(:,:,1);
greenMatrix = I(:,:,2);
blueMatrix = I(:,:,3);
by finding coordinates where in red, green and blue the values are greater or less than 150 I can get the coordinates of the background like
[r1 c1] = find(redMatrix < 150);
[r2 c2] = find(greenMatrix > 150);
[r3 c3] = find(blueMatrix > 150);
now I get coordinates of thousands of pixels in r1,c1,r2,c2,r3 and c3.
My questions:
How to find common values, like the coordinates of the pixels where red is less than 150 and green and blue are greater than 150?
I have to iterate every coordinate of r1 and c1 and check if they occur in r2 c2 and r3 c3 to check it is a common point. but that would be very expensive.
Can this be achieved without a loop ?
If somehow I came up with common points like [commonR commonC] and commonR and commonC are both of order 5000 X 1, so to access this background pixel of Image I, I have to access first commonR then commonC and then access image I like
I(commonR(i,1),commonC(i,1))
that is expensive too. So again my question is can this be done without loop.
Any help would be appreciated.
I got solution with #Science_Fiction answer's
Just elaborating his/her answer
I used
mask = I(:,:,1) < 150 & I(:,:,2) > 150 & I(:,:,3) > 150;
No loop is needed. You could do it like this:
I = imread('image.jpg');
redMatrix = I(:,:,1);
greenMatrix = I(:,:,2);
blueMatrix = I(:,:,3);
J(:,:,1) = redMatrix < 150;
J(:,:,2) = greenMatrix > 150;
J(:,:,3) = blueMatrix > 150;
J = 255 * uint8(J);
imshow(J);
A greyscale image would also suffice to separate the background.
K = ((redMatrix < 150) + (greenMatrix > 150) + (blueMatrix > 150))/3;
imshow(K);
EDIT
I had another look, also using the other images you linked to.
Given the variance in background colors, I thought you would get better results deriving a threshold value from the image histogram instead of hardcoding it.
Occasionally, this algorithm is a little to rigorous, e.g. erasing part of the clothes together with the background. But I think over 90% of the images are separated pretty well, which is more robust than what you could hope to achieve with a fixed threshold.
close all;
path = 'C:\path\to\CUHK_training_cropped_photos\photos';
files = dir(path);
bins = 16;
for f = 3:numel(files)
fprintf('%i/%i\n', f, numel(files));
file = files(f);
if isempty(strfind(file.name, 'jpg'))
continue
end
I = imread([path filesep file.name]);
% Take the histogram of the blue channel
B = I(:,:,3);
h = imhist(B, bins);
h2 = h(bins/2:end);
% Find the most common bin in the *upper half*
% of the histogram
m = bins/2 + find(h2 == max(h2));
% Set the threshold value somewhat below
% the value corresponding to that bin
thr = m/bins - .25;
BW = im2bw(B, thr);
% Pad with ones to ensure background connectivity
BW = padarray(BW, [1 1], 1);
% Find connected regions in BW image
CC = bwconncomp(BW);
L = labelmatrix(CC);
% Crop back again
L = L(2:end-1,2:end-1);
% Set the largest region in the orignal image to white
for c = 1:3
channel = I(:,:,c);
channel(L==1) = 255;
I(:,:,c) = channel;
end
% Show the results with a pause every 16 images
subplot(4,4,mod(f-3,16)+1);
imshow(I);
title(sprintf('Img %i, thr %.3f', f, thr));
if mod(f-3,16)+1 == 16
pause
clf
end
end
pause
close all;
Results:
Your approach seems basic but decent. Since for this particular image the background is composed of mainly blue so you be crude and do:
mask = img(:,:,3) > 150;
This will set those pixels which evaluate to true for > 150 to 0 and false to 1. You will have a black and white image though.
imshow(mask);
To add colour back
mask3d(:,:,1) = mask;
mask3d(:,:,2) = mask;
mask3d(:,:,3) = mask;
img(mask3d) = 255;
imshow(img);
Should give you the colour image of face hopefully, with a pure white background. All this requires some trial and error.

How to interpolate the coordinates of a vector on an image using MATLAB?

For instance, if I have got a vector describing a rectangle
xy=[165 88;
401 88;
401 278;
165 278];
on an image.
How can I obtain the following vector
[165 88; % increase X - hold y
166 88;
167 88;
... ;
399 88;
400 88;
401 88; % hold x - increase y
401 89;
401 90;
401 91;
... ;
401 276;
401 277;
401 278; % decrease X - hold y
400 278;
399 278;
398 278;
... ;
167 278;
166 278;
165 278; % hold x - decrease y
165 277;
165 276;
... ;
165 87];
using a MATLAB inbuilt function or do I need to write it using FOR LOOPS?
The algorithm must work for a generic vector with n-points and xy coordinates.
If you have the image processing toolbox, you can do this by creating an image of the polygon followed by finding the contour:
xy=[165 88; 401 88; 401 278; 165 278];
%# create the image - check the help for impolygon for how to make sure that
%# your line is inside the pixel
img = poly2mask(xy(:,1),xy(:,2),max(xy(:,1))+3,max(xy(:,2))+3);
figure,imshow(img) %# show the image
%# extract the perimeter. Note that you have to inverse x and y, and that I had to
%# add 1 to hit the rectangle - this shows one has to be careful with rectangular
%# polygons
boundary = bwtraceboundary(logical(img),xy(1,[2,1])+1,'n',8,inf,'clockwise');
%# overlay extracted boundary
hold on, plot(boundary(:,2),boundary(:,1),'.r')
Edited to show how to use bwtraceboundary and to warn of pixel offset with rectangles.
One solution using IND2SUB:
xy=[165 88; 401 88; 401 278; 165 278];
xmin = min(xy(:,1))-1;
xmax = max(xy(:,1));
ymin = min(xy(:,2))-1;
ymax = max(xy(:,2));
ncol=xmax-xmin;
nrow=ymax-ymin;
[xn yn]=ind2sub([nrow ncol],1:nrow*ncol);
xypairs = [xn'+xmin yn'+ymin];
The quick and dirty way to draw a straights into an off-screen matrix is by evaluating the formula a*X+b*Y=c.
Let h and w be width and height of your buffer:
X = repmat([0:w-1], h, 1)
Y = repmat([0:h-1]', 1, w)
For every pair of points (x1,y1)->(x2,y2) a, b and c are:
a = y2-y1
b = x1-x2
c = x1*y2-x2*y1
Now calculating the straigt:
st = a*X+b*Y-c
st(abs(st)>1) = 1
st = 1 - abs(st)
Matrix st is a w*h matrix containing an anti-aliased straight passing through the points (x1,y1) and (x2,y2). Now let's go from straight to line by masking out the unwanted parts:
[xs] = sort([x1 x2])
st = st .* [zeros(h, xs(1)) ones(h, xs(2)-xs(1)) zeros(h, w-xs(2))]
[ys] = sort([y1 y2])
st = st .* [zeros(ys(1), w) ; ones(ys(2)-ys(1), w) ; zeros(h-ys(2), w)]
We have just manually drawn a single line without any explicit looping. No guarantees about the code's efficiency though :-)
Finally: Add another dimension to every formula above (left as an exercise for the reader).

Resources