1 /**
2 Module contains utilities common for all algorithms which operate with feature points.
3 
4 Copyright: Copyright Relja Ljubobratovic 2016.
5 
6 Authors: Relja Ljubobratovic
7 
8 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0).
9 */
10 
11 module dcv.features.utils;
12 
13 import std.traits : isNumeric;
14 
15 import mir.ndslice;
16 
17 /**
18 Feature point.
19 */
20 struct Feature
21 {
22     /// x coordinate of the feature centroid
23     size_t x;
24     /// y coordinate of the feature centroid
25     size_t y;
26     /// octave in which the feature is detected.
27     size_t octave;
28     /// width of the feature
29     float width;
30     /// height of the feature
31     float height;
32     /// feature strengh.
33     float score;
34 }
35 
36 /**
37 Extract corners as array of 2D points, from response matrix.
38 
39 Params:
40     cornerResponse = Response matrix, collected as output from corner
41     detection algoritms such as harrisCorners, or shiTomasiCorners.
42     count = Number of corners which need to be extracted. Default is
43     -1 which indicate that all responses with value above the threshold
44     will be returned.
45     threshold = Response threshold - response values in the matrix
46     larger than this are considered as valid corners.
47 
48 Returns:
49     Lazy array of size_t[2], as in array of 2D points, of corner reponses
50     which fit the given criteria.
51 */
52 pure nothrow
53 auto extractCorners(T)
54 (
55     Slice!(Contiguous, [2], T*) cornerResponse,
56     int count = -1,
57     T threshold = 0
58 ) if ( isNumeric!T )
59 in
60 {
61     assert(!cornerResponse.empty, "Corner response matrix should not be empty.");
62 }
63 body
64 {
65     import std.array : Appender;
66     import std.typecons : Tuple;
67 
68     import mir.ndslice.sorting : sort;
69     import mir.ndslice.topology : zip, flattened, ndiota;
70 
71     alias Pair = Tuple!(T, "value", size_t[2], "position");
72 
73     // TODO: test if corner response is contiguous, or better yet - change
74     //       the implementation not to depend on contiguous slice.
75 
76     auto resultAppender = Appender!(Pair[])();
77 
78     size_t r = 0, c;
79     foreach(row; cornerResponse) {
80         c = 0;
81         foreach(value; row) {
82             if (value > threshold) {
83                 resultAppender.put(Pair(value, [r, c]));
84             }
85             c++;
86         }
87         r++;
88     }
89 
90     auto result = resultAppender
91         .data
92         .sliced
93         .sort!( (a, b) => a.value > b.value )
94         .map!( p => p.position );
95 
96     if (count > 0) {
97         result = result[0..count];
98     }
99 
100     return result;
101 }
102 
103 ///
104 unittest
105 {
106     auto image = [0., 0., 0.,
107                   0., 1., 0.,
108                   0., 0., 0.].sliced(3, 3);
109 
110     auto res = image.extractCorners;
111 
112     assert(res.length == 1);
113     assert(res[0] == [1, 1]);
114 }
115 
116 ///
117 unittest
118 {
119     auto image = [0., 0.1, 0.,
120                   0., 0.3, 0.,
121                   0., 0.2, 0.].sliced(3, 3);
122 
123     auto res = image.extractCorners;
124 
125     assert(res.length == 3);
126     assert(res[0] == [1, 1]);
127     assert(res[1] == [2, 1]);
128     assert(res[2] == [0, 1]);
129 }
130 
131 ///
132 unittest
133 {
134     auto image = [0., 0.1, 0.,
135                   0., 0.3, 0.,
136                   0., 0.2, 0.].sliced(3, 3);
137 
138     auto res = image.extractCorners(1);
139 
140     assert(res.length == 1);
141     assert(res[0] == [1, 1]);
142 }
143 
144 ///
145 unittest
146 {
147     auto image = [0., 0.1, 0.,
148                   0., 0.3, 0.,
149                   0., 0.2, 0.].sliced(3, 3);
150 
151     auto res = image.extractCorners(-1, 0.2);
152 
153     assert(res.length == 1);
154     assert(res[0] == [1, 1]);
155 }
156