Rescale curve so that it is at constant distance from original one - matlab

A question I strangely could not find on the internet. Given a complicated curve C (i.e. a curve that you can't fit with polynomials) defined by N points and centered around x0=0.5,0 (blue curve in figure), how can I rescale the curve so that the center is the same and the new curve is located at a constant distance d from the curve C (e.g. green curve in figure)?
So far the only way I could find is using the MATLAB function bwdist (https://fr.mathworks.com/help/images/ref/bwdist.html) which computes the Euclidean distance map of a binary image (see code below). However, I'm constrained by the size of my matrix i.e. a curve of 1e5 points is fine but a matrix of size (1e5,1e5) is big for bwdist...so the results using a coarse matrix is an ugly step-wise function. The code is
%%% profile
x = linspace(0,1,1e5);
y = -(x-0.5).^2/0.5^2 + 1 - 0.5*(exp(-(x-0.5).^2/2/0.2^2) - exp(-(-0.5).^2/2/0.2^2));
%%% define mask on a region that encompasses the curve
N=512;
mask = ones(N,N);
xm = linspace(0.9*min(x),1.1*max(x),N);
ym = linspace(0.9*min(y),1.1*max(y),N);
[Xm,Ym] = meshgrid(xm,ym);
%%% project curve on mask (i.e. put 0 below curve)
% get point of mask closer to each point of y
DT = delaunayTriangulation(Xm(:),Ym(:));
vi = nearestNeighbor(DT,x',y');
[iv,jv] = ind2sub(size(mask),vi);
% put 1 to indices of mask that are below projected curve
for p=1:length(iv)
mask(1:iv(p)-1,jv(p)) = 0;
end
%%% get euclidean distance
Ed = bwdist(logical(mask));
Ed = double(Ed);
%%% get contours of Ed at given values (i.e. distances)
cont = contour(Ed,linspace(0,1,50));
% cont has the various curves at given distances from original curve y
I add that I first tried moving a point of curve C for a distance d using the normal of the tangent but since the curve is non-linear, this direction is actually not necessarily the one giving the appropriate point. So at some distance, the curve becomes discontinuous because using the tangent does not give the point at a given distance from the curve, only from the considered point on curve C.
The code is
% profil
x = linspace(0,1,1e5);
y = -(x-0.5).^2/0.5^2 + 1 - 0.5*(exp(-(x-0.5).^2/2/0.2^2) - exp(-(-0.5).^2/2/0.2^2));
% create lines at Dist from original line
Dist = linspace(0,2e-1,6);
Dist = Dist(2:end);
Cdist(1).x = x;
Cdist(1).y = y;
Cdist(1).v = 0;
step = 10; % every step points compute normal to point and move points
points = [1:1:length(y)];
for d=1:length(Dist)
xd = x;
yd = y;
for p=1:length(points)
if points(p)==1
tang = [-(y(2)-y(1)) (x(2)-x(1))];
tang = tang/norm(tang);
xd(1) = xd(1) - Dist(d)*tang(1);
yd(1) = yd(1) - Dist(d)*tang(2);
elseif points(p)==length(y)
tang = [-(y(end)-y(end-1)) (x(end)-x(end-1))];
tang = tang/norm(tang);
xd(end) = xd(end) - Dist(d)*tang(1);
yd(end) = yd(end) - Dist(d)*tang(2);
else
tang = [-(y(p+1)-y(p-1)) (x(p+1)-x(p-1))];
tang = tang/norm(tang);
xd(p) = xd(p) - Dist(d)*tang(1);
yd(p) = yd(p) - Dist(d)*tang(2);
end
end
yd(yd<0)=NaN;
Cdist(d+1).x = xd;
Cdist(d+1).y = yd;
Cdist(d+1).v = Dist(d);
end
% plot
cmap=lines(10);
hold on
for c=1:length(Cdist)
plot(Cdist(c).x,Cdist(c).y,'linewidth',2,'color',cmap(c,:))
end
axis tight
axis equal
axis tight
Any idea ?

What you want to do is not possible.
Scaling a curve with respect to a center point while remaining equal distance to the original curve means that all the points on this curve are moving along its normal direction towards the center of scaling, and will eventually, reduce to a point.
Imagine drawing the normal direction of each point on this curve, and extend them to infinity. All these lines should pass through a same point, which is the center of scaling. Unfortunately, this is not the case for your curve.

Related

How to convert the estimated radius of a circle to its actual range?

I generate a random number between 1 and 2 as a radius for my circle. Then I plot my circle and saves it as a png. Also, several data points are generated both inside and outside the circle to make it noisy.
Then I will use Hough Transform to estimate the radius for the circle but, the number which it returns is more than 100. Although they are the same circles(I plotted to make sure).
I have tried to use polyfit function to map these two numbers but, the estimated radius seems to be smaller than the real one in some examples. After mapping It returns the same number. For example, a random radius is 1.2 and Hough estimates it 110 however it seems that it should be near 1.(Because I plot it and it is clear that is it smaller). Also, for Hough Transform I am using this code https://www.mathworks.com/matlabcentral/fileexchange/35223-circle-detection-using-hough-transforms
I tried the predefined Matlab function (imfindcircles) but it returns null for all my circles.
r1 = 1 + abs((1)*randn(1,1)); %radius of inner circle
r1 = abs(r1);
r2 = (1.2)*r1; %radius of outercircle
k = 500; %number of random numbers
% circle coordinate points
t = 0:2*pi/59:2*pi;
xv = cos(t)';
yv = sin(t)';
%%%%%%%%random points
xq = 2*randn(k,1)-1;
yq = 2*randn(k,1)-1; %distribution 1
xq1 = 2*(rand(k,1))-1; %distribution 2
yq1 = 2*(rand(k,1))-1;
in = inpolygon(xq1,yq1,r1*xv,r1*yv); %number of points inside the geometry
in1= inpolygon(xq,yq,r2*xv,r2*yv); %points inside outer circle
in2 = xor(in,in1); %points between circle
in3= inpolygon(xq1,yq1,r2*xv,r2*yv); %points inside outer circle
fig = figure(22);hold on;
% Random points
plot(xq(in2),yq(in2),'bo','MarkerFaceColor','r','MarkerSize',5,'Marker','o','MarkerEdgeColor','none');
axis equal;
plot(xq1(~in2),yq1(~in2),'bo','MarkerFaceColor','r','MarkerSize',5,'Marker','o','MarkerEdgeColor','none');
axis equal;
img= getframe(fig);
figure;
imshow(img.cdata)
hold on;
[r,c,rad] = circlefinder(img.cdata);
[m I] = max(rad);
%ploting the bigest estimated circle
angle = 2*pi*randn(k,1);
haffpointX = rad(I).*cos(angle)+ c(I);
haffpointY = rad(I).*sin(angle)+ r(I);
scatter(haffpointX,haffpointY,'g');
axis equal
I expect that for different random circles with noisy data Hough Transform estimates its circle with the number between the range of 1 and 2 so, I can use its results.
Thank you in advance

mean value in a sphere

I'm trying to calculate the mean value of the pixels inside a circle. In the future this needs to be extended to 3D, but for now a 2D sollution would already help me out.
As can be seen in the image, some pixels are entirely inside the circle, but some are only partly inside the circle. The ones partly in the circle also need to contribute only partly to the mean value. The pixels are square. This will simplify the mathematics I hope.
I can calculate the distance from the pixelcorners to the central point, from this you can find the pixels enterly inside and enterly outside. The rest needs correction. But how to find this correction.
[edit] thanks to Heath Raftery the problem is solved! [/edit]
the integral of a circle with radius r
As an example: I want to know the average pixelvalue of pixels in this circle. I know it is 0.3425, since 34.25% of the circle has a value of 1 and the rest is 0.
Function to check what part of a pixel is in the circle:
function [ a ] = incirc( x,y,r )
%only handles the top right quadrant of a circle
if x<0||y<0,error('only positive x,y');end
%integral of sqrt(r^2-x^2) dx
F = #(x,r) (1/2)*(x*sqrt(r^2-x^2)+r^2*atan(x/sqrt(r^2-x^2)));
%find corner locations
x=[x-0.5,x+0.5];
y=[y-0.5,y+0.5];
d = sqrt(x.^2+y.^2); %distance to closed and furthest corner
if max(d)<r,a=1;return;end %inside circle
if min(d)>r,a=0;return;end %outside circle
%intersections with edges (r^2 = x^2+y^2)
inters = [sqrt(r^2-y(1)^2),sqrt(r^2-y(2)^2),sqrt(r^2-x(1)^2),sqrt(r^2-x(2)^2)]; %x(1) x(2) y(1) y(2)
%remove imaginary and out of range intersections
inters(imag(inters)~=0)=NaN;
inters(inters<1E-5)=NaN; %to find values that are zero
inters([~((x(1)<inters(1:2))&(inters(1:2)<x(2))),~((y(1)<inters(3:4))&(inters(3:4)<y(2)))])=NaN;
idx = find(~isnan(inters));
if numel(idx)~=2,error('need two intersections of circle with pixel');end
%check area of pixel inside circumference
if all(idx==[1,2]) %2 intersections on y-edge
a=(F(y(2),r)-F(y(1),r)) - x(1); %area
elseif all(idx==[3,4]) %2 intersections on x-edge
a=(F(x(2),r)-F(x(1),r)) - y(1); %area
elseif all(idx==[1,3]) %one intersection on y-edge one on x-edge (left&bottom)
a=(F(inters(1),r)-F(x(1),r))- (y(1)*(inters(1)-x(1)));
elseif all(idx==[2,4]) %one intersection on y-edge one on x-edge (top&right)
a=(inters(2)-x(1))+(F(x(2),r)-F(inters(2),r))-(y(1)*(x(2)-inters(2)));
else
error('geometry')
end
a=real(a);
if a<0||a>1
error('computational error');
end
end
Script to test the function
M = ones(100); %data
M(1:50,:)=0;
pos=[50.2,50];
r = 2;
%calculate what the result should be
h=50-pos(2)+0.5;
A=pi*r^2;
wedge = acos(h/r)/pi;
triangle = h*sqrt(r^2-h^2);
res=(A*wedge-triangle)/A
S=0;N=0;
for i = 1:size(M,1)
for j = 1:size(M,2)
x=abs(j-pos(1));
y=abs(i-pos(2));
n=incirc( x,y,r );
M_(i,j)=n;
S = S+M(i,j)*n;
N = N+n;
end
end
result = S/N
result = 0.3425
You can see the algorithm finds the part of the pixel in the circle.
The question is missing a question, but I'll assume that it's not how to calculate whether pixels are fully inside or outside the circle. That's a relatively simple task. That is, a pixel is fully inside if the furtherest corner of the pixel to the centre is less than a radius away from the centre, and a pixel is fully outside if the closest corner of the pixel to the centre is more than a radius away from the centre.
The question of what proportion of pixels on the circumference fall within the circumference is much trickier. There are two fundamental solutions:
Exact and hard.
Approximate and a bit easier.
In both cases, note the horizontal and vertical symmetry means only the top right quadrant need be considered.
Then, for (1), translate the circle centre to the origin (0, 0) and treat the circumference as the function y(x) = sqrt(r^2 - x^2). Then, the area of an overlapping pixel within the circle is the integral:
integral(y(x) - y0, from x0 to x1, with respect to x)
where y0 is the bottom coordinate of the pixel, x0 is the left coordinate and x1 is the right coordinate.
This integral can be solved exactly with a trigonometric identity and a trigonometric substitution.
For (2), just generate a set of random points within the pixel and count how many of them fall within the circumference. As the set gets larger, the proportion of points that fall within the circumference to the count of all point approaches the proportion of the pixel within the circumference.
You can use inpolygon, to get the indices which lie inside the circle, once you have those indices you can get your pixels and do what you want.
M = rand(100); %data
[nx,ny] = size(M) ;
[X,Y] = meshgrid(1:ny,1:nx) ;
pos=[20,20];
r = 5;
phi=linspace(0,2*pi,100);
imagesc(M);
axis image
hold on
plot(pos(1),pos(2),'rx')
xc = pos(1)+r*sin(phi) ;
yc = pos(2)+r*cos(phi) ;
plot(xc,yc,'-r');
% hold off
%% get indices which are inside the circle
idx = inpolygon(X(:),Y(:),xc,yc) ;
xi = X(idx) ; yi = Y(idx) ;
plot(xi,yi,'.r')
mypixels = M(idx) ;
You can also use rangesearch to get the points lying within the given radius of the circle. As below:
M = rand(100); %data
[nx,ny] = size(M) ;
[X,Y] = meshgrid(1:ny,1:nx) ;
pos=[20,20];
r = 5;
phi=linspace(0,2*pi,100);
imagesc(M);
axis image
hold on
plot(pos(1),pos(2),'rx')
xc = pos(1)+r*sin(phi) ;
yc = pos(2)+r*cos(phi) ;
plot(xc,yc,'-r');
% hold off
%% Use nearest neighbour search
idx = rangesearch([X(:),Y(:)],pos,r) ;
xi = X(idx{1}) ; yi = Y(idx{1}) ;
plot(xi,yi,'.r')
mypixels = M(idx{1}) ;

Matlab - Find Coordinates between a straight line and a perimeter

I segmented a mouse and get its image-properties using bwlabel. Thereby I have access to the position of the centroid and the orientation of the mouse. I also get the perimeter of the mouse using bwperim.
I want to find the two points of the straight line passing through the centroid and having the same direction than the orientation of the mouse cutting the perimeter.
I find the equation of the straight line using that code :
% E is a 2*2 matrix containing the coordinates of the centroid and the
% coordinates of the point which belong to the straight line and making
% the right angle given by the orientation
coeffs = polyfit(E(:,1),E(:,2),1);
% Create the equation of the straight line
x = 1:width;
yfit = coeffs(1)*x+coeffs(2);
% Make sure there are only int values.
yfit = uint16(yfit);
I convert my values to uint16 because i want to fill a new matrix that I will compare with the matrix containing the perimeter. Here is what I do then :
% Create a matrix of zeros and set to 1 all the pixels which belong to the
% straight line
k = 1;
temp = false;
m = false(size(iPerim));
while temp~=true
temp = false;
if yfit(k) > 0
m(yfit(k),k)=1;
temp = true;
end
k = k+1;
end
[t,p] = ind2sub(size(m), find(m==1));
minM = [min(p),min(t)];
% complete the straight line to don't have little holes
x = linspace(minM(1),D(1),width);
y = coeffs(1)*x+coeffs(2);
idx = sub2ind(size(m),round(y),round(x));
m(idx) = 1;
Then I compare m with iPerim which is the matrix containing my perimeter:
% Compare the matrix of the perimeter and the matrix of the straight line
% and find the two points in common. It is the points where the straight
% line cut the perimeter
p = m & iPerim;
% Extract thoses coordinates
[coordsY,coordsX] = ind2sub(size(p), find(p==1));
Well I am a new user of Matlab so I think this is not a elegant solution but there is the result:
Matrix m
Perimeter in which I plot yfit
As you can see the algorithm detects only one point and not the second one (the yellow spot)... I figure why but I can't find the solution. It is because the line straight is cutting the perimeter through a diagonal but there are not coordinates in common...
Somebody has a solution to my problem ? And of course I am taking any advises conerning my code :)
Thank you very much !
Edit: If there is a easier solution I take it obviously
When the coordinate of the point where the mouse-perimeter and the line cross are E(2,:), then the position of this point in the line is where the distance is minimal. E.g. like:
[xLine, yLine] = find(m); % x,y positions of the line
dX = abs(xline-E(2,1)) % x-distance to x-coordinate of direction-point
dY = abs(yLine-E(2,2)) % y-distance to y-coordinate of direction-point
distP = sqrt(dX.^2+dY.^2) % distance of line-points to directon-point
[~,indMin] = min(distP); % index of line-point which has the minimum distance
xPoint = xLine(indMin(1));
yPoint = yLine(indMin(1));
The abs and sqrtfunctions are not necessary here for finding the right point, only for the correct intermediate values...
From the Matlab Documentation about ind2sub:
For matrices, [I,J] = ind2sub(size(A),find(A>5)) returns the same values as [I,J] = find(A>5).

Fourier transform for fiber alignment

I'm working on an application to determine from an image the degree of alignment of a fiber network. I've read several papers on this issue and they basically do this:
Find the 2D discrete Fourier transform (DFT = F(u,v)) of the image (gray, range 0-255)
Find the Fourier Spectrum (FS = abs(F(u,v))) and the Power Spectrum (PS = FS^2)
Convert spectrum to polar coordinates and divide it into 1º intervals.
Calculate number-averaged line intensities (FI) for each interval (theta), that is, the average of all the intensities (pixels) forming "theta" degrees with respect to the horizontal axis.
Transform FI(theta) to cartesian coordinates
Cxy(theta) = [FI*cos(theta), FI*sin(theta)]
Find eigenvalues (lambda1 and lambda2) of the matrix Cxy'*Cxy
Find alignment index as alpha = 1 - lamda2/lambda1
I've implemented this in MATLAB (code below), but I'm not sure whether it is ok since point 3 and 4 are not really clear for me (I'm getting similar results to those of the papers, but not in all cases). For instance, in point 3, "spectrum" is referring to FS or to PS?. And in point 4, how should this average be done? are all the pixels considered? (even though there are more pixels in the diagonal).
rgb = imread('network.tif');%513x513 pixels
im = rgb2gray(rgb);
im = imrotate(im,-90);%since FFT space is rotated 90º
FT = fft2(im) ;
FS = abs(FT); %Fourier spectrum
PS = FS.^2; % Power spectrum
FS = fftshift(FS);
PS = fftshift(PS);
xoffset = (513-1)/2;
yoffset = (513-1)/2;
% Avoid low frequency points
x1 = 5;
y1 = 0;
% Maximum high frequency pixels
x2 = 255;
y2 = 0;
for theta = 0:pi/180:pi
% Transposed rotation matrix
Rt = [cos(theta) sin(theta);
-sin(theta) cos(theta)];
% Find radial lines necessary for improfile
xy1_rot = Rt * [x1; y1] + [xoffset; yoffset];
xy2_rot = Rt * [x2; y2] + [xoffset; yoffset];
plot([xy1_rot(1) xy2_rot(1)], ...
[xy1_rot(2) xy2_rot(2)], ...
'linestyle','none', ...
'marker','o', ...
'color','k');
prof = improfile(F,[xy1_rot(1) xy2_rot(1)],[xy1_rot(2) xy2_rot(2)]);
i = i + 1;
FI(i) = sum(prof(:))/length(prof);
Cxy(i,:) = [FI(i)*cos(theta), FI(i)*sin(theta)];
end
C = Cxy'*Cxy;
[V,D] = eig(C)
lambda2 = D(1,1);
lambda1 = D(2,2);
alpha = 1 - lambda2/lambda1
Figure: A) original image, B) plot of log(P+1), C) polar plot of FI.
My main concern is that when I choose an artificial image perfectly aligned (attached figure), I get alpha = 0.91, and it should be exactly 1.
Any help will be greatly appreciated.
PD: those black dots in the middle plot are just the points used by improfile.
I believe that there are a couple sources of potential error here that are leading to you not getting a perfect alpha value.
Discrete Fourier Transform
You have discrete imaging data which forces you to take a discrete Fourier transform which inevitably (depending on the resolution of the input data) have some accuracy issues.
Binning vs. Sampling Along a Line
The way that you have done the binning is that you literally drew a line (rotated by a particular angle) and sampled the image along that line using improfile. Using improfile performs interpolation of your data along that line introducing yet another potential source of error. The default is nearest neighbor interpolation which in the example shown below can cause multiple "profiles" to all pick up the same points.
This was with a rotation of 1-degree off-vertical when technically you'd want those peaks to only appear for a perfectly vertical line. It is clear to see how this sort of interpolation of the Fourier spectrum can lead to a spread around the "correct" answer.
Data Undersampling
Similar to Nyquist sampling in the Fourier domain, sampling in the spatial domain has some requirements as well.
Imagine for a second that you wanted to use 45-degree bin widths instead of the 1-degree. Your approach would still sample along a thin line and use that sample to represent 45-degrees worth or data. Clearly, this is a gross under-sampling of the data and you can imagine that the result wouldn't be very accurate.
It becomes more and more of an issue the further you get from the center of the image since the data in this "bin" is really pie wedge shaped and you're approximating it with a line.
A Potential Solution
A different approach to binning would be to determine the polar coordinates (r, theta) for all pixel centers in the image. Then to bin the theta components into 1-degree bins. Then sum all of the values that fall into that bin.
This has several advantages:
It removes the undersampling that we talked about and draws samples from the entire "pie wedge" regardless of the sampling angle.
It ensures that each pixel belongs to one and only one angular bin
I have implemented this alternate approach in the code below with some false horizontal line data and am able to achieve an alpha value of 0.988 which I'd say is pretty good given the discrete nature of the data.
% Draw a bunch of horizontal lines
data = zeros(101);
data([5:5:end],:) = 1;
fourier = fftshift(fft2(data));
FS = abs(fourier);
PS = FS.^2;
center = fliplr(size(FS)) / 2;
[xx,yy] = meshgrid(1:size(FS,2), 1:size(FS, 1));
coords = [xx(:), yy(:)];
% De-mean coordinates to center at the middle of the image
coords = bsxfun(#minus, coords, center);
[theta, R] = cart2pol(coords(:,1), coords(:,2));
% Convert to degrees and round them to the nearest degree
degrees = mod(round(rad2deg(theta)), 360);
degreeRange = 0:359;
% Band pass to ignore high and low frequency components;
lowfreq = 5;
highfreq = size(FS,1)/2;
% Now average everything with the same degrees (sum over PS and average by the number of pixels)
for k = degreeRange
ps_integral(k+1) = mean(PS(degrees == k & R > lowfreq & R < highfreq));
fs_integral(k+1) = mean(FS(degrees == k & R > lowfreq & R < highfreq));
end
thetas = deg2rad(degreeRange);
Cxy = [ps_integral.*cos(thetas);
ps_integral.*sin(thetas)]';
C = Cxy' * Cxy;
[V,D] = eig(C);
lambda2 = D(1,1);
lambda1 = D(2,2);
alpha = 1 - lambda2/lambda1;

How to create random points alongside a complex polyline?

I would like to populate random points on a 2D plot, in such a way that the points fall in proximity of a "C" shaped polyline.
I managed to accomplish this for a rather simple square shaped "C":
This is how I did it:
% Marker color
c = 'k'; % Black
% Red "C" polyline
xl = [8,2,2,8];
yl = [8,8,2,2];
plot(xl,yl,'r','LineWidth',2);
hold on;
% Axis settings
axis equal;
axis([0,10,0,10]);
set(gca,'xtick',[],'ytick',[]);
step = 0.05; % Affects point quantity
coeff = 0.9; % Affects point density
% Top Horizontal segment
x = 2:step:9.5;
y = 8 + coeff*randn(size(x));
scatter(x,y,'filled','MarkerFaceColor',c);
% Vertical segment
y = 1.5:step:8.5;
x = 2 + coeff*randn(size(y));
scatter(x,y,'filled','MarkerFaceColor',c);
% Bottom Horizontal segment
x = 2:step:9.5;
y = 2 + coeff*randn(size(x));
scatter(x,y,'filled','MarkerFaceColor',c);
hold off;
As you can see in the code, for each segment of the polyline I generate the scatter point coordinates artificially using randn.
For the previous example, splitting the polyline into segments and generating the points manually is fine. However, what if I wanted to experiment with a more sophisticated "C" shape like this one:
Note that with my current approach, when the geometric complexity of the polyline increases so does the coding effort.
Before going any further, is there a better approach for this problem?
A simpler approach, which generalizes to any polyline, is to run a loop over the segments. For each segment, r is its length, and m is the number of points to be placed along that segment (it closely corresponds to the prescribed step size, with slight deviation in case the step size does not evenly divide the length). Note that both x and y are subject to random perturbation.
for n = 1:numel(xl)-1
r = norm([xl(n)-xl(n+1), yl(n)-yl(n+1)]);
m = round(r/step) + 1;
x = linspace(xl(n), xl(n+1), m) + coeff*randn(1,m);
y = linspace(yl(n), yl(n+1), m) + coeff*randn(1,m);
scatter(x,y,'filled','MarkerFaceColor',c);
end
Output:
A more complex example, using coeff = 0.4; and xl = [8,4,2,2,6,8];
yl = [8,6,8,2,4,2];
If you think this point cloud is too thin near the endpoints, you can artifically lengthen the first and last segments before running the loop. But I don't see the need: it makes sense that the fuzzied curve is thinning out at the extremities.
With your original approach, two places with the same distance to a line can sampled with a different probability, especially at the corners where two lines meet. I tried to fix this rephrasing the random experiment. The random experiment my code does is: "Pick a random point. Accept it with a probability of normpdf(d)<rand where d is the distance to the next line". This is a rejection sampling strategy.
xl = [8,4,2,2,6,8];
yl = [8,6,8,2,4,2];
resolution=50;
points_to_sample=200;
step=.5;
sigma=.4; %lower value to get points closer to the line.
xmax=(max(xl)+2);
ymax=(max(yl)+2);
dist=zeros(xmax*resolution+1,ymax*resolution+1);
x=[];
y=[];
for n = 1:numel(xl)-1
r = norm([xl(n)-xl(n+1), yl(n)-yl(n+1)]);
m = round(r/step) + 1;
x = [x,round(linspace(xl(n)*resolution+1, xl(n+1)*resolution+1, m*resolution))];
y = [y,round(linspace(yl(n)*resolution+1, yl(n+1)*resolution+1, m*resolution))];
end
%dist contains the lines:
dist(sub2ind(size(dist),x,y))=1;
%dist contains the normalized distance of each rastered pixel to the line.
dist=bwdist(dist)/resolution;
pseudo_pdf=normpdf(dist,0,sigma);
%scale up to have acceptance rate of 1 for most likely pixels.
pseudo_pdf=pseudo_pdf/max(pseudo_pdf(:));
sampled_points=zeros(0,2);
while size(sampled_points,1)<points_to_sample
%sample a random point
sx=rand*xmax;
sy=rand*ymax;
%accept it if criteria based on normal distribution matches.
if pseudo_pdf(round(sx*resolution)+1,round(sy*resolution)+1)>rand
sampled_points(end+1,:)=[sx,sy];
end
end
plot(xl,yl,'r','LineWidth',2);
hold on
scatter(sampled_points(:,1),sampled_points(:,2),'filled');