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 }