Subversion Repositories seema-scanner

Rev

Rev 210 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
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