209 |
jakw |
1 |
function bestFits = ellipseDetection(img, params)
|
|
|
2 |
% ellipseDetection: Ellipse detection
|
|
|
3 |
%
|
|
|
4 |
% Overview:
|
|
|
5 |
% --------
|
|
|
6 |
% Fits an ellipse by examining all possible major axes (all pairs of points) and
|
|
|
7 |
% getting the minor axis using Hough transform. The algorithm complexity depends on
|
|
|
8 |
% the number of valid non-zero points, therefore it is beneficial to provide as many
|
|
|
9 |
% restrictions in the "params" input arguments as possible if there is any prior
|
|
|
10 |
% knowledge about the problem.
|
|
|
11 |
%
|
|
|
12 |
% The code is reasonably fast due to (optional) randomization and full code vectorization.
|
|
|
13 |
% However, as the algorithm needs to compute pairwise point distances, it can be quite memory
|
|
|
14 |
% intensive. If you get out of memory errors, either downsample the input image or somehow
|
|
|
15 |
% decrease the number of non-zero points in it.
|
|
|
16 |
% It can deal with big amount of noise but can have severe problem with occlusions (major axis
|
|
|
17 |
% end points need to be visible)
|
|
|
18 |
%
|
|
|
19 |
% Input arguments:
|
|
|
20 |
% --------
|
|
|
21 |
% img
|
|
|
22 |
% - One-channel input image (greyscale or binary).
|
|
|
23 |
% params
|
|
|
24 |
% - Parameters of the algorithm:
|
|
|
25 |
% minMajorAxis: Minimal length of major axis accepted.
|
|
|
26 |
% maxMajorAxis: Maximal length of major axis accepted.
|
|
|
27 |
% rotation, rotationSpan: Specification of restriction on the angle of the major axis in degrees.
|
|
|
28 |
% If rotationSpan is in (0,90), only angles within [rotation-rotationSpan,
|
|
|
29 |
% rotation+rotationSpan] are accepted.
|
|
|
30 |
% minAspectRatio: Minimal aspect ratio of an ellipse (in (0,1))
|
|
|
31 |
% randomize: Subsampling of all possible point pairs. Instead of examining all N*N pairs, runs
|
|
|
32 |
% only on N*randomize pairs. If 0, randomization is turned off.
|
|
|
33 |
% numBest: Top numBest to return
|
|
|
34 |
% uniformWeights: Used to prefer some points over others. If false, accumulator points are weighted
|
|
|
35 |
% by their grey intensity in the image. If true, the input image is regarded as binary.
|
|
|
36 |
% smoothStddev: In order to provide more stability of the solution, the accumulator is convolved with
|
|
|
37 |
% a gaussian kernel. This parameter specifies its standard deviation in pixels.
|
|
|
38 |
%
|
|
|
39 |
% Return value:
|
|
|
40 |
% --------
|
|
|
41 |
% Returns a matrix of best fits. Each row (there are params.numBest of them) contains six elements:
|
|
|
42 |
% [x0 y0 a b alpha score] being the center of the ellipse, its major and minor axis, its angle in degrees and score.
|
|
|
43 |
%
|
|
|
44 |
% Based on:
|
|
|
45 |
% --------
|
|
|
46 |
% - "A New Efficient Ellipse Detection Method" (Yonghong Xie Qiang , Qiang Ji / 2002)
|
|
|
47 |
% - random subsampling inspired by "Randomized Hough Transform for Ellipse Detection with Result Clustering"
|
|
|
48 |
% (CA Basca, M Talos, R Brad / 2005)
|
|
|
49 |
%
|
|
|
50 |
% Update log:
|
|
|
51 |
% --------
|
211 |
jakw |
52 |
% 1.2: Added more efficient distance calculation, less memory-usage, non-maximum suppression.
|
209 |
jakw |
53 |
% 1.1: More memory efficient code, better documentation, more parameters, more solutions possible, example code.
|
|
|
54 |
% 1.0: Initial version
|
|
|
55 |
%
|
|
|
56 |
%
|
|
|
57 |
% Author: Martin Simonovsky
|
|
|
58 |
% e-mail: <mys007@seznam.cz>
|
|
|
59 |
% Release: 1.1
|
|
|
60 |
% Release date: 25.7.2013
|
|
|
61 |
%
|
|
|
62 |
%
|
|
|
63 |
% --------
|
|
|
64 |
%
|
|
|
65 |
% Redistribution and use in source and binary forms, with or without
|
|
|
66 |
% modification, are permitted provided that the following conditions are
|
|
|
67 |
% met:
|
|
|
68 |
%
|
|
|
69 |
% * Redistributions of source code must retain the above copyright
|
|
|
70 |
% notice, this list of conditions and the following disclaimer.
|
|
|
71 |
% * Redistributions in binary form must reproduce the above copyright
|
|
|
72 |
% notice, this list of conditions and the following disclaimer in
|
|
|
73 |
% the documentation and/or other materials provided with the distribution
|
|
|
74 |
%
|
|
|
75 |
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
76 |
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
77 |
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
78 |
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
79 |
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
80 |
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
81 |
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
82 |
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
83 |
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
84 |
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
85 |
% POSSIBILITY OF SUCH DAMAGE.
|
|
|
86 |
|
|
|
87 |
% default values
|
|
|
88 |
if nargin==1; params=[]; end
|
|
|
89 |
% - parameters to contrain the search
|
|
|
90 |
if ~isfield(params,'minMajorAxis'); params.minMajorAxis = 10; end
|
|
|
91 |
if ~isfield(params,'maxMajorAxis'); params.maxMajorAxis = 200; end
|
|
|
92 |
if ~isfield(params,'rotation'); params.rotation = 0; end
|
|
|
93 |
if ~isfield(params,'rotationSpan'); params.rotationSpan = 0; end
|
|
|
94 |
if ~isfield(params,'minAspectRatio'); params.minAspectRatio = 0.1; end
|
|
|
95 |
if ~isfield(params,'randomize'); params.randomize = 2; end
|
|
|
96 |
% - others
|
|
|
97 |
if ~isfield(params,'numBest'); params.numBest = 3; end
|
|
|
98 |
if ~isfield(params,'uniformWeights'); params.uniformWeights = true; end
|
|
|
99 |
if ~isfield(params,'smoothStddev'); params.smoothStddev = 1; end
|
|
|
100 |
|
|
|
101 |
eps = 0.0001;
|
|
|
102 |
bestFits = zeros(params.numBest,6);
|
|
|
103 |
params.rotationSpan = min(params.rotationSpan, 90);
|
|
|
104 |
H = fspecial('gaussian', [params.smoothStddev*6 1], params.smoothStddev);
|
|
|
105 |
|
|
|
106 |
[Y,X]=find(img);
|
|
|
107 |
Y = single(Y); X = single(X);
|
|
|
108 |
N = length(Y);
|
|
|
109 |
|
|
|
110 |
fprintf('Possible major axes: %d * %d = %d\n', N, N, N*N);
|
|
|
111 |
|
|
|
112 |
% compute pairwise distances between points (memory intensive!) and filter
|
|
|
113 |
% TODO: do this block-wise, just appending the filtered results (I,J)
|
|
|
114 |
distsSq = bsxfun(@minus,X,X').^2 + bsxfun(@minus,Y,Y').^2;
|
|
|
115 |
[I,J] = find(distsSq>=params.minMajorAxis^2 & distsSq<=params.maxMajorAxis^2);
|
|
|
116 |
idx = I<J;
|
|
|
117 |
I = uint32(I(idx)); J = uint32(J(idx));
|
|
|
118 |
|
|
|
119 |
fprintf('..after distance constraint: %d\n', length(I));
|
|
|
120 |
|
|
|
121 |
% compute pairwise angles and filter
|
|
|
122 |
if params.rotationSpan>0
|
|
|
123 |
tangents = (Y(I)-Y(J)) ./ (X(I)-X(J));
|
|
|
124 |
tanLo = tand(params.rotation-params.rotationSpan);
|
|
|
125 |
tanHi = tand(params.rotation+params.rotationSpan);
|
|
|
126 |
if tanLo<tanHi
|
|
|
127 |
idx = tangents > tanLo & tangents < tanHi;
|
|
|
128 |
else
|
|
|
129 |
idx = tangents > tanLo | tangents < tanHi;
|
|
|
130 |
end
|
|
|
131 |
I = I(idx); J = J(idx);
|
|
|
132 |
fprintf('..after angular constraint: %d\n', length(I));
|
|
|
133 |
else
|
|
|
134 |
fprintf('..angular constraint not used\n');
|
|
|
135 |
end
|
|
|
136 |
|
|
|
137 |
npairs = length(I);
|
|
|
138 |
|
|
|
139 |
% compute random choice and filter
|
|
|
140 |
if params.randomize>0
|
|
|
141 |
perm = randperm(npairs);
|
|
|
142 |
pairSubset = perm(1:min(npairs,N*params.randomize));
|
|
|
143 |
clear perm;
|
|
|
144 |
fprintf('..after randomization: %d\n', length(pairSubset));
|
|
|
145 |
else
|
|
|
146 |
pairSubset = 1:npairs;
|
|
|
147 |
end
|
|
|
148 |
|
|
|
149 |
% check out all hypotheses
|
|
|
150 |
for p=pairSubset
|
|
|
151 |
x1=X(I(p)); y1=Y(I(p));
|
|
|
152 |
x2=X(J(p)); y2=Y(J(p));
|
|
|
153 |
|
|
|
154 |
%compute center & major axis
|
|
|
155 |
x0=(x1+x2)/2; y0=(y1+y2)/2;
|
|
|
156 |
aSq = distsSq(I(p),J(p))/4;
|
|
|
157 |
thirdPtDistsSq = (X-x0).^2 + (Y-y0).^2;
|
|
|
158 |
K = thirdPtDistsSq <= aSq; % (otherwise the formulae in paper do not work)
|
|
|
159 |
|
|
|
160 |
%get minor ax propositions for all other points
|
|
|
161 |
fSq = (X(K)-x2).^2 + (Y(K)-y2).^2;
|
|
|
162 |
cosTau = (aSq + thirdPtDistsSq(K) - fSq) ./ (2*sqrt(aSq*thirdPtDistsSq(K)));
|
|
|
163 |
cosTau = min(1,max(-1,cosTau)); %inexact float arithmetic?!
|
|
|
164 |
sinTauSq = 1 - cosTau.^2;
|
|
|
165 |
b = sqrt( (aSq * thirdPtDistsSq(K) .* sinTauSq) ./ (aSq - thirdPtDistsSq(K) .* cosTau.^2 + eps) );
|
|
|
166 |
|
|
|
167 |
%proper bins for b
|
210 |
jakw |
168 |
idxs = round(b)+1;
|
209 |
jakw |
169 |
|
|
|
170 |
if params.uniformWeights
|
|
|
171 |
weights = 1;
|
|
|
172 |
else
|
|
|
173 |
weights = img(sub2ind(size(img),Y(K),X(K)));
|
|
|
174 |
end
|
|
|
175 |
accumulator = accumarray(idxs, weights, [params.maxMajorAxis 1]);
|
|
|
176 |
|
|
|
177 |
%a bit of smoothing and finding the most busy bin
|
211 |
jakw |
178 |
accumulator = conv(accumulator,H,'same');
|
209 |
jakw |
179 |
accumulator(1:ceil(sqrt(aSq)*params.minAspectRatio)) = 0;
|
|
|
180 |
[score, idx] = max(accumulator);
|
|
|
181 |
|
211 |
jakw |
182 |
%keeping only the params.numBest best hypothesis
|
209 |
jakw |
183 |
if (bestFits(end,end) < score)
|
211 |
jakw |
184 |
%non-maximum suppression (based on center distance for now)
|
|
|
185 |
squaredDists = (bestFits(:,1)-x0).^2 + (bestFits(:,2)-y0).^2;
|
|
|
186 |
[minSqDist, minSqDistIdx] = min(squaredDists);
|
|
|
187 |
if(minSqDist < 20^2 && score > bestFits(minSqDistIdx, 6))
|
|
|
188 |
bestFits(minSqDistIdx,:) = [x0 y0 sqrt(aSq) (idx-1) atand((y1-y2)/(x1-x2)) score];
|
|
|
189 |
elseif(minSqDist >= 10^2)
|
|
|
190 |
bestFits(end,:) = [x0 y0 sqrt(aSq) (idx-1) atand((y1-y2)/(x1-x2)) score];
|
|
|
191 |
end
|
209 |
jakw |
192 |
if params.numBest>1
|
|
|
193 |
[~,si]=sort(bestFits(:,end),'descend');
|
|
|
194 |
bestFits = bestFits(si,:);
|
|
|
195 |
end
|
|
|
196 |
end
|
|
|
197 |
end
|
|
|
198 |
end
|