Scale 2D coordinates with respect to the centroid

I would like to scale, with respect to the centroid, curves_2.txt such that the Y (or X) coordinates of the marked points of curves_2.txt go to coincide with the Y (or X) coordinates of the marked points of curves_1.txt.
For example, in the image below:
  • the point in the green box should go from a value of X=194.887 to X=222
  • the point in the pink box must go from a value of X=320,887 to X=293
  • the point in the blue box must go from a value of Y=372.21 to Y=360
  • the point in the purple box must go from a value of Y=323.21 to Y=333
Is there any way to do this scaling operation (or something similar)? Thanks in advance!

3 Comments

How are the corresponding pairs of points selected? Are they given?
In future, please attach your data in .mat file format, like I have done here, to make downloading easier for us. Notice that all data is in a single .mat file.
I edited the post with the data in .mat format.
You can find the coordinates (yellow dots) calculated in the code.
I would like to do a scaling operation so that curve_2 (red) is the same size as curve_1 (black).

Sign in to comment.

 Accepted Answer

hello again Alberto
I opted for a simple x, y shrink ratios computation, sufficient to make the red curve match the black one (the new curve is the green one)
I didn't use any of your selected points , I simply used the points with max x and y distance to the centroids
I admit , a very basic approach (no need to worry about centroids shift as they are already overlaid)
load('Curves.mat')
x_curve_1 = sum(curve_1(:,1))/length(curve_1(:,1));
y_curve_1 = sum(curve_1(:,2))/length(curve_1(:,2));
G_curve_1 = [x_curve_1, y_curve_1];
x_curve_2 = sum(curve_2(:,1))/length(curve_2(:,1));
y_curve_2 = sum(curve_2(:,2))/length(curve_2(:,2));
G_curve_2 = [x_curve_2, y_curve_2];
% compute x, y shrink factors based on max ratios
x_factor = max(curve_2(:,1) - G_curve_2(1))./max(curve_1(:,1) - G_curve_1(1));
y_factor = max(curve_2(:,2) - G_curve_2(2))./max(curve_1(:,2) - G_curve_1(2));
x_curve_2new = G_curve_2(1) + (curve_2(:,1) - G_curve_2(1))/x_factor;
y_curve_2new = G_curve_2(2) + (curve_2(:,2) - G_curve_2(2))/y_factor;
figure
plot(curve_1(:,1), curve_1(:,2),'k.', 'MarkerSize', 10);
hold on
plot(curve_2(:,1), curve_2(:,2),'r.', 'MarkerSize', 10);
plot(x_curve_2new, y_curve_2new,'g.', 'MarkerSize', 10);
plot(G_curve_1(:,1), G_curve_1(:,2),'k*', 'MarkerSize', 10);
plot(G_curve_2(:,1), G_curve_2(:,2),'rd', 'MarkerSize', 10);

2 Comments

Hi @Mathieu NOE and thank you for your reply!
In the code you provided is it possible to know the scaling value? That is, by how much did the red curve reduce so that it became the green curve?
@Alberto Acri he used x_factor and y_factor. You could put in any factor youi want. Did you even see my answer below where I changed the size from 0.4 to 1.6?

Sign in to comment.

More Answers (2)

Let's call the curves "curve a" and "curve b". You want to map Xa1=194.887 to Xb1=222, and Xa2=320.887 to Xb2=293. Use the transformation Xb = c1 + c2*Xa:
222=c1 + c2*194.887
293=c1 + c2*320.887
which can be written
which is easily solved to get c1 and c2.
c=inv([1,194.887;1,320.887])*[222;293]
c = 2×1
112.1827 0.5635
Likewise, for the y transformation, you want to map Ya1=372.21 to Yb1=360 and Ya2=323.21 to Yb2=333, so you do the transformation Yb = d1 + d2*Ya. You determine d1 and d2 by solving
360=d1 + d2*372.21
333=d1 + d2*323.21
d=inv([1,372.21;1,323.21])*[360;333]
d = 2×1
154.9047 0.5510
Then you use c to transform all the X values from curve a to curve b. You use d to transform the Y values. Good luck!
Try this (Thanks Matt for uploading the mat file):
% Optional initialization steps
clc; % Clear the command window.
close all; % Close all figures (except those of imtool.)
clear; % Erase all existing variables. Or clearvars if you want.
workspace; % Make sure the workspace panel is showing.
format long g;
format compact;
fontSize = 18;
s = load('Curves.mat')
s = struct with fields:
curve_1: [158×2 double] curve_2: [275×2 double]
x = s.curve_1(:, 1);
y = s.curve_1(:, 2);
plot(x, y, 'b.', 'MarkerSize',14)
binaryImage = false(max(y), max(x));
for k = 1 : length(x)
binaryImage(y(k), x(k)) = true;
end
imshow(binaryImage)
axis xy; % Flip image
% Get distance transform
edtImage = bwdist(binaryImage);
imshow(edtImage, []);
% threshold(1, 4, edtImage)
mask = imfill(edtImage == 1, 'holes');
% Take largest blob
mask = bwareafilt(mask, 1);
% Get edges
% Plot the borders of all the blobs in the overlay above the original grayscale image
% using the coordinates returned by bwboundaries().
imshow(mask);
% bwboundaries() returns a cell array, where each cell contains the row/column coordinates for an object in the image.
% Here is where we actually get the boundaries for each blob.
boundaries = bwboundaries(mask);
% boundaries is a cell array - one cell for each blob.
% In each cell is an N-by-2 list of coordinates in a (row, column) format. Note: NOT (x,y).
% Column 1 is rows, or y. Column 2 is columns, or x.
numberOfBoundaries = size(boundaries, 1); % Count the boundaries so we can use it in our for loop
% Here is where we actually plot the boundaries of each blob in the overlay.
hold on; % Don't let boundaries blow away the displayed image.
for k = 1 : numberOfBoundaries
thisBoundary = boundaries{k}; % Get boundary for this specific blob.
x = thisBoundary(:,2); % Column 2 is the columns, which is x.
y = thisBoundary(:,1); % Column 1 is the rows, which is y.
plot(x, y, 'r-', 'LineWidth', 2); % Plot boundary in red.
end
hold off;
caption = sprintf('%d Outlines, from bwboundaries()', numberOfBoundaries);
fontSize = 15;
title(caption, 'FontSize', fontSize);
axis('on', 'xy'); % Make sure image is not artificially stretched because of screen's aspect ratio.
% Find centroid and bounding box.
props = regionprops(mask, 'BoundingBox');
% Crop image.
mask = imcrop(mask, props.BoundingBox);
% Recompute centroid on sub image.
props = regionprops(mask, 'Centroid');
figure;
imshow(mask, 'InitialMagnification',1000);
Warning: When running MATLAB Online, InitialMagnification can only be 'fit'.
hold on
axis('on', 'image')
xCentroid = props.Centroid(1);
yCentroid = props.Centroid(2);
plot(xCentroid, yCentroid, 'r+', 'LineWidth', 2, 'MarkerSize', 120);
axis xy; % Flip image
% If you want you can get the boundary (x,y) in the new subimage coordinates.
boundaries = bwboundaries(mask);
thisBoundary = boundaries{1}; % Get boundary for this specific blob.
x = thisBoundary(:,2); % Column 2 is the columns, which is x.
y = thisBoundary(:,1); % Column 1 is the rows, which is y.
% Plot curve with a variety of magnification factors
figure
mags = 0.4 : 0.2 : 1.6;
for k = 1 : numel(mags)
thisCurvex = mags(k) * (x - xCentroid) + xCentroid;
thisCurvey = mags(k) * (y - yCentroid) + yCentroid;
plot(thisCurvex, thisCurvey, '-', 'LineWidth', 3);
hold on;
end
plot(xCentroid, yCentroid, 'r+', 'LineWidth', 2, 'MarkerSize', 120);
grid on;
You can see the curves, which has the (x,y) sorted clockwise, displayed all with the same centroid but different magnifications.

10 Comments

Thank you for your reply.
Your code works in case "curve_1" is scaled.
My question referred to the scaling of "curve_2" until "curve_1" is obtained.
I tried modifying these lines
x = s.curve_2(:, 1);
y = s.curves_2(:, 2);
But it gives me the "false" error at this line:
binaryImage = false(max(y), max(x));
I thank you if you can tell me what I need to change.
I don't understand. Do you have two completely independent curves and you want to scale one until it's the same as the other? I'd try just computing the areas of each closed curve with polyarea then scale curve 2 with a scale factor that is the ratio of the areas.
Do you have two completely independent curves and you want to scale one until it's the same as the other?
Yes. I want to scale "curve_2" (curve with red dots) in such a way that it is similar to "curve_1" (curve with black dots) as in @Mathieu NOE's proposed solution (curve with green dots).
I don't understand.
What don't you understand? Correct me if I am wrong, but your code scales "curve_1" (curve with black dots) to me in such a way that it is similar to "curve_2". I want the opposite.
--------------------------------------
Beyond that, my question now is this: how can I determine a single scaling parameter (I don't want to use 'x_factor' and 'y_factor' found in Mathieu's code) that allows me to scale the red curve as the green curve? I using a scaling parameter of 0.565 get something similar (see blue curve).
I want to somehow obtain the scaling parameter of 0.565 (or similar).
I tried, as suggested, with the polyarea command:
a = polyarea(curve_1(:,1), curve_1(:,2)); % is the black curve
b = polyarea(curve_3(:,1), curve_3(:,2)); % is the green curve
parameter = a/b;
But I get a ratio of 0.609 and consequently the result is this:
You can't use polyarea unless your data points are sorted, like in my code. You can't use it with your original unsorted points. Just plot them with plot(x, y, 'b-') and see what the polygon looks like. It's not what you'd expect from just plotting markers.
Thanks Image Analyst for the clarification. I sorted the coordinates clockwise for the three curves, and for each curve, I joined all the coordinates with a line. The result is this:
I calculated the areas of the red curve (curve_2) and the green curve (curve_3) but I get a "scale" value of 0.3064 (quite different from the value of about 0.565 that I expected).
a = polyarea(curve_2_order(:,1), curve_2_order(:,2)); % red curve
b = polyarea(curve_3_order(:,1), curve_3_order(:,2)); % green curve
scale = b/a;
Can you tell me where I am going wrong ?
Attach curve_2_order and curve_3_order in a .mat file so I can use them.
Here are attached.
load('curve_1_order.mat')
load('curve_2_order.mat')
load('curve_3_order.mat')
a = polyarea(curve_2_order(:,1), curve_2_order(:,2));
b = polyarea(curve_3_order(:,1), curve_3_order(:,2));
scale = b/a;
figure
plot(curve_1_order(:,1), curve_1_order(:,2),'k-', 'LineWidth', 2);
hold on
plot(curve_2_order(:,1), curve_2_order(:,2),'r-', 'LineWidth', 2);
plot(curve_3_order(:,1), curve_3_order(:,2),'g-', 'LineWidth', 2);
xlim([180 340])
ylim([310 380])
axis equal
Try this:
load('curve_2_order.mat')
load('curve_3_order.mat')
x2 = curve_2_order(:,1);
y2 = curve_2_order(:,2);
x3 = curve_3_order(:,1);
y3 = curve_3_order(:,2);
% Get centroids
p2 = polyshape(x2, y2);
Warning: Polyshape has duplicate vertices, intersections, or other inconsistencies that may produce inaccurate or unexpected results. Input data has been modified to create a well-defined polyshape.
[xc2, yc2] = centroid(p2)
xc2 = 255.1984
yc2 = 347.3198
p3 = polyshape(x3, y3);
Warning: Polyshape has duplicate vertices, intersections, or other inconsistencies that may produce inaccurate or unexpected results. Input data has been modified to create a well-defined polyshape.
[xc3, yc3] = centroid(p3)
xc3 = 255.8210
yc3 = 346.5258
% Get areas and scale factor.
area2 = polyarea(x2, y2)
area2 = 2.9635e+03
area3 = polyarea(x3, y3)
area3 = 908.0046
% Want to scale x and y of curve 3 to match curve 2.
scale = sqrt(area2/area3)
scale = 1.8066
% Plot curves.
figure
hold on
plot(x2, y2,'r-', 'LineWidth', 2);
hold on
plot(x3, y3,'g-', 'LineWidth', 2);
% Scale curve 3 to match curve 2 as well as possible with no rotation.
xFit = scale * (x3 - xc3) + xc2;
yFit = scale * (y3 - yc3) + yc2;
plot(xFit, yFit, 'b--', 'LineWidth', 3);
legend('Curve 2', 'Curve 3', 'Curve 3 scaled to fit curve 2', 'Location', 'northwest')
axis equal
grid on;
% Check that areaFit should be very close to area2.
areaFit = polyarea(xFit, yFit)
areaFit = 2.9635e+03
Thank you! How should I modify these lines of code so that curve_2 scales like curve_3?
xFit = scale * (x3 - xc3) + xc2;
yFit = scale * (y3 - yc3) + yc2;
To scale curve 2 so that it matched curve 3, make these changes:
scale = sqrt(area3/area2)
xFit = scale * (x2 - xc2) + xc3;
yFit = scale * (y2 - yc2) + yc3;
Basically just swap 2 and 3.

Sign in to comment.

Categories

Find more on Interpolation in Help Center and File Exchange

Products

Release

R2021b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!