gusucode.com > vision工具箱matlab源码程序 > vision/pcfitcylinder.m
function [model, inlierIndices, outlierIndices, rmse] = pcfitcylinder(varargin) %PCFITCYLINDER Fit cylinder to a 3-D point cloud. % model = PCFITCYLINDER(ptCloud, maxDistance) fits a cylinder to the % point cloud, ptCloud. The cylinder is described by a cylinderModel % object. maxDistance is a maximum allowed distance from an inlier % point to the cylinder. This function uses the M-estimator SAmple % Consensus (MSAC) algorithm to find the cylinder. % % model = PCFITCYLINDER(..., referenceVector) fits a cylinder to the % point cloud with additional orientation constraints. referenceVector is % a 1-by-3 vector used as a reference orientation. % % model = PCFITCYLINDER(..., referenceVector, maxAngularDistance) fits a % cylinder to the point cloud with additional orientation constraints. % maxAngularDistance specifies the maximum allowed absolute angular % distance in degrees between the direction of fitted cylinder and the % reference orientation. If it is not specified, the default is 5 % degrees. % % [..., inlierIndices, outlierIndices] = PCFITCYLINDER(...) additionally % returns linear indices to the inlier and outlier points in ptCloud. % % [..., rmse] = PCFITCYLINDER(...) additionally returns root mean square % error of the distance of inlier points to the model. % % [...] = PCFITCYLINDER(..., Name,Value) specifies additional name-value % pair arguments described below: % % 'SampleIndices' A vector specifying the linear indices of points % to be sampled in the input point cloud. The % indices, for example, can be generated by % findPointsInROI method of pointCloud object. % By default, the entire point cloud is processed. % % Default: [] % % 'MaxNumTrials' A positive integer scalar specifying the maximum % number of random trials for finding the inliers. % Increasing this value will improve the robustness % of the output at the expense of additional % computation. % % Default: 1000 % % 'Confidence' A numeric scalar, C, 0 < C < 100, specifying the % desired confidence (in percentage) for finding % the maximum number of inliers. Increasing this % value will improve the robustness of the output % at the expense of additional computation. % % Default: 99 % % Notes: % ------ % - Point cloud normals are required by the fitting algorithm. If the % Normal property of the input ptCloud is empty, the function fills it. % % - When the Normal property is filled automatically, the number of % points, K, to fit local plane is set to 6. This default may not work % under all circumstances. If the fitting fails, consider calling % pcnormals function with a custom value of K. % % Class Support % ------------- % ptCloud must be pointCloud object. % % Example: Detect a cylinder from point cloud % ------------------------------------------- % load('object3d.mat'); % % figure % pcshow(ptCloud) % xlabel('X(m)') % ylabel('Y(m)') % zlabel('Z(m)') % title('Original Point Cloud') % % % Set the maximum point-to-cylinder distance (5mm) for cylinder fitting % maxDistance = 0.005; % % % Set the roi to constrain the search % roi = [0.4, 0.6, -inf, 0.2, 0.1, inf]; % sampleIndices = findPointsInROI(ptCloud, roi); % % % Set the orientation constraint % referenceVector = [0, 0, 1]; % % % Detect the cylinder and extract it from the point cloud % [model, inlierIndices] = pcfitcylinder(ptCloud, maxDistance,... % referenceVector, 'SampleIndices', sampleIndices); % pc = select(ptCloud, inlierIndices); % % % Plot the cylinder % figure % pcshow(pc) % title('Cylinder Point Cloud') % % See also pointCloud, pointCloud>findPointsInROI, cylinderModel, pcshow, % pcfitplane, pcfitsphere % Copyright 2015 The MathWorks, Inc. % % References: % ---------- % P. H. S. Torr and A. Zisserman, "MLESAC: A New Robust Estimator with % Application to Estimating Image Geometry," Computer Vision and Image % Understanding, 2000. % Parse input arguments [ptCloud, ransacParams, sampleIndices, referenceVector, maxAngularDistance] = ... vision.internal.ransac.validateAndParseRansacInputs(mfilename, true, varargin{:}); % Use two points with normal to fit a cylinder sampleSize = 2; ransacParams.sampleSize = sampleSize; % Compute normal property if it is not available if isempty(ptCloud.Normal) % Use 6 neighboring points to estimate a normal vector ptCloud.Normal = surfaceNormalImpl(ptCloud, 6); end % Initialization [statusCode, status, pc, validPtCloudIndices] = ... vision.internal.ransac.initializeRansacModel(ptCloud, sampleIndices, sampleSize); % Compute the geometric model parameter with MSAC if status == statusCode.NoError ransacFuncs.fitFunc = @fitCylinder; ransacFuncs.evalFunc = @evalCylinder; % The initial model parameters are expressed as [x,y,z, dx,dy,dz, r], % where (x,y,z) is a point on the axis, (dx,dy,dz) specifies the % direction of the axis, and r is the radius. if isempty(referenceVector) ransacFuncs.checkFunc = @checkCylinder; [isFound, modelParams] = vision.internal.ransac.msac([pc.Location,... pc.Normal], ransacParams, ransacFuncs); else % Fit the cylinder with orientation constraint ransacFuncs.checkFunc = @checkOrientedCylinder; denorm = sqrt(dot(referenceVector,referenceVector)); normAxis = referenceVector ./ denorm; [isFound, modelParams] = vision.internal.ransac.msac(... [pc.Location, pc.Normal],ransacParams, ransacFuncs,... normAxis, maxAngularDistance); if isFound % Adjust the cylinder axis vector so that its direction matches the % referenceVector. This makes the absolute angular distance always % smaller than 90 degrees. a = min(1, max(-1, dot(normAxis, modelParams(4:6)))); angle = abs(acos(a)); if angle > pi/2 modelParams(4:6) = -modelParams(4:6); end end end if ~isFound status = statusCode.NotEnoughInliers; else % Re-evaluate the best model if ~isempty(sampleIndices) [pc, validPtCloudIndices] = removeInvalidPoints(ptCloud); end distances = evalCylinder(modelParams, pc.Location); inlierIndices = validPtCloudIndices(distances < ransacParams.maxDistance); % Convert to end-points expression modelParams = convertToFiniteCylinderModel(modelParams, ptCloud, inlierIndices); end end if status == statusCode.NoError % Construct the cylinder object model = cylinderModel(modelParams); else model = cylinderModel.empty; end % Report runtime error vision.internal.ransac.checkRansacRuntimeStatus(statusCode, status); % Extract inliers needInlierIndices = (nargout > 1); needOutlierIndices = (nargout > 2); needRMSE = (nargout > 3); if needInlierIndices if status ~= statusCode.NoError inlierIndices = []; end end % Extract outliers if needOutlierIndices if status == statusCode.NoError flag = true(ptCloud.Count, 1); flag(inlierIndices) = false; outlierIndices = find(flag); else outlierIndices = []; end end % Report RMSE if needRMSE if status == statusCode.NoError rmse = mean(distances(distances < ransacParams.maxDistance)); else rmse = []; end end %========================================================================== % Cylinder equations: % axis line: v(p) = p0 + (p - p0)*k % point-to-axis distance: r^2 = ||(p2-p1)x(p1-p0)|| / ||p2-p1|| % The cross product of two normals determines the direction of the axis. %========================================================================== function model = fitCylinder(points, varargin) p1 = points(1,1:3); n1 = points(1,4:6); p2 = points(2,1:3); n2 = points(2,4:6); w = p1 + n1 - p2; a = n1 * n1'; b = n1 * n2'; c = n2 * n2'; d = n1 * w'; e = n2 * w'; D = a * c - b * b; if abs(D) < 1e-5 s = 0; if b > c t = d / b; else t = e / c; end else s = (b * e - c * d) / D; t = (a * e - b * d) / D; end % P0 is a point on the axis p0 = p1 + n1 + s * n1; % dp is a normalized vector for the direction of the axis dp = p2 + t * n2 - p0; dp = dp / norm(dp); p1p0 = p1 - p0; p2p1 = dp; c = [p1p0(2)*p2p1(3) - p1p0(3)*p2p1(2), ... p1p0(3)*p2p1(1) - p1p0(1)*p2p1(3), ... p1p0(1)*p2p1(2) - p1p0(2)*p2p1(1)]; % p2p1 is a unit vector, so the denominator is not needed r = sqrt(sum(c.*c, 2)); model = [p0, dp, r]; %========================================================================== % Calculate the distance from the point P0 to the axis P2-P1 % D = ||(P2-P1) x (P1-P0)|| / ||P2-P1|| % The distance from P0 to the cylinder is ||D - r|| %========================================================================== function dis = evalCylinder(model, points, varargin) p1p0 = [points(:,1)-model(1), points(:,2)-model(2), points(:,3)-model(3)]; p2p1 = model(4:6); c = [p1p0(:,2)*p2p1(3) - p1p0(:,3)*p2p1(2), ... p1p0(:,3)*p2p1(1) - p1p0(:,1)*p2p1(3), ... p1p0(:,1)*p2p1(2) - p1p0(:,2)*p2p1(1)]; % p2p1 is a unit vector, so the denominator is not needed D = sum(c.*c, 2); dis = abs(sqrt(D) - model(7)); %========================================================================== % Validate the cylinder coefficients %========================================================================== function isValid = checkCylinder(model) isValid = (numel(model) == 7 & all(isfinite(model))); %========================================================================== % Validate the cylinder coefficients with orientation constraints %========================================================================== function isValid = checkOrientedCylinder(model, normAxis, threshold) isValid = checkCylinder(model); if isValid a = min(1, max(-1, normAxis*model(4:6)')); angle = abs(acos(a)); angle = min(angle, pi-angle); isValid = (angle < threshold); end %========================================================================== % Convert the point-axis format to end-points format % % The MSAC algorithm estimates a point on the axis and the direction of % axis. This function projects all inliers to the axis, computes two end % points, and use these two end-points along with the radius to describe a % finite cylinder. %========================================================================== function modelParams = convertToFiniteCylinderModel(modelParams, ptCloud, inlierIndices) % Get the direction of cylinder axis dp = modelParams(4:6); % Get the inlier points points = subsetImpl(ptCloud, inlierIndices); % Get a point on the axis p0 = modelParams(1:3); % Describe the axis as a line equation: p0 + k * dp, and find the % projections of inlier points on this line k = sum(points.*repmat(dp, numel(inlierIndices), 1),2) - p0 * dp'; % Find the two extreme points pa = p0 + min(k) * dp; pb = p0 + max(k) * dp; % Set to the parameters required by cylinderModel object modelParams(1:6) = [pa, pb];