Keystoning in Matlab - projecting a square on the floor - matlab

So, as a part of an experiment I need a function that could use the projector to project something on the floor. The projector is angled so the Keystone effect comes in to play and I'd like to correct it.
I try to do that by calculating the homography matrix H. I project a square at the bottom of the projected area, measure its properties (it is shown as a trapezoid on the ground) and calculate the matrix. And then I reverse the process with the idea to use the homography matrix H (it's inverse) to calculate and project a shape on the ground that would look like the original square and not a trapezoid.
But I'm not successful. I calculate a shorter quadrangle and still get a trapeze on the ground. I'm pretty sure I'm doing something wrong with the calculations but I'm not sure. I am new to Matlab :) Thx for the help. The code, with a lot of comments, is bellow.
function keystoning()
projArea = 100; % 100%
testSquare = 20; % test square dimension
%% Drawing a "what's supposed to be a square" on the floor
% black background, red "sqare" dimensions 20x20, attached to the base
% of the projection area so the base dimension of the "sqare" don't change
% and minimal measuring is required
testX=[40 40+testSquare 40+testSquare 40];
testY=[0 0 testSquare testSquare];
scrsz = get(groot,'screensize');
f = figure('position',[0 0 scrsz(3) scrsz(4)],...
'OuterPosition',[0 0 scrsz(3) scrsz(4)],...
'toolbar','none','menubar','none','name','none',...
'units','normalized',...
'Color','black','Resize','Off'...
);
figure(f); % focus window
fill(testX, testY,'r');
axis ([0 projArea 0 projArea])
pbaspect([1 1 1])
drawnow
disp('*** PLACE THE NEAR PART OF THE ELEMENT AT THE END OF THE TREADMILL ***');
%% Measuring of the shape in order to calculate the homography matrix H
disp('*** Measure the width of the base of the projection area in centimeters. ***');
prompt = 'Enter the measured distance: ';
projAreaM = input(prompt);
projAreaCoef=projAreaM/projArea; % coefficient between the axes scale and the projection area
disp('*** Measure the distance between the paralel lines of the trapezoid in centimeters. ***');
prompt = 'Enter the measured distance: ';
trapYm = input(prompt)/projAreaCoef
disp('*** Measure the length of the longer paralel line of the trapezoid in centimeters. ***');
prompt = 'Enter the measured length: ';
trapXm = input(prompt)/projAreaCoef
%% Calculating the homography matrix H
% trapezoid definition
% coordinates of the first two points for the trapezoid and the square
% are the same since its at the start of the projection area
difX=(trapXm-testSquare)/2; % length of the "pertrusions"
trapX=[testX(1) testX(2) testX(3)+difX testX(4)-difX];
trapY=[testY(1) testY(2) trapYm trapYm];
% variable definition
P=[testX;testY;ones(size(testX))]; % test rectangle
Q=[trapX;trapY;ones(size(trapX))]; % shown trapezoid
% homography matrix H calculation
H=Q/P; % solution to the equation HP = Q
%% Testing the homography matrix H
% we want to show the rectangle on the ground
Pnew=inv(H)*P;
fill(Pnew(1,:),Pnew(2,:),'r');
axis ([0 projArea 0 projArea])
pbaspect([1 1 1])
drawnow

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

How to draw smooth boundary for the image through the edge points in matlab

I have a segmented region as shown in figure 1. and i want to plot the lower boundary using matlab by connecting the lower edge points as shown in figure 2. I cannot plot like fig 2. So I did some morphological operations like fill,thicken,close but not getting the idea to plot.can you provide the matlab code.
figure 1
figure 2
Here is a solution
Threshold the image so it is just in binary, easy since this image is simple
Identify the position of the lowest pixel in each column
Smooth by just taking the max of every nth column. If you just want the lowest points then you can stop at line 4 of the below code!
Code is commented for more detail:
img = rgb2gray(imread('1.jpg')); % Read image
img = img > 0.5; % Threshold to get binary image
% Get last row where there is a 1 pixel in the image for each column
lastrow = max(repmat((1:size(img,1))', 1, size(img,2)).*img,[],1);
res = 30; % Pixel resolution for line averaging
% Ensure res divides num. columns by padding the end of the vector
lastrowpadded = [lastrow, NaN(1, res - mod(numel(lastrow),res))];
% Reshape into columns of length 'res', then get the max row number
lastrow2 = max(reshape(lastrowpadded,res,[]),[],1);
% Plots
imshow(img);
hold on
plot(1:size(img,2), lastrow, '.')
plot(res/2:res:size(lastrowpadded,2)-res/2, lastrow2, 'linewidth', 1.5)
hold off
legend('lowest points', 'smoothed lowest points')
Result:
Note: because the image is plotted with (0,0) in the upper-left corner, this plot will be upside down if you plotted it without the image. Subtract the lastrow2 or lastrow values from the height of the image to rectify this.
Edit: You might also be interested in convhull for creating a convex hull
[X,Y] = find(img); % After thresholding image as before, get X,Y coords
K = convhull(X,Y); % Get convex hull indices
imshow(img) % Show image
hold on
plot(Y(K),X(K),'linewidth',1.5) % Plot convex hull
Result:

Count circle objects in an image using matlab

How to count circle objects in a bright image using MATLAB?
The input image is:
imfindcircles function can't find any circle in this image.
Based on well known image processing techniques, you can write your own processing tool:
img = imread('Mlj6r.jpg'); % read the image
imgGray = rgb2gray(img); % convert to grayscale
sigma = 1;
imgGray = imgaussfilt(imgGray, sigma); % filter the image (we will take derivatives, which are sensitive to noise)
imshow(imgGray) % show the image
[gx, gy] = gradient(double(imgGray)); % take the first derivative
[gxx, gxy] = gradient(gx); % take the second derivatives
[gxy, gyy] = gradient(gy); % take the second derivatives
k = 0.04; %0.04-0.15 (see wikipedia)
blob = (gxx.*gyy - gxy.*gxy - k*(gxx + gyy).^2); % Harris corner detector (high second derivatives in two perpendicular directions)
blob = blob .* (gxx < 0 & gyy < 0); % select the top of the corner (i.e. positive second derivative)
figure
imshow(blob) % show the blobs
blobThresshold = 1;
circles = imregionalmax(blob) & blob > blobThresshold; % find local maxima and apply a thresshold
figure
imshow(imgGray) % show the original image
hold on
[X, Y] = find(circles); % find the position of the circles
plot(Y, X, 'w.'); % plot the circle positions on top of the original figure
nCircles = length(X)
This code counts 2710 circles, which is probably a slight (but not so bad) overestimation.
The following figure shows the original image with the circle positions indicated as white dots. Some wrong detections are made at the border of the object. You can try to make some adjustments to the constants sigma, k and blobThresshold to obtain better results. In particular, higher k may be beneficial. See wikipedia, for more information about the Harris corner detector.

Placing Circles into a given area

I have a given area (let's say 1000 x 1000), and I want to place circles in this area with the following requirements:
The number of circles is arbitrary, but fixed after it was chosen at the begin of the algorithm. The number should be so that most of the area is covered by the circles.
The circles shall have in general different radii, and the sizes of the radii shall be in a certain interval (e. g. between 20 and 80).
The circles shall not overlap.
I want to implement a code which does this with matlab. So far I have accomplished this code, which contains for simplicity only one value for the radius:
%% Area
perix=1000;
periy=1000;
%Number of circles
numbercircles=100;
radii(1:numbercircles)=70
%% Placing Circles
%first circle
xi=rand*perix; % array for storing x-values of circle centers
yi=radii(1); %array for storing y-values of circle centers
radiusarray=[radii(1)] ; %array for storing radii
plot(sin(linspace(0,2*pi,100))*radii(1)+xi,cos(linspace(0,2*pi,100))*radii(1)+yi);
hold on
axis([0 perix 0 perix])
% Idea:
%* Step 1: Random x coordinate for each circle middle point, y-coordinate at
% the top of the area, y_init=periy, and given radius.
%* Step 2: Lower y coordinate with constant x-coordinate until the distance to
%neighbour spheres is lower than the combined radii of those spheres.
%* Step 3: Move sphere on the x-axis to decrease the distance and further
%decrease the y-value if possible.
for lauf=2:numbercircles;
disp(numbercircles-lauf)
deltaz=10;
%% Step 1
% random x coordinate of sphere
x1=rand*100;
% y coordinate of circle is on the edge of the area and will be
% lower in the following
y1=periy;
Radnew=radii(lauf);
%% Step 2
%distance to other circle
d=min([sqrt((xi-x1).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi+perix)-x1).^2+(yi-y1).^2-(Radnew+radiusarray)) sqrt(((xi-perix)-x1).^2+(yi-y1).^2)-(Radnew+radiusarray)]);
while deltaz > 1e-4
%lower till y1=Radnew or distance to other spheres < 2*Rad
while ((y1>Radnew) & (d > deltaz))
%number=number+1
% lower y1
% if a<2
% deltaz
% end
y1=y1-deltaz;
% recalculate distance to all other spheres
d=min([sqrt((xi-x1).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi+perix)-x1).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi-perix)-x1).^2+(yi-y1).^2)-(Radnew+radiusarray)]);
end;
dmaxakt=d;
%adjust position in x direction and y direction: Increasing
%x coordinate iteratively in small steps
if (y1>Radnew)
xz(1)=x1+deltaz*rand;
if xz(1)>perix
xz(1)=x1-perix;
elseif xz(1)<0
xz(1)=x1+perix;
end;
dz(1)=min([sqrt((xi-xz(1)).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi+perix)-xz(1)).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi-perix)-xz(1)).^2+(yi-y1).^2)-(Radnew+radiusarray)]);
xz(2)=x1-deltaz*rand;
if xz(2)>perix
xz(2)=x1-perix;
elseif xz(1)<0
xz(2)=x1+perix;
end;
dz(2)=min([sqrt((xi-xz(2)).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi+perix)-xz(2)).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi-perix)-xz(2)).^2+(yi-y1).^2)-(Radnew+radiusarray)]);
%determine which distance z is the largest
vmax=find(max(dz)==dz);
%set the x-value to the value which belongs to the largest
%distance
x1=xz(vmax(1));
end;
%calculate new distance
d=min([sqrt((xi-x1).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi+perix)-x1).^2+(yi-y1).^2)-(Radnew+radiusarray) sqrt(((xi-perix)-x1).^2+(yi-y1).^2)-(Radnew+radiusarray)]);
if ((d>dmaxakt) & (y1>Radnew))
dmaxakt=d;
else
deltaz=deltaz*1e-1;
end;
end;
% if (y1<1e-5)
% y1=rand;
% end;
%last check: test if y-coordinate is still in the area
if (y1<periy-Radnew)
%Assembling the arrays for the circle places
xi=[xi x1];
yi=[yi y1];
radiusarray=[radiusarray Radnew];
end;
%Plotting
%zeit(lauf)=cputime-t;
plot(sin(linspace(0,2*pi,20))*Radnew+x1,cos(linspace(0,2*pi,20))*Radnew+y1);
%plot(sin(linspace(0,2*pi,20))*Rad1+x1+perix,cos(linspace(0,2*pi,20))*Rad1+y1);
%plot(sin(linspace(0,2*pi,20))*Rad1+x1-perix,cos(linspace(0,2*pi,20))*Rad1+y1);
hold on
axis([0 perix 0 perix])
pause(0.0001);
saveas(gcf, 'circle.png')
end;
The code basically assumes an initial x-coordinate and the maximum y-coordinate and lowers the y-coordinate until overlap is detected. Then the x-coordinate and the y-coordinate are modified to achieve high density of the circles.
The problem of this code is, that it is very slow, because the distance of lowering the y-coordinate is decreasing in every while-loop, which means that the time of lowering the spheres can be very long. I would appreciate if somebody could come up with an idea how to increase the speed of this code.
Not sure if this helps, but i checked your code as you posted it with the profiler and it says 98% of the time is saving the .png file
Line Number 111 / saveas(gcf, 'circle.png') / 99 / 47.741s / 98.4%
Do you want a picture everytime a new circle is drawn or just the last one? In the ladder case just put the 'saveas(...)' behind the last end, and caluclation is down to 1% of the time

How do I draw a texture-mapped triangle in MATLAB?

I have a triangle in (u,v) coordinates in an image. I would like to draw this triangle at 3D coordinates (X,Y,Z) texture-mapped with the triangle in the image.
Here, u,v,X,Y,Z are all vectors with three elements representing the three corners of the triangle.
I have a very ugly, slow and unsatisfactory solution in which I:
extract a rectangular part of the image
transform it to 3D space with the transformation defined by the three points
draw it with surface
finally masking out everything that is not part of the triangle with AlphaData
Surely there must be an easier way of doing this?
I have what I think is a better solution for you involving two steps. First, it extracts a rectangular part of your image, half of which is the triangular section to be used as a texture map and half of which will be ignored. Then this texture map is applied to a 3-D surface object whose points are adjusted to render it as a triangle instead of a quadrilateral.
For the example I will show here, I will use the following values for your various parameters, assuming you have a triangle whose points are labeled as the "origin" (triangle vertex), point "A", and point "B" in the image space (as in the first image below):
x = [0.1 0.9 0.8]; % [xorigin xA xB] coordinates in 3-D space
y = [0.9 0.1 0.8]; % [yorigin yA yB] coordinates in 3-D space
z = [0.1 0.1 0.9]; % [zorigin zA zB] coordinates in 3-D space
origin = [150 350]; % Vertex of triangle in image space
U = [300 -50]; % Vector from origin to point A in image space
V = [50 -250]; % Vector from origin to point B in image space
img = imread('peppers.png'); % Sample image for texture map
Extracting the texture map via projective transformation:
This step uses the Image Processing Toolbox functions maketform and imtransform to perform a projective transformation of the part of the image containing the triangle you want to use as a texture map. Note that since images have to be rectangular, an additional triangular section defined by points (O,B,C) has to be included.
The triangular part of the image you want will be in the lower right half of the image, while the additional triangular "filler" part will be in the upper left. Note that this additional triangle can extend outside of the image, which will cause part of it to be filled with black by default. Here's the code to perform the projective transform illustrated above:
A = origin+U; % Point A
B = origin+V; % Point B
C = B-U; % Point C
[nRows, nCols, nPages] = size(img); % Image dimensions
inputCorners = [origin; ... % Corner coordinates of input space
A; ...
B; ...
C];
outputCorners = [1 nRows; ... % Corner coordinates of output space
nCols nRows; ...
nCols 1; ...
1 1];
tform = maketform('projective', ... % Make the transformation structure
inputCorners, ...
outputCorners);
triTexture = imtransform(img,tform, 'bicubic', ... % Transform the image
'xdata', [1 nCols], ...
'ydata', [1 nRows], ...
'size', [nRows nCols]);
Note that this code will create a final image triTexture that is the same size as the input image img.
Plotting the triangular texture-mapped surface:
Plotting the surface is now quite simple, assuming you've ordered the values in your x,y,z variables such that the coordinates for the origin point are in the first indices, the coordinates for point A are in the second indices, and the coordinates for point B are in the third indices. You can now create new sets of 2-by-2 surface coordinates X,Y,Z that contain two copies of point B, which causes only half of the surface to be rendered (i.e. the half containing the desired triangular image as a texture map). Here's the code to do this:
index = [3 3; 1 2]; % Index used to create 2-by-2 surface coordinates
X = x(index); % x coordinates of surface
Y = y(index); % y coordinates of surface
Z = z(index); % z coordinates of surface
hSurface = surf(X, Y, Z, triTexture, ... % Plot texture-mapped surface
'FaceColor', 'texturemap', ...
'EdgeColor', 'none');
axis equal % Use equal scaling on axes
axis([0 1 0 1 0 1]); % Set axes limits
xlabel('x-axis'); % x-axis label
ylabel('y-axis'); % y-axis label
zlabel('z-axis'); % z-axis label
And here's the resulting texture-mapped triangular surface it creates, with an inset added to show that the texture map contains the correct triangular part of the original image:
Would WARP help?
http://www.mathworks.com/access/helpdesk/help/toolbox/images/warp.html