All Downloads are FREE. Search and download functionalities are using the official Maven repository.

boofcv.alg.flow.BroxWarpingSpacial Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.flow;

import boofcv.abst.filter.derivative.ImageGradient;
import boofcv.abst.filter.derivative.ImageHessian;
import boofcv.alg.interpolate.InterpolatePixelS;
import boofcv.alg.misc.ImageMiscOps;
import boofcv.alg.misc.PixelMath;
import boofcv.factory.filter.derivative.FactoryDerivative;
import boofcv.struct.image.GrayF32;
import boofcv.struct.image.ImageGray;
import boofcv.struct.pyramid.ImagePyramid;

import java.util.Arrays;

/**
 * 

* Dense optical flow which adheres to a brightness constancy assumption, a gradient constancy * assumption, and a discontinuity-preserving spatio-temporal smoothness constraint. Based on the * work of Brox [2] with implementation details taken from [1]. *

* *
    *
  1. * Javier Sánchez Pérez, Nelson Monzón López, and Agustín Salgado de la Nuez, Robust Optical Flow Estimation, * Image Processing On Line, 3 (2013), pp. 252–270. http://dx.doi.org/10.5201/ipol.2013.21
  2. *
  3. * Thomas Brox, Andr´es Bruhn, Nils Papenberg, and Joachim Weickert. High accuracy optical * flow estimation based on a theory for warping. In T. Pajdla and J. Matas, editors, European * Conference on Computer Vision (ECCV), volume 3024 of Lecture Notes in Computer Science, * pages 25–36, Prague, Czech Republic, May 2004. Springer. *
  4. *
* * @author Peter Abeles */ public class BroxWarpingSpacial extends DenseFlowPyramidBase { // regularization term private static final double EPSILON = 0.001; // brightness error weighting factor protected float alpha; // gradient error weighting factor protected float gamma; // relaxation parameter for SOR 0 < w < 2. Recommended default is 1.9 private float SOR_RELAXATION; // number of iterations for inner and outer loops private int numOuter; private int numInner; // maximum number of iterations for SOR private int maxIterationsSor; // convergence tolerance for SOR private float convergeTolerance; // derivative of first image private GrayF32 deriv1X = new GrayF32(1,1); private GrayF32 deriv1Y = new GrayF32(1,1); // derivatives of second image private GrayF32 deriv2X = new GrayF32(1,1); private GrayF32 deriv2Y = new GrayF32(1,1); private GrayF32 deriv2XX = new GrayF32(1,1); private GrayF32 deriv2YY = new GrayF32(1,1); private GrayF32 deriv2XY = new GrayF32(1,1); private ImageGradient gradient = FactoryDerivative.three(GrayF32.class, GrayF32.class); private ImageHessian hessian = FactoryDerivative.hessianThree(GrayF32.class); // flow estimation at the start of the iteration protected GrayF32 flowU = new GrayF32(1,1); // flow along x-axis protected GrayF32 flowV = new GrayF32(1,1); // flow along y-axis // storage for the warped flow protected GrayF32 warpImage2 = new GrayF32(1,1); protected GrayF32 warpDeriv2X = new GrayF32(1,1); protected GrayF32 warpDeriv2Y = new GrayF32(1,1); protected GrayF32 warpDeriv2XX = new GrayF32(1,1); protected GrayF32 warpDeriv2YY = new GrayF32(1,1); protected GrayF32 warpDeriv2XY = new GrayF32(1,1); // derivative of flow protected GrayF32 derivFlowUX = new GrayF32(1,1); protected GrayF32 derivFlowUY = new GrayF32(1,1); protected GrayF32 derivFlowVX = new GrayF32(1,1); protected GrayF32 derivFlowVY = new GrayF32(1,1); protected GrayF32 psiSmooth = new GrayF32(1,1); protected GrayF32 psiData = new GrayF32(1,1); protected GrayF32 psiGradient = new GrayF32(1,1); // divergence for u,v,d protected GrayF32 divU = new GrayF32(1,1); protected GrayF32 divV = new GrayF32(1,1); protected GrayF32 divD = new GrayF32(1,1); // motion increments for optical flow protected GrayF32 du = new GrayF32(1,1); protected GrayF32 dv = new GrayF32(1,1); /** * Configures flow estimation * * @param config Configuration parameters * @param interp Interpolation for image flow between image layers and warping. Overrides selection in config. */ public BroxWarpingSpacial(ConfigBroxWarping config, InterpolatePixelS interp) { super(config.pyrScale,config.pyrSigma,config.pyrMaxLayers,interp); this.alpha = config.alpha; this.gamma = config.gamma; this.SOR_RELAXATION = config.SOR_RELAXATION; this.numOuter = config.numOuter; this.numInner = config.numInner; this.maxIterationsSor = config.maxIterationsSor; this.convergeTolerance = config.convergeToleranceSor; } /** * Computes dense optical flow from the provided image pyramid. Image gradient for each layer should be * computed directly from the layer images. * * @param image1 Pyramid of first image * @param image2 Pyramid of second image */ public void process(ImagePyramid image1 , ImagePyramid image2 ) { // Process the pyramid from low resolution to high resolution boolean first = true; for( int i = image1.getNumLayers()-1; i >= 0; i-- ) { GrayF32 layer1 = image1.getLayer(i); GrayF32 layer2 = image2.getLayer(i); resizeForLayer(layer1.width,layer2.height); // compute image derivatives gradient.process(layer1,deriv1X,deriv1Y); gradient.process(layer2,deriv2X,deriv2Y); hessian.process(deriv2X,deriv2Y,deriv2XX,deriv2YY,deriv2XY); if( !first ) { // interpolate initial flow from previous layer interpolateFlowScale(layer1.width, layer1.height); } else { // for the very first layer there is no information on flow so set everything to 0 first = false; flowU.reshape(layer1.width, layer1.height); flowV.reshape(layer1.width, layer1.height); ImageMiscOps.fill(flowU,0); ImageMiscOps.fill(flowV,0); } // compute flow for this layer processLayer(layer1,layer2,deriv1X,deriv1Y,deriv2X,deriv2Y,deriv2XX,deriv2YY,deriv2XY); } } /** * Resize images for the current layer being processed */ protected void resizeForLayer( int width , int height ) { deriv1X.reshape(width,height); deriv1Y.reshape(width,height); deriv2X.reshape(width,height); deriv2Y.reshape(width,height); deriv2XX.reshape(width,height); deriv2YY.reshape(width,height); deriv2XY.reshape(width,height); warpImage2.reshape(width,height); warpDeriv2X.reshape(width,height); warpDeriv2Y.reshape(width,height); warpDeriv2XX.reshape(width,height); warpDeriv2YY.reshape(width,height); warpDeriv2XY.reshape(width,height); derivFlowUX.reshape(width,height); derivFlowUY.reshape(width,height); derivFlowVX.reshape(width,height); derivFlowVY.reshape(width,height); psiData.reshape(width,height); psiGradient.reshape(width,height); psiSmooth.reshape(width,height); divU.reshape(width,height); divV.reshape(width,height); divD.reshape(width,height); du.reshape(width,height); dv.reshape(width,height); } /** * Provides an initial estimate for the flow by interpolating values from the previous layer. */ protected void interpolateFlowScale(int widthNew, int heightNew) { // warping isn't done until later so use those images as temporary storage GrayF32 enlargedU = warpDeriv2X; GrayF32 enlargedV = warpDeriv2Y; // use the previous low resolution flow estimate to initialize the new image interpolateFlowScale(flowU, enlargedU); interpolateFlowScale(flowV, enlargedV); flowU.reshape(widthNew, heightNew); flowV.reshape(widthNew, heightNew); // save the initial flow values flowU.setTo(enlargedU); flowV.setTo(enlargedV); } /** * Computes the flow for a layer using Taylor series expansion and Successive Over-Relaxation linear solver. * Flow estimates from previous layers are feed into this by setting initFlow and flow to their values. */ protected void processLayer(GrayF32 image1 , GrayF32 image2 , GrayF32 deriv1X , GrayF32 deriv1Y, GrayF32 deriv2X , GrayF32 deriv2Y, GrayF32 deriv2XX , GrayF32 deriv2YY, GrayF32 deriv2XY) { int N = image1.width*image1.height; int stride = image1.stride; // outer Taylor expansion iterations for( int indexOuter = 0; indexOuter < numOuter; indexOuter++ ) { // warp the image and the first + second derivatives warpImageTaylor(image2, flowU, flowV, warpImage2); warpImageTaylor(deriv2X, flowU, flowV, warpDeriv2X); warpImageTaylor(deriv2Y, flowU, flowV, warpDeriv2Y); warpImageTaylor(deriv2XX, flowU, flowV, warpDeriv2XX); warpImageTaylor(deriv2YY, flowU, flowV, warpDeriv2YY); warpImageTaylor(deriv2XY, flowU, flowV, warpDeriv2XY); gradient.process(flowU,derivFlowUX,derivFlowUY); gradient.process(flowV,derivFlowVX,derivFlowVY); computePsiSmooth(derivFlowUX,derivFlowUY,derivFlowVX,derivFlowVY,psiSmooth); computeDivUVD(flowU, flowV,psiSmooth,divU,divV,divD); // initialize the motion increments to zero Arrays.fill(du.data,0,N,0); Arrays.fill(dv.data,0,N,0); for( int indexInner = 0; indexInner < numInner; indexInner++ ) { computePsiDataPsiGradient(image1, image2, deriv1X, deriv1Y, deriv2X, deriv2Y, deriv2XX, deriv2YY, deriv2XY, du, dv, psiData, psiGradient); float error; int iter = 0; do { // inner SOR iteration. error = 0; // inner portion for (int y = 1; y < image1.height - 1; y++) { int i = y * image1.width + 1; for (int x = 1; x < image1.width - 1; x++, i++) { error += iterationSor(image1, deriv1X, deriv1Y, i, i + 1, i - 1, i + stride, i - stride); } } // border regions require special treatment int y0 = 0; int y1 = image1.height-1; for (int x = 0; x < image1.width; x++ ) { error += iterationSor(image1, deriv1X, deriv1Y, s(x, y0), s(x + 1, y0), s(x - 1, y0), s(x, y0 - 1), s(x, y0 + 1)); error += iterationSor(image1, deriv1X, deriv1Y, s(x, y1), s(x + 1, y1), s(x - 1, y1), s(x, y1 - 1), s(x, y1 + 1)); } int x0 = 0; int x1 = image1.width-1; for (int y = 1; y < image1.height - 1; y++) { error += iterationSor(image1, deriv1X, deriv1Y, s(x0, y), s(x0 - 1, y), s(x0 + 1, y), s(x0, y - 1), s(x0, y + 1)); error += iterationSor(image1, deriv1X, deriv1Y, s(x1, y), s(x1 - 1, y), s(x1 + 1, y), s(x1, y - 1), s(x1, y + 1)); } } while (error > convergeTolerance * image1.width * image1.height && ++iter < maxIterationsSor); } // update the flow with the motion increments PixelMath.add(flowU,du, flowU); PixelMath.add(flowV,dv, flowV); } } /** * Inner SOR iteration step * * @param i Index of target pixel at (x,y) * @param ipx (x+1,y) * @param imx (x-1,y) * @param ipy (x,y+1) * @param imy (x,y-1) */ private float iterationSor(GrayF32 image1, GrayF32 deriv1X, GrayF32 deriv1Y, int i, int ipx, int imx, int ipy, int imy) { float w = SOR_RELAXATION; // these variables could be precomputed once. See equation 11 float psid = psiData.data[i]; float psig = gamma*psiGradient.data[i]; float di = warpImage2.data[i] - image1.data[i]; float dx2 = warpDeriv2X.data[i]; float dy2 = warpDeriv2Y.data[i]; float dxx2 = warpDeriv2XX.data[i]; float dyy2 = warpDeriv2YY.data[i]; float dxy2 = warpDeriv2XY.data[i]; float Au = -psid*di*dx2 + alpha*divU.data[i] - psig*((dx2 - deriv1X.data[i])*warpDeriv2XX.data[i] + (dy2 - deriv1Y.data[i])*warpDeriv2XY.data[i]); float Av = -psid*di*dy2 + alpha*divV.data[i] - psig*((dx2 - deriv1X.data[i])*warpDeriv2XY.data[i] + (dy2 - deriv1Y.data[i])*warpDeriv2YY.data[i]); float Du = psid*dx2*dx2 + psig*(dxx2*dxx2 + dxy2*dxy2) + alpha*divD.data[i]; float Dv = psid*dy2*dy2 + psig*(dyy2*dyy2 + dxy2*dxy2) + alpha*divD.data[i]; float D = psid*dx2*dy2 + psig*(dxx2 + dyy2)*dxy2; // update the change in flow float psi_index = psiSmooth.data[i]; float coef0 = 0.5f*(psiSmooth.data[ipx] + psi_index); float coef1 = 0.5f*(psiSmooth.data[imx] + psi_index); float coef2 = 0.5f*(psiSmooth.data[ipy] + psi_index); float coef3 = 0.5f*(psiSmooth.data[imy] + psi_index); float div_du = coef0 * du.data[ipx] + coef1 * du.data[imx] + coef2 * du.data[ipy] + coef3 * du.data[imy]; float div_dv = coef0 * dv.data[ipx] + coef1 * dv.data[imx] + coef2 * dv.data[ipy] + coef3 * dv.data[imy] ; final float dui = du.data[i]; final float dvi = dv.data[i]; du.data[i] = (1f-w)*dui + w*(Au - D*dvi + alpha*div_du)/Du; dv.data[i] = (1f-w)*dvi + w*(Av - D*du.data[i] + alpha*div_dv)/Dv; return (du.data[i] - dui) * (du.data[i] - dui) + (dv.data[i] - dvi) * (dv.data[i] - dvi); } /** * Equation 5. Psi_s */ private void computePsiSmooth(GrayF32 ux , GrayF32 uy , GrayF32 vx , GrayF32 vy , GrayF32 psiSmooth ) { int N = derivFlowUX.width * derivFlowUX.height; for( int i = 0; i < N; i++ ) { float vux = ux.data[i]; float vuy = uy.data[i]; float vvx = vx.data[i]; float vvy = vy.data[i]; float mu = vux*vux + vuy*vuy; float mv = vvx*vvx + vvy*vvy; psiSmooth.data[i] = (float)(1.0/(2.0*Math.sqrt(mu + mv + EPSILON*EPSILON))); } } /** * Compute Psi-data using equation 6 and approximation in equation 5 */ protected void computePsiDataPsiGradient(GrayF32 image1, GrayF32 image2, GrayF32 deriv1x, GrayF32 deriv1y, GrayF32 deriv2x, GrayF32 deriv2y, GrayF32 deriv2xx, GrayF32 deriv2yy, GrayF32 deriv2xy, GrayF32 du, GrayF32 dv, GrayF32 psiData, GrayF32 psiGradient ) { int N = image1.width * image1.height; for( int i = 0; i < N; i++ ) { float du_ = du.data[i]; float dv_ = dv.data[i]; // compute Psi-data float taylor2 = image2.data[i] + deriv2x.data[i]*du_ + deriv2y.data[i]*dv_; float v = taylor2 - image1.data[i]; psiData.data[i] = (float)(1.0/(2.0*Math.sqrt(v*v + EPSILON*EPSILON))); // compute Psi-gradient float dIx = deriv2x.data[i] + deriv2xx.data[i]*du_ + deriv2xy.data[i]*dv_ - deriv1x.data[i]; float dIy = deriv2y.data[i] + deriv2xy.data[i]*du_ + deriv2yy.data[i]*dv_ - deriv1y.data[i]; float dI2 = dIx*dIx + dIy*dIy; psiGradient.data[i] = (float)(1.0/(2.0*Math.sqrt(dI2 + EPSILON*EPSILON))); } } /** * Computes the divergence for u,v, and d. Equation 8 and Equation 10. */ private void computeDivUVD(GrayF32 u , GrayF32 v , GrayF32 psi , GrayF32 divU , GrayF32 divV , GrayF32 divD ) { final int stride = psi.stride; // compute the inside pixel for (int y = 1; y < psi.height-1; y++) { // index of the current pixel int index = y*stride + 1; for (int x = 1; x < psi.width-1; x++ , index++) { float psi_index = psi.data[index]; float coef0 = 0.5f*(psi.data[index+1] + psi_index); float coef1 = 0.5f*(psi.data[index-1] + psi_index); float coef2 = 0.5f*(psi.data[index+stride] + psi_index); float coef3 = 0.5f*(psi.data[index-stride] + psi_index); float u_index = u.data[index]; divU.data[index] = coef0*(u.data[index+1] - u_index) + coef1*(u.data[index-1] - u_index) + coef2*(u.data[index+stride] - u_index) + coef3*(u.data[index-stride] - u_index); float v_index = v.data[index]; divV.data[index] = coef0*(v.data[index+1] - v_index) + coef1*(v.data[index-1] - v_index) + coef2*(v.data[index+stride] - v_index) + coef3*(v.data[index-stride] - v_index); divD.data[index] = coef0 + coef1 + coef2 + coef3; } } // handle the image borders for( int x = 0; x < psi.width; x++ ) { computeDivUVD_safe(x,0,u,v,psi,divU,divV,divD); computeDivUVD_safe(x,psi.height-1,u,v,psi,divU,divV,divD); } for( int y = 1; y < psi.height-1; y++ ) { computeDivUVD_safe(0,y,u,v,psi,divU,divV,divD); computeDivUVD_safe(psi.width-1,y,u,v,psi,divU,divV,divD); } } protected void computeDivUVD_safe(int x , int y , GrayF32 u , GrayF32 v , GrayF32 psi , GrayF32 divU , GrayF32 divV , GrayF32 divD ) { int index = u.getIndex(x, y); int index_px = s(x + 1, y); int index_mx = s(x - 1, y); int index_py = s(x, y + 1); int index_my = s(x, y - 1); float psi_index = psi.data[index]; float coef0 = 0.5f*(psi.data[index_px] + psi_index); float coef1 = 0.5f*(psi.data[index_mx] + psi_index); float coef2 = 0.5f*(psi.data[index_py] + psi_index); float coef3 = 0.5f*(psi.data[index_my] + psi_index); float u_index = u.data[index]; divU.data[index] = coef0*(u.data[index_px] - u_index) + coef1*(u.data[index_mx] - u_index) + coef2*(u.data[index_py] - u_index) + coef3*(u.data[index_my] - u_index); float v_index = v.data[index]; divV.data[index] = coef0*(v.data[index_px] - v_index) + coef1*(v.data[index_mx] - v_index) + coef2*(v.data[index_py] - v_index) + coef3*(v.data[index_my] - v_index); divD.data[index] = coef0 + coef1 + coef2 + coef3; } protected int s( int x , int y ) { if( x < 0 ) x = 0; else if( x >= warpImage2.width ) x = warpImage2.width-1; if( y < 0 ) y = 0; else if( y >= warpImage2.height ) y = warpImage2.height-1; return warpImage2.getIndex(x,y); } public GrayF32 getFlowX() { return flowU; } public GrayF32 getFlowY() { return flowV; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy