1 /** 2 Module contains $(LINK3 https://en.wikipedia.org/wiki/Horn%E2%80%93Schunck_method, Horn-Schunck) optical flow algorithm implementation. 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.tracking.opticalflow.hornschunck; 12 13 import mir.ndslice; 14 15 import dcv.core.image; 16 import dcv.tracking.opticalflow.base; 17 import dcv.core.utils : emptySlice; 18 19 public import dcv.tracking.opticalflow.base : DenseFlow; 20 21 /** 22 Horn-Schunck algorithm properties. 23 */ 24 struct HornSchunckProperties 25 { 26 /// How many iterations is algorithm evaluated to estimate the flow values. 27 size_t iterationCount = 100; 28 /// Smoothing weight parameter for the flow field. 29 float alpha = 20; 30 /// Size of the gaussian kernel used to blur the image as pre-process step before the algorithm. 31 size_t gaussKernelSize = 5; 32 /// Sigma value of gaussian kernel used to blur the image. 33 float gaussSigma = 2; 34 /// iteration stopping criterion. 35 float tol = 1.0e-06; 36 } 37 38 /** 39 Horn-Schunck algorithm implementation. 40 */ 41 class HornSchunckFlow : DenseOpticalFlow 42 { 43 private 44 { 45 Slice!(SliceKind.contiguous, [2], float*) current; 46 Slice!(SliceKind.contiguous, [2], float*) next; 47 HornSchunckProperties props; 48 } 49 50 /** 51 Initialize the algorithm with given set of properties. 52 */ 53 this(HornSchunckProperties props = HornSchunckProperties()) 54 { 55 this.props = props; 56 } 57 58 ~this() 59 { 60 } 61 62 /** 63 Evaluate Horn-Schunck dense optical flow method between two consecutive frames. 64 65 Params: 66 f1 = First image, i.e. previous frame in the video. 67 f2 = Second image of same size and type as $(D f1), i.e. current frame in the video. 68 prealloc = Optional pre-allocated flow buffer. If provided, has to be of same size as input images are, and with 2 channels (u, v). 69 usePrevious = Should the previous flow be used. If true $(D prealloc) is treated as previous flow, and has to satisfy size requirements. 70 71 Returns: 72 Calculated flow field. 73 */ 74 override DenseFlow evaluate(inout Image f1, inout Image f2, DenseFlow prealloc = emptySlice!([3], 75 float), bool usePrevious = false) 76 in 77 { 78 assert(!f1.empty && !f2.empty && f1.channels == 1 && f1.size == f2.size && f1.depth == f2.depth); 79 if (usePrevious) 80 { 81 assert(prealloc.length!0 == f1.height && prealloc.length!1 == f1.width && prealloc.length!2 == 2); 82 } 83 } 84 body 85 { 86 size_t height = f1.height; 87 size_t width = f1.width; 88 import mir.ndslice.allocation; 89 import mir.ndslice.topology; 90 if (current.shape == [height, width] && next.shape == [height, width] && f1.depth == BitDepth.BD_32) 91 { 92 auto f1s = f1.sliced!float.flattened.sliced([height, width]); 93 auto f2s = f2.sliced!float.flattened.sliced([height, width]); 94 95 current[] = f1s[]; 96 next[] = f2s[]; 97 } 98 else 99 { 100 switch (f1.depth) 101 { 102 case BitDepth.BD_32: 103 current = f1.sliced!float.flattened.sliced([f1.height, f1.width]); 104 next = f2.sliced!float.flattened.sliced([f2.height, f2.width]); 105 break; 106 case BitDepth.BD_16: 107 current = f1.sliced!ushort.flattened.sliced([f1.height, f1.width]).as!float.slice; 108 next = f2.sliced!ushort.flattened.sliced([f2.height, f2.width]).as!float.slice; 109 break; 110 default: 111 current = f1.sliced.flattened.sliced([f1.height, f1.width]).as!float.slice; 112 next = f2.sliced.flattened.sliced([f2.height, f2.width]).as!float.slice; 113 } 114 } 115 116 if (prealloc.shape[0 .. 2] != current.shape) 117 prealloc = slice!float([current.length!0, current.length!1, 2], 0.0f); 118 else if (!usePrevious) 119 prealloc[] = 0.0f; 120 121 // smooth images 122 if (props.gaussSigma) 123 { 124 import dcv.imgproc.filter : gaussian; 125 import dcv.imgproc.convolution; 126 import dcv.core.utils : neumann; 127 128 auto g = gaussian!float(props.gaussSigma, props.gaussKernelSize, props.gaussKernelSize); 129 130 current = conv!neumann(current, g); 131 next = conv!neumann(next, g); 132 } 133 134 int iter = 0; 135 float err = props.tol; 136 137 auto flow_b = slice!float(prealloc.shape); 138 139 auto const rows = cast(int)current.length!0; 140 auto const cols = cast(int)current.length!1; 141 auto const a2 = props.alpha * props.alpha; 142 143 while (++iter < props.iterationCount && err >= props.tol) 144 { 145 err = 0; 146 flow_b[] = prealloc[]; 147 hsflowImpl(rows, cols, current._iterator, next._iterator, flow_b._iterator, prealloc._iterator, a2, err); 148 } 149 150 return prealloc; 151 } 152 } 153 154 // TODO: implement functional tests. 155 unittest 156 { 157 HornSchunckFlow flow = new HornSchunckFlow; 158 auto f1 = new Image(3, 3, ImageFormat.IF_MONO, BitDepth.BD_8); 159 auto f2 = new Image(3, 3, ImageFormat.IF_MONO, BitDepth.BD_8); 160 auto f = flow.evaluate(f1, f2); 161 assert(f.length!0 == f1.height && f.length!1 == f1.width && f.length!2 == 2); 162 } 163 164 unittest 165 { 166 HornSchunckFlow flow = new HornSchunckFlow; 167 auto f1 = new Image(3, 3, ImageFormat.IF_MONO, BitDepth.BD_8); 168 auto f2 = new Image(3, 3, ImageFormat.IF_MONO, BitDepth.BD_8); 169 auto f = new float[9 * 2].sliced(3, 3, 2); 170 auto fe = flow.evaluate(f1, f2, f); 171 assert(f.shape[] == fe.shape[]); 172 assert(&f[0, 0, 0] == &fe[0, 0, 0]); 173 } 174 175 private: 176 177 nothrow @nogc void hsflowImpl(in int rows, in int cols, float* current, float* next, float* flow_b, 178 float* prealloc, float a2, ref float err) 179 { 180 181 immutable div12 = (1.0f / 12.0f); 182 immutable div6 = (1.0f / 6.0f); 183 184 auto const cols2 = cols * 2; 185 186 foreach (i; 1 .. rows - 1) 187 { 188 auto const ro = i * cols; // row offset 189 foreach (j; 1 .. cols - 1) 190 { 191 192 auto fx_val = ((current[ro + j - 1] - current[ro + j]) + (next[ro + j - 1] - next[ro + j])) / 2.0f; 193 auto fy_val = ((current[(i - 1) * cols + j] - current[ro + j]) + (next[(i - 1) * cols + j] - next[ro + j])) / 2.0f; 194 auto ft_val = next[ro + j] - current[ro + j]; 195 196 auto prev_u = flow_b[ro * 2 + j * 2]; 197 auto prev_v = flow_b[ro * 2 + j * 2 + 1]; 198 199 auto u_val = div12 * (flow_b[(i - 1) * cols2 + (j - 1) * 2] + flow_b[(i - 1) * cols2 + ( 200 j + 1) * 2] + flow_b[(i + 1) * cols2 + (j - 1) * 2] + flow_b[(i + 1) * cols2 + (j + 1) * 2]) 201 + div6 * (flow_b[i * cols2 + (j - 1) * 2] + flow_b[(i - 1) * cols2 + j * 2] + flow_b[( 202 i + 1) * cols2 + j * 2] + flow_b[i * cols2 + (j + 1) * 2]); 203 auto v_val = div12 * (flow_b[(i - 1) * cols2 + (j - 1) * 2 + 1] + flow_b[(i - 1) * cols2 + ( 204 j + 1) * 2 + 1] + flow_b[(i + 1) * cols2 + (j - 1) * 2 + 1] + flow_b[(i + 1) * cols2 + (j + 1) 205 * 2 + 1]) + div6 * (flow_b[i * cols2 + (j - 1) * 2 + 1] + flow_b[( 206 i - 1) * cols2 + j * 2 + 1] + flow_b[(i + 1) * cols2 + j * 2 + 1] + flow_b[i * cols2 + (j + 1) 207 * 2 + 1]); 208 209 auto p = fx_val * u_val + fy_val * v_val + ft_val; 210 auto d = (a2 + fx_val * fx_val + fy_val * fy_val); 211 212 if (p && d) 213 p /= d; 214 else 215 p = 0.0f; 216 217 prealloc[i * cols2 + j * 2] = u_val - (fx_val * p); 218 prealloc[i * cols2 + j * 2 + 1] = v_val - (fy_val * p); 219 220 err += (prealloc[i * cols2 + j * 2] - prev_u) * (prealloc[i * cols2 + j * 2] - prev_u) + ( 221 (prealloc[i * cols2 + j * 2 + 1] - prev_v) * (prealloc[i * cols2 + j * 2 + 1] - prev_v)); 222 223 } 224 } 225 }