Subversion Repositories seema-scanner

Rev

Rev 210 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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

Generated by GNU Enscript 1.6.6.
200

Generated by GNU Enscript 1.6.6.
193
 
201
 
194
 
202
 
195
 
203