How to improve the linear best fit for this image? - matlab

I am trying to get a Linear best fit line through certain points in an image but am unable to get a line that would seem logical. I am not sure if I am doing something wrong here, or if that really is the best fit line in this case.
This is the original image:
This is the result after selecting an area of interest, filtering the edge, and then plotting the best fit line through the pixels of that edge.
I expected the best fit line (blue line in the results) to be almost vertical. However, as shown in the image, it is tilted at an angle while the red points are all close to a vertical and not scattered around the image. How can I correctly detect the vertical line?
Code:
image = imread('https://i.stack.imgur.com/rQ1SQ.png');
image = rgb2gray(image(85:270, 165:210, :));
filter = [ones(1, 6);zeros(1,6);-ones(1,6)];
filter = filter.';
filtered =imfilter(image,filter);
regions = regionprops(bwlabel(filtered,8), 'Area', 'PixelList');
[maxarea, maxindex] = max([regions.Area]);
p = polyfit(regions(maxindex).PixelList(:,1), regions(maxindex).PixelList(:,2), 1);
x = 1:size(image,2);
y = polyval(p,x);
subplot(1,3,1), imshow(image, 'border','tight');
hold on;
plot(x,y,'b');
title('Area of Interest in Original Image')
subplot(1,3,2), imshow(filtered);
hold on;
plot(x,y,'b');
title('Vertical Edge after Filtering')
subplot(1,3,3), imshow(fim);
hold on;
plot(x,y,'b');
plot(regions(maxindex).PixelList(:,1), regions(maxindex).PixelList(:,2), 'r.');
title('Image with points plotted')
Code Reference:
Shai's answer on a related question.

First a few comments. First you clearly have a vertical line here, but polyfit isn't fitting a vertical line. Why? because you are using a least squares fit on points with a high aspect ratio (4 px wide, 168 px high). One way to work around it is to rotate your data, fit a line, then rotate it back.
% rotate imroi points by 45 degrees
X = regions(maxindex).PixelList(:,1);
Y = regions(maxindex).PixelList(:,2);
N = size(X,1);
a = 45; %rotation angle
A = [cosd(a), sind(a); -sind(a), cosd(a)];
Z = (A * [X,Y]')'; %rotate points
figure;
subplot(1,2,1)
% plot rotated points
plot(Z(:,1), Z(:,2), 'r.')
hold on;
XRot = ones(N, 2); XRot(:,1) = Z(:,1);
b = XRot \ Z(:,2);
z = [linspace(min(Z(:,1)),max(Z(:,1)),N)', ones(N,1)];
plot(z(:,1), z*b, 'b-');
axis ij
title('Rotated imroi points')
subplot(1,2,2)
% plot un-rotated points
a = -45; %rotation angle
A = [cosd(a), sind(a); -sind(a), cosd(a)];
imshow(filtered);
hold on;
plot(X, Y, 'r.')
W = (A * [z(:,1) ,z * b]')';
plot(W(:,1), W(:,2), 'b-')
title('Image with points plotted')
I'd say in general your best bet for this kind of work is a computer vision package like vl_feat, which has a nice MATLAB (MEX) wrapper.

As pointed out by Peter in his answer, the issue in this problem is that the aspect ratio of the data points is very high: they are almost a vertical line already. The default Least Squares Method for a best fit line does not work well in this case. This can be resolved by using Orthogonal Least Squares method and to better understand that concept, have a look here which I found from Andrey's answer to a similar problem.
Luckily, there is a package developed by F.Carr which makes it extremely easy to implement the Orthogonal Linear Regression method.
The results by using this method are shown here.
This seems to have solved the problem for now although I haven't tried it on a large number of cases yet.

Related

convert image from Cartesian to Polar

I want to convert an image from Cartesian to Polar and to use it for opengl texture.
So I used matlab referring to the two articles below.
Link 1
Link 2
My code is exactly same with Link 2's anwser
% load image
img = imread('my_image.png');
% convert pixel coordinates from cartesian to polar
[h,w,~] = size(img);
[X,Y] = meshgrid((1:w)-floor(w/2), (1:h)-floor(h/2));
[theta,rho] = cart2pol(X, Y);
Z = zeros(size(theta));
% show pixel locations (subsample to get less dense points)
XX = X(1:8:end,1:4:end);
YY = Y(1:8:end,1:4:end);
tt = theta(1:8:end,1:4:end);
rr = rho(1:8:end,1:4:end);
subplot(121), scatter(XX(:),YY(:),3,'filled'), axis ij image
subplot(122), scatter(tt(:),rr(:),3,'filled'), axis ij square tight
% show images
figure
subplot(121), imshow(img), axis on
subplot(122), warp(theta, rho, Z, img), view(2), axis square
The result was exactly what I wanted, and I was very satisfied except for one thing. It's the area (red circled area) in the picture just below. Considering that the opposite side (blue circled area) is not, I think this part should also be filled. Because of this part is empty, so there is a problem when using it as a texture.
And I wonder how I can fill this part. Thank you.
(little difference from Link 2's answer code like degree<->radian and axis values. but i think it is not important.)
Those issues you show in your question happen because your algorithm is wrong.
What you did (push):
throw a grid on the source image
transform those points
try to plot these colored points and let MATLAB do some magic to make it look like a dense picture
Do it the other way around (pull):
throw a grid on the output
transform that backwards
sample the input at those points
The distinction is called "push" (into output) vs "pull" (from input). Only Pull gives proper results.
Very little MATLAB code is necessary. You just need pol2cart and interp2, and a meshgrid.
With interp2 you get to choose the interpolation (linear, cubic, ...). Nearest-neighbor interpolation leaves visible artefacts.
im = im2single(imread("PQFax.jpg"));
% center of polar map, manually picked
cx = 10 + 409/2;
cy = 7 + 413/2;
% output parameters
radius = 212;
dRho = 1;
dTheta = 2*pi / (2*pi * radius);
Thetas = pi/2 - (0:dTheta:2*pi);
Rhos = (0:dRho:radius);
% polar mesh
[Theta, Rho] = meshgrid(Thetas, Rhos);
% transform...
[Xq,Yq] = pol2cart(Theta, Rho);
% translate to sit on the circle's center
Xq = Xq + cx;
Yq = Yq + cy;
% sample image at those points
Ro = interp2(im(:,:,1), Xq,Yq, "cubic");
Go = interp2(im(:,:,2), Xq,Yq, "cubic");
Bo = interp2(im(:,:,3), Xq,Yq, "cubic");
Vo = cat(3, Ro, Go, Bo);
Vo = imrotate(Vo, 180);
imshow(Vo)
The other way around (get a "torus" from a "ribbon") is quite similar. Throw a meshgrid on the torus space, subtract center, transform from cartesian to polar, and use those to sample from the "ribbon" image into the "torus" image.
I'm more familiar with OpenCV than with MATLAB. Perhaps MATLAB has something like OpenCV's warpPolar(), or a generic remap(). In any case, the operation is trivial to do entirely "by hand" but there are enough supporting functions to take the heavy lifting off your hands (interp2, pol2cart, meshgrid).
1.- The white arcs tell that the used translation pol-cart introduces significant errors.
2.- Reversing the following script solves your question.
It's a script that goes from cart-pol without introducing errors or ignoring input data, which is what happens when such wide white arcs show up upon translation apparently correct.
clear all;clc;close all
clc,cla;
format long;
A=imread('shaffen dass.jpg');
[sz1 sz2 sz3]=size(A);
szx=sz2;szy=sz1;
A1=A(:,:,1);A2=A(:,:,2);A3=A(:,:,3); % working with binary maps or grey scale images this wouldn't be necessary
figure(1);imshow(A);
hold all;
Cx=floor(szx/2);Cy=floor(szy/2);
plot(Cx,Cy,'co'); % because observe image centre not centered
Rmin=80;Rmax=400; % radius search range for imfindcircles
[centers, radii]=imfindcircles(A,[Rmin Rmax],... % outer circle
'ObjectPolarity','dark','Sensitivity',0.9);
h=viscircles(centers,radii);
hold all; % inner circle
[centers2, radii2]=imfindcircles(A,[Rmin Rmax],...
'ObjectPolarity','bright');
h=viscircles(centers2,radii2);
% L=floor(.5*(radii+radii2)); % this is NOT the length X that should have the resulting XY morphed graph
L=floor(2*pi*radii); % expected length of the morphed graph
cx=floor(.5*(centers(1)+centers2(1))); % coordinates of averaged circle centres
cy=floor(.5*(centers(2)+centers2(2)));
plot(cx,cy,'r*'); % check avg centre circle is not aligned to figure centre
plot([cx 1],[cy 1],'r-.');
t=[45:360/L:404+1-360/L]; % if step=1 then we only get 360 points but need an amount of L points
% if angle step 1/L over minute waiting for for loop to finish
R=radii+5;x=R*sind(t)+cx;y=R*cosd(t)+cy; % build outer perimeter
hL1=plot(x,y,'m'); % axis equal;grid on;
% hold all;
% plot(hL1.XData,hL1.YData,'ro');
x_ref=hL1.XData;y_ref=hL1.YData;
% Sx=zeros(ceil(R),1);Sy=zeros(ceil(R),1);
Sx={};Sy={};
for k=1:1:numel(hL1.XData)
Lx=floor(linspace(x_ref(k),cx,ceil(R)));
Ly=floor(linspace(y_ref(k),cy,ceil(R)));
% plot(Lx,Ly,'go'); % check
% plot([cx x(k)],[cy y(k)],'r');
% L1=unique([Lx;Ly]','rows');
Sx=[Sx Lx'];Sy=[Sy Ly'];
end
sx=cell2mat(Sx);sy=cell2mat(Sy);
[s1 s2]=size(sx);
B1=uint8(zeros(s1,s2));
B2=uint8(zeros(s1,s2));
B3=uint8(zeros(s1,s2));
for n=1:1:s2
for k=1:1:s1
B1(k,n)=A1(sx(k,n),sy(k,n));
B2(k,n)=A2(sx(k,n),sy(k,n));
B3(k,n)=A3(sx(k,n),sy(k,n));
end
end
C=uint8(zeros(s1,s2,3));
C(:,:,1)=B1;
C(:,:,2)=B2;
C(:,:,3)=B3;
figure(2);imshow(C);
the resulting
3.- let me know if you'd like some assistance writing pol-cart from this script.
Regards
John BG

Find points within polygon with multiple self intersections with Matlab

I have a polygon which intersects itself multiple times. I try to create a mask from this polygon, i.e., to find all points/pixels location within the polygon. I use the Matlab function poly2mask for this. However, due to the multiple self-intersections this is the results I obtain:
Resulting mask from poly2mask for multi-self-intersecting polygon
So, some areas remain unmasked, because of the intersections. I think Matlab sees this as some sort of inclusions. The Matlab help for poly2mask doesn't mention anything about this. Does anyone have an idea how to also include these regions in the mask?
I obtain good results combining a small erosion/dilation step and imfill as follows:
data = load('polygon_edge.mat');
x = data.polygon_edge(:, 1);
y = data.polygon_edge(:, 2);
bw1 = poly2mask(x,y,ceil(max(y)),ceil(max(x)));
se = strel('sphere',1);
bw2 = imerode(imdilate(bw1,se), se);
bw3 = imfill(bw2, 'holes');
figure
imshow(bw3)
hold on
plot(x(:, 1),y(:, 1),'g','LineWidth',2)
The small erosion and dilation step is needed to be sure that all the regions are connected even at places where the polygon is only connected through a single point, otherwise imfill may see some non-existing holes.
you can use inpolygon:
bw1 = poly2mask(x,y,1000,1000);
subplot(131)
imshow(bw1)
hold on
plot(x([1:end 1]),y([1:end 1]),'g','LineWidth',2)
title('using poly2mask')
[xq,yq] = meshgrid(1:1000);
[IN,ON] = inpolygon(xq,yq,x,y);
bw2 = IN | ON;
subplot(132)
imshow(bw2)
hold on
plot(x([1:end 1]),y([1:end 1]),'g','LineWidth',2)
title('using inpolygon')
% boundary - seggested by another answer
k = boundary(x, y, 1); % 1 == tightest single-region boundary
bw3 = poly2mask(x(k), y(k), 1000, 1000);
subplot(133)
imshow(bw3)
hold on
plot(x([1:end 1]),y([1:end 1]),'g','LineWidth',2)
title('using boundary')
Update - I updated my answer to include boundary - it not seems to work well in my case.
You should first calculate the boundary of your polygon and use this to create your mask.
k = boundary(x, y, 0.99); % 1 == tightest single-region boundary
BW = poly2mask(x(k), y(k), m, n)
Using a shrink factor of 0.99 instead of 1 avoids undercutting, but sharp non-convex corners are still not fitted correctly.

Rotating Axes Around Line of Fit MATLAB

I'm currently frustrated by the following problem:
I've got trajectory data (i.e.: Longitude and Latitude data) which I interpolate to find a linear fitting (using polyfit and polyval in matlab).
What I'd like to do is to rotate the axes in a way that the x-axis (the Longitude one) ends up lying on the best-fit line, and therefore my data should now lie on this (rotated) axis.
What I've tried is to evaluate the rotation matrix from the slope of the line-of-fit (m in the formula for a first grade polynomial y=mx+q) as
[cos(m) -sin(m);sin(m) cos(m)]
and then multiply my original data by this matrix...to no avail!
I keep obtaining a plot where my data lay in the middle and not on the x-axis where I expect them to be.
What am I missing?
Thank you for any help!
Best Regards,
Wintermute
A couple of things:
If you have a linear function y=mx+b, the angle of that line is atan(m), not m. These are approximately the same for small m', but very different for largem`.
The linear component of a 2+ order polyfit is different than the linear component of a 1st order polyfit. You'll need to fit the data twice, once at your working level, and once with a first order fit.
Given a slope m, there are better ways of computing the rotation matrix than using trig functions (e.g. cos(atan(m))). I always try to avoid trig functions when performing geometry and replace them with linear algebra operations. This is usually faster, and leads to fewer problems with singularities. See code below.
This method is going to lead to problems for some trajectories. For example, consider a north/south trajectory. But that is a longer discussion.
Using the method described, plus the notes above, here is some sample code which implements this:
%Setup some sample data
long = linspace(1.12020, 1.2023, 1000);
lat = sin ( (long-min(long)) / (max(long)-min(long))*2*pi )*0.0001 + linspace(.2, .31, 1000);
%Perform polynomial fit
p = polyfit(long, lat, 4);
%Perform linear fit to identify rotation
pLinear = polyfit(long, lat, 1);
m = pLinear(1); %Assign a common variable for slope
angle = atan(m);
%Setup and apply rotation
% Compute rotation metrix using trig functions
rotationMatrix = [cos(angle) sin(angle); -sin(angle) cos(angle)];
% Compute same rotation metrix without trig
a = sqrt(m^2/(1+m^2)); %a, b are the solution to the system:
b = sqrt(1/(1+m^2)); % {a^2+b^2 = 1}, {m=a/b}
% %That is, the point (b,a) is on the unit
% circle, on a line with slope m
rotationMatrix = [b a; -a b]; %This matrix rotates the point (b,a) to (1,0)
% Generally you rotate data after removing the mean value
longLatRotated = rotationMatrix * [long(:)-mean(long) lat(:)-mean(lat)]';
%Plot to confirm
figure(2937623)
clf
subplot(211)
hold on
plot(long, lat, '.')
plot(long, polyval(p, long), 'k-')
axis tight
title('Initial data')
xlabel('Longitude')
ylabel('Latitude')
subplot(212)
hold on;
plot(longLatRotated(1,:), longLatRotated(2,:),'.b-');
axis tight
title('Rotated data')
xlabel('Rotated x axis')
ylabel('Rotated y axis')
The angle you are looking for in the rotation matrix is the angle of the line makes to the horizontal. This can be found as the arc-tangent of the slope since:
tan(\theta) = Opposite/Adjacent = Rise/Run = slope
so t = atan(m) and noting that you want to rotate the line back to horizontal, define the rotation matrix as:
R = [cos(-t) sin(-t)
sin(-t) cos(-t)]
Now you can rotate your points with R

Looking for a variation of waterfall plot in matlab

I have a 3-dimensional data to be plotted in matlab. The data set are built by stacking 10 exponential curves with different parameters along y directions such as
x = 0:0.01:15;
x0 = 0.5;
y = [beta1, beta2, beta3, beta4, beta5, beta6, beta7, beta8, beta9, beta10];
Z(1, :) = A*exp(-(x-x0).^2/beta1);
Z(2, :) = A*exp(-(x-x0).^2/beta2);
Z(3, :) = A*exp(-(x-x0).^2/beta3);
Z(4, :) = A*exp(-(x-x0).^2/beta4);
...
Z(10, :) = A*exp(-(x-x0).^2/beta10);
% here A could be change based on beta too (no code shown here)
I am trying to plot Z with waterfall except for I don't want the height (i.e. the vertical line) appears on the edge. I don't know if there is any other way to plot the data as waterfall-like curves but without those vertical lines. Thanks
"it is plotted with lines instead of patch with surface".
In other words, you want the boundary lines to be invisible. Well that's no trivial feat as the boundary lines are separate from any color scheme you can directly include. What you need to do is get the data after it drawn then modify it accordingly:
e.g.
[X,Y,Z] = peaks(30);
h = waterfall (X,Y,Z);
CD = get (h, 'CData');
CD(1,:) = nan;
CD(end-2:end,:) = nan;
set (h, 'CData', CD)
note that CD(1,:) is for the "rising" boundary, while CD(end-2:end-1,:) is for the falling boundary, and CD(end,:) is for the bottom.
i know this is an old post, but the below will make the region under the curve transparent:
figure;
[X,Y,Z] = peaks(10);
handle_figure = waterfall( X, Y, Z );
set( handle_figure, 'FaceColor', 'none' );

How to draw a straight across the centroid points of the barcode using best fit points Matlab

This is the processed image and I can't increase the bwareaopen() as it won't work for my other image.
Anyway I'm trying to find the shortest points in the centre points of the barcode, to get the straight line across the centre points in the barcode.
Example:
After doing a centroid command, the points in the barcode are near to each other. Therefore, I just wanted to get the shortest points(which is the barcode) and draw a straight line across.
All the points need not be join, best fit points will do.
Step 1
Step 2
Step 3
If you dont have the x,y elements Andrey uses, you can find them by segmenting the image and using a naive threshold value on the area to avoid including the number below the bar code.
I've hacked out a solution in MATLAB doing the following:
Loading the image and making it binary
Extracting all connected components using bwlabel().
Getting useful information about each of them via regionprops() [.centroid will be a good approximation to the middel point for the lines].
Thresholded out small regions (noise and numbers)
Extracted x,y coordinates
Used Andreys linear fit solution
Code:
set(0,'DefaultFigureWindowStyle','docked');
close all;clear all;clc;
Im = imread('29ekeap.jpg');
Im=rgb2gray(Im);
%%
%Make binary
temp = zeros(size(Im));
temp(Im > mean(Im(:)))=1;
Im = temp;
%Visualize
f1 = figure(1);
imagesc(Im);colormap(gray);
%Find connected components
LabelIm = bwlabel(Im);
RegionInfo = regionprops(LabelIm);
%Remove background region
RegionInfo(1) = [];
%Get average area of regions
AvgArea = mean([RegionInfo(1:end).Area]);
%Vector to keep track of likely "bar elements"
Bar = zeros(length(RegionInfo),1);
%Iterate over regions, plot centroids if area is big enough
for i=1:length(RegionInfo)
if RegionInfo(i).Area > AvgArea
hold on;
plot(RegionInfo(i).Centroid(1),RegionInfo(i).Centroid(2),'r*')
Bar(i) = 1;
end
end
%Extract x,y points for interpolation
X = [RegionInfo(Bar==1).Centroid];
X = reshape(X,2,length(X)/2);
x = X(1,:);
y = X(2,:);
%Plot line according to Andrey
p = polyfit(x,y,1);
xMin = min(x(:));
xMax = max(x(:));
xRange = xMin:0.01:xMax;
yRange = p(1).*xRange + p(2);
plot(xRange,yRange,'LineWidth',2,'Color',[0.9 0.2 0.2]);
The result is a pretty good fitted line. You should be able to extend it to the ends by using the 'p' polynomal and evaluate when you dont encounter any more '1's if needed.
Result:
If you already found the x,y of the centers, you should use polyfit function:
You will then find the polynomial coefficients of the best line. In order to draw a segment, you can take the minimal and maximal x
p = polyfit(x,y,1);
xMin = min(x(:));
xMax = max(x(:));
xRange = xMin:0.01:xMax;
yRange = p(1).*xRange + p(2);
plot(xRange,yRange);
If your ultimate goal is to generate a line perpendicular to the bars in the bar code and passing roughly through the centroids of the bars, then I have another option for you to consider...
A simple solution would be to perform a Hough transform to detect the primary orientation of lines in the bar code. Once you find the angle of the lines in the bar code, all you have to do is rotate that by 90 degrees to get the slope of a perpendicular line. The centroid of the entire bar code can then be used as an intercept for this line. Using the functions HOUGH and HOUGHPEAKS from the Image Processing Toolbox, here's the code starting with a cropped version of your image from step 1:
img = imread('bar_code.jpg'); %# Load the image
img = im2bw(img); %# Convert from RGB to BW
[H, theta, rho] = hough(img); %# Perform the Hough transform
peak = houghpeaks(H); %# Find the peak pt in the Hough transform
barAngle = theta(peak(2)); %# Find the angle of the bars
slope = -tan(pi*(barAngle + 90)/180); %# Compute the perpendicular line slope
[y, x] = find(img); %# Find the coordinates of all the white image points
xMean = mean(x); %# Find the x centroid of the bar code
yMean = mean(y); %# Find the y centroid of the bar code
xLine = 1:size(img,2); %# X points of perpendicular line
yLine = slope.*(xLine - xMean) + yMean; %# Y points of perpendicular line
imshow(img); %# Plot bar code image
hold on; %# Add to the plot
plot(xMean, yMean, 'r*'); %# Plot the bar code centroid
plot(xLine, yLine, 'r'); %# Plot the perpendicular line
And here's the resulting image: