/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2015, Baisheng Lai (laibaisheng@gmail.com), Zhejiang University,
// all rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

/**
 * This module was accepted as a GSoC 2015 project for OpenCV, authored by
 * Baisheng Lai, mentored by Bo Li.
 *
 * The omnidirectional camera in this module is denoted by the catadioptric
 * model. Please refer to Mei's paper for details of the camera model:
 *
 *      C. Mei and P. Rives, "Single view point omnidirectional camera
 *      calibration from planar grids", in ICRA 2007.
 *
 * The implementation of the calibration part is based on Li's calibration
 * toolbox:
 *
 *     B. Li, L. Heng, K. Kevin  and M. Pollefeys, "A Multiple-Camera System
 *     Calibration Toolbox Using A Feature Descriptor-Based Calibration
 *     Pattern", in IROS 2013.
 */
#include "precomp.hpp"
#include "opencv2/ccalib/omnidir.hpp"
#include <fstream>
#include <iostream>
namespace cv { namespace
{
    struct JacobianRow
    {
        Matx13d dom,dT;
        Matx12d df;
        double ds;
        Matx12d dc;
        double dxi;
        Matx14d dkp;    // distortion k1,k2,p1,p2
    };
}}

/////////////////////////////////////////////////////////////////////////////
//////// projectPoints
void cv::omnidir::projectPoints(InputArray objectPoints, OutputArray imagePoints,
                InputArray rvec, InputArray tvec, InputArray K, double xi, InputArray D, OutputArray jacobian)
{

    CV_Assert(objectPoints.type() == CV_64FC3 || objectPoints.type() == CV_32FC3);
    CV_Assert((rvec.depth() == CV_64F || rvec.depth() == CV_32F) && rvec.total() == 3);
    CV_Assert((tvec.depth() == CV_64F || tvec.depth() == CV_32F) && tvec.total() == 3);
    CV_Assert((K.type() == CV_64F || K.type() == CV_32F) && K.size() == Size(3,3));
    CV_Assert((D.type() == CV_64F || D.type() == CV_32F) && D.total() == 4);

    imagePoints.create(objectPoints.size(), CV_MAKETYPE(objectPoints.depth(), 2));

    int n = (int)objectPoints.total();

    Vec3d om = rvec.depth() == CV_32F ? (Vec3d)*rvec.getMat().ptr<Vec3f>() : *rvec.getMat().ptr<Vec3d>();
    Vec3d T  = tvec.depth() == CV_32F ? (Vec3d)*tvec.getMat().ptr<Vec3f>() : *tvec.getMat().ptr<Vec3d>();

    Vec2d f,c;
    double s;
    if (K.depth() == CV_32F)
    {
        Matx33f Kc = K.getMat();
        f = Vec2f(Kc(0,0), Kc(1,1));
        c = Vec2f(Kc(0,2),Kc(1,2));
        s = (double)Kc(0,1);
    }
    else
    {
        Matx33d Kc = K.getMat();
        f = Vec2d(Kc(0,0), Kc(1,1));
        c = Vec2d(Kc(0,2),Kc(1,2));
        s = Kc(0,1);
    }

    Vec4d kp = D.depth() == CV_32F ? (Vec4d)*D.getMat().ptr<Vec4f>() : *D.getMat().ptr<Vec4d>();
    //Vec<double, 4> kp= (Vec<double,4>)*D.getMat().ptr<Vec<double,4> >();

    const Vec3d* Xw_alld = objectPoints.getMat().ptr<Vec3d>();
    const Vec3f* Xw_allf = objectPoints.getMat().ptr<Vec3f>();
    Vec2d* xpd = imagePoints.getMat().ptr<Vec2d>();
    Vec2f* xpf = imagePoints.getMat().ptr<Vec2f>();

    Matx33d R;
    Matx<double, 3, 9> dRdom;
    Rodrigues(om, R, dRdom);

    JacobianRow *Jn = 0;
    if (jacobian.needed())
    {
        int nvars = 2+2+1+4+3+3+1; // f,c,s,kp,om,T,xi
        jacobian.create(2*int(n), nvars, CV_64F);
        Jn = jacobian.getMat().ptr<JacobianRow>(0);
    }

    double k1=kp[0],k2=kp[1];
    double p1 = kp[2], p2 = kp[3];

    for (int i = 0; i < n; i++)
    {
        // convert to camera coordinate
        Vec3d Xw = objectPoints.depth() == CV_32F ? (Vec3d)Xw_allf[i] : Xw_alld[i];

        Vec3d Xc = (Vec3d)(R*Xw + T);

        // convert to unit sphere
        Vec3d Xs = Xc/cv::norm(Xc);

        // convert to normalized image plane
        Vec2d xu = Vec2d(Xs[0]/(Xs[2]+xi), Xs[1]/(Xs[2]+xi));

        // add distortion
        Vec2d xd;
        double r2 = xu[0]*xu[0]+xu[1]*xu[1];
        double r4 = r2*r2;

        xd[0] = xu[0]*(1+k1*r2+k2*r4) + 2*p1*xu[0]*xu[1] + p2*(r2+2*xu[0]*xu[0]);
        xd[1] = xu[1]*(1+k1*r2+k2*r4) + p1*(r2+2*xu[1]*xu[1]) + 2*p2*xu[0]*xu[1];

        // convert to pixel coordinate
        Vec2d final;
        final[0] = f[0]*xd[0]+s*xd[1]+c[0];
        final[1] = f[1]*xd[1]+c[1];

        if (objectPoints.depth() == CV_32F)
        {
            xpf[i] = final;
        }
        else
        {
            xpd[i] = final;
        }
        /*xpd[i][0] = f[0]*xd[0]+s*xd[1]+c[0];
        xpd[i][1] = f[1]*xd[1]+c[1];*/

        if (jacobian.needed())
        {
            double dXcdR_a[] = {Xw[0],Xw[1],Xw[2],0,0,0,0,0,0,
                                0,0,0,Xw[0],Xw[1],Xw[2],0,0,0,
                                0,0,0,0,0,0,Xw[0],Xw[1],Xw[2]};
            Matx<double,3, 9> dXcdR(dXcdR_a);
            Matx33d dXcdom = dXcdR * dRdom.t();
            double r_1 = 1.0/norm(Xc);
            double r_3 = pow(r_1,3);
            Matx33d dXsdXc(r_1-Xc[0]*Xc[0]*r_3, -(Xc[0]*Xc[1])*r_3, -(Xc[0]*Xc[2])*r_3,
                           -(Xc[0]*Xc[1])*r_3, r_1-Xc[1]*Xc[1]*r_3, -(Xc[1]*Xc[2])*r_3,
                           -(Xc[0]*Xc[2])*r_3, -(Xc[1]*Xc[2])*r_3, r_1-Xc[2]*Xc[2]*r_3);
            Matx23d dxudXs(1/(Xs[2]+xi),    0,    -Xs[0]/(Xs[2]+xi)/(Xs[2]+xi),
                           0,    1/(Xs[2]+xi),    -Xs[1]/(Xs[2]+xi)/(Xs[2]+xi));
            // pre-compute some reusable things
            double temp1 = 2*k1*xu[0] + 4*k2*xu[0]*r2;
            double temp2 = 2*k1*xu[1] + 4*k2*xu[1]*r2;
            Matx22d dxddxu(k2*r4+6*p2*xu[0]+2*p1*xu[1]+xu[0]*temp1+k1*r2+1,    2*p1*xu[0]+2*p2*xu[1]+xu[0]*temp2,
                           2*p1*xu[0]+2*p2*xu[1]+xu[1]*temp1,    k2*r4+2*p2*xu[0]+6*p1*xu[1]+xu[1]*temp2+k1*r2+1);
            Matx22d dxpddxd(f[0], s,
                            0, f[1]);
            Matx23d dxpddXc = dxpddxd * dxddxu * dxudXs * dXsdXc;

            // derivative of xpd respect to om
            Matx23d dxpddom = dxpddXc * dXcdom;
            Matx33d dXcdT(1.0,0.0,0.0,
                          0.0,1.0,0.0,
                          0.0,0.0,1.0);
            // derivative of xpd respect to T

            Matx23d dxpddT = dxpddXc * dXcdT;
            Matx21d dxudxi(-Xs[0]/(Xs[2]+xi)/(Xs[2]+xi),
                           -Xs[1]/(Xs[2]+xi)/(Xs[2]+xi));

            // derivative of xpd respect to xi
            Matx21d dxpddxi = dxpddxd * dxddxu * dxudxi;
            Matx<double,2,4> dxddkp(xu[0]*r2, xu[0]*r4, 2*xu[0]*xu[1], r2+2*xu[0]*xu[0],
                                    xu[1]*r2, xu[1]*r4, r2+2*xu[1]*xu[1], 2*xu[0]*xu[1]);

            // derivative of xpd respect to kp
            Matx<double,2,4> dxpddkp = dxpddxd * dxddkp;

            // derivative of xpd respect to f
            Matx22d dxpddf(xd[0], 0,
                           0, xd[1]);

            // derivative of xpd respect to c
            Matx22d dxpddc(1, 0,
                           0, 1);

            Jn[0].dom = dxpddom.row(0);
            Jn[1].dom = dxpddom.row(1);
            Jn[0].dT = dxpddT.row(0);
            Jn[1].dT = dxpddT.row(1);
            Jn[0].dkp = dxpddkp.row(0);
            Jn[1].dkp = dxpddkp.row(1);
            Jn[0].dxi = dxpddxi(0,0);
            Jn[1].dxi = dxpddxi(1,0);
            Jn[0].df = dxpddf.row(0);
            Jn[1].df = dxpddf.row(1);
            Jn[0].dc = dxpddc.row(0);
            Jn[1].dc = dxpddc.row(1);
            Jn[0].ds = xd[1];
            Jn[1].ds = 0;
            Jn += 2;
         }
    }
}

/////////////////////////////////////////////////////////////////////////////
//////// undistortPoints
void cv::omnidir::undistortPoints( InputArray distorted, OutputArray undistorted,
    InputArray K, InputArray D, InputArray xi, InputArray R)
{
    CV_Assert(distorted.type() == CV_64FC2 || distorted.type() == CV_32FC2);
    CV_Assert(R.empty() || (!R.empty() && (R.size() == Size(3, 3) || R.total() * R.channels() == 3)
        && (R.depth() == CV_64F || R.depth() == CV_32F)));
    CV_Assert((D.depth() == CV_64F || D.depth() == CV_32F) && D.total() == 4);
    CV_Assert(K.size() == Size(3, 3) && (K.depth() == CV_64F || K.depth() == CV_32F));
    CV_Assert(xi.total() == 1 && (xi.depth() == CV_64F || xi.depth() == CV_32F));

    undistorted.create(distorted.size(), distorted.type());

    cv::Vec2d f, c;
    double s = 0.0;
    if (K.depth() == CV_32F)
    {
        Matx33f camMat = K.getMat();
        f = Vec2f(camMat(0,0), camMat(1,1));
        c = Vec2f(camMat(0,2), camMat(1,2));
        s = (double)camMat(0,1);
    }
    else if (K.depth() == CV_64F)
    {
        Matx33d camMat = K.getMat();
        f = Vec2d(camMat(0,0), camMat(1,1));
        c = Vec2d(camMat(0,2), camMat(1,2));
        s = camMat(0,1);
    }

    Vec4d kp = D.depth()==CV_32F ? (Vec4d)*D.getMat().ptr<Vec4f>():(Vec4d)*D.getMat().ptr<Vec4d>();
    Vec2d k = Vec2d(kp[0], kp[1]);
    Vec2d p = Vec2d(kp[2], kp[3]);

    double _xi = xi.depth() == CV_32F ? (double)*xi.getMat().ptr<float>() : *xi.getMat().ptr<double>();
    cv::Matx33d RR = cv::Matx33d::eye();
    // R is om
    if(!R.empty() && R.total()*R.channels() == 3)
    {
        cv::Vec3d rvec;
        R.getMat().convertTo(rvec, CV_64F);
        cv::Rodrigues(rvec, RR);
    }
    else if (!R.empty() && R.size() == Size(3,3))
    {
        R.getMat().convertTo(RR, CV_64F);
    }

    const cv::Vec2d *srcd = distorted.getMat().ptr<cv::Vec2d>();
    const cv::Vec2f *srcf = distorted.getMat().ptr<cv::Vec2f>();

    cv::Vec2d *dstd = undistorted.getMat().ptr<cv::Vec2d>();
    cv::Vec2f *dstf = undistorted.getMat().ptr<cv::Vec2f>();

    int n = (int)distorted.total();
    for (int i = 0; i < n; i++)
    {
        Vec2d pi = distorted.depth() == CV_32F ? (Vec2d)srcf[i]:(Vec2d)srcd[i];    // image point
        Vec2d pp((pi[0]*f[1]-c[0]*f[1]-s*(pi[1]-c[1]))/(f[0]*f[1]), (pi[1]-c[1])/f[1]); //plane
        Vec2d pu = pp;    // points without distortion

        // remove distortion iteratively
        for (int j = 0; j < 20; j++)
        {
            double r2 = pu[0]*pu[0] + pu[1]*pu[1];
            double r4 = r2*r2;
            pu[0] = (pp[0] - 2*p[0]*pu[0]*pu[1] - p[1]*(r2+2*pu[0]*pu[0])) / (1 + k[0]*r2 + k[1]*r4);
            pu[1] = (pp[1] - 2*p[1]*pu[0]*pu[1] - p[0]*(r2+2*pu[1]*pu[1])) / (1 + k[0]*r2 + k[1]*r4);
        }

        // project to unit sphere
        double r2 = pu[0]*pu[0] + pu[1]*pu[1];
        double a = (r2 + 1);
        double b = 2*_xi*r2;
        double cc = r2*_xi*_xi-1;
        double Zs = (-b + sqrt(b*b - 4*a*cc))/(2*a);
        Vec3d Xw = Vec3d(pu[0]*(Zs + _xi), pu[1]*(Zs +_xi), Zs);

        // rotate
        Xw = RR * Xw;

        // project back to sphere
        Vec3d Xs = Xw / cv::norm(Xw);

        // reproject to camera plane
        Vec3d ppu = Vec3d(Xs[0]/(Xs[2]+_xi), Xs[1]/(Xs[2]+_xi), 1.0);
        if (undistorted.depth() == CV_32F)
        {
            dstf[i] = Vec2f((float)ppu[0], (float)ppu[1]);
        }
        else if (undistorted.depth() == CV_64F)
        {
            dstd[i] = Vec2d(ppu[0], ppu[1]);
        }
    }
}


/////////////////////////////////////////////////////////////////////////////
//////// cv::omnidir::initUndistortRectifyMap
void cv::omnidir::initUndistortRectifyMap(InputArray K, InputArray D, InputArray xi, InputArray R, InputArray P,
    const cv::Size& size, int m1type, OutputArray map1, OutputArray map2, int flags)
{
    CV_Assert( m1type == CV_16SC2 || m1type == CV_32F || m1type <=0 );
    map1.create( size, m1type <= 0 ? CV_16SC2 : m1type );
    map2.create( size, map1.type() == CV_16SC2 ? CV_16UC1 : CV_32F );

    CV_Assert((K.depth() == CV_32F || K.depth() == CV_64F) && (D.depth() == CV_32F || D.depth() == CV_64F));
    CV_Assert(K.size() == Size(3, 3) && (D.empty() || D.total() == 4));
    CV_Assert(P.empty()|| (P.depth() == CV_32F || P.depth() == CV_64F));
    CV_Assert(P.empty() || P.size() == Size(3, 3) || P.size() == Size(4, 3));
    CV_Assert(R.empty() || (R.depth() == CV_32F || R.depth() == CV_64F));
    CV_Assert(R.empty() || R.size() == Size(3, 3) || R.total() * R.channels() == 3);
    CV_Assert(flags == RECTIFY_PERSPECTIVE || flags == RECTIFY_CYLINDRICAL || flags == RECTIFY_LONGLATI
        || flags == RECTIFY_STEREOGRAPHIC);
    CV_Assert(xi.total() == 1 && (xi.depth() == CV_32F || xi.depth() == CV_64F));

    cv::Vec2d f, c;
    double s;
    if (K.depth() == CV_32F)
    {
        Matx33f camMat = K.getMat();
        f = Vec2f(camMat(0, 0), camMat(1, 1));
        c = Vec2f(camMat(0, 2), camMat(1, 2));
        s = (double)camMat(0,1);
    }
    else
    {
        Matx33d camMat = K.getMat();
        f = Vec2d(camMat(0, 0), camMat(1, 1));
        c = Vec2d(camMat(0, 2), camMat(1, 2));
        s = camMat(0,1);
    }

    Vec4d kp = Vec4d::all(0);
    if (!D.empty())
        kp = D.depth() == CV_32F ? (Vec4d)*D.getMat().ptr<Vec4f>(): *D.getMat().ptr<Vec4d>();
    double _xi = xi.depth() == CV_32F ? (double)*xi.getMat().ptr<float>() : *xi.getMat().ptr<double>();
    Vec2d k = Vec2d(kp[0], kp[1]);
    Vec2d p = Vec2d(kp[2], kp[3]);
    cv::Matx33d RR  = cv::Matx33d::eye();
    if (!R.empty() && R.total() * R.channels() == 3)
    {
        cv::Vec3d rvec;
        R.getMat().convertTo(rvec, CV_64F);
        cv::Rodrigues(rvec, RR);
    }
    else if (!R.empty() && R.size() == Size(3, 3))
        R.getMat().convertTo(RR, CV_64F);

    cv::Matx33d PP = cv::Matx33d::eye();
    if (!P.empty())
        P.getMat().colRange(0, 3).convertTo(PP, CV_64F);
    else
        PP = K.getMat();

    cv::Matx33d iKR = (PP*RR).inv(cv::DECOMP_SVD);
    cv::Matx33d iK = PP.inv(cv::DECOMP_SVD);
    cv::Matx33d iR = RR.inv(cv::DECOMP_SVD);

    if (flags == omnidir::RECTIFY_PERSPECTIVE)
    {
        for (int i = 0; i < size.height; ++i)
        {
            float* m1f = map1.getMat().ptr<float>(i);
            float* m2f = map2.getMat().ptr<float>(i);
            short*  m1 = (short*)m1f;
            ushort* m2 = (ushort*)m2f;

            double _x = i*iKR(0, 1) + iKR(0, 2),
                   _y = i*iKR(1, 1) + iKR(1, 2),
                   _w = i*iKR(2, 1) + iKR(2, 2);
            for(int j = 0; j < size.width; ++j, _x+=iKR(0,0), _y+=iKR(1,0), _w+=iKR(2,0))
            {
                // project back to unit sphere
                double r = sqrt(_x*_x + _y*_y + _w*_w);
                double Xs = _x / r;
                double Ys = _y / r;
                double Zs = _w / r;
                // project to image plane
                double xu = Xs / (Zs + _xi),
                    yu = Ys / (Zs + _xi);
                // add distortion
                double r2 = xu*xu + yu*yu;
                double r4 = r2*r2;
                double xd = (1+k[0]*r2+k[1]*r4)*xu + 2*p[0]*xu*yu + p[1]*(r2+2*xu*xu);
                double yd = (1+k[0]*r2+k[1]*r4)*yu + p[0]*(r2+2*yu*yu) + 2*p[1]*xu*yu;
                // to image pixel
                double u = f[0]*xd + s*yd + c[0];
                double v = f[1]*yd + c[1];

                if( m1type == CV_16SC2 )
                {
                    int iu = cv::saturate_cast<int>(u*cv::INTER_TAB_SIZE);
                    int iv = cv::saturate_cast<int>(v*cv::INTER_TAB_SIZE);
                    m1[j*2+0] = (short)(iu >> cv::INTER_BITS);
                    m1[j*2+1] = (short)(iv >> cv::INTER_BITS);
                    m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1)));
                }
                else if( m1type == CV_32FC1 )
                {
                    m1f[j] = (float)u;
                    m2f[j] = (float)v;
                }
            }
        }
    }
    else if(flags == omnidir::RECTIFY_CYLINDRICAL || flags == omnidir::RECTIFY_LONGLATI ||
        flags == omnidir::RECTIFY_STEREOGRAPHIC)
    {
        for (int i = 0; i < size.height; ++i)
        {
            float* m1f = map1.getMat().ptr<float>(i);
            float* m2f = map2.getMat().ptr<float>(i);
            short*  m1 = (short*)m1f;
            ushort* m2 = (ushort*)m2f;

            // for RECTIFY_LONGLATI, theta and h are longittude and latitude
            double theta = i*iK(0, 1) + iK(0, 2),
                   h     = i*iK(1, 1) + iK(1, 2);

            for (int j = 0; j < size.width; ++j, theta+=iK(0,0), h+=iK(1,0))
            {
                double _xt = 0.0, _yt = 0.0, _wt = 0.0;
                if (flags == omnidir::RECTIFY_CYLINDRICAL)
                {
                    //_xt = std::sin(theta);
                    //_yt = h;
                    //_wt = std::cos(theta);
                    _xt = std::cos(theta);
                    _yt = std::sin(theta);
                    _wt = h;
                }
                else if (flags == omnidir::RECTIFY_LONGLATI)
                {
                    _xt = -std::cos(theta);
                    _yt = -std::sin(theta) * std::cos(h);
                    _wt = std::sin(theta) * std::sin(h);
                }
                else if (flags == omnidir::RECTIFY_STEREOGRAPHIC)
                {
                    double a = theta*theta + h*h + 4;
                    double b = -2*theta*theta - 2*h*h;
                    double c2 = theta*theta + h*h -4;

                    _yt = (-b-std::sqrt(b*b - 4*a*c2))/(2*a);
                    _xt = theta*(1 - _yt) / 2;
                    _wt = h*(1 - _yt) / 2;
                }
                double _x = iR(0,0)*_xt + iR(0,1)*_yt + iR(0,2)*_wt;
                double _y = iR(1,0)*_xt + iR(1,1)*_yt + iR(1,2)*_wt;
                double _w = iR(2,0)*_xt + iR(2,1)*_yt + iR(2,2)*_wt;

                double r = sqrt(_x*_x + _y*_y + _w*_w);
                double Xs = _x / r;
                double Ys = _y / r;
                double Zs = _w / r;
                // project to image plane
                double xu = Xs / (Zs + _xi),
                       yu = Ys / (Zs + _xi);
                // add distortion
                double r2 = xu*xu + yu*yu;
                double r4 = r2*r2;
                double xd = (1+k[0]*r2+k[1]*r4)*xu + 2*p[0]*xu*yu + p[1]*(r2+2*xu*xu);
                double yd = (1+k[0]*r2+k[1]*r4)*yu + p[0]*(r2+2*yu*yu) + 2*p[1]*xu*yu;
                // to image pixel
                double u = f[0]*xd + s*yd + c[0];
                double v = f[1]*yd + c[1];

                if( m1type == CV_16SC2 )
                {
                    int iu = cv::saturate_cast<int>(u*cv::INTER_TAB_SIZE);
                    int iv = cv::saturate_cast<int>(v*cv::INTER_TAB_SIZE);
                    m1[j*2+0] = (short)(iu >> cv::INTER_BITS);
                    m1[j*2+1] = (short)(iv >> cv::INTER_BITS);
                    m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1)));
                }
                else if( m1type == CV_32FC1 )
                {
                    m1f[j] = (float)u;
                    m2f[j] = (float)v;
                }
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// cv::omnidir::undistortImage

void cv::omnidir::undistortImage(InputArray distorted, OutputArray undistorted,
    InputArray K, InputArray D, InputArray xi, int flags, InputArray Knew, const Size& new_size, InputArray R)
{
    Size size = new_size.area() != 0 ? new_size : distorted.size();

    cv::Mat map1, map2;
    omnidir::initUndistortRectifyMap(K, D, xi, R, Knew, size, CV_16SC2, map1, map2, flags);
    cv::remap(distorted, undistorted, map1, map2, INTER_LINEAR, BORDER_CONSTANT);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// cv::omnidir::internal::initializeCalibration

void cv::omnidir::internal::initializeCalibration(InputArrayOfArrays patternPoints, InputArrayOfArrays imagePoints, Size size,
    OutputArrayOfArrays omAll, OutputArrayOfArrays tAll, OutputArray K, double& xi, OutputArray idx)
{
    // For details please refer to Section III from Li's IROS 2013 paper

    double u0 = size.width / 2;
    double v0 = size.height / 2;

    int n_img = (int)imagePoints.total();

    std::vector<cv::Vec3d> v_omAll(n_img), v_tAll(n_img);

    std::vector<double> gammaAll(n_img);

    K.create(3, 3, CV_64F);
    Mat _K;
    for (int image_idx = 0; image_idx < n_img; ++image_idx)
    {
        cv::Mat objPoints, imgPoints;
		patternPoints.getMat(image_idx).copyTo(objPoints);
		imagePoints.getMat(image_idx).copyTo(imgPoints);

		int n_point = imgPoints.rows * imgPoints.cols;
		if (objPoints.rows != n_point)
			objPoints = objPoints.reshape(3, n_point);
		if (imgPoints.rows != n_point)
			imgPoints = imgPoints.reshape(2, n_point);

        // objectPoints should be 3-channel data, imagePoints should be 2-channel data
        CV_Assert(objPoints.type() == CV_64FC3 && imgPoints.type() == CV_64FC2 );

        std::vector<cv::Mat> xy, uv;
        cv::split(objPoints, xy);
        cv::split(imgPoints, uv);


        cv::Mat x = xy[0].reshape(1, n_point), y = xy[1].reshape(1, n_point),
                u = uv[0].reshape(1, n_point) - u0, v = uv[1].reshape(1, n_point) - v0;

        cv::Mat sqrRho = u.mul(u) + v.mul(v);
        // compute extrinsic parameters
        cv::Mat M(n_point, 6, CV_64F);
        Mat(-v.mul(x)).copyTo(M.col(0));
        Mat(-v.mul(y)).copyTo(M.col(1));
        Mat(u.mul(x)).copyTo(M.col(2));
        Mat(u.mul(y)).copyTo(M.col(3));
        Mat(-v).copyTo(M.col(4));
        Mat(u).copyTo(M.col(5));

        Mat W,U,V;
        cv::SVD::compute(M, W, U, V,SVD::FULL_UV);
        V = V.t();

        double miniReprojectError = 1e5;
        // the signs of r1, r2, r3 are unknown, so they can be flipped.
        for (int coef = 1; coef >= -1; coef-=2)
        {
            double r11 = V.at<double>(0, 5) * coef;
            double r12 = V.at<double>(1, 5) * coef;
            double r21 = V.at<double>(2, 5) * coef;
            double r22 = V.at<double>(3, 5) * coef;
            double t1 = V.at<double>(4, 5) * coef;
            double t2 = V.at<double>(5, 5) * coef;

            Mat roots;
            double r31s;
            solvePoly(Matx13d(-(r11*r12+r21*r22)*(r11*r12+r21*r22), r11*r11+r21*r21-r12*r12-r22*r22, 1), roots);

            if (roots.at<Vec2d>(0)[0] > 0)
                r31s = sqrt(roots.at<Vec2d>(0)[0]);
            else
                r31s = sqrt(roots.at<Vec2d>(1)[0]);

            for (int coef2 = 1; coef2 >= -1; coef2-=2)
            {
                double r31 = r31s * coef2;
                double r32 = -(r11*r12 + r21*r22) / r31;

                cv::Vec3d r1(r11, r21, r31);
                cv::Vec3d r2(r12, r22, r32);
                cv::Vec3d t(t1, t2, 0);
                double scale = 1 / cv::norm(r1);
                r1 = r1 * scale;
                r2 = r2 * scale;
                t = t * scale;

                // compute intrisic parameters
                // Form equations in Scaramuzza's paper
                // A Toolbox for Easily Calibrating Omnidirectional Cameras
                Mat A(n_point*2, 3, CV_64F);
                Mat((r1[1]*x + r2[1]*y + t[1])/2).copyTo(A.rowRange(0, n_point).col(0));
                Mat((r1[0]*x + r2[0]*y + t[0])/2).copyTo(A.rowRange(n_point, 2*n_point).col(0));
                Mat(-A.col(0).rowRange(0, n_point).mul(sqrRho)).copyTo(A.col(1).rowRange(0, n_point));
                Mat(-A.col(0).rowRange(n_point, 2*n_point).mul(sqrRho)).copyTo(A.col(1).rowRange(n_point, 2*n_point));
                Mat(-v).copyTo(A.rowRange(0, n_point).col(2));
                Mat(-u).copyTo(A.rowRange(n_point, 2*n_point).col(2));

                // Operation to avoid bad numerical-condition of A
                Vec3d maxA, minA;
                for (int j = 0; j < A.cols; j++)
                {
                    cv::minMaxLoc(cv::abs(A.col(j)), &minA[j], &maxA[j]);
                    A.col(j) = A.col(j) / maxA[j];
                }

                Mat B(n_point*2 , 1, CV_64F);
                Mat(v.mul(r1[2]*x + r2[2]*y)).copyTo(B.rowRange(0, n_point));
                Mat(u.mul(r1[2]*x + r2[2]*y)).copyTo(B.rowRange(n_point, 2*n_point));

                Mat res = A.inv(DECOMP_SVD) * B;
                res = res.mul(1/Mat(maxA));

                double gamma = sqrt(res.at<double>(0) / res.at<double>(1));
                t[2] = res.at<double>(2);

                cv::Vec3d r3 = r1.cross(r2);

                Matx33d R(r1[0], r2[0], r3[0],
                          r1[1], r2[1], r3[1],
                          r1[2], r2[2], r3[2]);
                Vec3d om;
                Rodrigues(R, om);

                // project pattern points to images
                Mat projedImgPoints;
                Matx33d Kc(gamma, 0, u0, 0, gamma, v0, 0, 0, 1);

                // reproject error
                cv::omnidir::projectPoints(objPoints, projedImgPoints, om, t, Kc, 1, Matx14d(0, 0, 0, 0), cv::noArray());
                double reprojectError = omnidir::internal::computeMeanReproErr(imgPoints, projedImgPoints);

                // if this reproject error is smaller
                if (reprojectError < miniReprojectError)
                {
                    miniReprojectError = reprojectError;
                    v_omAll[image_idx] = om;
                    v_tAll[image_idx] = t;
                    gammaAll[image_idx] = gamma;
                }
            }
        }
    }

    // filter initial results whose reproject errors are too large
    std::vector<double> reProjErrorFilter,v_gammaFilter;
    std::vector<Vec3d> omFilter, tFilter;
    double gammaFinal = 0;

    // choose median value
    size_t n = gammaAll.size() / 2;
    std::nth_element(gammaAll.begin(), gammaAll.begin()+n, gammaAll.end());
    gammaFinal = gammaAll[n];

    _K = Mat(Matx33d(gammaFinal, 0, u0, 0, gammaFinal, v0, 0, 0, 1));
    _K.convertTo(K, CV_64F);
    std::vector<int> _idx;
    // recompute reproject error using the final gamma
    for (int i = 0; i< n_img; i++)
    {
        Mat _projected;
        cv::omnidir::projectPoints(patternPoints.getMat(i), _projected, v_omAll[i], v_tAll[i], _K, 1, Matx14d(0, 0, 0, 0), cv::noArray());
        double _error = omnidir::internal::computeMeanReproErr(imagePoints.getMat(i), _projected);
        if(_error < 100)
        {
            _idx.push_back(i);
            omFilter.push_back(v_omAll[i]);
            tFilter.push_back(v_tAll[i]);
        }
    }

    if (idx.needed())
    {
        idx.create(1, (int)_idx.size(), CV_32S);
        Mat idx_m = idx.getMat();
        for (int j = 0; j < (int)idx_m.total(); j++)
        {
            idx_m.at<int>(j) = _idx[j];
        }
    }

    if(omAll.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for (int i = 0; i < (int)omFilter.size(); ++i)
        {
            omAll.getMat(i) = Mat(omFilter[i]);
            tAll.getMat(i) = Mat(tFilter[i]);
        }
    }
    else
    {
        cv::Mat(omFilter).convertTo(omAll, CV_64FC3);
        cv::Mat(tFilter).convertTo(tAll, CV_64FC3);
    }
    xi = 1;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// cv::omnidir::internal::initializeStereoCalibration

void cv::omnidir::internal::initializeStereoCalibration(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2,
    const Size& size1, const Size& size2, OutputArray om, OutputArray T, OutputArrayOfArrays omL, OutputArrayOfArrays tL, OutputArray K1, OutputArray D1, OutputArray K2, OutputArray D2,
    double &xi1, double &xi2, int flags, OutputArray idx)
{
    Mat idx1, idx2;
    Matx33d _K1, _K2;
    Matx14d _D1, _D2;
    Mat _xi1m, _xi2m;

    std::vector<Vec3d> omAllTemp1, omAllTemp2, tAllTemp1, tAllTemp2;

    omnidir::calibrate(objectPoints, imagePoints1, size1, _K1, _xi1m, _D1, omAllTemp1, tAllTemp1, flags, TermCriteria(3, 100, 1e-6), idx1);
    omnidir::calibrate(objectPoints, imagePoints2, size2, _K2, _xi2m, _D2, omAllTemp2, tAllTemp2, flags, TermCriteria(3, 100, 1e-6), idx2);

    // find the intersection idx
    Mat interIdx1, interIdx2, interOri;

    getInterset(idx1, idx2, interIdx1, interIdx2, interOri);
    if (idx.empty())
        idx.create(1, (int)interOri.total(), CV_32S);
    interOri.copyTo(idx.getMat());

    int n_inter = (int)interIdx1.total();

    std::vector<Vec3d> omAll1(n_inter), omAll2(n_inter), tAll1(n_inter), tAll2(n_inter);
    for(int i = 0; i < (int)interIdx1.total(); ++i)
    {
        omAll1[i] = omAllTemp1[interIdx1.at<int>(i)];
        tAll1[i] = tAllTemp1[interIdx1.at<int>(i)];
        omAll2[i] = omAllTemp2[interIdx2.at<int>(i)];
        tAll2[i] = tAllTemp2[interIdx2.at<int>(i)];
    }

    // initialize R,T
    Mat omEstAll(1, n_inter, CV_64FC3), tEstAll(1, n_inter, CV_64FC3);
    Mat R1, R2, T1, T2, omLR, TLR, RLR;
    for (int i = 0; i < n_inter; ++i)
    {
        Rodrigues(omAll1[i], R1);
        Rodrigues(omAll2[i], R2);
        T1 = Mat(tAll1[i]).reshape(1, 3);
        T2 = Mat(tAll2[i]).reshape(1, 3);
        RLR = R2 * R1.t();
        TLR = T2 - RLR*T1;
        Rodrigues(RLR, omLR);
        omLR.reshape(3, 1).copyTo(omEstAll.col(i));
        TLR.reshape(3, 1).copyTo(tEstAll.col(i));
    }
    Vec3d omEst = internal::findMedian3(omEstAll);
    Vec3d tEst = internal::findMedian3(tEstAll);

    Mat(omEst).copyTo(om.getMat());
    Mat(tEst).copyTo(T.getMat());

    if (omL.empty())
    {
        omL.create((int)omAll1.size(), 1, CV_64FC3);
    }
    if (tL.empty())
    {
        tL.create((int)tAll1.size(), 1, CV_64FC3);
    }

    if(omL.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for(int i = 0; i < n_inter; ++i)
        {
            omL.create(3, 1, CV_64F, i, true);
            tL.create(3, 1, CV_64F, i, true);
            omL.getMat(i) = Mat(omAll1[i]);
            tL.getMat(i) = Mat(tAll1[i]);
        }
    }
    else
    {
        cv::Mat(omAll1).convertTo(omL, CV_64FC3);
        cv::Mat(tAll1).convertTo(tL, CV_64FC3);
    }
    if (K1.empty())
    {
        K1.create(3, 3, CV_64F);
        K2.create(3, 3, CV_64F);
    }
    if (D1.empty())
    {
        D1.create(1, 4, CV_64F);
        D2.create(1, 4, CV_64F);
    }
    Mat(_K1).copyTo(K1.getMat());
    Mat(_K2).copyTo(K2.getMat());

    Mat(_D1).copyTo(D1.getMat());
    Mat(_D2).copyTo(D2.getMat());

    xi1 = _xi1m.at<double>(0);
    xi2 = _xi2m.at<double>(0);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// cv::omnidir::internal::computeJacobian

void cv::omnidir::internal::computeJacobian(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,
    InputArray parameters, Mat& JTJ_inv, Mat& JTE, int flags, double epsilon)
{
    CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3);
    CV_Assert(!imagePoints.empty() && imagePoints.type() == CV_64FC2);

    int n = (int)objectPoints.total();

    Mat JTJ = Mat::zeros(10 + 6*n, 10 + 6*n, CV_64F);
    JTJ_inv = Mat::zeros(10 + 6*n, 10 + 6*n, CV_64F);
    JTE = Mat::zeros(10 + 6*n, 1, CV_64F);

    int nPointsAll = 0;
    for (int i = 0; i < n; ++i)
    {
        nPointsAll += (int)objectPoints.getMat(i).total();
    }

    Mat J = Mat::zeros(2*nPointsAll, 10+6*n, CV_64F);
    Mat exAll = Mat::zeros(2*nPointsAll, 10+6*n, CV_64F);
    double *para = parameters.getMat().ptr<double>();
    Matx33d K(para[6*n], para[6*n+2], para[6*n+3],
        0,    para[6*n+1], para[6*n+4],
        0,    0,  1);
    Matx14d D(para[6*n+6], para[6*n+7], para[6*n+8], para[6*n+9]);
    double xi = para[6*n+5];
    for (int i = 0; i < n; i++)
    {
        Mat objPoints, imgPoints, om, T;
		objectPoints.getMat(i).copyTo(objPoints);
		imagePoints.getMat(i).copyTo(imgPoints);
		objPoints = objPoints.reshape(3, objPoints.rows*objPoints.cols);
		imgPoints = imgPoints.reshape(2, imgPoints.rows*imgPoints.cols);

        om = parameters.getMat().colRange(i*6, i*6+3);
        T = parameters.getMat().colRange(i*6+3, (i+1)*6);
        Mat imgProj, jacobian;
        omnidir::projectPoints(objPoints, imgProj, om, T, K, xi, D, jacobian);
        Mat projError = imgPoints - imgProj;

        // The intrinsic part of Jacobian
        Mat JIn(jacobian.rows, 10, CV_64F);
        Mat JEx(jacobian.rows, 6, CV_64F);

        jacobian.colRange(6, 16).copyTo(JIn);
        jacobian.colRange(0, 6).copyTo(JEx);

        JTJ(Rect(6*n, 6*n, 10, 10)) = JTJ(Rect(6*n, 6*n, 10, 10)) + JIn.t()*JIn;

        JTJ(Rect(i*6, i*6, 6, 6)) = JEx.t() * JEx;

        Mat JExTIn = JEx.t() * JIn;

        JExTIn.copyTo(JTJ(Rect(6*n, i*6, 10, 6)));

        Mat(JIn.t()*JEx).copyTo(JTJ(Rect(i*6, 6*n, 6, 10)));

        JTE(Rect(0, 6*n, 1, 10)) = JTE(Rect(0, 6*n,1, 10)) + JIn.t() * projError.reshape(1, 2*(int)projError.total());
        JTE(Rect(0, i*6, 1, 6)) = JEx.t() * projError.reshape(1, 2*(int)projError.total());

        //int nPoints = objectPoints.getMat(i).total();
        //JIn.copyTo(J(Rect(6*n, i*nPoints*2, 10, nPoints*2)));
        //JEx.copyTo(J(Rect(6*i, i*nPoints*2, 6, nPoints*2)));
        //projError.reshape(1, 2*projError.rows).copyTo(exAll.rowRange(i*2*nPoints, (i+1)*2*nPoints));
    }
    //JTJ = J.t()*J;
    //JTE = J.t()*exAll;
    std::vector<int> _idx(6*n+10, 1);
    flags2idx(flags, _idx, n);

    subMatrix(JTJ, JTJ, _idx, _idx);
    subMatrix(JTE, JTE, std::vector<int>(1, 1), _idx);
    // in case JTJ is singular
	//SVD svd(JTJ, SVD::NO_UV);
	//double cond = svd.w.at<double>(0)/svd.w.at<double>(5);

	//if (cond_JTJ.needed())
	//{
	//	cond_JTJ.create(1, 1, CV_64F);
	//	cond_JTJ.getMat().at<double>(0) = cond;
	//}

    //double epsilon = 1e-4*std::exp(cond);
    JTJ_inv = Mat(JTJ+epsilon).inv();
}

void cv::omnidir::internal::computeJacobianStereo(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2,
    InputArray parameters, Mat& JTJ_inv, Mat& JTE, int flags, double epsilon)
{
    CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3);
    CV_Assert(!imagePoints1.empty() && imagePoints1.type() == CV_64FC2);
    CV_Assert(!imagePoints2.empty() && imagePoints2.type() == CV_64FC2);
    CV_Assert((imagePoints1.total() == imagePoints2.total()) && (imagePoints1.total() == objectPoints.total()));

    // compute Jacobian matrix by naive way
    int n_img = (int)objectPoints.total();
    int n_points = (int)objectPoints.getMat(0).total();
    Mat J = Mat::zeros(4 * n_points * n_img, 20 + 6 * (n_img + 1), CV_64F);
    Mat exAll = Mat::zeros(4 * n_points * n_img, 1, CV_64F);
    double *para = parameters.getMat().ptr<double>();
    int offset1 = (n_img + 1) * 6;
    int offset2 = offset1 + 10;
    Matx33d K1(para[offset1], para[offset1+2], para[offset1+3],
        0,    para[offset1+1], para[offset1+4],
        0,    0,  1);
    Matx14d D1(para[offset1+6], para[offset1+7], para[offset1+8], para[offset1+9]);
    double xi1 = para[offset1+5];

    Matx33d K2(para[offset2], para[offset2+2], para[offset2+3],
        0,    para[offset2+1], para[offset2+4],
        0,    0,  1);
    Matx14d D2(para[offset2+6], para[offset2+7], para[offset2+8], para[offset2+9]);
    double xi2 = para[offset2+5];

    Mat om = parameters.getMat().reshape(1, 1).colRange(0, 3);
    Mat T = parameters.getMat().reshape(1, 1).colRange(3, 6);

    for (int i = 0; i < n_img; i++)
    {
        Mat objPointsi, imgPoints1i, imgPoints2i, om1, T1;
        objectPoints.getMat(i).copyTo(objPointsi);
        imagePoints1.getMat(i).copyTo(imgPoints1i);
        imagePoints2.getMat(i).copyTo(imgPoints2i);
        objPointsi = objPointsi.reshape(3, objPointsi.rows*objPointsi.cols);
        imgPoints1i = imgPoints1i.reshape(2, imgPoints1i.rows*imgPoints1i.cols);
        imgPoints2i = imgPoints2i.reshape(2, imgPoints2i.rows*imgPoints2i.cols);

        om1 = parameters.getMat().reshape(1, 1).colRange((1 + i) * 6, (1 + i) * 6 + 3);
        T1 = parameters.getMat().reshape(1, 1).colRange((1 + i) * 6 + 3, (i + 1) * 6 + 6);


        Mat imgProj1, imgProj2, jacobian1, jacobian2;

        // jacobian for left image
        cv::omnidir::projectPoints(objPointsi, imgProj1, om1, T1, K1, xi1, D1, jacobian1);
        Mat projError1 = imgPoints1i - imgProj1;
        //Mat JIn1(jacobian1.rows, 10, CV_64F);
        //Mat JEx1(jacobian1.rows, 6, CV_64F);
        jacobian1.colRange(6, 16).copyTo(J(Rect(6*(n_img+1), i*n_points*4, 10, n_points*2)));
        jacobian1.colRange(0, 6).copyTo(J(Rect(6+i*6, i*n_points*4, 6, n_points*2)));
        projError1.reshape(1, 2*n_points).copyTo(exAll.rowRange(i*4*n_points, (i*4+2)*n_points));

        //jacobian for right image
        Mat om2, T2, dom2dom1, dom2dT1, dom2dom, dom2dT, dT2dom1, dT2dT1, dT2dom, dT2dT;
        cv::omnidir::internal::compose_motion(om1, T1, om, T, om2, T2, dom2dom1, dom2dT1, dom2dom, dom2dT, dT2dom1, dT2dT1, dT2dom, dT2dT);
        cv::omnidir::projectPoints(objPointsi, imgProj2, om2, T2, K2, xi2, D2, jacobian2);
        Mat projError2 = imgPoints2i - imgProj2;
        projError2.reshape(1, 2*n_points).copyTo(exAll.rowRange((i*4+2)*n_points, (i*4+4)*n_points));
        Mat dxrdom = jacobian2.colRange(0, 3) * dom2dom + jacobian2.colRange(3, 6) * dT2dom;
        Mat dxrdT = jacobian2.colRange(0, 3) * dom2dT + jacobian2.colRange(3, 6) * dT2dT;
        Mat dxrdom1 = jacobian2.colRange(0, 3) * dom2dom1 + jacobian2.colRange(3, 6) * dT2dom1;
        Mat dxrdT1 = jacobian2.colRange(0, 3) * dom2dT1 + jacobian2.colRange(3, 6) * dT2dT1;

        dxrdom.copyTo(J(Rect(0, (i*4+2)*n_points, 3, n_points*2)));
        dxrdT.copyTo(J(Rect(3, (i*4+2)*n_points, 3, n_points*2)));
        dxrdom1.copyTo(J(Rect(6+i*6, (i*4+2)*n_points, 3, n_points*2)));
        dxrdT1.copyTo(J(Rect(6+i*6+3, (i*4+2)*n_points, 3, n_points*2)));
        jacobian2.colRange(6, 16).copyTo(J(Rect(6*(n_img+1)+10, (4*i+2)*n_points, 10, n_points*2)));
    }

    std::vector<int> _idx(6*(n_img+1)+20, 1);
    flags2idxStereo(flags, _idx, n_img);

    Mat JTJ = J.t()*J;
    JTE = J.t()*exAll;
    subMatrix(JTJ, JTJ, _idx, _idx);
    subMatrix(JTE, JTE, std::vector<int>(1, 1), _idx);

    JTJ_inv = Mat(JTJ+epsilon).inv();
}

// This function is from fisheye.cpp
void cv::omnidir::internal::compose_motion(InputArray _om1, InputArray _T1, InputArray _om2, InputArray _T2, Mat& om3, Mat& T3, Mat& dom3dom1,
    Mat& dom3dT1, Mat& dom3dom2, Mat& dom3dT2, Mat& dT3dom1, Mat& dT3dT1, Mat& dT3dom2, Mat& dT3dT2)
{
    Mat om1 = _om1.getMat();
    Mat om2 = _om2.getMat();
    Mat T1 = _T1.getMat().reshape(1, 3);
    Mat T2 = _T2.getMat().reshape(1, 3);

    //% Rotations:
    Mat R1, R2, R3, dR1dom1(9, 3, CV_64FC1), dR2dom2;
    Rodrigues(om1, R1, dR1dom1);
    Rodrigues(om2, R2, dR2dom2);
    //JRodriguesMatlab(dR1dom1, dR1dom1);
    //JRodriguesMatlab(dR2dom2, dR2dom2);
    dR1dom1 = dR1dom1.t();
    dR2dom2 = dR2dom2.t();

    R3 = R2 * R1;
    Mat dR3dR2, dR3dR1;
    //dAB(R2, R1, dR3dR2, dR3dR1);
    matMulDeriv(R2, R1, dR3dR2, dR3dR1);

    Mat dom3dR3;
    Rodrigues(R3, om3, dom3dR3);
    //JRodriguesMatlab(dom3dR3, dom3dR3);
    dom3dR3 = dom3dR3.t();
    dom3dom1 = dom3dR3 * dR3dR1 * dR1dom1;
    dom3dom2 = dom3dR3 * dR3dR2 * dR2dom2;
    dom3dT1 = Mat::zeros(3, 3, CV_64FC1);
    dom3dT2 = Mat::zeros(3, 3, CV_64FC1);

    //% Translations:
    Mat T3t = R2 * T1;
    Mat dT3tdR2, dT3tdT1;
    //dAB(R2, T1, dT3tdR2, dT3tdT1);
    matMulDeriv(R2, T1, dT3tdR2, dT3tdT1);
    Mat dT3tdom2 = dT3tdR2 * dR2dom2;
    T3 = T3t + T2;
    dT3dT1 = dT3tdT1;
    dT3dT2 = Mat::eye(3, 3, CV_64FC1);
    dT3dom2 = dT3tdom2;
    dT3dom1 = Mat::zeros(3, 3, CV_64FC1);
}

double cv::omnidir::calibrate(InputArrayOfArrays patternPoints, InputArrayOfArrays imagePoints, Size size,
    InputOutputArray K, InputOutputArray xi, InputOutputArray D, OutputArrayOfArrays omAll, OutputArrayOfArrays tAll,
    int flags, TermCriteria criteria, OutputArray idx)
{
    CV_Assert(!patternPoints.empty() && !imagePoints.empty() && patternPoints.total() == imagePoints.total());
    CV_Assert((patternPoints.type() == CV_64FC3 && imagePoints.type() == CV_64FC2) ||
        (patternPoints.type() == CV_32FC3 && imagePoints.type() == CV_32FC2));
    CV_Assert(patternPoints.getMat(0).channels() == 3 && imagePoints.getMat(0).channels() == 2);
    CV_Assert((!K.empty() && K.size() == Size(3,3)) || K.empty());
    CV_Assert((!D.empty() && D.total() == 4) || D.empty());
    CV_Assert((!xi.empty() && xi.total() == 1) || xi.empty());
    CV_Assert((!omAll.empty() && omAll.depth() == patternPoints.depth()) || omAll.empty());
    CV_Assert((!tAll.empty() && tAll.depth() == patternPoints.depth()) || tAll.empty());
    int depth = patternPoints.depth();

    std::vector<Mat> _patternPoints, _imagePoints;

    for (int i = 0; i < (int)patternPoints.total(); ++i)
    {
        _patternPoints.push_back(patternPoints.getMat(i));
        _imagePoints.push_back(imagePoints.getMat(i));
        if (depth == CV_32F)
        {
            _patternPoints[i].convertTo(_patternPoints[i], CV_64FC3);
            _imagePoints[i].convertTo(_imagePoints[i], CV_64FC2);
        }
    }

    double _xi;
    // initialization
    std::vector<Vec3d> _omAll, _tAll;
    Matx33d _K;
    Matx14d _D;
    Mat _idx;
    cv::omnidir::internal::initializeCalibration(_patternPoints, _imagePoints, size, _omAll, _tAll, _K, _xi, _idx);
    std::vector<Mat> _patternPointsTmp = _patternPoints;
    std::vector<Mat> _imagePointsTmp = _imagePoints;

    _patternPoints.clear();
    _imagePoints.clear();
    // erase
    for (int i = 0; i < (int)_idx.total(); i++)
    {
        _patternPoints.push_back(_patternPointsTmp[_idx.at<int>(i)]);
        _imagePoints.push_back(_imagePointsTmp[_idx.at<int>(i)]);
    }

    int n = (int)_patternPoints.size();
    Mat finalParam(1, 10 + 6*n, CV_64F);
    Mat currentParam(1, 10 + 6*n, CV_64F);
    cv::omnidir::internal::encodeParameters(_K, _omAll, _tAll, Mat::zeros(1,4,CV_64F), _xi, currentParam);

    // optimization
    const double alpha_smooth = 0.01;
    //const double thresh_cond = 1e6;
    double change = 1;
    for(int iter = 0; ; ++iter)
    {
        if ((criteria.type == 1 && iter >= criteria.maxCount)  ||
            (criteria.type == 2 && change <= criteria.epsilon) ||
            (criteria.type == 3 && (change <= criteria.epsilon || iter >= criteria.maxCount)))
            break;
        double alpha_smooth2 = 1 - std::pow(1 - alpha_smooth, (double)iter + 1.0);
        Mat JTJ_inv, JTError;
		double epsilon = 0.01 * std::pow(0.9, (double)iter/10);
        cv::omnidir::internal::computeJacobian(_patternPoints, _imagePoints, currentParam, JTJ_inv, JTError, flags, epsilon);

        // Gauss - Newton
        Mat G = alpha_smooth2*JTJ_inv * JTError;

        omnidir::internal::fillFixed(G, flags, n);

        finalParam = currentParam + G.t();

        change = norm(G) / norm(currentParam);

        currentParam = finalParam.clone();

        cv::omnidir::internal::decodeParameters(currentParam, _K, _omAll, _tAll, _D, _xi);
        //double repr = internal::computeMeanReproErr(_patternPoints, _imagePoints, _K, _D, _xi, _omAll, _tAll);
    }
    cv::omnidir::internal::decodeParameters(currentParam, _K, _omAll, _tAll, _D, _xi);

    //double repr = internal::computeMeanReproErr(_patternPoints, _imagePoints, _K, _D, _xi, _omAll, _tAll);

    if (omAll.needed())
    {
        omAll.create((int)_omAll.size(), 1, CV_64FC3);
    }
    if (tAll.needed())
    {
        tAll.create((int)_tAll.size(), 1, CV_64FC3);
    }
    if (omAll.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for (int i = 0; i < n; ++i)
        {
            omAll.create(3, 1, CV_64F, i, true);
            tAll.create(3, 1, CV_64F, i, true);
            Mat tmpom = Mat(_omAll[i]);
            Mat tmpt = Mat(_tAll[i]);
            tmpom.convertTo(tmpom, CV_64F);
            tmpt.convertTo(tmpt, CV_64F);
            tmpom.copyTo(omAll.getMat(i));
            tmpt.copyTo(tAll.getMat(i));
        }
    }
    else
    {
        Mat(_omAll).convertTo(omAll, CV_64FC3);
        Mat(_tAll).convertTo(tAll, CV_64FC3);
    }

    if(K.empty())
    {
        K.create(3, 3, CV_64F);
    }
    if (D.empty())
    {
        D.create(1, 4, CV_64F);
    }

    Mat(_K).convertTo(K.getMat(), K.empty()? CV_64F : K.type());
    Mat(_D).convertTo(D.getMat(), D.empty() ? CV_64F: D.type());

    if (xi.empty())
    {
        xi.create(1, 1, CV_64F);
    }
    Mat xi_m = Mat(1, 1, CV_64F);
    xi_m.at<double>(0) = _xi;
    xi_m.convertTo(xi.getMat(), xi.empty() ? CV_64F : xi.type());

    if (idx.needed())
    {
        idx.create(1, (int)_idx.total(), CV_32S);
        _idx.copyTo(idx.getMat());
    }

    Vec2d std_error;
    double rms;
    Mat errors;
    cv::omnidir::internal::estimateUncertainties(_patternPoints, _imagePoints, finalParam, errors, std_error, rms, flags);
    return rms;
}

double cv::omnidir::stereoCalibrate(InputOutputArrayOfArrays objectPoints, InputOutputArrayOfArrays imagePoints1, InputOutputArrayOfArrays imagePoints2,
    const Size& imageSize1, const Size& imageSize2, InputOutputArray K1, InputOutputArray xi1, InputOutputArray D1, InputOutputArray K2, InputOutputArray xi2,
    InputOutputArray D2, OutputArray om, OutputArray T, OutputArrayOfArrays omL, OutputArrayOfArrays tL, int flags, TermCriteria criteria, OutputArray idx)
{
    CV_Assert(!objectPoints.empty() && (objectPoints.type() == CV_64FC3 || objectPoints.type() == CV_32FC3));
    CV_Assert(!imagePoints1.empty() && (imagePoints1.type() == CV_64FC2 || imagePoints1.type() == CV_32FC2));
    CV_Assert(!imagePoints2.empty() && (imagePoints2.type() == CV_64FC2 || imagePoints2.type() == CV_32FC2));

    CV_Assert(((flags & CALIB_USE_GUESS) && !K1.empty() && !D1.empty() && !K2.empty() && !D2.empty()) || !(flags & CALIB_USE_GUESS));

    int depth = objectPoints.depth();

    std::vector<Mat> _objectPoints, _imagePoints1, _imagePoints2,
					_objectPointsFilt, _imagePoints1Filt, _imagePoints2Filt;
    for (int i = 0; i < (int)objectPoints.total(); ++i)
    {
        _objectPoints.push_back(objectPoints.getMat(i));
        _imagePoints1.push_back(imagePoints1.getMat(i));
        _imagePoints2.push_back(imagePoints2.getMat(i));
        if (depth == CV_32F)
        {
            _objectPoints[i].convertTo(_objectPoints[i], CV_64FC3);
            _imagePoints1[i].convertTo(_imagePoints1[i], CV_64FC2);
            _imagePoints2[i].convertTo(_imagePoints2[i], CV_64FC2);
        }
    }

    Matx33d _K1, _K2;
    Matx14d _D1, _D2;

    double _xi1, _xi2;

    std::vector<Vec3d> _omL, _TL;
    Vec3d _om, _T;

    // initializaition
    Mat _idx;
    internal::initializeStereoCalibration(_objectPoints, _imagePoints1, _imagePoints2, imageSize1, imageSize2, _om, _T, _omL, _TL, _K1, _D1, _K2, _D2, _xi1, _xi2, flags, _idx);
    if(idx.needed())
    {
        idx.create(1, (int)_idx.total(), CV_32S);
        _idx.copyTo(idx.getMat());
    }
	for (int i = 0; i < (int)_idx.total(); ++i)
	{
		_objectPointsFilt.push_back(_objectPoints[_idx.at<int>(i)]);
		_imagePoints1Filt.push_back(_imagePoints1[_idx.at<int>(i)]);
		_imagePoints2Filt.push_back(_imagePoints2[_idx.at<int>(i)]);
	}

    int n = (int)_objectPointsFilt.size();
    Mat finalParam(1, 10 + 6*n, CV_64F);
    Mat currentParam(1, 10 + 6*n, CV_64F);

    //double repr1 = internal::computeMeanReproErrStereo(_objectPoints, _imagePoints1, _imagePoints2, _K1, _K2, _D1, _D2, _xi1, _xi2, _om,
    //    _T, _omL, _TL);
    cv::omnidir::internal::encodeParametersStereo(_K1, _K2, _om, _T, _omL, _TL, _D1, _D2, _xi1, _xi2, currentParam);

    // optimization
    const double alpha_smooth = 0.01;
    double change = 1;
    for(int iter = 0; ; ++iter)
    {
        if ((criteria.type == 1 && iter >= criteria.maxCount)  ||
            (criteria.type == 2 && change <= criteria.epsilon) ||
            (criteria.type == 3 && (change <= criteria.epsilon || iter >= criteria.maxCount)))
            break;
        double alpha_smooth2 = 1 - std::pow(1 - alpha_smooth, (double)iter + 1.0);
        Mat JTJ_inv, JTError;
		double epsilon = 0.01 * std::pow(0.9, (double)iter/10);

        cv::omnidir::internal::computeJacobianStereo(_objectPointsFilt, _imagePoints1Filt, _imagePoints2Filt, currentParam,
			JTJ_inv, JTError, flags, epsilon);

        // Gauss - Newton
        Mat G = alpha_smooth2*JTJ_inv * JTError;

        omnidir::internal::fillFixedStereo(G, flags, n);

        finalParam = currentParam + G.t();

        change = norm(G) / norm(currentParam);

        currentParam = finalParam.clone();
        cv::omnidir::internal::decodeParametersStereo(currentParam, _K1, _K2, _om, _T, _omL, _TL, _D1, _D2, _xi1, _xi2);
        //double repr = internal::computeMeanReproErrStereo(_objectPoints, _imagePoints1, _imagePoints2, _K1, _K2, _D1, _D2, _xi1, _xi2, _om,
        //    _T, _omL, _TL);

    }
    cv::omnidir::internal::decodeParametersStereo(finalParam, _K1, _K2, _om, _T, _omL, _TL, _D1, _D2, _xi1, _xi2);
    //double repr = internal::computeMeanReproErrStereo(_objectPoints, _imagePoints1, _imagePoints2, _K1, _K2, _D1, _D2, _xi1, _xi2, _om,
    //    _T, _omL, _TL);

    if (K1.empty())
    {
        K1.create(3, 3, CV_64F);
        D1.create(1, 4, CV_64F);
        K2.create(3, 3, CV_64F);
        D2.create(1, 4, CV_64F);
    }
    if (om.empty())
    {
        om.create(3, 1, CV_64F);
        T.create(3, 1, CV_64F);
    }
    if (omL.empty())
    {
        omL.create(1, n, CV_64FC3);
        tL.create(1, n, CV_64FC3);
    }

    Mat(_K1).convertTo(K1.getMat(), K1.empty() ? CV_64F : K1.type());
    Mat(_D1).convertTo(D1.getMat(), D1.empty() ? CV_64F : D1.type());
    Mat(_K2).convertTo(K2.getMat(), K2.empty() ? CV_64F : K2.type());
    Mat(_D2).convertTo(D2.getMat(), D2.empty() ? CV_64F : D2.type());

    Mat(_om).convertTo(om.getMat(), om.empty() ? CV_64F: om.type());
    Mat(_T).convertTo(T.getMat(), T.empty() ? CV_64F: T.type());

    if (omL.needed())
    {
        omL.create((int)_omL.size(), 1, CV_64FC3);
    }
    if (tL.needed())
    {
        tL.create((int)_TL.size(), 1, CV_64FC3);
    }

    if (omL.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for (int i = 0; i < n; ++i)
        {
            omL.create(3, 1, CV_64F, i, true);
            tL.create(3, 1, CV_64F, i, true);
            Mat(_omL[i]).copyTo(omL.getMat(i));
            Mat(_TL[i]).copyTo(tL.getMat(i));
        }
    }
    else
    {
        Mat(_omL).convertTo(omL, omL.empty() ? CV_64FC3 : omL.type());
        Mat(_TL).convertTo(tL, tL.empty() ? CV_64FC3 : tL.type());
    }

    Mat xi1_m = Mat(1, 1, CV_64F),
        xi2_m = Mat(1, 1, CV_64F);
    xi1_m.at<double>(0) = _xi1;
    xi2_m.at<double>(0) = _xi2;

    if (xi1.empty())
    {
        xi1.create(1, 1, CV_64F);
    }
    if (xi2.empty())
    {
        xi2.create(1, 1, CV_64F);
    }
    xi1_m.convertTo(xi1, xi1.empty() ? CV_64F : xi1.type());
    xi2_m.convertTo(xi2, xi2.empty() ? CV_64F : xi2.type());

    // compute uncertainty
    Vec2d std_error;
    double rms;
    Mat errors;

    cv::omnidir::internal::estimateUncertaintiesStereo(_objectPointsFilt, _imagePoints1Filt, _imagePoints2Filt,
		finalParam, errors, std_error, rms, flags);
    return rms;
}

void cv::omnidir::stereoReconstruct(InputArray image1, InputArray image2, InputArray K1, InputArray D1,
    InputArray xi1, InputArray K2, InputArray D2, InputArray xi2, InputArray R, InputArray T, int flag,
    int numDisparities, int SADWindowSize, OutputArray disparity, OutputArray image1Rec, OutputArray image2Rec,
    const Size& newSize, InputArray Knew, OutputArray pointCloud, int pointType)
{
    CV_Assert(!K1.empty() && K1.size() == Size(3,3) && (K1.type() == CV_64F || K1.type() == CV_32F));
    CV_Assert(!K2.empty() && K2.size() == Size(3,3) && (K2.type() == CV_64F || K2.type() == CV_32F));
    CV_Assert(!D1.empty() && D1.total() == 4 && (D1.type() == CV_64F || D1.type() == CV_32F));
    CV_Assert(!D2.empty() && D2.total() == 4 && (D2.type() == CV_64F || D2.type() == CV_32F));
    CV_Assert(!R.empty() && (R.size() == Size(3,3) || R.total() == 3) && (R.type() == CV_64F || R.type() == CV_32F));
    CV_Assert(!T.empty() && T.total() == 3 && (T.type() == CV_64F || T.type() == CV_32F));
    CV_Assert(!image1.empty() && (image1.type() == CV_8U || image1.type() == CV_8UC3));
    CV_Assert(!image2.empty() && (image2.type() == CV_8U || image2.type() == CV_8UC3));
    CV_Assert(flag == omnidir::RECTIFY_LONGLATI || flag == omnidir::RECTIFY_PERSPECTIVE);

    Mat _K1, _D1, _K2, _D2, _R, _T;

    K1.getMat().convertTo(_K1, CV_64F);
    K2.getMat().convertTo(_K2, CV_64F);
    D1.getMat().convertTo(_D1, CV_64F);
    D2.getMat().convertTo(_D2, CV_64F);
    T.getMat().reshape(1, 3).convertTo(_T, CV_64F);

    if (R.size() == Size(3, 3))
    {
        R.getMat().convertTo(_R, CV_64F);
    }
    else if (R.total() == 3)
    {
        Rodrigues(R.getMat(), _R);
        _R.convertTo(_R, CV_64F);
    }
    // stereo rectify so that stereo matching can be applied in one line
    Mat R1, R2;
    stereoRectify(_R, _T, R1, R2);
    Mat undis1, undis2;
    Matx33d _Knew = Matx33d(_K1);
    if (!Knew.empty())
    {
        Knew.getMat().convertTo(_Knew, CV_64F);
    }

    undistortImage(image1.getMat(), undis1, _K1, _D1, xi1, flag, _Knew, newSize, R1);
    undistortImage(image2.getMat(), undis2, _K2, _D2, xi2, flag, _Knew, newSize, R2);

    undis1.copyTo(image1Rec);
    undis2.copyTo(image2Rec);

    // stereo matching by semi-global
    Mat _disMap;
    int channel = image1.channels();

    //cv::StereoSGBM matching(0, numDisparities, SADWindowSize, 8*channel*SADWindowSize*SADWindowSize, 32*channel*SADWindowSize*SADWindowSize);
    //matching(undis1, undis2, _depthMap);
	Ptr<StereoSGBM> sgbm = StereoSGBM::create(0, numDisparities, SADWindowSize, 8 * channel*SADWindowSize*SADWindowSize, 32 * channel*SADWindowSize*SADWindowSize);

	sgbm->compute(undis1, undis2, _disMap);

    // some regions of image1 is black, the corresponding regions of disparity map is also invalid.
    Mat realDis;
	_disMap.convertTo(_disMap, CV_32F);
    Mat(_disMap/16.0f).convertTo(realDis, CV_32F);

    Mat grayImg, binaryImg, idx;
    if (undis1.channels() == 3)
    {
        cvtColor(undis1, grayImg, COLOR_RGB2GRAY);
    }
    else
    {
        grayImg = undis1;
    }

    binaryImg = (grayImg <= 0);
    findNonZero(binaryImg, idx);
    for (int i = 0; i < (int)idx.total(); ++i)
    {
        Vec2i _idx = idx.at<Vec2i>(i);
        realDis.at<float>(_idx[1], _idx[0]) = 0.0f;
    }

    disparity.create(realDis.size(), realDis.type());
    realDis.copyTo(disparity.getMat());

    std::vector<Vec3f> _pointCloud;
    std::vector<Vec6f> _pointCloudColor;
    double baseline = cv::norm(T);
    double f = _Knew(0, 0);
    Matx33d K_inv = _Knew.inv();

    std::vector<Mat> rgb;
    if (undis1.channels() == 3)
    {
        split(undis1, rgb);
    }

    if (pointCloud.needed())
    {
        for (int i = 0; i < newSize.width; ++i)
        {
            for(int j = 0; j < newSize.height; ++j)
            {
                Vec3f point;
                Vec6f pointColor;
                if (realDis.at<float>(j, i) > 15)
                {
                    float depth = float(baseline * f /realDis.at<float>(j, i));
                    // for RECTIFY_PERSPECTIVE, (x,y) are image plane points,
                    // for RECTIFY_LONGLATI, (x,y) are (theta, phi) angles
                    float x = float(K_inv(0,0) * i + K_inv(0,1) * j + K_inv(0,2));
                    float y = float(K_inv(1,0) * i + K_inv(1,1) * j + K_inv(1,2));
                    if (flag == omnidir::RECTIFY_LONGLATI)
                    {
                        point = Vec3f((float)-std::cos(x), (float)(-std::sin(x)*std::cos(y)), (float)(std::sin(x)*std::sin(y))) * depth;
                    }
                    else if(flag == omnidir::RECTIFY_PERSPECTIVE)
                    {
                        point = Vec3f(float(x), float(y), 1.0f) * depth;
                    }
                    if (pointType == XYZ)
                    {
                        _pointCloud.push_back(point);
                    }
                    else if (pointType == XYZRGB)
                    {
                        pointColor[0] = point[0];
                        pointColor[1] = point[1];
                        pointColor[2] = point[2];

                        if (undis1.channels() == 1)
                        {
                            pointColor[3] = float(undis1.at<uchar>(j, i));
                            pointColor[4] = pointColor[3];
                            pointColor[5] = pointColor[3];
                        }
                        else if (undis1.channels() == 3)
                        {
                            pointColor[3] = rgb[0].at<uchar>(j, i);
                            pointColor[4] = rgb[1].at<uchar>(j, i);
                            pointColor[5] = rgb[2].at<uchar>(j, i);
                        }
                        _pointCloudColor.push_back(pointColor);
                    }
                }
            }
        }

        if (pointType == XYZ)
        {
            Mat(_pointCloud).convertTo(pointCloud, CV_MAKE_TYPE(CV_32F, 3));
        }
        else if (pointType == XYZRGB)
        {
            Mat(_pointCloudColor).convertTo(pointCloud, CV_MAKE_TYPE(CV_32F, 6));
        }
    }
}

void cv::omnidir::internal::encodeParameters(InputArray K, InputArrayOfArrays omAll, InputArrayOfArrays tAll, InputArray distoaration, double xi, OutputArray parameters)
{
    CV_Assert(K.type() == CV_64F && K.size() == Size(3,3));
    CV_Assert(distoaration.total() == 4 && distoaration.type() == CV_64F);
    int n = (int)omAll.total();
    Mat _omAll = omAll.getMat(), _tAll = tAll.getMat();

    Matx33d _K = K.getMat();
    Vec4d _D = (Vec4d)distoaration.getMat();
    parameters.create(1, 10+6*n,CV_64F);
    Mat _params = parameters.getMat();
    for (int i = 0; i < n; i++)
    {
        Mat(_omAll.at<Vec3d>(i)).reshape(1, 1).copyTo(_params.colRange(i*6, i*6+3));
        Mat(_tAll.at<Vec3d>(i)).reshape(1, 1).copyTo(_params.colRange(i*6+3, (i+1)*6));
    }

    _params.at<double>(0, 6*n) = _K(0,0);
    _params.at<double>(0, 6*n+1) = _K(1,1);
    _params.at<double>(0, 6*n+2) = _K(0,1);
    _params.at<double>(0, 6*n+3) = _K(0,2);
    _params.at<double>(0, 6*n+4) = _K(1,2);
    _params.at<double>(0, 6*n+5) = xi;
    _params.at<double>(0, 6*n+6) = _D[0];
    _params.at<double>(0, 6*n+7) = _D[1];
    _params.at<double>(0, 6*n+8) = _D[2];
    _params.at<double>(0, 6*n+9) = _D[3];
}

void cv::omnidir::internal::encodeParametersStereo(InputArray K1, InputArray K2, InputArray om, InputArray T, InputArrayOfArrays omL, InputArrayOfArrays tL,
    InputArray D1, InputArray D2, double xi1, double xi2, OutputArray parameters)
{
    CV_Assert(!K1.empty() && K1.type() == CV_64F && K1.size() == Size(3,3));
    CV_Assert(!K2.empty() && K2.type() == CV_64F && K2.size() == Size(3,3));
    CV_Assert(!om.empty() && om.type() == CV_64F && om.total() == 3);
    CV_Assert(!T.empty() && T.type() == CV_64F && T.total() == 3);
    CV_Assert(omL.total() == tL.total() && omL.type() == CV_64FC3 && tL.type() == CV_64FC3);
    CV_Assert(D1.type() == CV_64F && D1.total() == 4 && D2.type() == CV_64F && D2.total() == 4);

    int n = (int)omL.total();
    // om, T, omL, tL, intrinsic left, intrinsic right
    parameters.create(1, 20 + 6 * (n + 1), CV_64F);

    Mat _params = parameters.getMat();

    om.getMat().reshape(1, 1).copyTo(_params.colRange(0, 3));
    T.getMat().reshape(1, 1).copyTo(_params.colRange(3, 6));
    for(int i = 0; i < n; ++i)
    {
        Mat(omL.getMat().at<Vec3d>(i)).reshape(1, 1).copyTo(_params.colRange(6 + i*6, 6 + i*6 + 3));
        Mat(tL.getMat().at<Vec3d>(i)).reshape(1, 1).copyTo(_params.colRange(6 + i*6 + 3, 6 + i*6 + 6));
    }

    Matx33d _K1 = K1.getMat();
    Matx33d _K2 = K2.getMat();
    Vec4d _D1 = D1.getMat();
    Vec4d _D2 = D2.getMat();
    _params.at<double>(0, 6*(n+1)) = _K1(0,0);
    _params.at<double>(0, 6*(n+1)+1) = _K1(1,1);
    _params.at<double>(0, 6*(n+1)+2) = _K1(0,1);
    _params.at<double>(0, 6*(n+1)+3) = _K1(0,2);
    _params.at<double>(0, 6*(n+1)+4) = _K1(1,2);
    _params.at<double>(0, 6*(n+1)+5) = xi1;
    _params.at<double>(0, 6*(n+1)+6) = _D1[0];
    _params.at<double>(0, 6*(n+1)+7) = _D1[1];
    _params.at<double>(0, 6*(n+1)+8) = _D1[2];
    _params.at<double>(0, 6*(n+1)+9) = _D1[3];

    _params.at<double>(0, 6*(n+1)+10) = _K2(0,0);
    _params.at<double>(0, 6*(n+1)+11) = _K2(1,1);
    _params.at<double>(0, 6*(n+1)+12) = _K2(0,1);
    _params.at<double>(0, 6*(n+1)+13) = _K2(0,2);
    _params.at<double>(0, 6*(n+1)+14) = _K2(1,2);
    _params.at<double>(0, 6*(n+1)+15) = xi2;
    _params.at<double>(0, 6*(n+1)+16) = _D2[0];
    _params.at<double>(0, 6*(n+1)+17) = _D2[1];
    _params.at<double>(0, 6*(n+1)+18) = _D2[2];
    _params.at<double>(0, 6*(n+1)+19) = _D2[3];
}


 void cv::omnidir::internal::decodeParameters(InputArray parameters, OutputArray K, OutputArrayOfArrays omAll, OutputArrayOfArrays tAll, OutputArray distoration, double& xi)
 {
    if(K.empty())
        K.create(3,3,CV_64F);
    Matx33d _K;
    int n = (int)(parameters.total()-10)/6;
    if(omAll.empty())
        omAll.create(1, n, CV_64FC3);
    if(tAll.empty())
        tAll.create(1, n, CV_64FC3);
    if(distoration.empty())
        distoration.create(1, 4, CV_64F);
    Matx14d _D = distoration.getMat();
    Mat param = parameters.getMat();
    double *para = param.ptr<double>();
    _K = Matx33d(para[6*n], para[6*n+2], para[6*n+3],
        0,    para[6*n+1], para[6*n+4],
        0,    0,  1);
    _D  = Matx14d(para[6*n+6], para[6*n+7], para[6*n+8], para[6*n+9]);
    xi = para[6*n+5];
    std::vector<Vec3d> _omAll(n), _tAll(n);
    for (int i = 0; i < n; i++)
    {
        _omAll[i] = Vec3d(param.colRange(i*6, i*6+3));
        _tAll[i] = Vec3d(param.colRange(i*6+3, i*6+6));
    }
    Mat(_D).convertTo(distoration, CV_64F);
    Mat(_K).convertTo(K, CV_64F);

    if (omAll.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for (int i = 0; i < n; ++i)
        {
            Mat(_omAll[i]).copyTo(omAll.getMat(i));
            Mat(_tAll[i]).copyTo(tAll.getMat(i));
        }
    }
    else
    {
        Mat(_omAll).convertTo(omAll, CV_64FC3);
        Mat(_tAll).convertTo(tAll, CV_64FC3);
    }
}

 void cv::omnidir::internal::decodeParametersStereo(InputArray parameters, OutputArray K1, OutputArray K2, OutputArray om, OutputArray T, OutputArrayOfArrays omL,
     OutputArrayOfArrays tL, OutputArray D1, OutputArray D2, double& xi1, double& xi2)
 {
    if(K1.empty())
        K1.create(3, 3, CV_64F);
    if(K2.empty())
        K2.create(3, 3, CV_64F);
    if(om.empty())
        om.create(3, 1, CV_64F);
    if(T.empty())
        T.create(3, 1, CV_64F);

    int n = ((int)parameters.total() - 20) / 6 - 1;

    if(omL.empty())
        omL.create(1, n, CV_64FC3);
    if(tL.empty())
        tL.create(1, n, CV_64FC3);
    if(D1.empty())
        D1.create(1, 4, CV_64F);
    if(D2.empty())
        D2.create(1, 4, CV_64F);

    Mat param = parameters.getMat().reshape(1, 1);
    param.colRange(0, 3).reshape(1, 3).copyTo(om.getMat());
    param.colRange(3, 6).reshape(1, 3).copyTo(T.getMat());
    std::vector<Vec3d> _omL, _tL;

    for(int i = 0; i < n; i++)
    {
        _omL.push_back(Vec3d(param.colRange(6 + i*6, 6 + i*6 + 3)));
        _tL.push_back(Vec3d(param.colRange(6 + i*6 + 3, 6 + i*6 + 6)));
    }

    double* para = param.ptr<double>();
    int offset1 = (n + 1)*6;
    Matx33d _K1(para[offset1], para[offset1+2], para[offset1+3],
                0,      para[offset1+1],     para[offset1+4],
                0,          0,                  1);
    xi1 = para[offset1+5];
    Matx14d _D1(para[offset1+6], para[offset1+7], para[offset1+8], para[offset1+9]);

    int offset2 = (n + 1)*6 + 10;
    Matx33d _K2(para[offset2], para[offset2+2], para[offset2+3],
                0,      para[offset2+1],     para[offset2+4],
                0,          0,                  1);
    xi2 = para[offset2+5];
    Matx14d _D2(para[offset2+6], para[offset2+7], para[offset2+8], para[offset2+9]);

    Mat(_K1).convertTo(K1, CV_64F);
    Mat(_D1).convertTo(D1, CV_64F);
    Mat(_K2).convertTo(K2, CV_64F);
    Mat(_D2).convertTo(D2, CV_64F);
    if(omL.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for(int i = 0; i < n; ++i)
        {
            Mat(_omL[i]).copyTo(omL.getMat(i));
            Mat(_tL[i]).copyTo(tL.getMat(i));
        }
    }
    else
    {
        Mat(_omL).convertTo(omL, CV_64FC3);
        Mat(_tL).convertTo(tL, CV_64FC3);
    }
 }

void cv::omnidir::internal::estimateUncertainties(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, InputArray parameters,
    Mat& errors, Vec2d& std_error, double& rms, int flags)
{
    CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3);
    CV_Assert(!imagePoints.empty() && imagePoints.type() == CV_64FC2);
    CV_Assert(!parameters.empty() && parameters.type() == CV_64F);

    int n = (int) objectPoints.total();
    // assume every image has the same number of objectpoints
    int nPointsAll = 0;
    for (int i = 0; i < n; ++i)
    {
        nPointsAll += (int)objectPoints.getMat(i).total();
    }

    Mat reprojError = Mat(nPointsAll, 1, CV_64FC2);

    double* para = parameters.getMat().ptr<double>();
    Matx33d K(para[6*n], para[6*n+2], para[6*n+3],
              0,    para[6*n+1], para[6*n+4],
              0,    0,  1);
    Matx14d D(para[6*n+6], para[6*n+7], para[6*n+8], para[6*n+9]);
    double xi = para[6*n+5];
    int nPointsAccu = 0;

    for(int i=0; i < n; ++i)
    {
		Mat imgPoints, objPoints;
		imagePoints.getMat(i).copyTo(imgPoints);
		objectPoints.getMat(i).copyTo(objPoints);
		imgPoints = imgPoints.reshape(2, imgPoints.rows*imgPoints.cols);
		objPoints = objPoints.reshape(3, objPoints.rows*objPoints.cols);

        Mat om = parameters.getMat().colRange(i*6, i*6+3);
        Mat T = parameters.getMat().colRange(i*6+3, (i+1)*6);

        Mat x;
        omnidir::projectPoints(objPoints, x, om, T, K, xi, D, cv::noArray());

        Mat errorx = (imgPoints - x);

        //reprojError.rowRange(errorx.rows*i, errorx.rows*(i+1)) = errorx.clone();
        errorx.copyTo(reprojError.rowRange(nPointsAccu, nPointsAccu + (int)errorx.total()));
        nPointsAccu += (int)errorx.total();
    }

    meanStdDev(reprojError, noArray(), std_error);
    std_error *= sqrt((double)reprojError.total()/((double)reprojError.total() - 1.0));

    Mat sigma_x;
    meanStdDev(reprojError.reshape(1,1), noArray(), sigma_x);
    sigma_x *= sqrt(2.0*(double)reprojError.total()/(2.0*(double)reprojError.total() - 1.0));
    double s = sigma_x.at<double>(0);

    Mat _JTJ_inv, _JTE;
    computeJacobian(objectPoints, imagePoints, parameters, _JTJ_inv, _JTE, flags, 0.0);
    sqrt(_JTJ_inv, _JTJ_inv);

    errors = 3 * s * _JTJ_inv.diag();

    checkFixed(errors, flags, n);

    rms = 0;
    const Vec2d* ptr_ex = reprojError.ptr<Vec2d>();
    for (int i = 0; i < (int)reprojError.total(); i++)
    {
        rms += ptr_ex[i][0] * ptr_ex[i][0] + ptr_ex[i][1] * ptr_ex[i][1];
    }

    rms /= (double)reprojError.total();
    rms = sqrt(rms);
}

// estimateUncertaintiesStereo
void cv::omnidir::internal::estimateUncertaintiesStereo(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2,
    InputArray parameters, Mat& errors, Vec2d& std_error, double& rms, int flags)
{
    CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3);
    CV_Assert(!imagePoints1.empty() && imagePoints1.type() == CV_64FC2 && imagePoints1.total() == objectPoints.total());
    CV_Assert(!imagePoints2.empty() && imagePoints2.type() == CV_64FC2 && imagePoints1.total() == imagePoints2.total());
    int n_img = (int)objectPoints.total();
    CV_Assert((int)parameters.total() == (6*(n_img+1)+20));

    Mat _K1, _K2, _D1, _D2;
    Vec3d _om, _T;
    std::vector<Vec3d> _omL(n_img), _tL(n_img);
    Mat _parameters = parameters.getMat().reshape(1, 1);
    double _xi1, _xi2;
    internal::decodeParametersStereo(_parameters, _K1, _K2, _om, _T, _omL, _tL, _D1, _D2, _xi1, _xi2);

    int n_points = (int)objectPoints.getMat(0).total();
    Mat reprojErrorAll = Mat::zeros(2*n_points*n_img, 1, CV_64FC2);

    // error for left image
    for (int i = 0; i < n_img; ++i)
    {
        Mat objPointsi, imgPointsi;
        objectPoints.getMat(i).copyTo(objPointsi);
        imagePoints1.getMat(i).copyTo(imgPointsi);
        objPointsi = objPointsi.reshape(3, objPointsi.rows*objPointsi.cols);
        imgPointsi = imgPointsi.reshape(2, imgPointsi.rows*imgPointsi.cols);

        Mat x;
        omnidir::projectPoints(objPointsi, x, _omL[i], _tL[i], _K1, _xi1, _D1, cv::noArray());

        Mat errorx = imgPointsi - x;

        errorx.copyTo(reprojErrorAll.rowRange(i*2*n_points, (i*2+1)*n_points));
    }
    // error for right image
    for (int i = 0; i < n_img; ++i)
    {
        Mat objPointsi, imgPointsi;
        objectPoints.getMat(i).copyTo(objPointsi);
        imagePoints2.getMat(i).copyTo(imgPointsi);
        objPointsi = objPointsi.reshape(3, objPointsi.rows*objPointsi.cols);
        imgPointsi = imgPointsi.reshape(2, imgPointsi.rows*imgPointsi.cols);

        Mat x;
        Mat _R, _R2, _R1, _T2, _T1, _om2;
        Rodrigues(_om, _R);
        Rodrigues(_omL[i], _R1);
        _T1 = Mat(_tL[i]);
        _R2 = _R * _R1;
        _T2 = _R * _T1 + Mat(_T);
        Rodrigues(_R2, _om2);

        omnidir::projectPoints(objPointsi, x, _om2, _T2, _K2, _xi2, _D2, cv::noArray());

        Mat errorx = imgPointsi - x;

        errorx.copyTo(reprojErrorAll.rowRange((i*2+1)*n_points, (i+1)*2*n_points));
    }

    meanStdDev(reprojErrorAll, cv::noArray(), std_error);
    std_error *= sqrt((double)reprojErrorAll.total()/((double)reprojErrorAll.total() - 1.0));

    Mat sigma_x;
    meanStdDev(reprojErrorAll.reshape(1,1), noArray(), sigma_x);
    sigma_x *= sqrt(2.0*(double)reprojErrorAll.total()/(2.0*(double)reprojErrorAll.total() - 1.0));
    double s = sigma_x.at<double>(0);

    Mat _JTJ_inv, _JTE;
    computeJacobianStereo(objectPoints, imagePoints1, imagePoints2, _parameters, _JTJ_inv, _JTE, flags, 0.0);
    cv::sqrt(_JTJ_inv, _JTJ_inv);

    errors = 3 * s * _JTJ_inv.diag();

    rms = 0;

    const Vec2d* ptr_ex = reprojErrorAll.ptr<Vec2d>();
    for (int i = 0; i < (int)reprojErrorAll.total(); i++)
    {
        rms += ptr_ex[i][0] * ptr_ex[i][0] + ptr_ex[i][1] * ptr_ex[i][1];
    }
    rms /= (double)reprojErrorAll.total();
    rms = sqrt(rms);
}

//
double cv::omnidir::internal::computeMeanReproErr(InputArrayOfArrays imagePoints, InputArrayOfArrays proImagePoints)
{
    CV_Assert(!imagePoints.empty() && imagePoints.type()==CV_64FC2);
    CV_Assert(!proImagePoints.empty() && proImagePoints.type() == CV_64FC2);
    CV_Assert(imagePoints.total() == proImagePoints.total());

    int n = (int)imagePoints.total();
    double reprojError = 0;
    int totalPoints = 0;
    if (imagePoints.kind() == _InputArray::STD_VECTOR_MAT)
    {
        for (int i = 0; i < n; i++)
        {
			Mat x, proj_x;
			imagePoints.getMat(i).copyTo(x);
			proImagePoints.getMat(i).copyTo(proj_x);
			Mat errorI = x.reshape(2, x.rows*x.cols) - proj_x.reshape(2, proj_x.rows*proj_x.cols);
            //Mat errorI = imagePoints.getMat(i) - proImagePoints.getMat(i);
            totalPoints += (int)errorI.total();
            Vec2d* ptr_err = errorI.ptr<Vec2d>();
            for (int j = 0; j < (int)errorI.total(); j++)
            {
                reprojError += sqrt(ptr_err[j][0]*ptr_err[j][0] + ptr_err[j][1]*ptr_err[j][1]);
            }
        }
    }
    else
    {
		Mat x, proj_x;
		imagePoints.getMat().copyTo(x);
		proImagePoints.getMat().copyTo(proj_x);
		Mat errorI = x.reshape(2, x.rows*x.cols) - proj_x.reshape(2, proj_x.rows*proj_x.cols);
        //Mat errorI = imagePoints.getMat() - proImagePoints.getMat();
        totalPoints += (int)errorI.total();
        Vec2d* ptr_err = errorI.ptr<Vec2d>();
        for (int j = 0; j < (int)errorI.total(); j++)
        {
            reprojError += sqrt(ptr_err[j][0]*ptr_err[j][0] + ptr_err[j][1]*ptr_err[j][1]);
        }
    }
    double meanReprojError = reprojError / totalPoints;
    return meanReprojError;
}

double cv::omnidir::internal::computeMeanReproErr(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, InputArray K, InputArray D, double xi, InputArrayOfArrays omAll,
    InputArrayOfArrays tAll)
{
    CV_Assert(objectPoints.total() == imagePoints.total());
    CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3);
    CV_Assert(!imagePoints.empty() && imagePoints.type() == CV_64FC2);
    std::vector<Mat> proImagePoints;
    int n = (int)objectPoints.total();
    Mat _omAll = omAll.getMat();
    Mat _tAll = tAll.getMat();
    for(int i = 0; i < n; ++i)
    {
        Mat imgPoint;
        //cv::omnidir::projectPoints(objetPoints.getMat(i), imgPoint, omAll.getMat(i), tAll.getMat(i), K.getMat(), xi, D.getMat(), noArray());
        cv::omnidir::projectPoints(objectPoints.getMat(i), imgPoint, _omAll.at<Vec3d>(i), _tAll.at<Vec3d>(i), K.getMat(), xi, D.getMat(), noArray());
        proImagePoints.push_back(imgPoint);
    }

    return internal::computeMeanReproErr(imagePoints, proImagePoints);
}

double cv::omnidir::internal::computeMeanReproErrStereo(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputArray K1, InputArray K2,
    InputArray D1, InputArray D2, double xi1, double xi2, InputArray om, InputArray T, InputArrayOfArrays omL, InputArrayOfArrays TL)
{
    CV_Assert(objectPoints.total() == imagePoints1.total() && imagePoints1.total() == imagePoints2.total());
    CV_Assert(!objectPoints.empty() && objectPoints.type() == CV_64FC3);
    CV_Assert(!imagePoints1.empty() && imagePoints1.type() == CV_64FC2);
    CV_Assert(!imagePoints2.empty() && imagePoints2.type() == CV_64FC2);

    std::vector<Mat> proImagePoints1, proImagePoints2;
    int n = (int)objectPoints.total();
    Mat _omL = omL.getMat(), _TL = TL.getMat();
    Mat _om = om.getMat(), _R, _T = T.getMat();
    Rodrigues(_om, _R);
    Mat _K1 = K1.getMat(), _K2 = K2.getMat();
    Mat _D1 = D1.getMat(), _D2 = D2.getMat();

    // reprojection error for left image
    for (int i = 0; i < n; ++i)
    {
        Mat imgPoints;
        cv::omnidir::projectPoints(objectPoints.getMat(i), imgPoints, _omL.at<Vec3d>(i), _TL.at<Vec3d>(i), _K1, xi1, _D1, cv::noArray());
        proImagePoints1.push_back(imgPoints);
    }

    // reprojection error for right image
    for (int i = 0; i < n; ++i)
    {
        Mat imgPoints;
        Mat _omRi,_RRi,_TRi,_RLi, _TLi;
        Rodrigues(_omL.at<Vec3d>(i), _RLi);
        _TLi = Mat(_TL.at<Vec3d>(i)).reshape(1, 3);
        _RRi = _R * _RLi;
        _TRi = _R * _TLi + _T;
        Rodrigues(_RRi, _omRi);
        cv::omnidir::projectPoints(objectPoints.getMat(i), imgPoints, _omRi, _TRi, _K2, xi2, _D2, cv::noArray());
        proImagePoints2.push_back(imgPoints);
    }
    double reProErr1 = internal::computeMeanReproErr(imagePoints1, proImagePoints1);
    double reProErr2 = internal::computeMeanReproErr(imagePoints2, proImagePoints2);

    double reProErr = (reProErr1 + reProErr2) / 2.0;
    //double reProErr = reProErr1*reProErr1 + reProErr2* reProErr2;
    return reProErr;
}

void cv::omnidir::internal::checkFixed(Mat& G, int flags, int n)
{
    int _flags = flags;
    if(_flags >= omnidir::CALIB_FIX_CENTER)
    {
        G.at<double>(6*n+3) = 0;
        G.at<double>(6*n+4) = 0;
        _flags -= omnidir::CALIB_FIX_CENTER;
    }
    if(_flags >= omnidir::CALIB_FIX_GAMMA)
    {
        G.at<double>(6*n) = 0;
        G.at<double>(6*n+1) = 0;
        _flags -= omnidir::CALIB_FIX_GAMMA;
    }
    if(_flags >= omnidir::CALIB_FIX_XI)
    {
        G.at<double>(6*n + 5) = 0;
        _flags -= omnidir::CALIB_FIX_XI;
    }
    if(_flags >= omnidir::CALIB_FIX_P2)
    {
        G.at<double>(6*n + 9) = 0;
        _flags -= omnidir::CALIB_FIX_P2;
    }
    if(_flags >= omnidir::CALIB_FIX_P1)
    {
        G.at<double>(6*n + 8) = 0;
        _flags -= omnidir::CALIB_FIX_P1;
    }
    if(_flags >= omnidir::CALIB_FIX_K2)
    {
        G.at<double>(6*n + 7) = 0;
        _flags -= omnidir::CALIB_FIX_K2;
    }
    if(_flags >= omnidir::CALIB_FIX_K1)
    {
        G.at<double>(6*n + 6) = 0;
        _flags -= omnidir::CALIB_FIX_K1;
    }
    if(_flags >= omnidir::CALIB_FIX_SKEW)
    {
        G.at<double>(6*n + 2) = 0;
    }
}

// This function is from fisheye.cpp
void cv::omnidir::internal::subMatrix(const Mat& src, Mat& dst, const std::vector<int>& cols, const std::vector<int>& rows)
{
    CV_Assert(src.type() == CV_64FC1);

    int nonzeros_cols = cv::countNonZero(cols);
    Mat tmp(src.rows, nonzeros_cols, CV_64FC1);

    for (int i = 0, j = 0; i < (int)cols.size(); i++)
    {
        if (cols[i])
        {
            src.col(i).copyTo(tmp.col(j++));
        }
    }

    int nonzeros_rows  = cv::countNonZero(rows);
    Mat tmp1(nonzeros_rows, nonzeros_cols, CV_64FC1);
    for (int i = 0, j = 0; i < (int)rows.size(); i++)
    {
        if (rows[i])
        {
            tmp.row(i).copyTo(tmp1.row(j++));
        }
    }

    dst = tmp1.clone();
}

void cv::omnidir::internal::flags2idx(int flags, std::vector<int>& idx, int n)
{
    idx = std::vector<int>(6*n+10,1);
    int _flags = flags;
    if(_flags >= omnidir::CALIB_FIX_CENTER)
    {
        idx[6*n+3] = 0;
        idx[6*n+4] = 0;
        _flags -= omnidir::CALIB_FIX_CENTER;
    }
    if(_flags >= omnidir::CALIB_FIX_GAMMA)
    {
        idx[6*n] = 0;
        idx[6*n+1] = 0;
        _flags -= omnidir::CALIB_FIX_GAMMA;
    }
    if(_flags >= omnidir::CALIB_FIX_XI)
    {
        idx[6*n + 5] = 0;
        _flags -= omnidir::CALIB_FIX_XI;
    }
    if(_flags >= omnidir::CALIB_FIX_P2)
    {
        idx[6*n + 9] = 0;
        _flags -= omnidir::CALIB_FIX_P2;
    }
    if(_flags >= omnidir::CALIB_FIX_P1)
    {
        idx[6*n + 8] = 0;
        _flags -= omnidir::CALIB_FIX_P1;
    }
    if(_flags >= omnidir::CALIB_FIX_K2)
    {
        idx[6*n + 7] = 0;
        _flags -= omnidir::CALIB_FIX_K2;
    }
    if(_flags >= omnidir::CALIB_FIX_K1)
    {
        idx[6*n + 6] = 0;
        _flags -= omnidir::CALIB_FIX_K1;
    }
    if(_flags >= omnidir::CALIB_FIX_SKEW)
    {
        idx[6*n + 2] = 0;
    }
}

void cv::omnidir::internal::flags2idxStereo(int flags, std::vector<int>& idx, int n)
{
    idx = std::vector<int>(6*(n+1)+20, 1);
    int _flags = flags;
    int offset1 = 6*(n+1);
    int offset2 = offset1 + 10;
    if(_flags >= omnidir::CALIB_FIX_CENTER)
    {
        idx[offset1+3] = 0;
        idx[offset1+4] = 0;
        idx[offset2+3] = 0;
        idx[offset2+4] = 0;
        _flags -= omnidir::CALIB_FIX_CENTER;
    }
    if(_flags >= omnidir::CALIB_FIX_GAMMA)
    {
        idx[offset1] = 0;
        idx[offset1+1] = 0;
        idx[offset2] = 0;
        idx[offset2+1] = 0;
        _flags -= omnidir::CALIB_FIX_GAMMA;
    }
    if(_flags >= omnidir::CALIB_FIX_XI)
    {
        idx[offset1 + 5] = 0;
        idx[offset2 + 5] = 0;
        _flags -= omnidir::CALIB_FIX_XI;
    }
    if(_flags >= omnidir::CALIB_FIX_P2)
    {
        idx[offset1 + 9] = 0;
        idx[offset2 + 9] = 0;
        _flags -= omnidir::CALIB_FIX_P2;
    }
    if(_flags >= omnidir::CALIB_FIX_P1)
    {
        idx[offset1 + 8] = 0;
        idx[offset2 + 8] = 0;
        _flags -= omnidir::CALIB_FIX_P1;
    }
    if(_flags >= omnidir::CALIB_FIX_K2)
    {
        idx[offset1 + 7] = 0;
        idx[offset2 + 7] = 0;
        _flags -= omnidir::CALIB_FIX_K2;
    }
    if(_flags >= omnidir::CALIB_FIX_K1)
    {
        idx[offset1 + 6] = 0;
        idx[offset2 + 6] = 0;
        _flags -= omnidir::CALIB_FIX_K1;
    }
    if(_flags >= omnidir::CALIB_FIX_SKEW)
    {
        idx[offset1 + 2] = 0;
        idx[offset2 + 2] = 0;
    }
}

// fill in zerso for fixed parameters
void cv::omnidir::internal::fillFixed(Mat&G, int flags, int n)
{
    Mat tmp = G.clone();
    std::vector<int> idx(6*n + 10, 1);
    flags2idx(flags, idx, n);
    G.release();
    G.create(6*n +10, 1, CV_64F);
    G = cv::Mat::zeros(6*n +10, 1, CV_64F);
    for (int i = 0,j=0; i < (int)idx.size(); i++)
    {
        if (idx[i])
        {
            G.at<double>(i) = tmp.at<double>(j++);
        }
    }
}

void cv::omnidir::internal::fillFixedStereo(Mat& G, int flags, int n)
{
    Mat tmp = G.clone();
    std::vector<int> idx(6*(n+1)+20, 1);
    flags2idxStereo(flags, idx, n);
    G.release();
    G.create(6 * (n+1) + 20, 1, CV_64F);
    G = cv::Mat::zeros(6* (n + 1) + 20, 1, CV_64F);
    for (int i = 0,j=0; i < (int)idx.size(); i++)
    {
        if (idx[i])
        {
            G.at<double>(i) = tmp.at<double>(j++);
        }
    }
}

double cv::omnidir::internal::findMedian(const Mat& row)
{
    CV_Assert(!row.empty() && row.rows == 1 && row.type() == CV_64F);
    Mat tmp = row.clone();
    cv::sort(tmp, tmp, 0);
    if((int)tmp.total()%2 == 0)
        return tmp.at<double>((int)tmp.total() / 2);
    else
        return 0.5 * (tmp.at<double>((int)tmp.total() / 2) + tmp.at<double>((int)tmp.total()/2 - 1));
}

cv::Vec3d cv::omnidir::internal::findMedian3(InputArray mat)
{
    CV_Assert(mat.depth() == CV_64F && mat.getMat().rows == 1);
    Mat M = Mat(mat.getMat().t()).reshape(1).t();
    return Vec3d(findMedian(M.row(0)), findMedian(M.row(1)), findMedian(M.row(2)));
}

void cv::omnidir::stereoRectify(InputArray R, InputArray T, OutputArray R1, OutputArray R2)
{
    CV_Assert((R.size() == Size(3,3) || R.total() == 3) && (R.depth() == CV_32F || R.depth() == CV_64F));
    CV_Assert(T.total() == 3  && (T.depth() == CV_32F || T.depth() == CV_64F));

    Mat _R, _T;
    if (R.size() == Size(3, 3))
    {
        R.getMat().convertTo(_R, CV_64F);
    }
    else if (R.total() == 3)
    {
        Rodrigues(R.getMat(), _R);
        _R.convertTo(_R, CV_64F);
    }

    T.getMat().reshape(1, 3).convertTo(_T, CV_64F);

    R1.create(3, 3, CV_64F);
    Mat _R1 = R1.getMat();
    R2.create(3, 3, CV_64F);
    Mat _R2 = R2.getMat();

    Mat R21 = _R.t();
    Mat T21 = -_R.t() * _T;

    Mat e1, e2, e3;
    e1 = T21.t() / norm(T21);
    e2 = Mat(Matx13d(T21.at<double>(1)*-1, T21.at<double>(0), 0.0));
    e2 = e2 / norm(e2);
    e3 = e1.cross(e2);
    e3 = e3 / norm(e3);
    e1.copyTo(_R1.row(0));
    e2.copyTo(_R1.row(1));
    e3.copyTo(_R1.row(2));
    _R2 = R21 * _R1;

}

void cv::omnidir::internal::getInterset(InputArray idx1, InputArray idx2, OutputArray inter1, OutputArray inter2,
    OutputArray inter_ori)
{
    Mat _idx1 = idx1.getMat();
    Mat _idx2 = idx2.getMat();

    int n1 = (int)idx1.total();
    int n2 = (int)idx2.total();

    std::vector<int> _inter1, _inter2, _inter_ori;
    for (int i = 0; i < n1; ++i)
    {
        for (int j = 0; j < n2; ++j)
        {
            if(_idx1.at<int>(i) == _idx2.at<int>(j))
            {
                _inter1.push_back(i);
                _inter2.push_back(j);
                _inter_ori.push_back(_idx1.at<int>(i));
            }
        }
    }

    inter1.create(1, (int)_inter1.size(), CV_32S);
    inter2.create(1, (int)_inter2.size(), CV_32S);
    inter_ori.create(1, (int)_inter_ori.size(), CV_32S);

    for (int i = 0; i < (int)_inter1.size(); ++i)
    {
        inter1.getMat().at<int>(i) = _inter1[i];
    }
    for (int i = 0; i < (int)_inter2.size(); ++i)
    {
        inter2.getMat().at<int>(i) = _inter2[i];
    }
    for (int i = 0; i < (int)_inter_ori.size(); ++i)
    {
        inter_ori.getMat().at<int>(i) = _inter_ori[i];
    }
}