1 /**
2 Module implements $(LINK3 https://en.wikipedia.org/wiki/Corner_detection#The_Harris_.26_Stephens_.2F_Plessey_.2F_Shi.E2.80.93Tomasi_corner_detection_algorithms, Harris and Shi-Tomasi) corner detectors.
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 module dcv.features.corner.harris;
11 
12 import std.parallelism : parallel, taskPool, TaskPool;
13 
14 import mir.math.common : fastmath;
15 
16 import mir.ndslice;
17 import mir.ndslice.algorithm : each;
18 
19 import dcv.core.utils : emptySlice;
20 import dcv.imgproc.filter : calcPartialDerivatives;
21 
22 /**
23 Calculate per-pixel corner impuls response using Harris corner detector.
24 
25 Params:
26     image       = Input image slice.
27     winSize     = Window (square) size used in corner detection.
28     k           = Sensitivity parameter defined in the algorithm.
29     gauss       = Gauss sigma value used as window weighting parameter.
30     prealloc    = Optional pre-allocated buffer for return response image.
31     pool        = TaskPool instance used parallelise the algorithm.
32 
33 Returns:
34     Response matrix the same size of the input image, where each pixel represents
35     corner response value - the bigger the value, more probably it represents the
36     actual corner in the image.
37  */
38 Slice!(SliceKind.contiguous, [2], OutputType*) harrisCorners(InputType, OutputType = InputType, SliceKind inputKind)
39 (
40     Slice!(inputKind, [2], InputType*) image,
41     in uint winSize = 3,
42     in float k = 0.64f,
43     in float gauss = 0.84f,
44     Slice!(SliceKind.contiguous, [2], OutputType*) prealloc = emptySlice!([2], OutputType),
45     TaskPool pool = taskPool
46 )
47 in
48 {
49     assert(!image.empty, "Empty image given.");
50     assert(winSize % 2 != 0, "Kernel window size has to be odd.");
51     assert(gauss > 0.0, "Gaussian sigma value has to be greater than 0.");
52     assert(k > 0.0, "K value has to be greater than 0.");
53 }
54 body
55 {
56     if (prealloc.shape != image.shape)
57     {
58         prealloc = uninitializedSlice!OutputType(image.shape);
59     }
60     HarrisDetector detector;
61     detector.k = k;
62     return calcCorners(image, winSize, gauss, prealloc, detector, pool);
63 }
64 
65 /**
66 Calculate per-pixel corner impuls response using Shi-Tomasi corner detector.
67 
68 Params:
69     image       = Input image slice.
70     winSize     = Window (square) size used in corner detection.
71     gauss       = Gauss sigma value used as window weighting parameter.
72     prealloc    = Optional pre-allocated buffer for return response image.
73     pool        = TaskPool instance used parallelise the algorithm.
74 
75 Returns:
76     Response matrix the same size of the input image, where each pixel represents
77     corner response value - the bigger the value, more probably it represents the
78     actual corner in the image.
79  */
80 Slice!(SliceKind.contiguous, [2], OutputType*) shiTomasiCorners(InputType, OutputType = InputType, SliceKind inputKind)
81 (
82     Slice!(inputKind, [2], InputType*) image,
83     in uint winSize = 3,
84     in float gauss = 0.84f,
85     Slice!(SliceKind.contiguous, [2], OutputType*) prealloc = emptySlice!([2], OutputType),
86     TaskPool pool = taskPool
87 )
88 in
89 {
90     assert(!image.empty, "Empty image given.");
91     assert(winSize % 2 != 0, "Kernel window size has to be odd.");
92     assert(gauss > 0.0, "Gaussian sigma value has to be greater than 0.");
93 }
94 body
95 {
96     if (prealloc.shape != image.shape)
97     {
98         prealloc = uninitializedSlice!OutputType(image.shape);
99     }
100 
101     ShiTomasiDetector detector;
102     return calcCorners(image, winSize, gauss, prealloc, detector, pool);
103 }
104 
105 unittest
106 {
107     auto image = new float[9].sliced(3, 3);
108     auto result = harrisCorners(image, 3, 0.64, 0.84);
109     assert(result.shape == image.shape);
110 }
111 
112 unittest
113 {
114     import std.range : lockstep;
115 
116     auto image = new float[9].sliced(3, 3);
117     auto resultBuffer = new double[9].sliced(3, 3);
118     auto result = harrisCorners!(float, double)(image, 3, 0.64, 0.84, resultBuffer);
119     assert(result.shape == image.shape);
120     foreach (ref r1, ref r2; lockstep(result.flattened, resultBuffer.flattened))
121     {
122         assert(&r1 == &r2);
123     }
124 }
125 
126 unittest
127 {
128     import std.algorithm.comparison : equal;
129 
130     auto image = new float[9].sliced(3, 3);
131     auto result = shiTomasiCorners(image, 3, 0.84);
132     assert(result.shape[].equal(image.shape[]));
133 }
134 
135 unittest
136 {
137     import std.algorithm.comparison : equal;
138     import std.range : lockstep;
139 
140     auto image = new float[9].sliced(3, 3);
141     auto resultBuffer = new double[9].sliced(3, 3);
142     auto result = shiTomasiCorners!(float, double)(image, 3, 0.84, resultBuffer);
143     assert(result.shape[].equal(image.shape[]));
144     foreach (ref r1, ref r2; lockstep(result.flattened, resultBuffer.flattened))
145     {
146         assert(&r1 == &r2);
147     }
148 }
149 
150 @nogc nothrow @fastmath
151 {
152     void calcCornersImpl(Window, Detector)(Window window, Detector detector)
153     {
154         import mir.ndslice.algorithm : reduce;
155 
156         float[3] r = [0.0f, 0.0f, 0.0f];
157         float winSqr = float(window.length!0);
158         winSqr *= winSqr;
159 
160         r = reduce!sumResponse(r, window);
161 
162         r[0] = (r[0] / winSqr) * 0.5f;
163         r[1] /= winSqr;
164         r[2] = (r[2] / winSqr) * 0.5f;
165 
166         auto rv = detector(r[0], r[1], r[2]);
167         if (rv > 0)
168             window[$ / 2, $ / 2].a = rv;
169     }
170 
171     float[3] sumResponse(Pack)(float[3] r, Pack pack)
172     {
173         auto gx = pack.b;
174         auto gy = pack.c;
175         return [r[0] + gx * gx, r[1] + gx * gy, r[2] + gy * gy];
176     }
177 }
178 
179 private:
180 
181 struct HarrisDetector
182 {
183     float k;
184 
185     @fastmath @nogc nothrow float opCall(float r1, float r2, float r3)
186     {
187         return (((r1 * r1) - (r2 * r3)) - k * ((r1 + r3) * r1 + r3));
188     }
189 }
190 
191 struct ShiTomasiDetector
192 {
193     @fastmath @nogc nothrow float opCall(float r1, float r2, float r3)
194     {
195         import mir.math.common : sqrt;
196         return ((r1 + r3) - sqrt((r1 - r3) * (r1 - r3) + r2 * r2));
197     }
198 }
199 
200 Slice!(SliceKind.contiguous, [2], OutputType*) calcCorners(Detector, InputType, SliceKind inputKind, OutputType)
201     (Slice!(inputKind, [2], InputType*) image, uint winSize, float gaussSigma,
202     Slice!(SliceKind.contiguous, [2], OutputType*) prealloc, Detector detector, TaskPool pool)
203 {
204     import mir.ndslice.topology : zip, iota;
205 
206     // TODO: implement gaussian weighting!
207 
208     Slice!(SliceKind.contiguous, [2], InputType*) fx, fy;
209     calcPartialDerivatives(image, fx, fy);
210 
211     auto windowPack = zip(prealloc, fx, fy).windows(winSize, winSize);
212 
213     foreach (windowRow; pool.parallel(windowPack))
214         windowRow.each!(win => calcCornersImpl(win, detector));
215 
216     return prealloc;
217 }