Mon, 22 Apr 2024 13:28:58 +0300
stable interval
0 | 1 | ################################ |
2 | # Code relevant to optical flow | |
3 | ################################ | |
4 | ||
5 | __precompile__() | |
6 | ||
7 | module OpticalFlow | |
8 | ||
9 | using AlgTools.Util | |
10 | using ImageTools.Gradient | |
11 | import ImageTools.Translate | |
12 | using ImageTools.ImFilter | |
13 | ||
14 | ########## | |
15 | # Exports | |
16 | ########## | |
17 | ||
18 | export flow!, | |
19 | pdflow!, | |
20 | flow_grad!, | |
21 | flow_interp!, | |
22 | estimate_Λ², | |
23 | estimate_linear_Λ², | |
24 | pointwise_gradiprod_2d!, | |
25 | pointwise_gradiprod_2dᵀ!, | |
26 | horn_schunck_reg_prox!, | |
27 | horn_schunck_reg_prox_op!, | |
28 | mldivide_step_plus_sym2x2!, | |
29 | linearised_optical_flow_error, | |
30 | Image, AbstractImage, ImageSize, | |
31 | Gradient, Displacement, | |
32 | DisplacementFull, DisplacementConstant, | |
33 | HornSchunckData, | |
34 | filter_hs | |
35 | ||
36 | ############################################### | |
37 | # Types (several imported from ImageTools.Translate) | |
38 | ############################################### | |
39 | ||
40 | Image = Translate.Image | |
41 | AbstractImage = AbstractArray{Float64,2} | |
42 | Displacement = Translate.Displacement | |
43 | DisplacementFull = Translate.DisplacementFull | |
44 | DisplacementConstant = Translate.DisplacementConstant | |
45 | Gradient = Array{Float64,3} | |
46 | ImageSize = Tuple{Int64,Int64} | |
47 | ||
48 | ################################# | |
49 | # Displacement field based flow | |
50 | ################################# | |
51 | ||
52 | function flow_interp!(x::AbstractImage, u::Displacement, tmp::AbstractImage; | |
53 | threads = false) | |
54 | tmp .= x | |
55 | Translate.translate_image!(x, tmp, u; threads=threads) | |
56 | end | |
57 | ||
58 | function flow_interp!(x::AbstractImage, u::Displacement; | |
59 | threads = false) | |
60 | tmp = copy(x) | |
61 | Translate.translate_image!(x, tmp, u; threads=threads) | |
62 | end | |
63 | ||
64 | flow! = flow_interp! | |
65 | ||
66 | function pdflow!(x, Δx, y, Δy, u, dual_flow; threads=:none) | |
67 | if dual_flow | |
68 | #flow!((x, @view(y[1, :, :]), @view(y[2, :, :])), diffu, | |
69 | # (Δx, @view(Δy[1, :, :]), @view(Δy[2, :, :]))) | |
70 | @backgroundif (threads==:outer) begin | |
71 | flow!(x, u, Δx; threads=(threads==:inner)) | |
72 | end begin | |
73 | flow!(@view(y[1, :, :]), u, @view(Δy[1, :, :]); threads=(threads==:inner)) | |
74 | flow!(@view(y[2, :, :]), u, @view(Δy[2, :, :]); threads=(threads==:inner)) | |
75 | end | |
76 | else | |
77 | flow!(x, u, Δx) | |
78 | end | |
79 | end | |
80 | ||
81 | function pdflow!(x, Δx, y, Δy, z, Δz, u, dual_flow; threads=:none) | |
82 | if dual_flow | |
83 | @backgroundif (threads==:outer) begin | |
84 | flow!(x, u, Δx; threads=(threads==:inner)) | |
85 | flow!(z, u, Δz; threads=(threads==:inner)) | |
86 | end begin | |
87 | flow!(@view(y[1, :, :]), u, @view(Δy[1, :, :]); threads=(threads==:inner)) | |
88 | flow!(@view(y[2, :, :]), u, @view(Δy[2, :, :]); threads=(threads==:inner)) | |
89 | end | |
90 | else | |
91 | flow!(x, u, Δx; threads=(threads==:inner)) | |
92 | flow!(z, u, Δz; threads=(threads==:inner)) | |
93 | end | |
94 | end | |
95 | ||
96 | ########################## | |
97 | # Linearised optical flow | |
98 | ########################## | |
99 | ||
100 | # ⟨⟨u, ∇b⟩⟩ | |
101 | function pointwise_gradiprod_2d!(y::Image, vtmp::Gradient, | |
102 | u::DisplacementFull, b::Image; | |
103 | add = false) | |
104 | ∇₂c!(vtmp, b) | |
105 | ||
106 | u′=reshape(u, (size(u, 1), prod(size(u)[2:end]))) | |
107 | vtmp′=reshape(vtmp, (size(vtmp, 1), prod(size(vtmp)[2:end]))) | |
108 | y′=reshape(y, prod(size(y))) | |
109 | ||
110 | if add | |
111 | @simd for i = 1:length(y′) | |
112 | @inbounds y′[i] += dot(@view(u′[:, i]), @view(vtmp′[:, i])) | |
113 | end | |
114 | else | |
115 | @simd for i = 1:length(y′) | |
116 | @inbounds y′[i] = dot(@view(u′[:, i]), @view(vtmp′[:, i])) | |
117 | end | |
118 | end | |
119 | end | |
120 | ||
121 | function pointwise_gradiprod_2d!(y::Image, vtmp::Gradient, | |
122 | u::DisplacementConstant, b::Image; | |
123 | add = false) | |
124 | ∇₂c!(vtmp, b) | |
125 | ||
126 | vtmp′=reshape(vtmp, (size(vtmp, 1), prod(size(vtmp)[2:end]))) | |
127 | y′=reshape(y, prod(size(y))) | |
128 | ||
129 | if add | |
130 | @simd for i = 1:length(y′) | |
131 | @inbounds y′[i] += dot(u, @view(vtmp′[:, i])) | |
132 | end | |
133 | else | |
134 | @simd for i = 1:length(y′) | |
135 | @inbounds y′[i] = dot(u, @view(vtmp′[:, i])) | |
136 | end | |
137 | end | |
138 | end | |
139 | ||
140 | # ∇b ⋅ y | |
141 | function pointwise_gradiprod_2dᵀ!(u::DisplacementFull, y::Image, b::Image) | |
142 | ∇₂c!(u, b) | |
143 | ||
144 | u′=reshape(u, (size(u, 1), prod(size(u)[2:end]))) | |
145 | y′=reshape(y, prod(size(y))) | |
146 | ||
147 | @simd for i=1:length(y′) | |
148 | @inbounds @. u′[:, i] *= y′[i] | |
149 | end | |
150 | end | |
151 | ||
152 | function pointwise_gradiprod_2dᵀ!(u::DisplacementConstant, y::Image, b::Image) | |
153 | @assert(size(y)==size(b) && size(u)==(2,)) | |
154 | u .= 0 | |
155 | ∇₂cfold!(b, nothing) do g, st, (i, j) | |
156 | @inbounds u .+= g.*y[i, j] | |
157 | return st | |
158 | end | |
159 | # Reweight to be with respect to 𝟙^*𝟙 inner product. | |
160 | u ./= prod(size(b)) | |
161 | end | |
162 | ||
163 | mutable struct ConstantDisplacementHornSchunckData | |
164 | M₀::Array{Float64,2} | |
165 | z::Array{Float64,1} | |
166 | Mv::Array{Float64,2} | |
167 | av::Array{Float64,1} | |
168 | cv::Float64 | |
169 | ||
170 | function ConstantDisplacementHornSchunckData() | |
171 | return new(zeros(2, 2), zeros(2), zeros(2,2), zeros(2), 0) | |
172 | end | |
173 | end | |
174 | ||
175 | # For DisplacementConstant, for the simple prox step | |
176 | # | |
177 | # (1) argmin_u 1/(2τ)|u-ũ|^2 + (θ/2)|b⁺-b+<<u-ŭ,∇b>>|^2 + (λ/2)|u-ŭ|^2, | |
178 | # | |
179 | # construct matrix M₀ and vector z such that we can solve u from | |
180 | # | |
181 | # (2) (I/τ+M₀)u = M₀ŭ + ũ/τ - z | |
182 | # | |
183 | # Note that the problem | |
184 | # | |
185 | # argmin_u 1/(2τ)|u-ũ|^2 + (θ/2)|b⁺-b+<<u-ŭ,∇b>>|^2 + (λ/2)|u-ŭ|^2 | |
186 | # + (θ/2)|b⁺⁺-b⁺+<<uʹ-u,∇b⁺>>|^2 + (λ/2)|u-uʹ|^2 | |
187 | # | |
188 | # has with respect to u the system | |
189 | # | |
190 | # (I/τ+M₀+M₀ʹ)u = M₀ŭ + M₀ʹuʹ + ũ/τ - z + zʹ, | |
191 | # | |
192 | # where the primed variables correspond to (2) for (1) for uʹ in place of u: | |
193 | # | |
194 | # argmin_uʹ 1/(2τ)|uʹ-ũʹ|^2 + (θ/2)|b⁺⁺-b⁺+<<uʹ-u,∇b⁺>>|^2 + (λ/2)|uʹ-u|^2 | |
195 | # | |
196 | function horn_schunck_reg_prox_op!(hs::ConstantDisplacementHornSchunckData, | |
197 | bnext::Image, b::Image, θ, λ, T) | |
198 | @assert(size(b)==size(bnext)) | |
199 | w = prod(size(b)) | |
200 | z = hs.z | |
201 | cv = 0 | |
202 | # Factors of symmetric matrix [a c; c d] | |
203 | a, c, d = 0.0, 0.0, 0.0 | |
204 | # This used to use ∇₂cfold but it is faster to allocate temporary | |
205 | # storage for the full gradient due to probably better memory and SIMD | |
206 | # instruction usage. | |
207 | g = zeros(2, size(b)...) | |
208 | ∇₂c!(g, b) | |
209 | @inbounds for i=1:size(b, 1) | |
210 | for j=1:size(b, 2) | |
211 | δ = bnext[i,j]-b[i,j] | |
212 | @. z += g[:,i,j]*δ | |
213 | cv += δ*δ | |
214 | a += g[1,i,j]*g[1,i,j] | |
215 | c += g[1,i,j]*g[2,i,j] | |
216 | d += g[2,i,j]*g[2,i,j] | |
217 | end | |
218 | end | |
219 | w₀ = λ | |
220 | w₂ = θ/w | |
221 | aʹ = w₀ + w₂*a | |
222 | cʹ = w₂*c | |
223 | dʹ = w₀ + w₂*d | |
224 | hs.M₀ .= [aʹ cʹ; cʹ dʹ] | |
225 | hs.Mv .= [w*λ+θ*a θ*c; θ*c w*λ+θ*d] | |
226 | hs.cv = cv*θ | |
227 | hs.av .= hs.z.*θ | |
228 | hs.z .*= w₂/T | |
229 | end | |
230 | ||
231 | # Solve the 2D system (I/τ+M₀)u = z | |
232 | @inline function mldivide_step_plus_sym2x2!(u, M₀, z, τ) | |
233 | a = 1/τ+M₀[1, 1] | |
234 | c = M₀[1, 2] | |
235 | d = 1/τ+M₀[2, 2] | |
236 | u .= ([d -c; -c a]*z)./(a*d-c*c) | |
237 | end | |
238 | ||
239 | function horn_schunck_reg_prox!(u::DisplacementConstant, bnext::Image, b::Image, | |
240 | θ, λ, T, τ) | |
241 | hs=ConstantDisplacementHornSchunckData() | |
242 | horn_schunck_reg_prox_op!(hs, bnext, b, θ, λ, T) | |
243 | mldivide_step_plus_sym2x2!(u, hs.M₀, (u./τ)-hs.z, τ) | |
244 | end | |
245 | ||
246 | function flow_grad!(x::Image, vtmp::Gradient, u::Displacement; δ=nothing) | |
247 | if !isnothing(δ) | |
248 | u = δ.*u | |
249 | end | |
250 | pointwise_gradiprod_2d!(x, vtmp, u, x; add=true) | |
251 | end | |
252 | ||
253 | # Error b-b_prev+⟨⟨u, ∇b⟩⟩ for Horn–Schunck type penalisation | |
254 | function linearised_optical_flow_error(u::Displacement, b::Image, b_prev::Image) | |
255 | imdim = size(b) | |
256 | vtmp = zeros(2, imdim...) | |
257 | tmp = b-b_prev | |
258 | pointwise_gradiprod_2d!(tmp, vtmp, u, b_prev; add=true) | |
259 | return tmp | |
260 | end | |
261 | ||
262 | ############################################## | |
263 | # Helper to smooth data for Horn–Schunck term | |
264 | ############################################## | |
265 | ||
266 | function filter_hs(b, b_next, b_next_filt, kernel) | |
267 | if kernel==nothing | |
268 | f = x -> x | |
269 | else | |
270 | f = x -> simple_imfilter(x, kernel; threads=false) | |
271 | end | |
272 | ||
273 | # We already filtered b in the previous step (b_next in that step) | |
274 | b_filt = b_next_filt==nothing ? f(b) : b_next_filt | |
275 | b_next_filt = f(b_next) | |
276 | ||
277 | return b_filt, b_next_filt | |
278 | end | |
279 | ||
280 | end # Module |