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 }