/*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) 2000, Intel Corporation, all rights reserved.
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// 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*/
//#include <math.h>

#include "precomp.hpp"
#include "opencl_kernels_video.hpp"

namespace cv
{

/*!
 The class implements the following algorithm:
 "Efficient Adaptive Density Estimation per Image Pixel for the Task of Background Subtraction"
 Z.Zivkovic, F. van der Heijden
 Pattern Recognition Letters, vol. 27, no. 7, pages 773-780, 2006
 http://www.zoranz.net/Publications/zivkovicPRL2006.pdf
*/

// default parameters of gaussian background detection algorithm
static const int defaultHistory2 = 500; // Learning rate; alpha = 1/defaultHistory2
static const int defaultNsamples = 7; // number of samples saved in memory
static const float defaultDist2Threshold = 20.0f*20.0f;//threshold on distance from the sample

// additional parameters
static const unsigned char defaultnShadowDetection2 = (unsigned char)127; // value to use in the segmentation mask for shadows, set 0 not to do shadow detection
static const float defaultfTau = 0.5f; // Tau - shadow threshold, see the paper for explanation

class BackgroundSubtractorKNNImpl : public BackgroundSubtractorKNN
{
public:
    //! the default constructor
    BackgroundSubtractorKNNImpl()
    {
    frameSize = Size(0,0);
    frameType = 0;
    nframes = 0;
    history = defaultHistory2;

    //set parameters
    // N - the number of samples stored in memory per model
    nN = defaultNsamples;

    //kNN - k nearest neighbour - number on NN for detecting background - default K=[0.1*nN]
    nkNN=MAX(1,cvRound(0.1*nN*3+0.40));

    //Tb - Threshold Tb*kernelwidth
    fTb = defaultDist2Threshold;

    // Shadow detection
    bShadowDetection = 1;//turn on
    nShadowDetection =  defaultnShadowDetection2;
    fTau = defaultfTau;// Tau - shadow threshold
    name_ = "BackgroundSubtractor.KNN";
    nLongCounter = 0;
    nMidCounter = 0;
    nShortCounter = 0;
#ifdef HAVE_OPENCL
    opencl_ON = true;
#endif
    }
    //! the full constructor that takes the length of the history,
    // the number of gaussian mixtures, the background ratio parameter and the noise strength
    BackgroundSubtractorKNNImpl(int _history,  float _dist2Threshold, bool _bShadowDetection=true)
    {
    frameSize = Size(0,0);
    frameType = 0;

    nframes = 0;
    history = _history > 0 ? _history : defaultHistory2;

    //set parameters
    // N - the number of samples stored in memory per model
    nN = defaultNsamples;
    //kNN - k nearest neighbour - number on NN for detcting background - default K=[0.1*nN]
    nkNN=MAX(1,cvRound(0.1*nN*3+0.40));

    //Tb - Threshold Tb*kernelwidth
    fTb = _dist2Threshold>0? _dist2Threshold : defaultDist2Threshold;

    bShadowDetection = _bShadowDetection;
    nShadowDetection =  defaultnShadowDetection2;
    fTau = defaultfTau;
    name_ = "BackgroundSubtractor.KNN";
    nLongCounter = 0;
    nMidCounter = 0;
    nShortCounter = 0;
#ifdef HAVE_OPENCL
    opencl_ON = true;
#endif
    }
    //! the destructor
    ~BackgroundSubtractorKNNImpl() {}
    //! the update operator
    void apply(InputArray image, OutputArray fgmask, double learningRate=-1);

    //! computes a background image which are the mean of all background gaussians
    virtual void getBackgroundImage(OutputArray backgroundImage) const;

    //! re-initialization method
    void initialize(Size _frameSize, int _frameType)
    {
        frameSize = _frameSize;
        frameType = _frameType;
        nframes = 0;

        int nchannels = CV_MAT_CN(frameType);
        CV_Assert( nchannels <= CV_CN_MAX );

        // Reserve memory for the model
        int size=frameSize.height*frameSize.width;
        //Reset counters
        nShortCounter = 0;
        nMidCounter = 0;
        nLongCounter = 0;

#ifdef HAVE_OPENCL
        if (ocl::isOpenCLActivated() && opencl_ON)
        {
            create_ocl_apply_kernel();

            kernel_getBg.create("getBackgroundImage2_kernel", ocl::video::bgfg_knn_oclsrc, format( "-D CN=%d -D NSAMPLES=%d", nchannels, nN));

            if (kernel_apply.empty() || kernel_getBg.empty())
                opencl_ON = false;
        }
        else opencl_ON = false;

        if (opencl_ON)
        {
            u_flag.create(frameSize.height * nN * 3, frameSize.width, CV_8UC1);
            u_flag.setTo(Scalar::all(0));

            if (nchannels==3)
                nchannels=4;
            u_sample.create(frameSize.height * nN * 3, frameSize.width, CV_32FC(nchannels));
            u_sample.setTo(Scalar::all(0));

            u_aModelIndexShort.create(frameSize.height, frameSize.width, CV_8UC1);
            u_aModelIndexShort.setTo(Scalar::all(0));
            u_aModelIndexMid.create(frameSize.height, frameSize.width, CV_8UC1);
            u_aModelIndexMid.setTo(Scalar::all(0));
            u_aModelIndexLong.create(frameSize.height, frameSize.width, CV_8UC1);
            u_aModelIndexLong.setTo(Scalar::all(0));

            u_nNextShortUpdate.create(frameSize.height, frameSize.width, CV_8UC1);
            u_nNextShortUpdate.setTo(Scalar::all(0));
            u_nNextMidUpdate.create(frameSize.height, frameSize.width, CV_8UC1);
            u_nNextMidUpdate.setTo(Scalar::all(0));
            u_nNextLongUpdate.create(frameSize.height, frameSize.width, CV_8UC1);
            u_nNextLongUpdate.setTo(Scalar::all(0));
        }
        else
#endif
        {
            // for each sample of 3 speed pixel models each pixel bg model we store ...
            // values + flag (nchannels+1 values)
            bgmodel.create( 1,(nN * 3) * (nchannels+1)* size,CV_8U);
            bgmodel = Scalar::all(0);

            //index through the three circular lists
            aModelIndexShort.create(1,size,CV_8U);
            aModelIndexMid.create(1,size,CV_8U);
            aModelIndexLong.create(1,size,CV_8U);
            //when to update next
            nNextShortUpdate.create(1,size,CV_8U);
            nNextMidUpdate.create(1,size,CV_8U);
            nNextLongUpdate.create(1,size,CV_8U);

            aModelIndexShort = Scalar::all(0);//random? //((m_nN)*rand())/(RAND_MAX+1);//0...m_nN-1
            aModelIndexMid = Scalar::all(0);
            aModelIndexLong = Scalar::all(0);
            nNextShortUpdate = Scalar::all(0);
            nNextMidUpdate = Scalar::all(0);
            nNextLongUpdate = Scalar::all(0);
        }
    }

    virtual int getHistory() const { return history; }
    virtual void setHistory(int _nframes) { history = _nframes; }

    virtual int getNSamples() const { return nN; }
    virtual void setNSamples(int _nN) { nN = _nN; }//needs reinitialization!

    virtual int getkNNSamples() const { return nkNN; }
    virtual void setkNNSamples(int _nkNN) { nkNN = _nkNN; }

    virtual double getDist2Threshold() const { return fTb; }
    virtual void setDist2Threshold(double _dist2Threshold) { fTb = (float)_dist2Threshold; }

    virtual bool getDetectShadows() const { return bShadowDetection; }
    virtual void setDetectShadows(bool detectshadows)
    {
        if ((bShadowDetection && detectshadows) || (!bShadowDetection && !detectshadows))
            return;
        bShadowDetection = detectshadows;
#ifdef HAVE_OPENCL
        if (!kernel_apply.empty())
        {
            create_ocl_apply_kernel();
            CV_Assert( !kernel_apply.empty() );
        }
#endif
    }

    virtual int getShadowValue() const { return nShadowDetection; }
    virtual void setShadowValue(int value) { nShadowDetection = (uchar)value; }

    virtual double getShadowThreshold() const { return fTau; }
    virtual void setShadowThreshold(double value) { fTau = (float)value; }

    virtual void write(FileStorage& fs) const
    {
        writeFormat(fs);
        fs << "name" << name_
        << "history" << history
        << "nsamples" << nN
        << "nKNN" << nkNN
        << "dist2Threshold" << fTb
        << "detectShadows" << (int)bShadowDetection
        << "shadowValue" << (int)nShadowDetection
        << "shadowThreshold" << fTau;
    }

    virtual void read(const FileNode& fn)
    {
        CV_Assert( (String)fn["name"] == name_ );
        history = (int)fn["history"];
        nN = (int)fn["nsamples"];
        nkNN = (int)fn["nKNN"];
        fTb = (float)fn["dist2Threshold"];
        bShadowDetection = (int)fn["detectShadows"] != 0;
        nShadowDetection = saturate_cast<uchar>((int)fn["shadowValue"]);
        fTau = (float)fn["shadowThreshold"];
    }

protected:
    Size frameSize;
    int frameType;
    int nframes;
    /////////////////////////
    //very important parameters - things you will change
    ////////////////////////
    int history;
    //alpha=1/history - speed of update - if the time interval you want to average over is T
    //set alpha=1/history. It is also useful at start to make T slowly increase
    //from 1 until the desired T
    float fTb;
    //Tb - threshold on the squared distance from the sample used to decide if it is well described
    //by the background model or not. A typical value could be 2 sigma
    //and that is Tb=2*2*10*10 =400; where we take typical pixel level sigma=10

    /////////////////////////
    //less important parameters - things you might change but be careful
    ////////////////////////
    int nN;//totlal number of samples
    int nkNN;//number on NN for detcting background - default K=[0.1*nN]

    //shadow detection parameters
    bool bShadowDetection;//default 1 - do shadow detection
    unsigned char nShadowDetection;//do shadow detection - insert this value as the detection result - 127 default value
    float fTau;
    // Tau - shadow threshold. The shadow is detected if the pixel is darker
    //version of the background. Tau is a threshold on how much darker the shadow can be.
    //Tau= 0.5 means that if pixel is more than 2 times darker then it is not shadow
    //See: Prati,Mikic,Trivedi,Cucchiara,"Detecting Moving Shadows...",IEEE PAMI,2003.

    //model data
    int nLongCounter;//circular counter
    int nMidCounter;
    int nShortCounter;
    Mat bgmodel; // model data pixel values
    Mat aModelIndexShort;// index into the models
    Mat aModelIndexMid;
    Mat aModelIndexLong;
    Mat nNextShortUpdate;//random update points per model
    Mat nNextMidUpdate;
    Mat nNextLongUpdate;

#ifdef HAVE_OPENCL
    mutable bool opencl_ON;

    UMat u_flag;
    UMat u_sample;
    UMat u_aModelIndexShort;
    UMat u_aModelIndexMid;
    UMat u_aModelIndexLong;
    UMat u_nNextShortUpdate;
    UMat u_nNextMidUpdate;
    UMat u_nNextLongUpdate;

    mutable ocl::Kernel kernel_apply;
    mutable ocl::Kernel kernel_getBg;
#endif

    String name_;

#ifdef HAVE_OPENCL
    bool ocl_getBackgroundImage(OutputArray backgroundImage) const;
    bool ocl_apply(InputArray _image, OutputArray _fgmask, double learningRate=-1);
    void create_ocl_apply_kernel();
#endif
};

CV_INLINE void
        _cvUpdatePixelBackgroundNP(int x_idx, const uchar* data, int nchannels, int m_nN,
        uchar* m_aModel,
        uchar* m_nNextLongUpdate,
        uchar* m_nNextMidUpdate,
        uchar* m_nNextShortUpdate,
        uchar* m_aModelIndexLong,
        uchar* m_aModelIndexMid,
        uchar* m_aModelIndexShort,
        int m_nLongCounter,
        int m_nMidCounter,
        int m_nShortCounter,
        uchar include
        )
{
    // hold the offset
    int ndata=1+nchannels;
    long offsetLong =  ndata * (m_aModelIndexLong[x_idx] + m_nN * 2);
    long offsetMid =   ndata * (m_aModelIndexMid[x_idx]  + m_nN * 1);
    long offsetShort = ndata * (m_aModelIndexShort[x_idx]);

    // Long update?
    if (m_nNextLongUpdate[x_idx] == m_nLongCounter)
    {
        // add the oldest pixel from Mid to the list of values (for each color)
        memcpy(&m_aModel[offsetLong],&m_aModel[offsetMid],ndata*sizeof(unsigned char));
        // increase the index
        m_aModelIndexLong[x_idx] = (m_aModelIndexLong[x_idx] >= (m_nN-1)) ? 0 : (m_aModelIndexLong[x_idx] + 1);
    };

    // Mid update?
    if (m_nNextMidUpdate[x_idx] == m_nMidCounter)
    {
        // add this pixel to the list of values (for each color)
        memcpy(&m_aModel[offsetMid],&m_aModel[offsetShort],ndata*sizeof(unsigned char));
        // increase the index
        m_aModelIndexMid[x_idx] = (m_aModelIndexMid[x_idx] >= (m_nN-1)) ? 0 : (m_aModelIndexMid[x_idx] + 1);
    };

    // Short update?
    if (m_nNextShortUpdate[x_idx] == m_nShortCounter)
    {
        // add this pixel to the list of values (for each color)
        memcpy(&m_aModel[offsetShort],data,nchannels*sizeof(unsigned char));
        //set the include flag
        m_aModel[offsetShort+nchannels]=include;
        // increase the index
        m_aModelIndexShort[x_idx] = (m_aModelIndexShort[x_idx] >= (m_nN-1)) ? 0 : (m_aModelIndexShort[x_idx] + 1);
    };
}

CV_INLINE int
        _cvCheckPixelBackgroundNP(const uchar* data, int nchannels,
        int m_nN,
        uchar* m_aModel,
        float m_fTb,
        int m_nkNN,
        float tau,
        bool m_bShadowDetection,
        uchar& include)
{
    int Pbf = 0; // the total probability that this pixel is background
    int Pb = 0; //background model probability
    float dData[CV_CN_MAX];

    //uchar& include=data[nchannels];
    include=0;//do we include this pixel into background model?

    int ndata=nchannels+1;
    // now increase the probability for each pixel
    for (int n = 0; n < m_nN*3; n++)
    {
        uchar* mean_m = &m_aModel[n*ndata];

        //calculate difference and distance
        float dist2;

        if( nchannels == 3 )
        {
            dData[0] = (float)mean_m[0] - data[0];
            dData[1] = (float)mean_m[1] - data[1];
            dData[2] = (float)mean_m[2] - data[2];
            dist2 = dData[0]*dData[0] + dData[1]*dData[1] + dData[2]*dData[2];
        }
        else
        {
            dist2 = 0.f;
            for( int c = 0; c < nchannels; c++ )
            {
                dData[c] = (float)mean_m[c] - data[c];
                dist2 += dData[c]*dData[c];
            }
        }

        if (dist2<m_fTb)
        {
            Pbf++;//all
            //background only
            //if(m_aModel[subPosPixel + nchannels])//indicator
            if(mean_m[nchannels])//indicator
            {
                Pb++;
                if (Pb >= m_nkNN)//Tb
                {
                    include=1;//include
                    return 1;//background ->exit
                };
            }
        };
    };

    //include?
    if (Pbf>=m_nkNN)//m_nTbf)
    {
        include=1;
    }

    int Ps = 0; // the total probability that this pixel is background shadow
    // Detected as moving object, perform shadow detection
    if (m_bShadowDetection)
    {
        for (int n = 0; n < m_nN*3; n++)
        {
            //long subPosPixel = posPixel + n*ndata;
            uchar* mean_m = &m_aModel[n*ndata];

            if(mean_m[nchannels])//check only background
            {
                float numerator = 0.0f;
                float denominator = 0.0f;
                for( int c = 0; c < nchannels; c++ )
                {
                    numerator   += (float)data[c] * mean_m[c];
                    denominator += (float)mean_m[c] * mean_m[c];
                }

                // no division by zero allowed
                if( denominator == 0 )
                    return 0;

                // if tau < a < 1 then also check the color distortion
                if( numerator <= denominator && numerator >= tau*denominator )
                {
                    float a = numerator / denominator;
                    float dist2a = 0.0f;

                    for( int c = 0; c < nchannels; c++ )
                    {
                        float dD= a*mean_m[c] - data[c];
                        dist2a += dD*dD;
                    }

                    if (dist2a<m_fTb*a*a)
                    {
                        Ps++;
                        if (Ps >= m_nkNN)//shadow
                            return 2;
                    };
                };
            };
        };
    }
    return 0;
}

class KNNInvoker : public ParallelLoopBody
{
public:
    KNNInvoker(const Mat& _src, Mat& _dst,
               uchar* _bgmodel,
               uchar* _nNextLongUpdate,
               uchar* _nNextMidUpdate,
               uchar* _nNextShortUpdate,
               uchar* _aModelIndexLong,
               uchar* _aModelIndexMid,
               uchar* _aModelIndexShort,
               int _nLongCounter,
               int _nMidCounter,
               int _nShortCounter,
               int _nN,
               float _fTb,
               int _nkNN,
               float _fTau,
               bool _bShadowDetection,
               uchar _nShadowDetection)
    {
        src = &_src;
        dst = &_dst;
        m_aModel0 = _bgmodel;
        m_nNextLongUpdate0 = _nNextLongUpdate;
        m_nNextMidUpdate0 = _nNextMidUpdate;
        m_nNextShortUpdate0 = _nNextShortUpdate;
        m_aModelIndexLong0 = _aModelIndexLong;
        m_aModelIndexMid0 = _aModelIndexMid;
        m_aModelIndexShort0 = _aModelIndexShort;
        m_nLongCounter = _nLongCounter;
        m_nMidCounter = _nMidCounter;
        m_nShortCounter = _nShortCounter;
        m_nN = _nN;
        m_fTb = _fTb;
        m_fTau = _fTau;
        m_nkNN = _nkNN;
        m_bShadowDetection = _bShadowDetection;
        m_nShadowDetection = _nShadowDetection;
    }

    void operator()(const Range& range) const
    {
        int y0 = range.start, y1 = range.end;
        int ncols = src->cols, nchannels = src->channels();
        int ndata=nchannels+1;

        for ( int y = y0; y < y1; y++ )
        {
            const uchar* data = src->ptr(y);
            uchar* m_aModel = m_aModel0 + ncols*m_nN*3*ndata*y;
            uchar* m_nNextLongUpdate = m_nNextLongUpdate0 + ncols*y;
            uchar* m_nNextMidUpdate = m_nNextMidUpdate0 + ncols*y;
            uchar* m_nNextShortUpdate = m_nNextShortUpdate0 + ncols*y;
            uchar* m_aModelIndexLong = m_aModelIndexLong0 + ncols*y;
            uchar* m_aModelIndexMid = m_aModelIndexMid0 + ncols*y;
            uchar* m_aModelIndexShort = m_aModelIndexShort0 + ncols*y;
            uchar* mask = dst->ptr(y);

            for ( int x = 0; x < ncols; x++ )
            {

                //update model+ background subtract
                uchar include=0;
                int result= _cvCheckPixelBackgroundNP(data, nchannels,
                        m_nN, m_aModel, m_fTb,m_nkNN, m_fTau,m_bShadowDetection,include);

                _cvUpdatePixelBackgroundNP(x,data,nchannels,
                        m_nN, m_aModel,
                        m_nNextLongUpdate,
                        m_nNextMidUpdate,
                        m_nNextShortUpdate,
                        m_aModelIndexLong,
                        m_aModelIndexMid,
                        m_aModelIndexShort,
                        m_nLongCounter,
                        m_nMidCounter,
                        m_nShortCounter,
                        include
                        );
                switch (result)
                {
                    case 0:
                        //foreground
                        mask[x] = 255;
                        break;
                    case 1:
                        //background
                        mask[x] = 0;
                        break;
                    case 2:
                        //shadow
                        mask[x] = m_nShadowDetection;
                        break;
                }
                data += nchannels;
                m_aModel += m_nN*3*ndata;
            }
        }
    }

    const Mat* src;
    Mat* dst;
    uchar* m_aModel0;
    uchar* m_nNextLongUpdate0;
    uchar* m_nNextMidUpdate0;
    uchar* m_nNextShortUpdate0;
    uchar* m_aModelIndexLong0;
    uchar* m_aModelIndexMid0;
    uchar* m_aModelIndexShort0;
    int m_nLongCounter;
    int m_nMidCounter;
    int m_nShortCounter;
    int m_nN;
    float m_fTb;
    float m_fTau;
    int m_nkNN;
    bool m_bShadowDetection;
    uchar m_nShadowDetection;
};

#ifdef HAVE_OPENCL
bool BackgroundSubtractorKNNImpl::ocl_apply(InputArray _image, OutputArray _fgmask, double learningRate)
{
    bool needToInitialize = nframes == 0 || learningRate >= 1 || _image.size() != frameSize || _image.type() != frameType;

    if( needToInitialize )
        initialize(_image.size(), _image.type());

    ++nframes;
    learningRate = learningRate >= 0 && nframes > 1 ? learningRate : 1./std::min( 2*nframes, history );
    CV_Assert(learningRate >= 0);

    _fgmask.create(_image.size(), CV_8U);
    UMat fgmask = _fgmask.getUMat();

    UMat frame = _image.getUMat();

    //recalculate update rates - in case alpha is changed
    // calculate update parameters (using alpha)
    int Kshort,Kmid,Klong;
    //approximate exponential learning curve
    Kshort=(int)(log(0.7)/log(1-learningRate))+1;//Kshort
    Kmid=(int)(log(0.4)/log(1-learningRate))-Kshort+1;//Kmid
    Klong=(int)(log(0.1)/log(1-learningRate))-Kshort-Kmid+1;//Klong

    //refresh rates
    int nShortUpdate = (Kshort/nN)+1;
    int nMidUpdate = (Kmid/nN)+1;
    int nLongUpdate = (Klong/nN)+1;

    int idxArg = 0;
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::ReadOnly(frame));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadOnly(u_nNextLongUpdate));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadOnly(u_nNextMidUpdate));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadOnly(u_nNextShortUpdate));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadWrite(u_aModelIndexLong));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadWrite(u_aModelIndexMid));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadWrite(u_aModelIndexShort));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadWrite(u_flag));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::PtrReadWrite(u_sample));
    idxArg = kernel_apply.set(idxArg, ocl::KernelArg::WriteOnlyNoSize(fgmask));

    idxArg = kernel_apply.set(idxArg, nLongCounter);
    idxArg = kernel_apply.set(idxArg, nMidCounter);
    idxArg = kernel_apply.set(idxArg, nShortCounter);
    idxArg = kernel_apply.set(idxArg, fTb);
    idxArg = kernel_apply.set(idxArg, nkNN);
    idxArg = kernel_apply.set(idxArg, fTau);
    if (bShadowDetection)
        kernel_apply.set(idxArg, nShadowDetection);

    size_t globalsize[2] = {(size_t)frame.cols, (size_t)frame.rows};
    if(!kernel_apply.run(2, globalsize, NULL, true))
        return false;

    nShortCounter++;//0,1,...,nShortUpdate-1
    nMidCounter++;
    nLongCounter++;
    if (nShortCounter >= nShortUpdate)
    {
        nShortCounter = 0;
        randu(u_nNextShortUpdate, Scalar::all(0),  Scalar::all(nShortUpdate));
    }
    if (nMidCounter >= nMidUpdate)
    {
        nMidCounter = 0;
        randu(u_nNextMidUpdate, Scalar::all(0),  Scalar::all(nMidUpdate));
    }
    if (nLongCounter >= nLongUpdate)
    {
        nLongCounter = 0;
        randu(u_nNextLongUpdate, Scalar::all(0),  Scalar::all(nLongUpdate));
    }
    return true;
}

bool BackgroundSubtractorKNNImpl::ocl_getBackgroundImage(OutputArray _backgroundImage) const
{
    _backgroundImage.create(frameSize, frameType);
    UMat dst = _backgroundImage.getUMat();

    int idxArg = 0;
    idxArg = kernel_getBg.set(idxArg, ocl::KernelArg::PtrReadOnly(u_flag));
    idxArg = kernel_getBg.set(idxArg, ocl::KernelArg::PtrReadOnly(u_sample));
    idxArg = kernel_getBg.set(idxArg, ocl::KernelArg::WriteOnly(dst));

    size_t globalsize[2] = {(size_t)dst.cols, (size_t)dst.rows};

    return kernel_getBg.run(2, globalsize, NULL, false);
}

void BackgroundSubtractorKNNImpl::create_ocl_apply_kernel()
{
    int nchannels = CV_MAT_CN(frameType);
    String opts = format("-D CN=%d -D NSAMPLES=%d%s", nchannels, nN, bShadowDetection ? " -D SHADOW_DETECT" : "");
    kernel_apply.create("knn_kernel", ocl::video::bgfg_knn_oclsrc, opts);
}

#endif

void BackgroundSubtractorKNNImpl::apply(InputArray _image, OutputArray _fgmask, double learningRate)
{
    CV_INSTRUMENT_REGION()

#ifdef HAVE_OPENCL
    if (opencl_ON)
    {
#ifndef __APPLE__
        CV_OCL_RUN(_fgmask.isUMat() && OCL_PERFORMANCE_CHECK(!ocl::Device::getDefault().isIntel() || _image.channels() == 1),
                   ocl_apply(_image, _fgmask, learningRate))
#else
        CV_OCL_RUN(_fgmask.isUMat() && OCL_PERFORMANCE_CHECK(!ocl::Device::getDefault().isIntel()),
                   ocl_apply(_image, _fgmask, learningRate))
#endif

        opencl_ON = false;
        nframes = 0;
    }
#endif

    bool needToInitialize = nframes == 0 || learningRate >= 1 || _image.size() != frameSize || _image.type() != frameType;

    if( needToInitialize )
        initialize(_image.size(), _image.type());

    Mat image = _image.getMat();
    _fgmask.create( image.size(), CV_8U );
    Mat fgmask = _fgmask.getMat();

    ++nframes;
    learningRate = learningRate >= 0 && nframes > 1 ? learningRate : 1./std::min( 2*nframes, history );
    CV_Assert(learningRate >= 0);

    //recalculate update rates - in case alpha is changed
    // calculate update parameters (using alpha)
    int Kshort,Kmid,Klong;
    //approximate exponential learning curve
    Kshort=(int)(log(0.7)/log(1-learningRate))+1;//Kshort
    Kmid=(int)(log(0.4)/log(1-learningRate))-Kshort+1;//Kmid
    Klong=(int)(log(0.1)/log(1-learningRate))-Kshort-Kmid+1;//Klong

    //refresh rates
    int nShortUpdate = (Kshort/nN)+1;
    int nMidUpdate = (Kmid/nN)+1;
    int nLongUpdate = (Klong/nN)+1;

    parallel_for_(Range(0, image.rows),
                  KNNInvoker(image, fgmask,
                             bgmodel.ptr(),
                             nNextLongUpdate.ptr(),
                             nNextMidUpdate.ptr(),
                             nNextShortUpdate.ptr(),
                             aModelIndexLong.ptr(),
                             aModelIndexMid.ptr(),
                             aModelIndexShort.ptr(),
                             nLongCounter,
                             nMidCounter,
                             nShortCounter,
                             nN,
                             fTb,
                             nkNN,
                             fTau,
                             bShadowDetection,
                             nShadowDetection),
                             image.total()/(double)(1 << 16));

    nShortCounter++;//0,1,...,nShortUpdate-1
    nMidCounter++;
    nLongCounter++;
    if (nShortCounter >= nShortUpdate)
    {
        nShortCounter = 0;
        randu(nNextShortUpdate, Scalar::all(0),  Scalar::all(nShortUpdate));
    }
    if (nMidCounter >= nMidUpdate)
    {
        nMidCounter = 0;
        randu(nNextMidUpdate, Scalar::all(0),  Scalar::all(nMidUpdate));
    }
    if (nLongCounter >= nLongUpdate)
    {
        nLongCounter = 0;
        randu(nNextLongUpdate, Scalar::all(0),  Scalar::all(nLongUpdate));
    }
}

void BackgroundSubtractorKNNImpl::getBackgroundImage(OutputArray backgroundImage) const
{
    CV_INSTRUMENT_REGION()

#ifdef HAVE_OPENCL
    if (opencl_ON)
    {
        CV_OCL_RUN(opencl_ON, ocl_getBackgroundImage(backgroundImage))

        opencl_ON = false;
    }
#endif

    int nchannels = CV_MAT_CN(frameType);
    //CV_Assert( nchannels == 3 );
    Mat meanBackground(frameSize, CV_8UC3, Scalar::all(0));

    int ndata=nchannels+1;
    int modelstep=(ndata * nN * 3);

    const uchar* pbgmodel=bgmodel.ptr(0);
    for(int row=0; row<meanBackground.rows; row++)
    {
        for(int col=0; col<meanBackground.cols; col++)
        {
            for (int n = 0; n < nN*3; n++)
            {
                const uchar* mean_m = &pbgmodel[n*ndata];
                if (mean_m[nchannels])
                {
                    meanBackground.at<Vec3b>(row, col) = Vec3b(mean_m);
                    break;
                }
            }
            pbgmodel=pbgmodel+modelstep;
        }
    }

    switch(CV_MAT_CN(frameType))
    {
        case 1:
        {
            std::vector<Mat> channels;
            split(meanBackground, channels);
            channels[0].copyTo(backgroundImage);
            break;
        }
        case 3:
        {
            meanBackground.copyTo(backgroundImage);
            break;
        }
        default:
            CV_Error(Error::StsUnsupportedFormat, "");
    }
}


Ptr<BackgroundSubtractorKNN> createBackgroundSubtractorKNN(int _history, double _threshold2,
                                                           bool _bShadowDetection)
{
    return makePtr<BackgroundSubtractorKNNImpl>(_history, (float)_threshold2, _bShadowDetection);
}

}

/* End of file. */
