MATLAB contour sharp corners - matlab

MATLAB contour plots appear to round corners, even when they should not be rounded.
In the plot
num_incs = 1000;
x = linspace(-1,1,num_incs);
[Xa,Ya] = meshgrid(x,x);
Z = abs(Xa) + abs(Ya);
contour(Xa,Ya,Z,20,'LineWidth',4);
the corners are rounded. Is there a way to turn off automatic rounding in order to make the corners "sharp"?
I've tried changing the renderer, but to no affect. Also, turning off graphics smoothing with
set(gcf,'GraphicsSmoothing','off');
does not produce sharp corners.
EDIT:
Note that on Matlab 2020b with num_incs = 1001 (or 3),
I still get rounded corners:
EDIT 2:
I'm trying to access and modify the LineJoin property:
num_incs = 3;
x = linspace(-1,1,num_incs);
[Xa,Ya] = meshgrid(x,x);
Z = abs(Xa) + abs(Ya);
[C,hContour] = contour(Xa,Ya,Z,20,'LineWidth',4);
drawnow;
for i = 1:length(hContour.EdgePrims)
hContour.EdgePrims(i).LineJoin = deal('miter');
hContour.EdgePrims(i).LineWidth = 1; % to see what is being adjusted
end
but this only affects the "outer" lines. This approach was inspired by this post.
Conclusion:
https://stackoverflow.com/a/68533356/3385432

You can set the LineJoin property of the lines to 'miter' to get sharp corners:
[C,h] = contour(Xa,Ya,Z,20, 'LineWidth',4);
draw now
set(h.EdgePrims,'LineJoin','miter')
By default this property is 'round'.
But be aware that the sharp corners produced can be unreasonably long, depending on the angle between the two lines meeting, and leading to misinterpretation of the data. A third option is 'chamfer', which gives sharp corners but cuts them off if they become too large.

The choice of renderer may affect how the antialiasing will be applied to the drawn lines, but in your case the rounded corner effect is simply because no data is defined at the coordinates which should be the actual corners.
If you zoom in on any of your "corners", you'll notice that there is no point defined for all the x=0 points, (nor for all the y=0). To overcome that, Matlab draws a line between the two closest defined points:
This is because of the way you define your grid. By choosing an even number of points (you chose 1000) over an interval, you can never have a point defined right at the center of the interval. To have the center point of the interval defined, you have to use an odd number of points.
The table below show the grid Ya close to the transition. You'll notice that the values go from negative to positive but there is no point defined at y=0:
To demonstrate without having to zoom in too much, we can use a lower number of points. Observe the difference between the 2 graphs below, they use the same code except for the first line. On the left I have used num_incs = 10;, while on the right I used num_incs = 11;:
So in your case, use num_incs = 1001; and your corners should diseapear. Or if your example was actually your case use, notice that for such a simple shape you can get exactly the same visual result with num_incs = 3;. This only defines the necessary corner points, which are the start and stop of each line (and do not use the unnecessary intermediate points on a line).

Related

Autonomous seam detection in Images on matlab

I'm trying to detect seams in welding images for an autonomous welding process.
I want to find pixel positions of the detected line (the red line in the desired image) in the original image.
I used the following code and finally removed noise from the image to reach the result below.
clc,clear,clf;
im = imread('https://i.stack.imgur.com/UJcKA.png');
imshow(im);title('Original image'); pause(0.5);
sim = edge(im, 'sobel');
imshow(sim);title('after Sobel'); pause(0.5);
mask = im > 5;
se = strel('square', 5);
mask_s = imerode(mask, se);
mask(mask_s) = false;
mask = imdilate(mask, se);
sim(mask) = false;
imshow(sim);title('after mask');pause(0.5);
sim= medfilt2(sim);
imshow(sim);title('after noise removal')
Unfortunately there is nothing remaining in the image to find the seam perfectly.
Any help would be appreciated.
Download Original image.
You need to make your filter more robust to noise. This can be done by giving it a larger support:
filter = [ones(2,9);zeros(1,9);-ones(2,9)];
msk = imerode(im > 0, ones(11)); % only object pixels, discarding BG
fim =imfilter(im,filter);
robust = bwmorph((fim>0.75).*msk,'skel',inf); % get only strong pixels
The robust mask looks like:
As you can see, the seam line is well detected, we just need to pick it as the largest connected component:
st = regionprops(bwlabel(robust,8), 'Area', 'PixelList');
[ma mxi] = max([st.Area]); % select the region with the largest area
Now we can fit a polygon (2nd degree) to the seem:
pp=polyfit(st(mxi).PixelList(:,1), st(mxi).PixelList(:,2), 2);
And here it is over the image:
imshow(im, 'border','tight');hold on;
xx=1:size(im,2);plot(xx,polyval(pp,xx)+2,'r');
Note the +2 Y offset due to filter width.
PS,
You might find this thread relevant.
Shai gives a great answer, but I wanted to add a bit more context about why your noise filtering doesn't work.
Why median filtering doesn't work
Wikipedia suggests that median filtering removes noise while preserving edges, which is why you might have chosen to use it. However, in your case it will almost certainly not work, here's why:
Median filtering slides a window across the image. In each area, it replaces the central pixel with the median value from the surrounding window. medfilt2 uses a 3x3 window by default. Let's look at a 3x3 block near your line,
A 3x3 block around [212 157] looks like this
[0 0 0
1 1 1
0 0 0]
The median value is 0! So even though we're in the middle of a line segment, the pixel will be filtered out.
The alternative to median filtering
Shai's method for removing noise instead finds the largest connected group of pixels and ignores smaller groups of pixels. If you also wanted to remove these small groups from your image, Matlab provides a filter bwareaopen which removes small objects from binary images.
For example, if you replace your line
sim= medfilt2(sim);
with
sim= bwareaopen(sim, 4);
The result is much better
Alternative edge detectors
One last note, Shai uses a horizontal gradient filter to find horizontal edges in your image. It works great because your edge is horizontal. If you edge will not always be horizontal, you might want to use another edge detection method. In your original code, you use Sobel, but Matlab provides many options, all of which perform better if you tune their thresholds. As an example, in the following image, I've highlighted the pixels selected by your code (with bwareaopen modification) using four different edge detectors.

How to find edge from dark line to grey smeared region

I am trying to detect the edge from black horizontal line to the gray-smeared foreground.
The desired edge/result is slightly marked red.
What have I tried so far :
My approach was to use the standard Chan-Vese Segmentation combined with several preprocseeing methods like gaussian blurring, maximum filter or morpholigocal operator like erosion. However, when I am initializing the level set function at the lower part of the image, the contour gets stuck right before the dersired edge.
Due to the noise I can't get rid off without destroying important information of the image, simple methods like the sobel or prewitt filtering might fail.
Another apporoach of me was to search for the maximum/minimum intensity columnwise of the image and mark the darkest pixel per column.
As you can assume, this will fail too because the edge I am looking for is not the only part that has dark pixels, that's why this method is very error-prone.
Edit
Snakes does not help either.
The active contour, marked as blue, simply goes over the edge and at the left and right the contour gets stuck. The Code I tried wasthe function Snake2D(I,P,Options) taken from here.
Here is the original image if you would like to help me.
I think your approach using the rows and finding the maximum is probably easiest.
The one problem you have is distinguishing the two main maxima. For this, you can apply a rough smoothing, to find the middle between the two maxima (blue line in image below). You can then only take the lower bit, which is the one you are interested in and find the maximum of this bit.
In a final step, just add up the two indices.
Result:
Could go like this:
ib = imread('LHkm2.png'); %Read image
sz = size(ib); %get dimensions
for i = 1:sz(2)
[~, ind_mid(i)] = max(smooth(-double(ib(:, i)), 130));%First round
line_to_smooth = ib(ind_mid(i):end, i);%Get line with one maximum
[~, ind(i)] = min(smooth(double(line_to_smooth), 10));%Second round
ind(i) = ind(i) + ind_mid(i);%Add indices to get final position
end
imshow(ib,[]);
hold on;
plot(ind_mid, 'LineWidth', 3);
plot(ind, 'LineWidth', 3);
Note: You can of course smooth the final line just like any other graphs to get rid of bumps like this:
ind = smooth(ind, 10)
where 10 is your smoothing window (the higher the broader, see here.

What algorithm does Matlab's boundary function use?

As you may have already noticed, in the newer versions of matlab the
boundary function (which computes the boundary for a set of 2d or 3d points) has been improved.
Now it is possible to give the function a parameter called 'shrink factor'. If the shrink factor is 0, then the boundary traced is the traditional convex hull. The boundary is more shrinked when the shrink parameter is bigger. The default value for the shrink factor is 0.5, in case you don't specify any value.
So, I understand its use and what it does (actually I've already used the function in a project), but I don't know how it works. What are the geometrical principles of this shrink factor?
Thanks!
Found your question while loooking for the answer myself. Hope you've solved it by now. I've figured it out and in case someone else finds this question, here's my understanding of the boundary() function.
The boundary function is an implementation of alpha shapes. Using alpha shapes, a set of points can be assigned a polygon by using a set of circles of a specific radius:
imagine an arbitrary shape drawn around the points and proceed to remove as much of this shape as possible using circles of a specific radius. Continue as long as possible, without enclosing any points. A small radius will mean more "material" can be removed, a larger radius means less "removal", i.e. a small radius creates a close cropped shape whereas an infinite radius recreates a convex hull of the set. The points determined to be edge points are then conencted with straight edges. This can create hollow areas inside the point set.
See e.g. http://doc.cgal.org/latest/Alpha_shapes_2/index.html
MATLAB has an alphashape() function which calculates alphashapes with all possible alpha radii giving different shapes. This is used in the boundary function.
boundary() workflow:
(1) Create alphashape
(2) Find critical alpha radius, needed to create a single region for alpha shape
(3) Extract all alphavalues that create unique shapes above this critical value
(4) Use the shrink factor, S, to select a single alpha value to use.
Example: with S=0.25, use alpha radius with index (1-.25)*numel(alphavalues>=alpha_crit). This creates an alpha shape
using the 75th smallest alpha radius giving rise to a single region
(for S=0.25).
If S=1 (max shrink), gives the lowest alpha-radius that gives a single
region for the alpha-shape.
If S=0 (no shrink), gives the maximum alpha-radius that gives a unique
shape. (Incraesing alpha radius further has no effect).
(5) set the threshold for filling in holes in the alphashape to be the same as the alphashape's area, i.e. fill in all holes
(6) Return the indices of the original point cloud correspocing to the vertices of this alphashape.
The relevant section of the boundary.m file (lines 79-86)
Acrit = shp.criticalAlpha('one-region'); %alpha-radius required for single region
spec = shp.alphaSpectrum();%all alphavalues
idx = find(spec==Acrit);
subspec = spec(1:idx);%alphavalues up to criticalAlpha
subspec = flipud(subspec);%reverse order
idx = max(ceil((1-S)*numel(subspec)),1); %find index from shrink factor
alphaval = subspec(idx);
shp.Alpha = alphaval; %set alpha value of alpha shape
shp.HoleThreshold = areavol; % remove holes in interior
Hope this is clear enough and useful to someone.
I use MATLAB R2014b
YiraDati's answer provides great details.
You can also type "open boundary" in command windows, and then all procedures are written in boundary function. And all subfunctions shown in boundary function are accessible using matlab documentation, like area(), criticalAlpha(), alphaSpectrum(), etc...

Matlab Solid Circles

What we want is to draw several solid circles at random locations, with random gray scale colors, on a dark gray background. How can we do this? Also, if the circles overlap, we need them to change color in the overlapping part.
Since this is an assignment for school, we are not looking for ready-made answers, but for a guide which tools to use in MATLAB!
Here's a checklist of things I would investigate if you want to do this properly:
Figure out how to draw circles in MATLAB. Because you don't have the Image Processing Toolbox (see comments), you will probably have to make a function yourself. I'll give you some starter code:
function [xout, yout] = circle(x,y,r,rows,cols)
[X,Y] = meshgrid(x-r:x+r, y-r:y+r);
ind = find(X.^2 + Y.^2 <= r^2 & X >= 1 & X <= cols & Y >= 1 & Y <= rows);
xout = X(ind);
yout = Y(ind);
end
What the above function does is that it takes in an (x,y) co-ordinate as well as the radius of
the circle. You also will need to specify how many rows and how many columns you want in your image. The reason why is because this function will prevent giving you co-ordinates that are out of bounds in the image that you can't draw. The final output of this will give you co-ordinates of all values inside and along the boundary of the circle. These co-ordinates will already be in integer so there's no need for any rounding and such things. In addition, these will perfectly fit when you're assigning these co-ordinates to locations in your image. One caveat to note is that the co-ordinates assume an inverted Cartesian. This means that the top left corner is the origin (0,0). x values increase from left to right, and y values increase from top to bottom. You'll need to keep this convention in mind when drawing circles in your image.
Take a look at the rand class of functions. rand will generate random values for you and so you can use these to generate a random set of co-ordinates - each of these co-ordinates can thus serve as your centre. In addition, you can use this class of functions to help you figure out how big you want your circles and also what shade of gray you want your circles to be.
Take a look at set operations (logical AND, logical OR) etc. You can use a logical AND to find any circles that are intersecting with each other. When you find these areas, you can fill each of these areas with a different shade of gray. Again, the rand functions will also be of use here.
As such, here is a (possible) algorithm to help you do this:
Take a matrix of whatever size you want, and initialize all of the elements to dark gray. Perhaps an intensity of 32 may work.
Generate a random set of (x,y) co-ordinates, a random set of radii and a random set of intensity values for each circle.
For each pair of circles, check to see if there are any co-ordinates that intersect with each other. If there are such co-ordinates, generate a random shade of gray and fill in these co-ordinates with this new shade of gray. A possible way to do this would be to take each set of co-ordinates of the two circles and draw them on separate temporary images. You would then use the logical AND operator to find where the circles intersect.
Now that you have your circles, you can plot them all. Take a look at how plot works with plotting matrices. That way you don't have to loop through all of the circles as it'll be inefficient.
Good luck!
Let's get you home, shall we? Now this stays away from the Image Processing Toolbox functions, so hopefully these must work for you too.
Code
%%// Paramters
numc = 5;
graph_size = [300 300];
max_r = 100;
r_arr = randperm(max_r/2,numc)+max_r/2
cpts = [randperm(graph_size(1)-max_r,numc)' randperm(graph_size(2)-max_r,numc)']
color1 = randperm(155,numc)+100
prev = zeros(graph_size(1),graph_size(2));
for k = 1:numc
r = r_arr(k);
curr = zeros(graph_size(1),graph_size(2));
curr(cpts(k,1):cpts(k,1)+r-1,cpts(k,2):cpts(k,2)+r-1)= color1(k)*imcircle(r);
common_blob = prev & curr;
curr = prev + curr;
curr(common_blob) = min(color1(1),color1(2))-50;
prev = curr;
end
figure,imagesc(curr), colormap gray
%// Please note that the code uses a MATLAB file-exchange tool called
%// imcircle, which is available at -
%// http://www.mathworks.com/matlabcentral/fileexchange/128-imcircle
Screenshot of a sample run
As you said that your problem is an assignment for school I will therefore not tell you exactly how to do it but what you should look at.
you should be familiar how 2d arrays (matrices) work and how to plot them using image/imagesc/imshow ;
you should look at the strel function ;
you should look at the rand/randn function;
such concepts should be enough for the assignment.

Matlab - Propagate points orthogonally on to the edge of shape boundaries

I have a set of points which I want to propagate on to the edge of shape boundary defined by a binary image. The shape boundary is defined by a 1px wide white edge.
I have the coordinates of these points stored in a 2 row by n column matrix. The shape forms a concave boundary with no holes within itself made of around 2500 points. I have approximately 80 to 150 points that I wish to propagate on the shape boundary.
I want to cast a ray from each point from the set of points in an orthogonal direction and detect at which point it intersects the shape boundary at. The orthogonal direction has already been determined. For the required purposes it is calculated taking the normal of the contour calculated for point, using point-1 and point+1.
What would be the best method to do this?
Are there some sort of ray tracing algorithms that could be used?
Thank you very much in advance for any help!
EDIT: I have tried to make the question much clearer and added a image describing the problem. In the image the grey line represents the shape contour, the red dots the points
I want to propagate and the green line an imaginary orthongally cast ray.
alt text http://img504.imageshack.us/img504/3107/orth.png
ANOTHER EDIT: For clarification I have posted the code used to calculate the normals for each point. Where the xt and yt are vectors storing the coordinates for each point. After calculating the normal value it can be propagated by using the linspace function and the requested length of the orthogonal line.
%#derivaties of contour
dx=[xt(2)-xt(1) (xt(3:end)-xt(1:end-2))/2 xt(end)-xt(end-1)];
dy=[yt(2)-yt(1) (yt(3:end)-yt(1:end-2))/2 yt(end)-yt(end-1)];
%#normals of contourpoints
l=sqrt(dx.^2+dy.^2);
nx = -dy./l;
ny = dx./l;
normals = [nx,ny];
It depends on how many unit vectors you want to test against one shape. If you have one shape and many tests, the easiest thing to do is probably to convert your shape coordinates to polar coordinates which implicitly represent your solution already. This may not be a very effective solution however if you have different shapes and only a few tests for every shape.
Update based on the edited question:
If the rays can start from arbitrary points, not only from the origin, you have to test against all the points. This can be done easily by transforming your shape boundary such that your ray to test starts in the origin in either coordinate direction (positive x in my example code)
% vector of shape boundary points (assumed to be image coordinates, i.e. integers)
shapeBoundary = [xs, ys];
% define the start point and direction you want to test
startPoint = [xsp, ysp];
testVector = unit([xv, yv]);
% now transform the shape boundary
shapeBoundaryTrans(:,1) = shapeBoundary(:,1)-startPoint(1);
shapeBoundaryTrans(:,2) = shapeBoundary(:,2)-startPoint(2);
rotMatrix = [testVector(2), testVector(1); ...
testVector(-1), testVector(2)];
% somewhat strange transformation to keep it vectorized
shapeBoundaryTrans = shapeBoundaryTrans * rotMatrix';
% now the test is easy: find the points close to the positive x-axis
selector = (abs(shapeBoundaryTrans(:,2)) < 0.5) & (shapeBoundaryTrans(:,1) > 0);
shapeBoundaryTrans(:,2) = 1:size(shapeBoundaryTrans, 1)';
shapeBoundaryReduced = shapeBoundaryTrans(selector, :);
if (isempty(shapeBoundaryReduced))
[dummy, idx] = min(shapeBoundaryReduced(:, 1));
collIdx = shapeBoundaryReduced(idx, 2);
% you have a collision with point collIdx of your shapeBoundary
else
% no collision
end
This could be done in a nicer way probably, but you get the idea...
If I understand your problem correctly (project each point onto the closest point of the shape boundary), you can
use sub2ind to convert the "2 row by n column matrix" description to a BW image with white pixels, something like
myimage=zeros(imagesize);
myimage(imagesize, x_coords, y_coords) = 1
use imfill to fill the outside of the boundary
run [D,L] = bwdist(BW) on the resulting image, and just read the answers from L.
Should be fairly straightforward.