Extracting boundaries from an image manually and preventing overlap of points selected - matlab

I am trying to import the image boundaries into CAD modeling software. For doing that I am manually selecting the boundaries from this image. The code I am using is the following:
I = imread('image.jpg'); %image file name
imshow(I);
uiwait(msgbox('Left click to choose points. Right click to exit'));
n = 0;
while true
[x, y, button] = ginput(1);hold on;
if isempty(x) || button(1) ~= 1; break; end
n = n+1;
x_n(n) = x; % save all points you continue getting
y_n(n) = y;
plot(x,y,'-o',...
'LineWidth',2,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[.49 1 .63],...
'MarkerSize',10);
end
figure;
x_n=x_n';
y_n=y_n';
plot(x_n,y_n);
A=[x_n,y_n];
Selecting points on the boundaries as shown by this image give the following outcome in CAD software model. The overlap in the boundaries is because of the way lines are drawn in CAD software as shown below:
s1.Line(point1=(-0.131218,39.556604),point2=(1.762436,40.503431))
s1.Line(point1=(1.762436,40.503431),point2=(4.602916,38.136364))
s1.Line(point1=(4.602916,38.136364),point2=(9.337050,31.035163))
s1.Line(point1=(9.337050,31.035163),point2=(11.230703,23.460549))
s1.Line(point1=(11.230703,23.460549),point2=(14.544597,17.779588))
s1.Line(point1=(14.544597,17.779588),point2=(15.491424,12.572041))
s1.Line(point1=(15.491424,12.572041),point2=(16.438250,6.417667))
s1.Line(point1=(16.438250,6.417667),point2=(17.385077,2.156947))
s1.Line(point1=(17.385077,2.156947),point2=(21.645798,5.944254))
s1.Line(point1=(21.645798,5.944254),point2=(26.853345,8.784734))
s1.Line(point1=(26.853345,8.784734),point2=(31.114065,11.625214))
s1.Line(point1=(31.114065,11.625214),point2=(35.848199,13.045455))
s1.Line(point1=(35.848199,13.045455),point2=(40.582333,14.939108))
Is there a way where I can prevent the overlapping during selection by pausing the selection? Or any other suggestion on how to get the model made efficiently? I had tried using image processing to determine the boundaries. But it was beyond me to think something about converting the image coordinates into a CAD software model.

Possible solution is creating a polygon out of the selected points, and not adding selected point to x_n, y_n if there are lines crossings.
The code uses polyshape function for creating a polygon object:
clearvars
I = imread('image.jpg'); %image file name
imshow(I);
uiwait(msgbox('Left click to choose points. Right click to exit'));
warning('Off', 'MATLAB:polyshape:repairedBySimplify'); %Disable warning "Polyshape has duplicate vertices, intersections..."
n = 0;
h_pgon = [];
while true
[x, y, button] = ginput(1);hold on;
if isempty(x) || button(1) ~= 1; break; end
n = n+1;
%Save points to temporary arrays
tmp_x_n(n) = x; % save all points you continue getting
tmp_y_n(n) = y;
is_overlapping = false;
if n > 2
%Check if there is "overlapping during selection":
%Creates a polygon object
pgon = polyshape(tmp_x_n, tmp_y_n);
if (pgon.NumRegions > 1) || (pgon.NumHoles > 0)
%There are intersections, so don't add the new x, y to x_n, y_n.
disp('Overlapping!'); %Print "Overlapping!" for testing.
is_overlapping = true;
n = n - 1;
end
end
if ~is_overlapping
%There is no overlapping - copy tmp_x_n, tmp_y_n to x_n, y_n.
x_n = tmp_x_n;
y_n = tmp_y_n;
plot(x,y,'-o',...
'LineWidth',2,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[.49 1 .63],...
'MarkerSize',10);
%Plot polygon for testing
if n > 2
if isobject(h_pgon), delete(h_pgon);end %Delete previouse polygon.
h_pgon = plot(pgon);
end
end
end
figure;
x_n=x_n';
y_n=y_n';
plot(x_n,y_n);
A=[x_n,y_n];

Related

How to cut part of the data out of a plot in Matlab

I just wanted to cut part of my data out in MATLAB, for example:
If I click on two points on the axis, it will cut the elements after the I click on with respect to the x-axis. I will post my code and a pic for further details
Thank you in advance
load sample.mat
X = sample.current;
X1 = sample.voltage;
Ts = 0.01;
Fs = 1/Ts;
Fm = Fs/2;
Fc = 2;
N =10;
d = fdesign.lowpass('N,Fc',N,Fc,Fs);
designmethods(d);
Hd = design(d);
%fvtool(Hd)
%X is a variable form csv
%X1 is a variable from csv
output = filter(Hd,X);
output1 = filter(Hd,X1);
figure;
plot(X,X1,'-g');
hold on
plot(output, output1,'r');
hold off
legend('raw signal','filtered signal')
grid on
x = output, output1;
y = output1;
figure
subplot(2,1,1)
plot(x,y,'r');
title('Original plot');
uiwait(msgbox('Select an x-value from which to crop','modal'));
[x_user ~] = ginput(1); % Let the user select an x-value from which to crop.
x(x>x_user) = [];
subplot(2,1,2);
plot(x,y,'r');
title('New plot with cropped values');
xlim([min(x(:)) max(x(:))]);
enter image description here
*Posting this as an answer to format code.
If its only one graphic you can just select the points that you want to delete using the "Brush/Select Data" (icon of a brush with a red square located at the menubar of the figure) selecting the data you want to be gone and then pressing the delete key.
If you want to do it with code you can try to find the index of the point where the signal starts to decrease over the X using something like:
% Find the index where X starts to decrease
maxIndex = find(data.x == max(data.x));
% In case of multiple indexs, ensure we get the first one
maxIndex = maxIndex(1);
% Copy data to new vector
saveData.x = data.x(1:maxIndex);
saveData.y = data.y(1:maxIndex);
If you want to use the users' click position you can use find to locate the index of the first element after the click:
% Get the coords of the first click
userFirstClick = ginput(1);
% Get the X component of the coords
xCoordInit = userFirstClick(1);
% Locate the index of the first element that is greater than
% the xCoord
firstXIndex = find(data.x >= xCoordInit);
% In case of multiple indexs, ensure we get the first one
firstXIndex = firstXIndex(1);
% Do the same to get the final index
userSecondClick = ginput(1);
xCoordFinal = userSecondClick(1);
finalXIndex = find(data.x > xCoordFinal);
finalXIndex = finalXIndex(1)-1;
% -1 because data.x(finalXIndex) is already greater than xCoordFinal
% Copy data to the new vector
saveData.x = data.x(firstXIndex:finalXIndex);
saveData.y = data.y(firstXIndex:finalXIndex);
Then just plot saveData.
Edit
There was a typo on my previous code, here you have a fully functional example where you just need to click over the two points where you want to crop.
function cropSine()
% create a period of a Sine to initialize our data
data.x = -pi*3:0.01:pi*3;
data.y = sin(data.x);
% we make it loop back just as in your picture
data.x = [data.x,data.x(end:-1:1)];
data.y = [data.y, -data.y*0.5+5];
% create a figure to show the signal we have just created
figure
% create the axes where the data will be displayed
mainAx = axes();
% Draw our fancy sine!
plot(data.x, data.y, 'b-', 'Parent', mainAx);
% Request the initial position to crop
userFirstClick = ginput(1);
% Get the index of the nearest point
initIndex = getNearest(userFirstClick, data);
% Do the same to get the final index
userSecondClick = ginput(1);
% Get the index of the nearest point
finalIndex = getNearest(userSecondClick, data);
% check if its a valid point
if isempty(initIndex) || isempty(finalIndex)
disp('No points in data vector!');
return;
end
% Ensure that final index is greater than first index
if initIndex > finalIndex
tempVal = initIndex;
initIndex = finalIndex;
finalIndex = tempVal;
end
% Copy the data that we want to save into a new variable
saveData.x = data.x(initIndex:finalIndex);
saveData.y = data.y(initIndex:finalIndex);
% Plot the cropped data in red!
hold(mainAx, 'on');
plot(saveData.x, saveData.y, 'r-', 'Parent', mainAx);
hold(mainAx, 'off');
end
function nearestIndex = getNearest(clickPos, vector)
nearestIndex = [];
numPoints = length(vector.x);
if numPoints == 0
return;
end
nearestIndex = 1;
minDist = calcDist(vector.x(1), vector.y(1), clickPos(1), clickPos(2));
for pointID = 1:numPoints
dist = calcDist(vector.x(pointID), vector.y(pointID), clickPos(1), clickPos(2));
if dist < minDist
nearestIndex = pointID;
minDist = dist;
end
end
end
function dist = calcDist(p1x, p1y, p2x, p2y)
dist = sqrt(power(p1x-p2x,2)+power(p1y-p2y,2));
end

How to make video in MATLAB from continous plots

This is part of the code the makes the video:
k = 10000;
j = 1;
v = VideoWriter('myVideo.avi');
open(v)
while j < k
axis([0 5 0 1000]);
plot(0:dr:R, u_sol_matrix(:,j))
frame = getframe(gcf);
writeVideo(v,frame);
j = j + 50;
% pause(0.01)
end
close(v)
Now, given a matrix u_sol_matrix, where every column represents a solution for a PDE at a certain time point, I plot the solutions and the by the getframe command capture these plots and make a movie out of it.
The problem is that the axis keeps changing as the plot keeps adjusting to the solution. I want the axis to be constant. How do I get this to work? I have tried adding axis but this does not work apparently.
k = 10000;
j = 1;
v = VideoWriter('myVideo.avi');
open(v)
while j < k
fig = figure(); % Explicitly create figure
plot(0:dr:R, u_sol_matrix(:,j))
axis([0 5 0 1000]); % first plot, then change axis
frame = getframe(gcf);
writeVideo(v,frame);
close(fig) % close figure explicitly.
j = j + 50;
% pause(0.01)
end
close(v)
Flipping the figure creation and setting axis limits should do the trick. When you call axis without an open figure, MATLAB creates one, only to overwrite it if you don't call hold on, thus changing the limits to whatever the plot "needs" to fit.
Like Adriaan answered, you just have to flip the order of plotting and setting axis limits to make this work.
However, when creating animations, it is faster to first initialize a figure and the graphics objects (i.e. lines, scatter points, etc.), and later update the data in a loop.
k = 10000;
j = 1;
v = VideoWriter('myVideo.avi');
open(v)
% some test data
x = 10;
y = sin(1:k);
% init a figure and plot handles
fig = figure(1);
p = plot(x, y(1), 'o'); % create line object, and store the handle
axis([9 11 -1 1]) % axis limits for test data
% update data during animation
while j < k
p.XData = x; % update X and Y data properties of line object
p.YData = y(j);
frame = getframe(gcf);
writeVideo(v,frame);
j = j + 50;
pause(0.01)
end
close(v)
Since you don't have to create a new line primitive each loop iteration, this will save a lot of time. You only have to adjust the X and Y data properties of the already existing line, which has considerable less overhead.

Visualizing matrix values in real time

Suppose I have a 5x5 matrix.
The elements of the matrix change (are refreshed) every second.
I would like to be able to display the matrix (not as a colormap but with the actual values in a grid) in realtime and watch the values in it change as time progresses.
How would I go about doing so in MATLAB?
A combination of clc and disp is the easiest approach (as answered by Tim), here's a "prettier" approach you might fancy, depending on your needs. This is not going to be as quick, but you might find some benefits, such as not having to clear the command window or being able to colour-code and save the figs.
Using dispMatrixInFig (code at the bottom of this answer) you can view the matrix in a figure window (or unique figure windows) at each stage.
Example test code:
fig = figure;
% Loop 10 times, pausing for 1sec each loop, display matrix
for i=1:10
A = rand(5, 5);
dispMatrixInFig(A,fig)
pause(1)
end
Output for one iteration:
Commented function code:
function dispMatrixInFig(A, fig, strstyle, figname)
%% Given a figure "fig" and a matrix "A", the matrix is displayed in the
% figure. If no figure is supplied then a new one is created.
%
% strstyle is optional to specify the string display of each value, for
% details see SPRINTF. Default is 4d.p. Can set to default by passing '' or
% no argument.
%
% figname will appear in the title bar of the figure.
if nargin < 2
fig = figure;
else
clf(fig);
end
if nargin < 3 || strcmp(strstyle, '')
strstyle = '%3.4f';
end
if nargin < 4
figname = '';
end
% Get size of matrix
[m,n] = size(A);
% Turn axes off, set origin to top left
axis off;
axis ij;
set(fig,'DefaultTextFontName','courier', ...
'DefaultTextHorizontalAlignment','left', ...
'DefaultTextVerticalAlignment','bottom', ...
'DefaultTextClipping','on');
fig.Name = figname;
axis([1, m-1, 1, n]);
drawnow
tmp = text(.5,.5,'t');
% height and width of character
ext = get(tmp, 'Extent');
dy = ext(4);
wch = ext(3);
dwc = 2*wch;
dx = 8*wch + dwc;
% set matrix values to fig positions
x = 1;
for i = 1:n
y = 0.5 + dy/2;
for j = 1:m
y = y + 1;
text(x,y,sprintf(strstyle,A(j,i)));
end
x = x + dx;
end
% Tidy up display
axis([1-dwc/2 1+n*dx-dwc/2 1 m+1]);
set(gca, 'YTick', [], 'XTickLabel',[],'Visible','on');
set(gca,'XTick',(1-dwc/2):dx:x);
set(gca,'XGrid','on','GridLineStyle','-');
end
I would have thought you could achieve this with disp:
for i=1:10
A = rand(5, 5);
disp(A);
end
If you mean that you don't want repeated outputs on top of each other in the console, you could include a clc to clear the console before each disp call:
for i=1:10
A = rand(5, 5);
clc;
disp(A);
end
If you want to display your matrix on a figure it is quite easy. Just make a dump matrix and display it. Then use text function to display your matrix on the figure. For example
randMatrix=rand(5);
figure,imagesc(ones(20));axis image;
hold on;text(2,10,num2str(randMatrix))
If you want to do it in a for loop and see the numbers change, try this:
for i=1:100;
randMatrix=rand(5);
figure(1),clf
imagesc(ones(20));axis image;
hold on;text(2,10,num2str(randMatrix));
drawnow;
end

Speed up creation of impoint objects

I have to create some draggable points on an axes. However, this seems to be a very slow process, on my machine taking a bit more than a second when done like so:
x = rand(100,1);
y = rand(100,1);
tic;
for i = 1:100
h(i) = impoint(gca, x(i), y(i));
end
toc;
Any ideas on speed up would be highly appreciated.
The idea is simply to provide the user with the possibility to correct positions in a figure that have been previously calculated by Matlab, here exemplified by the random numbers.
You can use the the ginput cursor within a while loop to mark all points you want to edit. Afterwards just click outside the axes to leave the loop, move the points and accept with any key.
f = figure(1);
scatter(x,y);
ax = gca;
i = 1;
while 1
[u,v] = ginput(1);
if ~inpolygon(u,v,ax.XLim,ax.YLim); break; end;
[~, ind] = min(hypot(x-u,y-v));
h(i).handle = impoint(gca, x(ind), y(ind));
h(i).index = ind;
i = i + 1;
end
Depending on how you're updating your plot you can gain a general speedup by using functions like clf (clear figure) and cla (clear axes) instead of always opening a new figure window as explained in this answer are may useful.
Alternatively the following is a very rough idea of what I meant in the comments. It throws various errors and I don't have the time to debug it right now. But maybe it helps as a starting point.
1) Conventional plotting of data and activating of datacursormode
x = rand(100,1);
y = rand(100,1);
xlim([0 1]); ylim([0 1])
f = figure(1)
scatter(x,y)
datacursormode on
dcm = datacursormode(f);
set(dcm,'DisplayStyle','datatip','Enable','on','UpdateFcn',#customUpdateFunction)
2) Custom update function evaluating the chosen datatip and creating an impoint
function txt = customUpdateFunction(empt,event_obj)
pos = get(event_obj,'Position');
ax = get(event_obj.Target,'parent');
sc = get(ax,'children');
x = sc.XData;
y = sc.YData;
mask = x == pos(1) & y == pos(2);
x(mask) = NaN;
y(mask) = NaN;
set(sc, 'XData', x, 'YData', y);
set(datacursormode(gcf),'Enable','off')
impoint(ax, pos(1),pos(2));
delete(findall(ax,'Type','hggroup','HandleVisibility','off'));
txt = {};
It works for the, if you'd just want to move one point. Reactivating the datacursormode and setting a second point fails:
Maybe you can find the error.

Draw line to connect centroids

I have an image
.
After I process to find centroid, it has four centroids.
My goal is I want to connect them using line and measure the angle between this area. To be clear about the centroid and my goal, you can open .
Here it is my code to achieve the centroid
I = imread('22c.jpg');
Ibw = im2bw(I);
Ibw = imfill(Ibw,'holes');
Ilabel = bwlabel(Ibw);
stat = regionprops(Ilabel,'centroid');
imshow(I); hold on;
for x = 1: numel(stat)
plot(stat(x).Centroid(1),stat(x).Centroid(2),'ro');
end
The problem is I am still confused to do the next (to connect each centroids and measure the angle). I need your help, thanks
Here is a file exchange link to bresenham.m
Changed your code to get all the 4 centroids
%// read your input image
im = imread('http://i.stack.imgur.com/xeqe8.jpg');
BW = im>220;
CC = bwconncomp(BW);
stat = regionprops(CC,'Centroid');
figure; imshow(BW); hold on
for x = 1: numel(stat)
plot(stat(x).Centroid(1),stat(x).Centroid(2),'ro');
end
Here is the output:
Further implementation:
%// putting all the Centroid coordinates into corresponding x,y variable
x = [stat(1).Centroid(1),stat(2).Centroid(1),stat(3).Centroid(1),stat(4).Centroid(1)];
y = [stat(1).Centroid(2),stat(2).Centroid(2),stat(3).Centroid(2),stat(4).Centroid(2)];
%// obtain row and col dim
[r,c] = size(BW);
%// get all x,y values connecting the centroid points
[xAll{1},yAll{1}] = bresenham(x(1),y(1),x(4),y(4));
[xAll{2},yAll{2}] = bresenham(x(2),y(2),x(3),y(3));
[xAll{3},yAll{3}] = bresenham(x(3),y(3),x(4),y(4));
%// change row and col subs to linear index
for ii = 1:3
idx{ii} = sub2ind(size(BW),yAll{ii},xAll{ii});
end
%// change grayscale image to 3D (as you want red line)
out = repmat(im,[1,1,3]);
%// obtaining corresponding index of all 3 slices
for ii = 1:3
idxall{ii} = bsxfun(#plus, idx{ii},[0:2].*(r*c));
end
%// keep only the index of 1st slice to 255 and changing rest to 0 to obtain a red line.
%// Similar process for blue line except keep the index in the 3rd slice to 255
out(cat(1,idxall{:})) = 0;
out(idx{1}) = 255;
out(idx{2}) = 255;
out(idx{3}+2*(r*c)) = 255;
%// see what you have obtained
figure; imshow(out);hold on
for x = 1: numel(stat)
plot(stat(x).Centroid(1),stat(x).Centroid(2),'bo');
end
Result:
Note: The line may look dotted due to the picture's large size, but its continuous
Last figure zoomed to see continuous line:
Going further:
You may have to take the advice of #Spektre to find the angle of inclination using atan2. Also refer his answer for more explanation.