How do I draw a texture-mapped triangle in MATLAB? - 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

Related

How to implement a 3D boolean operation in MATLAB to make an intersection like in Blender (or any other 3D software)?

I am working on creating 3D images from 3 individual binary images, that were taken with 3 cameras. I have a corresponding calibration and know the setup (see below). Since the image preprocessing is mostly done in MATLAB I would like to implement everything there.
The current idea of my code is to extrude the 2D binary image according to the camera calibration. Here's what a typical binary image looks like:
And an extruded image looks like this in MATLAB:
With all 3 cameras extruded and a binning algorithm, I can create my final 3D-shape. This works fine so far, but takes a long time to compute, since I need to create a lot of extrusion steps to get a good surface.
I was thinking now to speed this up by recreating the process I would do in a 3D-modeling software like Blender. There I also extrude the outline of the binary image and create an intersection easily by just creating a spline for the outline, extrude them and use a boolean operator. Here's a Blender example with 2 extruded images:
I have no idea how to implement something like this in MATLAB. I wanted to create two instances of my binary contour at the top and bottom end of the extrusion "tube" and then define faces between the individual points and create an intersection afterwards. The point creation is no problem but the face definition and the intersection (boolean operator) are. Does anyone have an idea how this could be implemented?
This might not be an easy thing to do in MATLAB, but it's possible. I'll outline one set of steps here, using two intersecting cylinders as an example...
Creating a tetrahedral mesh:
The first step is to create a tetrahedral mesh for your extrusion. If your 2D binary image that you're extruding is convex and has no holes, you could do this using the delaunayTriangulation function:
DT = delaunayTriangulation(P);
Here, P contains the coordinate points of the "end caps" of your extrusion (i.e. the faces on each end of your tube). However, when generating tetrahedral meshes, delaunayTriangulation doesn't allow you to specify constrained edges, and as such it can end up filling in holes or concavities in your extrusion. There may be some better mesh-generation alternatives in other toolboxes, such as the Partial Differential Equations Toolbox, but I don't have access to them and can't speak to their applicability.
If automated mesh-generation options don't work, you'll have to build your tetrahedral mesh yourself and pass that data to triangulation. This could be tricky, but I'll show you some steps for how you can do this for a cylinder, which may help you figure it out for more involved shapes. Below, we build a set of coordinate points P1 and an M-by-4 matrix T1 where each row contains indices into the rows of P1 which define one tetrahedron:
% Create circle coordinates for the end caps:
N = 21;
theta = linspace(0, 2*pi, N).';
x = sin(theta(1:(end-1)));
y = cos(theta(1:(end-1)))+0.5;
z = ones(N-1, 1);
% Build tetrahedrons for first cylinder, aligned along the z axis:
P1 = [0 0.5 -1; ... % Center point of bottom face
x y -z; ... % Edge coordinates of bottom face
0 0.5 1; ... % Center point of top face
x y z]; % Edge coordinates of top face
cBottom = ones(N-1, 1); % Row indices for bottom center coordinate
cEdgeBottom1 = (2:N).'; % Row indices for bottom edge coordinates
cEdgeBottom2 = [3:N 2].'; % Shifted row indices for bottom edge coordinates
cTop = cBottom+N; % Row indices for top center coordinate
cEdgeTop1 = cEdgeBottom1+N; % Row indices for top edge coordinates
cEdgeTop2 = cEdgeBottom2+N; % Shifted row indices for top edge coordinates
% There are 3 tetrahedrons per radial slice of the cylinder: one that includes the
% bottom face and half of the side face (all generated simultaneously by the first row
% below), one that includes the other half of the side face (second row below), and one
% that includes the top face (third row below):
T1 = [cEdgeBottom1 cEdgeBottom2 cEdgeTop1 cBottom; ...
cEdgeBottom2 cEdgeTop1 cEdgeTop2 cBottom; ...
cEdgeTop1 cEdgeTop2 cTop cBottom];
TR1 = triangulation(T1, P1);
To better visualize how the cylinder is being divided into tetrahedrons, here's an animation of an exploded view:
Now we can create a second cylinder, offset and rotated so it aligns with the x axis and intersecting the first:
% Build tetrahedrons for second cylinder:
P2 = [P1(:, 3) -P1(:, 2) P1(:, 1)];
T2 = T1;
TR2 = triangulation(T2, P2);
% Plot cylinders:
tetramesh(TR1, 'FaceColor', 'r', 'FaceAlpha', 0.6);
hold on;
tetramesh(TR2, 'FaceColor', 'g', 'FaceAlpha', 0.6);
axis equal;
xlabel('x');
ylabel('y');
zlabel('z');
And here's the plot to visualize them:
Finding the region of intersection:
Once we have tetrahedral representations of the volumes, we can generate a grid of points covering the region of intersection and use the pointLocation function to determine which points are within both cylinders:
nGrid = 101;
[X, Y, Z] = meshgrid(linspace(-1, 1, nGrid));
QP = [X(:) Y(:) Z(:)];
indexIntersect = (~isnan(pointLocation(TR1, QP))) & ...
(~isnan(pointLocation(TR2, QP)));
mask = double(reshape(indexIntersect, [nGrid nGrid nGrid]));
We now have volume data mask that contains zeroes and ones, with the ones defining the region of intersection. The finer you make your grid (by adjusting nGrid), the more accurately this will represent the true region of intersection between the cylinders.
Generating a 3D surface:
You may want to create a surface from this data, defining the boundary of the intersection region. There are a couple ways to do this. One is to generate the surface with isosurface, which you could then visualize using featureEdges. For example:
[F, V] = isosurface(mask, 0.5);
TR = triangulation(F, V);
FE = featureEdges(TR, pi/6).';
xV = V(:, 1);
yV = V(:, 2);
zV = V(:, 3);
trisurf(TR, 'FaceColor', 'c', 'FaceAlpha', 0.8, 'EdgeColor', 'none');
axis equal;
xlabel('x');
ylabel('y');
zlabel('z');
hold on;
plot3(xV(FE), yV(FE), zV(FE), 'k');
And the resulting plot:
Another option is to create a "voxelated" Minecraft-like surface, as I illustrate here:
[X, Y, Z, C] = build_voxels(permute(mask, [2 1 3]));
hSurface = patch(X, Y, Z, 'c', ...
'AmbientStrength', 0.5, ...
'BackFaceLighting', 'unlit', ...
'EdgeColor', 'none', ...
'FaceLighting', 'flat');
axis equal;
view(-37.5, 30);
set(gca, 'XLim', [0 101], 'YLim', [25 75], 'ZLim', [0 102]);
xlabel('x');
ylabel('y');
zlabel('z');
grid on;
light('Position', get(gca, 'CameraPosition'), 'Style', 'local');
And the resulting plot:

Plot vector (or arc) onto a rose plot. MATLAB

I have two datasets. One detailing a list of angles (which I am plotting onto a rose plot):
angles
-0.8481065519
0.0367932161
2.6273740453
...
n
The other, detailing directional statistics from this group of angles:
angle,error
-0.848106563,0.8452778824
Where angle essentially defines the directional mean, and error the circular variance, essentially an error bar either side of the angle
I have thus far plotted a rose histogram using the set of angles, as such:
h = rose(angles,36)
I would like to create a plot of the directional statistic angle (it does not need a length/magnitude - just to the edge of the circle plot) with the error around it.
As an example:
I added the lines by hand in Matlab. If possible it would be good to perhaps have shading within the arc too. Alternatively, (and possibly preferred) would be to have just a sliver above the rose plot bins (so it doesn't cover the data) with a centre line (showing the angle and shading surrounding for the error.
Thanks in advance.
How about this?
%// Data
angles = 2*pi*.8*randn(1,1e4);
angle = -0.848106563;
error = 0.8452778824;
%// Plot rose
rose(angles, 36);
axis image %// make axis square
hold on
%// Plot mean
a = axis;
a = a(2); %// size of axis
plot([0 cos(angle)*a], [0 sin(angle)*a], 'r')
%// Plot error as many shaded triangles that compose a circular wedge
t = linspace(-error/2+angle,error/2+angle,100); %// increase "100" if needed
for k = 1:numel(t)-1
h = patch([0 cos(t(k))*a cos(t(k+1))*a 0], ...
[0 sin(t(k))*a sin(t(k+1))*a 0], [.5 0 0], 'edgecolor', 'none');
%// change color [.5 0 0] to something else if desired. Note also alpha
set(h,'Facealpha',.3) %// make transparent
end
%// Place rose on top by rearranging order of axis children
ch = get(gca,'children');
set(gca,'children',[ch(2:end); ch(1)]);
For this to work, you need to use a figure renderer capable of transparency. So you may need to adjust the figure's renderer property.

plotting trajectory data in matlab

I have trajectory information in 3 dimensions in matlab. These are of a gesture someone is making. When I connect the points in matlab by using plot3, I can see the trajectory nicely.
However, the trajectory is a line in the plot, but I don't know in which direction the gesture has been made as the time is not visualized. Is it possible to visualize this in a 3d plot (where the dimensions are x, y and z)? For example, the colour at the start is bright red and the colour at the end is black.
Thanks for your help,
Héctor
You need the comet3 plot (if you don't mind animations).
If you do mind animations, and you're looking for a static figure, I'd use a quiver.
Example:
% value of the parameter in the parametric equation
t = 0:0.5:2*pi;
% modified coordinate axes
u = [1 0 0].';
v = [0 2 0].';
% coordinates of the ellipse
Ell = bsxfun(#plus, bsxfun(#times, u, cos(t)), bsxfun(#times, v, sin(t)));
% difference vectors between all data points will be used as "velocities"
dEll = diff(Ell, 1,2);
% Quiver the ellipse
quiver3(...
Ell(1,1:end-1), Ell(2,1:end-1), Ell(3,1:end-1), ...
dEll(1,:), dEll(2,:), dEll(3,:), ...
2, 'r') % = scale, LineSpec
axis tight equal
Result:

How to plot a surface with a texture map

I want to plot a surface with a texture map on it, but the conditions are not the "ideal" ones.
first lets explain what I have.
I have a set of points (~7000) that are image coordinates, in a grid. This points do NOT define perfect squares. IT IS NOT A MESHGRID. For the sake of the question, lets assume that we have 9 points. Lets ilustrate what we have with an image:
X=[310,270,330,430,410,400,480,500,520]
Y=[300,400,500,300,400,500,300,400,500]
Lets say we can get the "structure" of the grid, so
size1=3;
size2=3;
points=zeros(size1,size2,2)
X=[310,270,330;
430,410,400;
480,500,520]
Y=[300,400,500;
300,400,500;
300,400,500]
points(:,:,1)=X;
points(:,:,2)=Y;
And now lets say we have a 3rd dimension, Z.
EDIT: Forgot to add a piece if info. I triangulate the points in the image and get a 3D correspondence, so when displayed in a surface they don't have the X and Y coords of the image, for a simplification of the given data lets say X=X/2 Y=Y/3
And we have:
points=zeros(size1,size2,3)
Z=[300,330,340;
300,310,330;
290,300,300]
surf(points(:,:,1)/2,points(:,:,2)/3,points(:,:,3))
What I want is to plot the surface in 3D with the image texture. Each element should have the texture piece that have in the first image.
This needs to work for huge datasheets. I don't specially need it to be fast.
related post (but I has a meshgrid as initial set of points) : Texture map for a 2D grid
PD: I can post original images + real data if needed, just posted this because i think it is easier with small data.
You can use the texturemap property of surf which works with rectangular meshes as well as with non-rectangular ones.
Creating non-rectangular data points
% creating non-rectangular data points
[X, Y] = meshgrid(1:100, 1:100);
X = X+rand(size(X))*5;
Y = Y+rand(size(X))*5;
which results in the following data points:
Generating height data:
Z = sin(X/max(X(:))*2*pi).*sin(Y/max(Y(:))*2*pi);
Loading picture:
[imageTest]=imread('peppers.png');
and mapping it as texture to the mesh:
surf(X,Y,Z, imageTest, ...
'edgecolor', 'none','FaceColor','texturemap')
Note that, for the sake of demonstration, this non-rectangular grid is quite sparsely populated which results in a rather jagged texture. With more points, the result gets much better, irrespective of the distortion of the grid points.
Note also that the number of grid points does not have to match the number of pixels in the texture image.
~edit~
If X and Y coordinates are only available for parts of the image, you can adjust the texture accordingly by
minX = round(min(X(:)));
maxX = round(max(X(:)));
minY = round(min(Y(:)));
maxY = round(max(Y(:)));
surf(X,Y,Z, imageTest(minX:maxX, minY:maxY, :), ...
'edgecolor', 'none','FaceColor','texturemap')
I don't think you can do what you want with Matlab's built in commands and features. But using the technique from my other answer with a high-res version of the grid can do it for you.
By "high-res", I mean an interpolated version of the non-uniform grid with denser data points. That is used to sample the texture at denser data points so it can be drawn using the texturemap feature of surf. You can't use a normal 2D interpolation, however, because you need to preserve the non-uniform grid shape. This is what I came up with:
function g = nonUniformGridInterp2(g, sx, sy)
[a,b] = size(g);
g = interp1(linspace(0,1,a), g, linspace(0,1,sy)); % interp columns
g = interp1(linspace(0,1,b), g', linspace(0,1,sx))'; % interp rows
Note that you have to call this twice to interpolate the X and Y points independently. Here's an example of the original grid and an interpolated version with 10 points in each direction.
Here's how to use that high-res grid with interp2 and texturemap.
function nonUniformTextureMap
% define the non-uniform surface grid
X = [310,270,330; 430,410,400; 480,500,520];
Y = [300,400,500; 300,400,500; 300,400,500];
Z = [300,330,340; 300,310,330; 290,300,300];
% get texture data
load penny % loads data in variable P
% define texture grid based on image size
% note: using 250-550 so that a,b covers the range used by X,Y
[m,n] = size(P);
[a,b] = meshgrid(linspace(250,550,n), linspace(250,550,m));
% get a high-res version of the non-uniform grid
s = 200; % number of samples in each direction
X2 = nonUniformGridInterp2(X, s, s);
Y2 = nonUniformGridInterp2(Y, s, s);
% sample (map) the texture on the non-uniform grid
C = interp2(a, b, P, X2, Y2);
% plot the original and high-res grid
figure
plot(X(:),Y(:),'o',X2(:),Y2(:),'.')
legend('original','high-res')
% plot the surface using sampled points for color
figure
surf(X, Y, Z, C, 'edgecolor', 'none', 'FaceColor','texturemap')
colormap gray
I'm not sure I understand your question, but I think that what you need to do is sample (map) the texture at your grid's X,Y points. Then you can simply plot the surface and use those samples as colors.
Here's an example using the data you gave in your question. It doesn't look like much, but using more X,Y,Z points should give the result you're after.
% define the non-uniform surface grid
X = [310,270,330; 430,410,400; 480,500,520];
Y = [300,400,500; 300,400,500; 300,400,500];
Z = [300,330,340; 300,310,330; 290,300,300];
% get texture data
load penny % loads data in variable P
% define texture grid based on image size
% note: using 600 so that a,b covers the range used by X,Y
[m,n] = size(P);
[a,b] = meshgrid(linspace(0,600,n), linspace(0,600,m));
% sample (map) the texture on the non-uniform grid
C = interp2(a, b, P, X, Y);
% plot the surface using sampled points for color
figure
surf(X, Y, Z, C)
colormap gray

Interpolating along the 2-D image slices

I have a set of 100 2-D image slices of the same size. I have used MATLAB to stack them to create a volumetric data. While the size of the 2-D slices is 480x488 pixels, the direction in which the images are stacked is not wide enough to visualize the volume in different orientation when projected. I need to interpolate along the slices to increase the size for visualization.
Can somebody please give me an idea or tip about how to do it?
Edit: Anotated projected microscopy-images
The figure 1 is the top-view of the projected volume.
The figure 2 is the side-view of the projected volume.
When I change the rotation-angle, and try to visualize the volume in different orientation, e.g. side-view (figure 2), is what I see as in figure 2.
I want to expand the side view by interpolating along the image slices.
Here is an adapted example from the MATLAB documentation on how to visualize volumetric data (similar to yours) using isosurfaces:
%# load MRI dataset: 27 slices of 128x128 images
load mri
D = squeeze(D); %# 27 2D-images
%# view slices as countours
contourslice(D,[],[],1:size(D,3))
colormap(map), view(3), axis tight
%# apply isosurface
figure
%#D = smooth3(D);
p = patch( isosurface(D,5) );
isonormals(D, p);
set(p, 'FaceColor',[1,.75,.65], 'EdgeColor','none')
daspect([1 1 .5]), view(3), axis tight, axis vis3d
camlight, lighting gouraud
%# add isocaps
patch(isocaps(D,5), 'FaceColor','interp', 'EdgeColor','none');
colormap(map)
MATLAB has a function interp3 that can be used for interpolation, assuming that the data is uniformly discretised.
Check out the documentation.
Hope this helps.
EDIT: The MATLAB function interp3 works as follows:
vi = interp3(x, y, z, v, xi, yi, zi);
I assume that your "stack" of slices defines the arrays x, y, z, v as 3D arrays, where x, y are the coordinates of the pixels in the plane, z is the "height" of each slice and v is the actual image slices, maybe as "intensity" values for the pixels.
If you want to interpolate new image slices at intermediate z values you could specify these levels in the zi array. The arrays xi, yi would again represent the coordinates of the pixels in the plane.
I created a function to interpolate along image slices. Below is the code:
function res = interp_along_slices( vol, scale )
% Interpolation along the image slices
% Get the size of the volume
[r c p] = size(vol);
% Pre-allocate the array:
% the third dimension is scale times the p
vol_interp = zeros(r,c,scale*p);
% interpolate along the image slices
for inr = 1:r;
for jnr = 1:c;
xi = vol(inr,jnr,:);
vol_interp(inr,jnr,:) = interp(xi, scale);
end;
end;
res = vol_interp;
end