Code covered by the BSD License  

Highlights from
Minimal Bounding Box

image thumbnail
from Minimal Bounding Box by Johannes Korsawe
Minimal bounding box around points in the (x,y,z) space

minboundbox(x,y,z,metric,level)
function [rotmat,cornerpoints,volume,surface,edgelength] = minboundbox(x,y,z,metric,level)
% minboundbox: Compute the minimal bounding box of points in 3d
% usage: [rotmat,cornerpoints,volume,surface,edgelength] = minboundbox(x,y,z,metric,level)
%
% arguments: (input)
%  x,y,z - vectors of points, describing points in 3d as
%        (x,y,z) pairs. x, y and z must be the same lengths.
%
%  metric - (OPTIONAL) - single letter character flag which
%        denotes the use of minimal volume, surface or sum of edges as the
%        metric to be minimized. metric may be either 'v', 's' or 'e',
%        capitalization is ignored.
%
%        DEFAULT: 'v'    ('volume')
%
%  level - (OPTIONAL) - either 1, 2, 3 or 4. This denotes the level of
%       reliability of the resulting minimal bounding box.
%       '1' denotes the search for a bounding box, where one side of the
%           box coincides with a face of the convex hull. (fast)
%       '2' like '1', but also incorporates a search through each pair of edges
%           which form a plane which coincides with one side of the box (slow)
%       '3' like '1', but also incorporates a search through each edge which is
%           parallel to an edge of the box (reasonably fast)
%       '4' like '1', '2' and '3' together. (slowest) (Never needed that.)
%
%       It depends on the application, what should be chosen here.
%       See the example at the end for the effects of this parameter.
%
%        DEFAULT: '3'    
%
% arguments: (output)
%  rotmat - (3x3) rotation matrix for mapping of the pointcloud into a
%                 box which is axis-parallel (use inv(rotmat) for inverse
%                 mapping).
%
%  cornerpoints - (8x3) the cornerpoints of the bounding box.
%
%  volume - (scalar) volume of the minimal box itself.
%
%  surface - (scalar) surface of the minimal box as found.
%
%  edgelength - (scalar) sum of the edgelengths of the minimal box as found.
%
% Thanks to John d'Errico for providing the solution to the 2d version of
% this problem via minboundrect from the FEX. I took a huge amount of his 
% syntax, organisation and comments for this submission. (Hope he wont sue
% me, though.)
%
% Thanks to Roger Stafford and John d'Errico for helpful discussions and
% suggestions.
%
% The algorithm is still not proven to produce the smallest possible
% enclosing box. But i never found any counterexamples for the 'best'
% algorithm with level 4, whilst the second example below shows, there are
% differences between the levels. I would be happy to have a proof that
% level 3 suffices for each case.
%
% Example usage:
%
% (1)
%      x = rand(100,1);
%      y = rand(100,1);
%      z = rand(100,1);
%      [rotmat,cornerpoints,volume,surface] = minboundbox(x,y,z);
%      plot3(x,y,z,'b.');hold on;plotminbox(cornerpoints,'r');
%
% (2)
%      x=[1,0,0.1,1];y=[0,1,0.1,1];z=[0,0,0.9,1];
%      [nerd,cornerpoints1,nerd,nerd] = minboundbox(x,y,z,'v',1);
%      [nerd,cornerpoints2,nerd,nerd] = minboundbox(x,y,z,'v',2);
%      [nerd,cornerpoints3,nerd,nerd] = minboundbox(x,y,z,'v',3);
%      plot3(x,y,z,'bo','LineWidth',5);hold on;
%      plotminbox(cornerpoints1,'b');hold on;
%      plotminbox(cornerpoints2,'m');hold on;
%      plotminbox(cornerpoints3,'r');axis equal;grid on;hold off;
%
%
% See also: minboundcircle, minboundtri, minboundsphere, minboundrect
%
%
% Author: Johannes Korsawe
% E-mail: johannes.korsawe@volkswagen.de
% Release: 1.0
% Release date: 09/01/2008

% default for metric
if (nargin<4) || isempty(metric)
  metric = 'v';
elseif ~ischar(metric)
  error 'metric must be a character flag if it is supplied.'
else
  % check for 'v', 's' or 'e'
  metric = lower(metric(:)');
  ind = strmatch(metric,{'volume','surface','edges'});
  if isempty(ind)
    error 'metric does not match either ''volume'', ''surface'' or ''edges''.'
  end
  % just keep the first letter.
  metric = metric(1);
end

% default for level
if (nargin<5 || isempty(level)),
    level=4;
elseif ~isnumeric(level) || (level~=1 && level~=2 && level~=3 && level~=4),
        error 'metric does not match either ''1'', ''2'', ''3'' or ''3''.'
end

% preprocess data
x = x(:);
y = y(:);
z = z(:);

% not many error checks to worry about
n1 = length(x);n2 = length(y);n3 = length(z);
if n1 ~= n2 || n1 ~= n3 || n2 ~= n3,
  error 'x, y and z must be the same sizes'
end

% start out with the convex hull of the points to
% reduce the problem dramatically. Note that any
% points in the interior of the convex hull are
% never needed, so we drop them.

try
 K = convhulln([x,y,z],{'Qt'});  % 'Pp' will silence the warnings

  % exclude those points inside the hull as not relevant
  % also sorts the points into their convex hull as a
  % closed polygon
  
 Ki = unique(K(:));
 [tf,loc] = ismember(K(:),Ki);K = reshape(loc,size(K));
 x = x(Ki);
 y = y(Ki);
 z = z(Ki);
 n1 =length(x);
 
catch
    error 'The number and/or distribution of given points does not allow the construction of a convex hull.'
end

% now we must find the bounding box of those points that remain 

% get the angle of each face of the hull polygon.
% calculate local coordinate system
% x,y and z values of faces' cornerpoints
fx = [x(K(:,1)),x(K(:,2)),x(K(:,3))];                   
fy = [y(K(:,1)),y(K(:,2)),y(K(:,3))];
fz = [z(K(:,1)),z(K(:,2)),z(K(:,3))];
% faces' edges
v1 = [fx(:,2)-fx(:,1),fy(:,2)-fy(:,1),fz(:,2)-fz(:,1)];
v1 = v1./(repmat(sqrt(sum(v1.^2,2)),1,3));
v2 = [fx(:,3)-fx(:,1),fy(:,3)-fy(:,1),fz(:,3)-fz(:,1)];
v2 = v2-repmat(dot(v1,v2,2),1,3).*v1;
v2 = v2./(repmat(sqrt(sum(v2.^2,2)),1,3));
% faces' normals
nv = cross(v1,v2,2);
% faces' orientations measured in Euler angles
[alpha,beta,gamma]=euler123(v1,v2,nv);

nang = size(alpha,1);
d = inf;        % this will be the minimal value
rotmat=[];minmax=[];
xyz = [x,y,z];
if level==1 || level==3,    % check each face of the hull
    for i = 1:nang,
      % check current orientation
      % receive minimal value, rotation matrix and dimensions of the enclosing box
      [d, rotmat, minmax] = checkbox(alpha(i),beta(i),gamma(i),xyz,metric,d,rotmat,minmax);
    end
end

if level>1,
    e = edgelist(K);ne = size(e,1); % get a sorted list of edges
end

if level==2 || level==4,    % check each set of two edges of the convex hull (which obviously includes case '1' above)
    for i = 1:ne,   % go through edge list
        va = xyz(e(i,1),:)-xyz(e(i,2),:);
        va = va/norm(va);
        for j = i+1:ne, % go through remaining edge list
            vb = xyz(e(j,1),:)-xyz(e(j,2),:);
            vb = vb - dot(va,vb)*va; % orthogonaylize second edge wrt first
            nv = cross(va,vb);  % normal to plane formed by the two edges
            if sum(abs(nv))>0,
                vb = vb/norm(vb);nv = nv/norm(nv);
                [alp,bet,gam]=euler123(va,vb,nv);
                [d, rotmat, minmax] = checkbox(alp,bet,gam,xyz,metric,d,rotmat,minmax);
            end
        end
    end
end

if level==3 || level==4,    % check edge as parallel to one edge of the bounding box
    for i = 1:ne, % go through edge list
        % calculate rhs with edge as one of the axes
        va = xyz(e(i,1),:)-xyz(e(i,2),:);
        vb = [va(2),-va(1),0];if sum(abs(vb))==0,vb = [va(3),0,-va(1)];end
        va = va/norm(va);vb = vb/norm(vb);
        nv = cross(va,vb);
        % check all combinations of possible rhs
        [alp,bet,gam]=euler123(va,vb,nv);
        [d, rotmat, minmax] = checkbox(alp,bet,gam,xyz,metric,d,rotmat,minmax);
        [alp,bet,gam]=euler123(vb,nv,va);
        [d, rotmat, minmax] = checkbox(alp,bet,gam,xyz,metric,d,rotmat,minmax);
        [alp,bet,gam]=euler123(nv,va,vb);
        [d, rotmat, minmax] = checkbox(alp,bet,gam,xyz,metric,d,rotmat,minmax);
    end
end

% get the output values
h = [minmax(1,1),minmax(1,2),minmax(1,3);    % xmin,ymin,zmin
     minmax(2,1),minmax(1,2),minmax(1,3);    % xmax,ymin,zmin
     minmax(2,1),minmax(2,2),minmax(1,3);    % xmax,ymax,zmin
     minmax(1,1),minmax(2,2),minmax(1,3);    % xmin,ymax,zmin
     minmax(1,1),minmax(1,2),minmax(2,3);    % xmin,ymin,zmax
     minmax(2,1),minmax(1,2),minmax(2,3);    % xmax,ymin,zmax
     minmax(2,1),minmax(2,2),minmax(2,3);    % xmax,ymax,zmax
     minmax(1,1),minmax(2,2),minmax(2,3);    % xmin,ymax,zmax
     ];

cornerpoints = h*inv(rotmat); % minimal boxes' cornerpoints
h = minmax(2,:)-minmax(1,:);
volume = h(1)*h(2)*h(3);
surface = 2*(h(1)*h(2)+h(2)*h(3)+h(3)*h(1));
edgelength = 4*sum(h);

% all done

end % mainline end

function [alpha,beta,gamma]=euler123(v1,v2,nv)
% calculate Euler angles for the x, y', z'' Euler sequence
% see also
% http://en.wikipedia.org/wiki/Euler_angles
% http://de.wikipedia.org/wiki/Eulersche_Winkel

% --> beta
beta = asin(sign(nv(:,1)).*min(1,abs(nv(:,1))));
% --> alpha
alpha=0*beta;
i1 = find(nv(:,1)==1);i2 = setdiff(1:size(nv,1),i1);
if ~isempty(i1), 
    alpha(i1) = asin(sign(v2(i1,3)).*min(1,abs(v2(i1,3))));
end
if ~isempty(i2),
    alpha(i2) = acos(sign(nv(i2,3)./cos(beta(i2))).*min(1,abs(nv(i2,3)./cos(beta(i2)))));
    i3 = find(sign(nv(i2,2)) ~= sign(-sin(alpha(i2)).*cos(beta(i2))));
    alpha(i2(i3)) = -alpha(i2(i3));
end
% --> gamma
gamma = 0*alpha;
if ~isempty(i2),
    singamma = -v2(i2,1)./cos(beta(i2));
    i21 = find(v1(i2,1)>=0);i22=setdiff(1:length(i2),i21);
    gamma(i2(i21)) = asin(sign(singamma(i21)).*min(1,abs(singamma(i21))));
    gamma(i2(i22)) = -pi-asin(sign(singamma(i22)).*min(1,abs(singamma(i22))));
end

end % function euler123

function [d, rotmat, minmax] = checkbox(alpha,beta,gamma,xyz,metric,d,rotmat,minmax)

% will need a 3x3 rotation matrix through (Euler X, Y', Z'')-angles alpha, beta and gamma
Rmat = @(alpha,beta,gamma) [cos(beta)*cos(gamma) -cos(beta)*sin(gamma) sin(beta)
                            sin(alpha)*sin(beta)*cos(gamma)+cos(alpha)*sin(gamma) -sin(alpha)*sin(beta)*sin(gamma)+cos(alpha)*cos(gamma) -sin(alpha)*cos(beta)
                            -cos(alpha)*sin(beta)*cos(gamma)+sin(alpha)*sin(gamma) cos(alpha)*sin(beta)*sin(gamma)+sin(alpha)*cos(gamma) cos(alpha)*cos(beta)];

rot = Rmat(alpha,beta,gamma);
xyz_i = xyz*rot;                      % now the actual face is in the x-y plane

x_i = xyz_i(:,1);y_i = xyz_i(:,2);          % .. so take only the x and y values
rot2 = minrect(x_i,y_i,metric);             % find the optimal rotation around z-axis
rot = rot*[[rot2,[0;0]];[0,0,1]];           % combine that with the formar rotation
xyz_i = xyz*rot;                            % now again the xyz_i values, but in optimal axisparallel shape

xyzmin = min(xyz_i,[],1);
xyzmax = max(xyz_i,[],1);
h = xyzmax-xyzmin;

if metric == 'v',       % smallest volume
    d_i = h(1)*h(2)*h(3);
elseif metric == 's',   % smallest surface
    d_i = h(1)*h(2)+h(2)*h(3)+h(3)*h(1);
else,                   % smallest sum of edges
    d_i = sum(h);
end

if d_i < d,
    d = d_i;
    rotmat = rot;
    minmax = [xyzmin;xyzmax];
end

end % function checkbox

function rot = minrect(x,y,metric)
% see comments from minboundrect of John d'Errico
% i am only interested in the additional rotation matrix around [0,0,1]
edges = convhull(x,y,{'Qt'});
x = x(edges);y = y(edges);
ind = 1:length(x)-1;
Rmat = @(theta) [cos(theta) sin(theta);-sin(theta) cos(theta)];
edgeangles = atan2(y(ind+1) - y(ind),x(ind+1) - x(ind));edgeangles = unique(mod(edgeangles,pi/2));
nang = length(edgeangles);
area = inf;perimeter = inf;met = inf;xy = [x,y];
for i = 1:nang
  rot_i = Rmat(-edgeangles(i));xyr = xy*rot_i;
  xymin = min(xyr,[],1);xymax = max(xyr,[],1);
  A_i = prod(xymax - xymin);P_i = 2*sum(xymax-xymin);
  if metric=='v', M_i = A_i;else, M_i = P_i;end
  if M_i<met, rot = rot_i;met = M_i;end
end

end % function minrect

function e = edgelist(K)
% generate an edgelist from the convex body K
e = [K(:,1),K(:,2)];e = [e;K(:,2),K(:,3)];e = [e;K(:,1),K(:,3)];
e = [min(e,[],2),max(e,[],2)];ne = size(e,1);
e = [e,e(:,2)+ne*e(:,1)];
[nerd,I] = sort(e(:,3));
e = e(I(1:2:end),:);

end % edgelist

Contact us at files@mathworks.com