#include <QDebug>
#include <QDir>
#include "mycv.h"
#include "mycvfloodfill.h"
#include "defs.h"

class mycvClassFloodFill;

mycv::mycv()
{
}

//mycv::mycvImshow(string aStr)

void mycv::processImage(QString aStrDir, QString aStrFile, QImage &aImg, int aMode)
{

    // Load test electrometer
    mResFrames.clear();
    aStrDir.replace("\\", "//");
    Mat img = imread((aStrDir+aStrFile).toStdString());
//    maskBlackSpots(img, img);
    mStrDir = aStrDir;
    mStrFile = aStrFile;
    mStrFile.replace(".jpg", "");

    /// Find text
    if(aMode == 0)detectText(img, img);

    /// Numbers segmentation
    int numOfCount = 0;
    if(aMode == 0)/*numOfCount = numbersSeg()*/;
    else if(aMode == 1)
    {
        destroyAllWindows();
        mResFrames.push_back(img);

        numOfCount = numbersSeg2();
        // Digits segmentation class
//        mycvDigitsSegm cDigitsSegm(mcANN);
//        vector<Mat> digits;
//        Mat imgDigits;
//        cDigitsSegm.segmentNum(img, imgDigits, digits, MODE_DETECT_CONTOURS);

//        IMSHOW("imgDigits", imgDigits);

//        for(int i = 0 ; i < digits.size() ; i++)
//        {
//            std::ostringstream ostream1;
//            ostream1 << i;
//            IMSHOW(ostream1.str(), digits[i]);
//        }
    }

    /// OCR
    aImg = putImage(img);
}

void mycv::saveFrames()
{
    if(mResFrames.size() > 0)
    {
        // Check new folder
        QDir dir(mStrDir + "frames");
        if (!dir.exists()) {
            dir.mkpath(".");
        }
        for(int i = 0 ; i < (int)mResFrames.size() ; i++)
        {
            QString path = mStrDir + "frames/" + mStrFile + "_" + QString::number(i) + ".jpg";
            imwrite(path.toStdString() , mResFrames[i] );
        }
    }
}

void mycv::saveDigits()
{
    if(mResDigits.size() > 0)
    {
        // Check new folder
        QDir dir(mStrDir + "digits");
        if (!dir.exists()) {
            dir.mkpath(".");
        }
        for(int i = 0 ; i < (int)mResDigits.size() ; i++)
        {
            vector<Mat> digits = mResDigits[i];
            for(int j = 0 ; j < digits.size() ; j++)
            {
                QString path = mStrDir + "digits/" + mStrFile + "_" + QString::number(i) + QString::number(j) + ".jpg";
                imwrite(path.toStdString() , digits[j]);
            }
        }
    }
}

void mycv::mycvSaveNoFrameImg()
{
    if(!mImgNoFrame.empty())
    {
        QString path = mStrDir + "noFrame/" + mStrFile + "_nf.jpg";
        imwrite(path.toStdString() , mImgNoFrame );
    }
}

void mycv::addTrainingDataANN(QString aStrFile, int aLabel)
{
    /// Adds image to ANN training dataset with label
    // Load image
    aStrFile.replace("\\", "//");
    Mat img = imread(aStrFile.toStdString());
    mcANN.addData(img, aLabel);
}

void mycv::trainANN()
{
    // Train ANN with defined number of hidden neurons
    mcANN.train(100);
}

void mycv::clearTrainingDataANN()
{
    // Clear ANN dataset
    mcANN.clearData();
}

void mycv::predictANN(QString aStrFile)
{
    // Load image
    aStrFile.replace("\\", "//");
    Mat img = imread(aStrFile.toStdString(), CV_LOAD_IMAGE_GRAYSCALE);

    imshow("ANN testing img", img);

    // Normalize digit
    mcDigitPreproc.procNormalize(img, img);

    imshow("norm", img);

    float res = mcANN.test(img);
    qDebug() << "ANN predict res = " << res << endl;
}

void mycv::saveXMLANN(QString aStrFile)
{
    aStrFile.replace("\\", "//");
    mcANN.saveXML(aStrFile.toStdString());
}

void mycv::loadXMLANN(QString aStrFile)
{
    aStrFile.replace("\\", "//");
    mcANN.loadXML(aStrFile.toStdString());
}

void mycv::processImageSimple(QString aStrDir, QString aStrFile, int aMode)
{
    // Load test electrometer
    mResFrames.clear();
    aStrDir.replace("\\", "//");
    Mat img;

    switch(aMode)
    {
    case 0: img = imread((aStrDir+aStrFile).toStdString());
        mycvEqualizeIntensity(img, img);break;
    case 1: img = imread((aStrDir+aStrFile).toStdString(), CV_LOAD_IMAGE_GRAYSCALE);
        mcDigitPreproc.procNormalize(img, img);break;
    }

    // Check new folder
    QDir dir(aStrDir + "proc");
    if (!dir.exists()) {
        dir.mkpath(".");
    }

    QString path = aStrDir + "proc/" + aStrFile;
    imwrite(path.toStdString() , img );
}

double measureTime(string str)
{
#ifndef TIME_MEASUREMENT
    return 0;
#else
    static double t;

    if(!str.compare("START"))
    {
        t = (double)getTickCount();
    }
    else
    {
        double tt = ((double)getTickCount() - t)/getTickFrequency();
    #ifdef DEF_DEBUG
        QString qstr(str.c_str());
        qDebug() << qstr << " Time=" << (int)tt << " s " << tt*1000 << " ms" << endl;
    #endif
        t = (double)getTickCount();
    }
    return t/getTickFrequency();
#endif
}



void mycv::LoadXMLFrameHaar(QString aStrFile)
{
    aStrFile.replace("\\", "//");
    // load pattern numbers for contour matching
    mcFrameHaar.loadXML(aStrFile.toStdString());
}

int mycv::mycvLoadPatternNumbers2(QString aStrFile, int aMode)
{
    destroyAllWindows();
    aStrFile.replace("\\", "//");
    // load pattern numbers for contour matching
    Mat img = imread(aStrFile.toStdString());
    if(img.empty())return 0;
    if(aMode == 0) // Loading pattern
    {
        mcDigitFeatures.loadPattern(img);
    }
    else if(aMode == 1) // Loading pattern
    {
        mcDigitFeatures.match(img, 0);
    }
}

int mycv::mycvLoadPatternNumbers(QString aStrFile, int aMode)
{
    destroyAllWindows();
    aStrFile.replace("\\", "//");
    // load pattern numbers for contour matching
    Mat img = imread(aStrFile.toStdString());
    if(img.empty())return 0;

    if(aMode == 0) // Loading pattern
    {
        mcDigitGeometric.loadPattern(img);
    }
    else if(aMode == 1) // Load digit
    {
        mcDigitGeometric.match(img);
    }
    return 0;
}

int mycv::numbersSeg3()
{
    // if no counter stored in mResFrames -> error
    if(mResFrames.size() == 0)return 0;

    for(int i = 0 ; i < (int)mResFrames.size() ; i++)
    {
        Mat imgFrameOrig = (Mat)mResFrames[i];
        int rows = imgFrameOrig.rows;
//        int cols = imgFrameOrig.cols;
        int windowDockX = 0;
        int windowDockY = 1500;

        // To grayscale
        Mat imgGray;
        cvtColor(imgFrameOrig, imgGray, CV_BGR2GRAY);
        imshow("imgGray", imgGray);

        // General digit preprocessing
        Mat imgFrameThresh;
        mycvPreprocCount(imgFrameOrig, imgFrameThresh);
        imshow("imgFrameThresh", imgFrameThresh);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgFrameThresh", windowDockY, windowDockX++*(rows+25) + 5);
        #endif
    }
    return 0;
}

int mycv::numbersSeg2()
{
    // if no counter stored in mResFrames -> error
    if(mResFrames.size() == 0)return 0;
    for(int i = 0 ; i < mResDigits.size() ; i++)
        mResDigits[i].clear();
    mResDigits.clear();
    for(int i = 0 ; i < (int)mResFrames.size() ; i++)
    {
        Mat imgFrameOrig = (Mat)mResFrames[i], imgFrameThresh;
        int rows = imgFrameOrig.rows;
        int cols = imgFrameOrig.cols;
        int windowDockX = 0;
        int windowDockY = 1500;

        // General digit preprocessing
        mycvPreprocCount(imgFrameOrig, imgFrameThresh);

        int blockSz = blockSize(imgFrameOrig.rows, 10);

        Mat element = getStructuringElement(MORPH_ELLIPSE, Size(3, 1));
        morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_CLOSE, element);
        mycvRemoveHighLowObjects(imgFrameThresh, imgFrameThresh, 0, 0, 0, imgFrameThresh.rows/10, imgFrameThresh.rows/5);
        mycvRemoveHighLowObjects(imgFrameThresh, imgFrameThresh, 0, imgFrameThresh.cols/6, imgFrameThresh.rows*7/8);

//        mycvRemoveBorderSmalls(imgFrameThresh, imgFrameThresh, imgFrameThresh.rows/3);
//        imshow("imgFrameThresh border smalls", imgFrameThresh);
//        #ifdef DEF_ONE_SCREEN
//        moveWindow("imgFrameThresh border smalls", windowDockY, windowDockX++*(rows+25) + 5);
//        #endif

        // Morphology
        blockSz = blockSize(imgFrameThresh.cols,150);
//        element = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz));
//        morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_ERODE, element);
        blockSz = blockSize(imgFrameThresh.cols, 80);
    #ifdef DEF_DEBUG
        qDebug() << "Morph element size = " << blockSz << endl;
    #endif
        element = getStructuringElement(MORPH_RECT, Size(1+blockSz/2, blockSz*2));
        morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_CLOSE, element);

        blockSz = blockSize(imgFrameThresh.cols, 90);
        element = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz));
        morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_DILATE, element);

        imshow("imgFrameThresh morph", imgFrameThresh);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgFrameThresh morph", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        /// Character segmentation
        vector<vector<Point> > contours;
        findContours(imgFrameThresh.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
        // The array for storing the approximation curve
        std::vector<cv::Point> approx;

        Mat imgSegChars;
//        cvtColor(imgFrameOrig, imgSegChars, CV_GRAY2BGR);
        imgFrameOrig.copyTo(imgSegChars);

        // Create list of bounding rectangles
        vector<Rect> rects;
        for(int j = 0 ; j < (int)contours.size() ; j++)
        {
            approxPolyDP(
                cv::Mat(contours[j]),
                approx,
                cv::arcLength(cv::Mat(contours[j]), true) * 0.03,
                true
            );
            Rect rect;
            rect = boundingRect(approx);

            // Add more space up and down
            int increase = 10;
            rect.y -= increase;
            if(rect.y < 0)rect.y = 0;

            rect.height += increase;
            if(rect.y + rect.height >= imgFrameOrig.rows)rect.height = imgFrameOrig.rows - rect.y;

            // Filter inconvenient rects
            rects.push_back(rect);
            rectangle(imgSegChars, rect, Scalar(0, 0, 255));
        }
        imshow("segmented", imgSegChars);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("segmented", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        // Check digits
        mycvCheckDigit(imgFrameThresh, rects);

        Mat imgAdjRects;
        imgFrameOrig.copyTo(imgAdjRects);
        mcDigitPreproc.loadFrame(mResFrames[i]);

        int windowDockX2 = 0, windowDockY2 = windowDockY + 700;
        vector<Mat> digits;
        for(int j = 0 ; j < (int)rects.size() ; j++)
        {
//            Rect rect(centers[j].x - meanRectSize.x/2 - meanRectSize.x/10, minY, meanRectSize.x + meanRectSize.x/10, meanRectSize.y);
            Rect rect = rects[j];
            rectangle(imgAdjRects, rect, Scalar(0, 255, 0));
            Point circP = Point(rect.x+rect.width/2, rect.y+rect.height/2);
            circle(imgAdjRects, circP, 3, Scalar(0,0,255));
            Mat imgActDigit;
//            mycvRemoveHigherObjects(imgActDigit, imgActDigit, 0, 0, 0, imgActDigit.cols/15, imgActDigit.rows/4, imgActDigit.cols/2);

            if(mcDigitPreproc.preproc(imgActDigit, rect, PROC_MODE_NORMALIZE))
            {
                digits.push_back(imgActDigit);
//                float num = mcDigitFeatures.match(imgActDigit, 0);
//                float num = mcDigitGeometric.match(imgActDigit);
                float num = mcANN.test(imgActDigit);
                if(num >= 0)
                {
                    std::ostringstream ostream1;
                    ostream1 << num;
                    putText(imgAdjRects, ostream1.str(), Point(rect.x + 2, 30), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0,0,255));
                }
                std::ostringstream ostream;
                ostream << "D" << j << ":" << num;
                imshow(ostream.str(), imgActDigit);
                #ifdef DEF_ONE_SCREEN2
                moveWindow(ostream.str(), windowDockY2, windowDockX2++*(imgActDigit.rows+25) + 5);
                #endif
            }
        }
        imshow("imgAdjRects", imgAdjRects);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgAdjRects", windowDockY, windowDockX++*(rows+25) + 5);
        #endif
        mResDigits.push_back(digits);
    }
    return 0;
}

int mycv::numbersSeg()
{
    // if no counter stored in mResFrames -> error
    if(mResFrames.size() == 0)return 0;
    for(int i = 0 ; i < mResDigits.size() ; i++)
        mResDigits[i].clear();
    mResDigits.clear();
    for(int i = 0 ; i < (int)mResFrames.size() ; i++)
    {
        Mat imgFrameOrig = (Mat)mResFrames[i], imgFrameThresh;
        int rows = imgFrameOrig.rows;
        int cols = imgFrameOrig.cols;
        int windowDockX = 0;
        int windowDockY = 1500;

        // General frame preprocessing
        mycvPreprocCount(imgFrameOrig, imgFrameThresh);
        imshow("imgFrameThresh", imgFrameThresh);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgFrameThresh", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        // Morphology
        int blockSz = blockSize(imgFrameThresh.rows, 20);
    #ifdef DEF_DEBUG
        qDebug() << "Morph element size = " << blockSz << endl;
    #endif
        Mat element = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz));
        morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_DILATE, element);
//        imshow("imgFrameThresh dilate", imgFrameThresh);
//        #ifdef DEF_ONE_SCREEN2
//        moveWindow("imgFrameThresh dilate", windowDockY, windowDockX++*(rows+25) + 5);
//        #endif

        // Fill up holes
        Mat imgThreshInv;
        bitwise_not(imgFrameThresh.clone(), imgThreshInv);
//        imshow("imgThreshInv", imgThreshInv);
//        #ifdef DEF_ONE_SCREEN2
//        moveWindow("imgThreshInv", windowDockY, windowDockX++*(rows+25) + 5);
//        #endif

        // Remove small objects in inverted img -> small holes gone
        mycvRemoveHighLowObjects(imgThreshInv.clone(), imgThreshInv, 0, 0, 0, imgThreshInv.cols/7, imgThreshInv.rows-imgThreshInv.rows/5);
//        imshow("imgThreshInv filled", imgThreshInv);
//        #ifdef DEF_ONE_SCREEN2
//        moveWindow("imgThreshInv filled", windowDockY, windowDockX++*(rows+25) + 5);
//        #endif
        bitwise_not(imgThreshInv.clone(), imgFrameThresh);

        // Remove too large objects
        mycvRemoveHighLowObjects(imgFrameThresh.clone(), imgFrameThresh, 0, 0, 0, imgFrameThresh.rows/10, imgFrameThresh.rows/6);

        // Morphology connect object in vertical direction
        blockSz = blockSize(imgFrameThresh.rows, 10);
    #ifdef DEF_DEBUG
        qDebug() << "Morph element size = " << blockSz << endl;
    #endif
        element = getStructuringElement(MORPH_ELLIPSE, Size(1+blockSz/10, blockSz*5));
        morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_CLOSE, element);
        imshow("imgFrameThresh morph", imgFrameThresh);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgFrameThresh morph", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        // Cut upper and lower frame 1/3
        Mat imgCut;
        int perc = imgFrameThresh.rows/3;
        Rect roi(0, perc, imgFrameThresh.cols-1, imgFrameThresh.rows-2*perc);
        imgCut = imgFrameThresh(roi).clone();

//        mycvRemoveBorderSmalls(imgCut, imgCut, imgCut.rows/4);

        imshow("imgCut", imgCut);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgCut", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        /// Distinguish each digit using vertical projection
        Mat imgColSum;
        vector<Point> charSepars;   // x - start col, y - end col
        int maxSum = mycvVerHorProjection(imgCut, imgColSum, PROJ_VERTICAL, charSepars);
        imshow("imgColSum", imgColSum);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgColSum", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        if(maxSum == 0 || charSepars.size() == 0)return 0; // numbers not found

        Mat imgLines;
//        cvtColor(imgFrameOrig, imgLines, CV_GRAY2BGR);
        imgFrameOrig.copyTo(imgLines);
        // reduse redundant separators - leave 8 biggest
        while(charSepars.size() > 8)
        {
            uint minSep = numeric_limits<uint>::max();
            uint minPos = 0;
            for(int j = 0 ; j < (int)charSepars.size() ; j++)
            {
                // find smallest
                Point point = charSepars[j];
                if(minSep > point.y - point.x)
                {
                    minPos = j;
                    minSep = point.y - point.x;
                }
            }
            charSepars.erase(charSepars.begin()+minPos);
        }
        if(charSepars.size() == 0)return 0;

        // Evaluate mean width
        uint meanCharWidth = 0;
        for(int j = 0 ; j < (int)charSepars.size() ; j++)
        {
            meanCharWidth += charSepars[j].y - charSepars[j].x;
            line(imgLines, Point(charSepars[j].x, 0), Point(charSepars[j].x, imgColSum.cols-1), Scalar(255, 0, 0));
            line(imgLines, Point(charSepars[j].y, 0), Point(charSepars[j].y, imgColSum.cols-1), Scalar(0, 255, 0));
            line(imgLines, Point(charSepars[j].x, 0), Point(charSepars[j].y, 0), Scalar(0, 255, 0), 3);
        }
        meanCharWidth /= charSepars.size();
        meanCharWidth  += meanCharWidth/3;
        qDebug() << "meanWidth" << meanCharWidth;
        imshow("imgColSum lines", imgLines);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgColSum lines", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        // normalize digits width
        imgFrameOrig.copyTo(imgLines);
        for(int j = 0 ; j < (int)charSepars.size() ; j++)
        {
            Point act = charSepars[j];
            int width = act.y - act.x;
            int dif = meanCharWidth - width;
            charSepars[j].x -= dif/2;
            charSepars[j].y += dif/2;

            if(charSepars[j].x < 0)charSepars[j].x = 0;
            if(charSepars[j].y >= imgCut.cols)charSepars[j].y = imgCut.cols-1;

            line(imgLines, Point(charSepars[j].x, 0), Point(charSepars[j].x, imgColSum.cols-1), Scalar(255, 0, 0));
            line(imgLines, Point(charSepars[j].y, 0), Point(charSepars[j].y, imgColSum.cols-1), Scalar(0, 255, 0));
            line(imgLines, Point(charSepars[j].x, 0), Point(charSepars[j].y, 0), Scalar(0, 255, 0), 3);
        }

        // Remove overlaps
        for(int j = 0 ; j < (int)charSepars.size() ; j++)
        {
            // find overlap
            for(int k = 0 ; k < (int)charSepars.size() ; k++)
            {
                if(j != k)
                {
                    if((charSepars[k].x < charSepars[j].y && charSepars[k].x > charSepars[j].x) ||
                       (charSepars[k].y < charSepars[j].y && charSepars[k].y > charSepars[j].x))
                    {
                        charSepars.erase(charSepars.begin()+k);
                        k--;
                    }
                }
            }
        }

        Mat imgLines2;
        imgFrameOrig.copyTo(imgLines2);
        for(int j = 0 ; j < (int)charSepars.size() ; j++)
        {

            line(imgLines2, Point(charSepars[j].x, 0), Point(charSepars[j].x, imgColSum.cols-1), Scalar(255, 0, 0));
            line(imgLines2, Point(charSepars[j].y, 0), Point(charSepars[j].y, imgColSum.cols-1), Scalar(0, 255, 0));
            line(imgLines2, Point(charSepars[j].x, 0), Point(charSepars[j].y, 0), Scalar(0, 255, 0), 3);
        }

        // Create rectangles
        vector<Rect> charRects;
        for(int j = 0 ; j < charSepars.size() ; j++)
        {
            Rect newRect(charSepars[j].x, 0, charSepars[j].y-charSepars[j].x, imgLines.rows);
            charRects.push_back(newRect);
        }

        // Process digit
        mcDigitPreproc.loadFrame(mResFrames[i]);
        vector<Mat> digits;
        int windowDockX2 = 0, windowDockY2 = windowDockY+700;
        for(int j = 0 ; j < (int)charRects.size() ; j++)
        {
            Rect rect = charRects[j];
            Mat imgActDigit;
            if(mcDigitPreproc.preproc(imgActDigit, rect, PROC_MODE_GENERAL))
            {
                digits.push_back(imgActDigit);
                float num = mcDigitFeatures.match(imgActDigit, 0);

//                float num = mcDigitGeometric.match(imgActDigit);
                // If digit recognized
                if(num >= 0)
                {
                    std::ostringstream ostream1;
                    ostream1 << num;
                    putText(imgLines, ostream1.str(), Point(charRects[j].x + 2, 30), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0,0,255));
                }

//                std::ostringstream ostream;
//                ostream << "D" << j << ":" << num;
//                imshow(ostream.str(), imgActDigit);
//                #ifdef DEF_ONE_SCREEN2
//                moveWindow(ostream.str(), windowDockY2, windowDockX2++*(imgActDigit.rows+25) + 5);
//                #endif
            }
        }

        imshow("imgColSum lines 2", imgLines);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgColSum lines 2", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        imshow("imgColSum lines no overlaps", imgLines2);
        #ifdef DEF_ONE_SCREEN2
        moveWindow("imgColSum lines no overlaps", windowDockY, windowDockX++*(rows+25) + 5);
        #endif

        mResDigits.push_back(digits);
    }

    return mResFrames.size();
}

void mycv::mycvCheckDigit(const Mat &aSrc, vector<Rect> &aRects)
{
    vector<int> outRectsIndxs;
    vector<Rect> outRects;
    const float minRatio = 0.15;
    const float maxRatio = 1.6;
    int minVol = (aSrc.rows/5)*(aSrc.rows/10);

    for(int i = 0 ; i < aRects.size() ; i++)
    {
        bool bPassed = false;
        Rect rect = aRects[i];
        Mat imgDigit = aSrc(rect).clone();
        // 1. Check size
        float actRatio = (float)imgDigit.cols/imgDigit.rows;
        int minRows = 7;
        int minCols = 5;
        if( actRatio > minRatio && actRatio < maxRatio && \
            imgDigit.cols > minCols && imgDigit.rows > minRows && \
            rect.height*rect.width > minVol)
        {
            // 2. Check center position
            int minBorder = aSrc.rows/5;
            if(rect.y+(rect.height/2) > minBorder && rect.y+(rect.height/2) < aSrc.rows - minBorder)
            {
                // 3. Remove too close rects
                int minDist = aSrc.cols/12;
                bool bClose = false;
                for(int j = 0 ; j < aRects.size() ; j++)
                {
                    if(i == j)continue;
                    if(abs(rect.x - aRects[j].x) < minDist)
                    {
                        bClose = true;
                        if(rect.width*rect.height > aRects[j].width*aRects[j].height)
                        {
                            bPassed = true;
                        }
                    }
                }
                if(!bClose)bPassed = true;
            }
        }

        // If rect passed add it to out
        if(bPassed)
        {
            outRects.push_back(rect);
            aRects.erase(aRects.begin() + i);
            i--;
        }
    }
    aRects.clear();
    aRects = outRects;

    // Adjust frames to character raster
    Point meanRectSize;
    int meanSpace = 0;
    int maxY = 0;
    int minY = aSrc.rows;
    vector<Point> centers;
    for(int j = 0 ; j < (int)aRects.size() ; j++)
    {
        if(meanRectSize.x < aRects[j].width)meanRectSize.x = aRects[j].width;
        if(meanRectSize.y < aRects[j].height)meanRectSize.y = aRects[j].height;
        if(minY > aRects[j].y)minY = aRects[j].y;
        if(maxY < aRects[j].y+aRects[j].height)maxY = aRects[j].y+aRects[j].height;
        Point point(aRects[j].x + aRects[j].width/2, aRects[j].y + aRects[j].height/2);
        centers.push_back(point);
    }
    meanRectSize.y = maxY - minY;
    meanRectSize.x += meanRectSize.x/10;
    if(meanRectSize.x > aSrc.cols/8)meanRectSize.x = aSrc.cols/8;

    outRects.clear();
    for(int i = 0 ; i < aRects.size() ; i++)
    {
        Rect rect(centers[i].x - meanRectSize.x/2 - meanRectSize.x/10, minY, meanRectSize.x + meanRectSize.x/10 , meanRectSize.y);
        outRects.push_back(rect);
    }

    aRects.clear();

    // Sort frames according to horizontal position
    while(outRects.size() > 0)
    {
        // find minimum
        int min = numeric_limits<int>::max();
        int minIndx = 0;
        for(int i = 0 ; i < outRects.size() ; i ++)
        {

            if(min > outRects[i].x)
            {
                minIndx = i;
                min = outRects[i].x;
            }
        }
        // push min to out
        aRects.push_back(outRects[minIndx]);
        // delete min from list
        outRects.erase(outRects.begin()+minIndx);
    }

    // Remove redundand rects
    if(aRects.size() > 1)
    for(int i = 0 ; i < aRects.size() - 1 ; i++)
    {
        // check overlaps
        if((aRects[i].x > aRects[i+1].x && aRects[i].x < aRects[i+1].x+aRects[i+1].width) ||
           (aRects[i].x+aRects[i].width > aRects[i+1].x && aRects[i].x+aRects[i].width < aRects[i+1].x+aRects[i+1].width))
        {
            if(aRects[i].x > aSrc.cols/2)
                aRects.erase(aRects.begin() + i + 1);
            else
                aRects.erase(aRects.begin() + i);
        }
    }
}

void scaningLowNoiseFilter(const Mat &aSrc, Mat &aDst, int aLoose)
{
    // window
    Rect roi(0, 0, aSrc.cols/30, aSrc.rows);
    aDst = aSrc.clone();

    // Move window
    for(int x = 0 ; x < aDst.cols - roi.width ; x++)
    {
        roi.x = x;
        // cut window
        Mat imgW = aDst(roi);
        double max, min;
        cv::minMaxLoc(aDst, &min, &max);
        for(int i = 0 ; i < roi.width ; i++)
        {
            for(int j = 0 ; j < roi.height ; j++)
            {
                if(imgW.at<uchar>(j, i) < min + (max-min)/aLoose)
                    imgW.at<uchar>(j, i) = 0;
            }
        }
    }
}

void mycv::mycvPreprocCount(const Mat &aSrc, Mat &aDst, bool abTime)
{
    bool bTimeMeasure = abTime;
    imshow("__orig", aSrc);
    Mat imgGray;
    cvtColor(aSrc, imgGray, CV_BGR2GRAY);
if(bTimeMeasure)measureTime("START");
//    fastNlMeansDenoising(imgGray, imgGray); // too slow

    imshow("__imgGray", imgGray);

    int blockSz = blockSize(imgGray.cols, 120);
//    mycvSharpening(imgGray, imgGray);
//if(bTimeMeasure)measureTime("mycvSharpening");
//    imshow("__mycvSharpening", imgGray);
//    bilateralFilter(imgGray.clone(), imgGray, blockSz, 150, blockSz);
//if(bTimeMeasure)measureTime("bilateralFilter");
//    imshow("__bilateralFilter", imgGray);

    // Low noise mask
//    double max, min;
//    cv::minMaxLoc(imgGray, &min, &max);
//    Mat imgNoiseVMask;
//    threshold(imgGray, imgNoiseVMask,min + (max - min)/5, max, THRESH_BINARY);
////    scaningLowNoiseFilter(imgGray, imgGray, 3);
//    imshow("__Low noise mask", imgGray);
//    bitwise_and(imgGray,imgNoiseVMask, imgGray);

    /// Threshold
    Mat imgFrameThresh;
//    double min, max;
//    cv::minMaxLoc(imgGray, &min, &max);
//    blockSz = blockSize(imgGray.cols, 3);
//    adaptiveThreshold(imgGray, imgFrameThresh, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 1);


    blockSz = blockSize(imgGray.rows, 10);
    bilateralFilter(imgGray.clone(), imgGray, blockSz, 150, blockSz);
    // Threshold
    adaptiveThreshold(imgGray, imgFrameThresh, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 0);
    imshow("__imgFrameThresh", imgFrameThresh);
    mycvRemoveHighLowObjects(imgFrameThresh, imgFrameThresh, 0, 0, 0, imgFrameThresh.rows/9, imgFrameThresh.rows/8,
                             (imgFrameThresh.rows/8)*(imgFrameThresh.rows/8));
    imshow("__imgFrameThresh no high", imgFrameThresh);
    medianBlur(imgFrameThresh, imgFrameThresh, 3);

if(bTimeMeasure)measureTime("adaptiveThreshold");
    imshow("__Thresh", imgFrameThresh);

    // Remove filled collumns
    for(int i = 0 ; i < imgFrameThresh.cols ; i++)
    {
        int colSum = 0;
        for(int j = 0 ; j < imgFrameThresh.rows ; j++)
        {
            if(imgFrameThresh.at<uchar>(Point(i, j)) > 0)colSum++;
        }
        if(colSum == imgFrameThresh.rows)
        {
            for(int j = 0 ; j < imgFrameThresh.rows ; j++)
            {
                imgFrameThresh.at<uchar>(Point(i, j)) = 0;
            }
        }
    }
if(bTimeMeasure)measureTime("Remove filled collumns");
    // red mask
    Mat imgRed;
    mycvFindRed(aSrc, imgRed);
    blockSz = imgGray.cols/80;
    blockSz += (blockSz + 1) % 2;
#ifdef DEF_DEBUG
    qDebug() << "Morph 2 element size = " << blockSz << endl;
#endif
    blockSz = blockSize(imgFrameThresh.rows, 10);
    Mat element = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz));
    morphologyEx(imgRed, imgRed, CV_MOP_DILATE, element);
//    cv::Mat elementR(blockSz,blockSz,CV_8U,cv::Scalar(1));
//    cv::dilate(imgRed, imgRed, elementR);

    bitwise_not ( imgRed, imgRed );
    imgFrameThresh = imgFrameThresh & imgRed;
if(bTimeMeasure)measureTime("red mask");
    imshow("__red mask", imgRed);

    // Remove upper & lower borders
    Mat imgRowSum;
    vector<Point> charSep;
    mycvVerHorProjection(imgFrameThresh, imgRowSum, PROJ_HRIZONTAL, charSep);

    Point cutP = mycvCheckHorizBorders(imgFrameThresh, charSep);
    cvtColor(imgRowSum, imgRowSum, CV_GRAY2BGR);

    imshow("__imgRowSum", imgRowSum);

    uchar *p1, *p2;
    p1 = (uchar *) imgFrameThresh.ptr<uchar>(cutP.x);
    p2 = (uchar *) imgFrameThresh.ptr<uchar>(cutP.y);
    for(int j = 0 ; j < imgFrameThresh.cols ; j++)
    {
        p1[j] = 0;
        p2[j] = 0;
    }

if(bTimeMeasure)measureTime("mycvVerHorProjection");
    imshow("__Thresh borders", imgFrameThresh);

    mycvRemoveHighLowObjects(imgFrameThresh, imgFrameThresh, 0, imgFrameThresh.cols/5, 0);
    mycvRemoveHighLowObjects(imgFrameThresh, imgFrameThresh, 0, 0, 0, 10, 10);

    imshow("__Thresh smalls", imgFrameThresh);
if(bTimeMeasure)measureTime("mycvRemoveHigherObjects");

//    mycvRemoveHigherObjects(imgFrameThresh, imgFrameThresh, 0, 0, 0, 0, 0, imgFrameThresh.rows*2);

//    blockSz = blockSize(imgFrameThresh.rows, 10);
//    element = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz));
//    morphologyEx(imgFrameThresh, imgFrameThresh, CV_MOP_CLOSE, element);

    imgFrameThresh.copyTo(aDst);
}

Point mycv::mycvCheckHorizBorders(const Mat &aSrc, vector<Point> &aHorProj)
{
    // Check horizontal borders
    int bordArea = aSrc.rows/5;
    int rows = aSrc.rows;
    int topCutH = 0;   // cut row
    int downCutH = rows-1;   // cut row
    bool bBordFound = false;
    bool bBordFound2 = false;
    for(int j = 0 ; j < bordArea ; j++)
    {
        int act = aHorProj[j].x;
        if(act > aSrc.cols-aSrc.cols/4)
        {
            topCutH = j;
            bBordFound = true;
        }
        act = aHorProj[rows-j].x;
        if(act > aSrc.cols-aSrc.cols/4)
        {
            downCutH = rows-j;
            bBordFound2 = true;
        }
    }
    if(bBordFound)
    {
        // find first minimum
        int grad = 0; // 0 - nothing, 1 - down, 2 - up
        for(int j = topCutH+1; j < (int)aHorProj.size()-1; j++)
        {
            int act1 = aHorProj[j-1].x;
            int act2 = aHorProj[j].x;
            int act3 = aHorProj[j+1].x;
            if(act1 >= act2 && act2 >= act3)grad = 1;
            if(grad == 1 && act2 < act3)
            {
                topCutH = j;
                break;
            }
        }
    }
    if(bBordFound2)
    {
        // find first minimum
        int grad = 0; // 0 - nothing, 1 - down, 2 - up
        for(int j = downCutH-1; j > 0; j--)
        {
            int act1 = aHorProj[j+1].x;
            int act2 = aHorProj[j].x;
            int act3 = aHorProj[j-1].x;
            if(act1 >= act2 && act2 >= act3)grad = 1;
            if(grad == 1 && act2 < act3)
            {
                downCutH = j;
                break;
            }
        }
    }
    return Point(topCutH, downCutH);
}

void mycv::mycvSharpening(const Mat &aSrc, Mat &aDst)
{
    Mat image;
    cv::GaussianBlur(aSrc, image, cv::Size(0, 0), 3);
    cv::addWeighted(aSrc, 1.8, image, -1.5, 0, aDst);
}

int mycv::mycvVerHorProjection(const Mat &aSrc, Mat &aDst, const int aDir, vector<Point> &aSepar)
{
    // count collumns white pixels
    uint colsSum[aSrc.cols];
    uint rowsSum[aSrc.rows];
    uchar* p;
    aDst = Mat::zeros(/*maxSum+1*/aSrc.rows, aSrc.cols, CV_8UC1);
    for(int i = 0 ; i < (int)aSrc.rows ; i++)
    {
        p = (uchar*) aSrc.ptr<uchar>(i);  // get pointer to act. row
        for(int j = 0 ; j < aSrc.cols ; j++)
        {
            if(i == 0)colsSum[j] = 0;
            if(j == 0)rowsSum[i] = 0;
            if(p[j] > 0)
            {
                if(aDir == PROJ_VERTICAL)colsSum[j]++;
                else if(aDir == PROJ_HRIZONTAL)rowsSum[i]++;
            }
        }
    }
    uint maxSum = 0;
    uint minSum = numeric_limits<uint>::max();
    if(aDir == PROJ_VERTICAL)
    {
        for(int i = 0 ; i < aSrc.cols ; i++)
        {
            if(maxSum < colsSum[i])maxSum = colsSum[i];
            if(minSum > colsSum[i])minSum = colsSum[i];
        }
    }
    else if(aDir == PROJ_HRIZONTAL)
    {
        for(int i = 0 ; i < aSrc.rows ; i++)
        {
            if(maxSum < rowsSum[i])maxSum = rowsSum[i];
            if(minSum > rowsSum[i])minSum = rowsSum[i];
        }
    }
    if(maxSum == 0)return 0;

    if(aDir == PROJ_VERTICAL)
    {
        aDst = Mat::zeros(maxSum+1, aSrc.cols, CV_8UC1);
        for(int i = 0 ; i < aSrc.cols ; i++)
        {
            line(aDst, Point(i, 0), Point(i, colsSum[i]), Scalar(255));
        }
        // evaluate separators between "hills"
        uint addMin = 0;
        vector<uint> spaces;
        while(addMin < (maxSum-minSum)/5)
        {
            for(int i = 0 ; i < aSrc.cols ; i++)
            {
                if(colsSum[i] == minSum+addMin)spaces.push_back(i);
            }
            addMin++;
        }
        sort(spaces.begin(), spaces.end());
        for(int i = 1 ; i < (int)spaces.size() ; i++)
        {
            if(spaces[i]-spaces[i-1] != 1)
            {
                aSepar.push_back(Point(spaces[i-1],spaces[i]));
            }
        }

    }
    else if(aDir == PROJ_HRIZONTAL)
    {
        aSepar.clear();
        aDst = Mat::zeros(aSrc.rows, maxSum+1, CV_8UC1);
        for(int i = 0 ; i < aSrc.rows ; i++)
        {
            aSepar.push_back(Point(rowsSum[i], 0));
            line(aDst, Point(0, i), Point(rowsSum[i], i), Scalar(255));
        }
    }
    return maxSum;
}

static const Vec3b bcolors[] =
{
    Vec3b(0,0,255),
    Vec3b(0,128,255),
    Vec3b(0,255,255),
    Vec3b(0,255,0),
    Vec3b(255,128,0),
    Vec3b(255,255,0),
    Vec3b(255,0,0),
    Vec3b(255,0,255),
    Vec3b(255,255,255)
};

void mycv::mycvDrawMSERsPoints(Mat &aImgMSER, Mat &aImgEllipse, const vector<vector<Point>> aP)
{
    // draw mser's with different colors
    for( int i = (int)aP.size()-1; i >= 0; i-- )
    {
        const vector<Point>& r = aP[i];
        for ( int j = 0; j < (int)r.size(); j++ )
        {
            Point pt = r[j];
            aImgMSER.at<Vec3b>(pt) = bcolors[i%9];
        }

        // find ellipse (it seems cvfitellipse2 have error or sth?)
        RotatedRect box = fitEllipse( r );

        box.angle=(float)CV_PI/2-box.angle;
        ellipse( aImgEllipse, box, Scalar(196,255,255), 2 );
    }
}

void mycv::mycvDrawMSERMask(Mat &aImgMSER, const vector<vector<Point>> aP)
{
    Mat img = Mat::zeros(aImgMSER.size(), CV_8UC1);
    // draw mser's with different colors
    for( int i = (int)aP.size()-1; i >= 0; i-- )
    {
        const vector<Point>& r = aP[i];
        for ( int j = 0; j < (int)r.size(); j++ )
        {
            Point pt = r[j];
            img.at<uchar>(pt) = 255;
        }
    }
    aImgMSER = img;
}

int mycv::blockSize(int aNum, int aDiv)
{
    int blockSz = aNum/aDiv;
    blockSz += (blockSz + 1) % 2;
    if(blockSz <= 2)blockSz = 3;
    return blockSz;
}

void mycv::detectText3(const Mat &aScr, Mat &aDst)
{
    int blockSz;
    double min, max;

measureTime("START");
    Mat imgBGR = aScr.clone();

    // Resize if image is too big
//    int width = GLOBAL_WIDTH;
//    double scaleFactor = (double)width/imgBGR.cols;
//    if(imgBGR.cols > GLOBAL_WIDTH)
//        resize(imgBGR, imgBGR, Size(), scaleFactor, scaleFactor, INTER_CUBIC);

    /// Preprocessing
    // To grayscale
    Mat imgGray;
    cvtColor(imgBGR, imgGray, CV_BGR2GRAY);
    // Equalize intenzity
    mycvEqualizeIntensity(imgBGR, imgBGR);
//    equalizeHist(imgGray, imgGray);

    // Detect frame
    vector<Rect> framesRect;
    mcFrameHaar.detectFrame(imgBGR, framesRect);

    // Draw rects
    for(int  i = 0 ; i < framesRect.size() ; i++)
    {
        rectangle(imgBGR, framesRect[i], Scalar(0, 255, 0), 2);
    }
    IMSHOW("Cascades founded", imgBGR);

    aDst = imgBGR;
}

void mycv::detectText2(const Mat &aScr, Mat &aDst)
{
    int blockSz;
    double min, max;

measureTime("START");
    Mat imgBGR = aScr.clone();

    // Resize if image is too big
    int width = GLOBAL_WIDTH;
    double scaleFactor = (double)width/imgBGR.cols;
    if(imgBGR.cols > GLOBAL_WIDTH)
        resize(imgBGR, imgBGR, Size(), scaleFactor, scaleFactor, INTER_CUBIC);

    /// Preprocessing

    // To grayscale
    Mat imgGray;
    cvtColor(imgBGR, imgGray, CV_BGR2GRAY);
    // Noise filter keeping edges
    bilateralFilter(imgGray.clone(), imgGray, 9, 25, 9);
    // Equalize intenzity
    equalizeHist(imgGray, imgGray);

    // Autorotation
    Mat imgRot, imgBGRRot;
    float angle = 0;
    Mat rotMat = mycvRotateAcDominantLines(imgGray, angle);
    warpAffine(imgGray, imgRot, rotMat, imgGray.size());
    warpAffine(imgBGR, imgBGRRot, rotMat, imgBGR.size());
    aDst = imgBGRRot.clone();

    // Find red areas
    Mat imgRed;
    blockSz = blockSize(imgRot.cols, 10);
    mycvFindRed(imgBGRRot, imgRed);
    Mat elem = getStructuringElement(MORPH_RECT, Size(blockSz, blockSz*2));
    morphologyEx(imgRed, imgRed, CV_MOP_CLOSE, elem);
    IMSHOW("imgRed", imgRed);

    // Find black areas
    Mat imgToThresh;
    // Morphology
    blockSz = blockSize(imgRot.cols, 50);
    elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz*2, blockSz));
    morphologyEx(imgRot, imgToThresh, CV_MOP_CLOSE, elem);
    IMSHOW("imgToThresh", imgToThresh);

    Mat imgThresh;
    blockSz = blockSize(imgRot.cols, 5);
    threshold(imgToThresh, imgThresh, 0, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
//    adaptiveThreshold(imgToThresh, imgThresh, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSz, 1);
    // Morphology connecting lines
    blockSz = blockSize(imgThresh.cols, 30);
    elem = getStructuringElement(MORPH_RECT, Size(blockSz, blockSz));
    morphologyEx(imgThresh, imgThresh, CV_MOP_CLOSE, elem);
    IMSHOW("imgThresh", imgThresh);

    // Add red to black mask
    bitwise_or(imgThresh, imgRed, imgThresh);

    // Sobel vertical edges
    Mat imgEdgesV;
    Sobel(imgRot, imgEdgesV, CV_8U, 1, 0, 3);
    IMSHOW("SOBEL", imgEdgesV);

    // Remove noise using minmax part threshold
    cv::minMaxLoc(imgEdgesV, &min, &max);
    Mat imgNoiseVMask;
    threshold(imgEdgesV, imgNoiseVMask, (max - min)/20, max, THRESH_BINARY);
    bitwise_and(imgEdgesV,imgNoiseVMask, imgEdgesV);

    // Mask SOBEL
    bitwise_and(imgEdgesV, imgThresh, imgEdgesV);
    IMSHOW("SOBEL black mask", imgEdgesV);

    // Threshold verticals
    Mat imgThreshV;
    blockSz = blockSize(imgEdgesV.cols, 2);
    adaptiveThreshold(imgEdgesV, imgThreshV, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 1);
    IMSHOW("SOBEL thresh", imgThreshV);

    // Morphology connecting lines
    blockSz = blockSize(imgThreshV.cols, 100);
    elem = getStructuringElement(MORPH_RECT, Size(blockSz*5, blockSz));
    morphologyEx(imgThreshV, imgThreshV, CV_MOP_CLOSE, elem);
    IMSHOW("SOBEL connected", imgThreshV);

measureTime("detectText2");
}

void mycv::detectText(const Mat &aScr, Mat &aDst)
{
    /// Load image
    Mat imgBGR = aScr.clone();
    Mat imgBGROrigSize = aScr.clone();

    // Start time measurement
    measureTime("START");

    // Scale to defined width if larger
    int width = GLOBAL_WIDTH;
    double scaleFactor = (double)width/imgBGR.cols;
    if(imgBGR.cols > GLOBAL_WIDTH)
        resize(imgBGR, imgBGR, Size(), scaleFactor, scaleFactor, INTER_CUBIC);

    // Convert to grayscale
    Mat imgGray;
    cvtColor(imgBGR, imgGray, CV_BGR2GRAY);
    IMSHOW("imgGray", imgGray);

    // Gausian filter
    GaussianBlur(imgBGR, imgBGR, Size(5,5), 3, 3);
    GaussianBlur(imgGray, imgGray, Size(5,5), 3, 3);
    // Bilateral filter
//    bilateralFilter(imgGray.clone(), imgGray, 9, 20, 9);
    // Apply Histogram Equalization
    equalizeHist( imgGray, imgGray );
    IMSHOW("imgGray proc", imgGray);
    // Rotate image according dominant lines (Haugh lines)
    Mat imgRot, imgBGRRot;
    float angle = 0;
    // Find rotation matrix
    Mat rotMat = mycvRotateAcDominantLines(imgGray, angle);

    // Rotate images
    warpAffine( imgGray, imgRot, rotMat, imgGray.size() );
    warpAffine( imgBGR, imgBGRRot, rotMat, imgBGR.size() );
//    imgGray.copyTo(imgRot);
//    imgBGR.copyTo(imgBGRRot);
    IMSHOW("imgBGRRot", imgBGRRot);
//    IMSHOW("imgRot", imgRot);

    // Create Red mask
    Mat imgRed;
    int blockSz = blockSize(imgRot.cols, 10);
    mycvFindRed(imgBGRRot, imgRed);
    // Connect near horizontal lines in red mask
    Mat elem = getStructuringElement(MORPH_RECT, Size(blockSz, blockSz*2));
    morphologyEx(imgRed, imgRed, CV_MOP_CLOSE, elem);
    // Remove too large regions in red mask
    mycvRemoveHighLowObjects(imgRed, imgRed, 0, imgRed.cols/2, imgRed.cols/2);
    IMSHOW("Thresh Red", imgRed);

    // Create black mask
    Mat imgToThresh, imgThresh;
    double min, max;
//    blockSz = blockSize(imgToThresh.cols, 50);
//    GaussianBlur(imgRot, imgToThresh, Size(blockSz,blockSz), 5, 5);
//    IMSHOW("Img Rot GaussianBlur", imgToThresh);
    // Suppress rexts using morphology
    blockSz = blockSize(imgRot.cols, 40);
    elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz*2, blockSz));
    morphologyEx(imgRot, imgToThresh, CV_MOP_CLOSE, elem);
    IMSHOW("Img Rot to thresh", imgToThresh);

    // Threahold image
    cv::minMaxLoc(imgGray, &min, &max);
//    threshold(imgToThresh, imgThresh, (max - min)/3, max, THRESH_BINARY_INV | CV_THRESH_OTSU);
    blockSz = blockSize(imgGray.cols, 4);
    adaptiveThreshold(imgToThresh, imgThresh, max, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSz, 1);
    IMSHOW("Thresh black", imgThresh);

    // Compose Red and Black masks
//    bitwise_or(imgThresh, imgRed, imgThresh);
    // Remove small holes
    blockSz = blockSize(imgRot.cols, 60);
    elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz*2, blockSz));
    morphologyEx(imgThresh, imgThresh, CV_MOP_CLOSE, elem);
//    IMSHOW("Thresh before removing smalls", imgThresh);
    // Remove too small dark objects
//    mycvRemoveHigherObjects(imgThresh, imgThresh, 0, 0, 0, imgThresh.cols/15, imgThresh.rows/30);
    IMSHOW("Thresh black&Red", imgThresh);

    // Dilatation
    blockSz = blockSize(imgRot.cols, 120);
    elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz*2, blockSz));
    morphologyEx(imgThresh, imgThresh, CV_MOP_DILATE, elem);
    IMSHOW("Thresh black Morph", imgThresh);

    /// Extract vertical edges
    Mat imgEdgesV;
    Sobel(imgRot, imgEdgesV, CV_8U, 1, 0, 3);
    IMSHOW("SOBEL", imgEdgesV);
    // Mask vertical image
    bitwise_and(imgThresh,imgEdgesV, imgEdgesV);
//    IMSHOW("SOBEL vertical", imgEdgesV);
    // Remove noise using minmax part threshold
    cv::minMaxLoc(imgEdgesV, &min, &max);
    Mat imgNoiseVMask;
    threshold(imgEdgesV, imgNoiseVMask, (max - min)/20, max, THRESH_BINARY);
    bitwise_and(imgEdgesV,imgNoiseVMask, imgEdgesV);
    IMSHOW("SOBEL vertical noise mask", imgEdgesV);

    // Threshold vertical edges
    Mat imgThreshV;
    blockSz = blockSize(2*imgEdgesV.cols, 3);
    cv::minMaxLoc(imgEdgesV, &min, &max);
    adaptiveThreshold(imgEdgesV, imgThreshV, max, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 1);
//    threshold(imgEdgesV, imgThreshV, max - 100, max, CV_THRESH_BINARY);
    IMSHOW("SOBEL V thresh", imgThreshV);
    // Remove too large objects
    int szMaxX = imgThreshV.cols/4;
    int szMaxY = imgThreshV.cols/4;
    mycvRemoveHighLowObjects(imgThreshV, imgThreshV, imgThreshV.cols*4, szMaxX, szMaxY);
//    IMSHOW("SOBEL V thresh NoHigh.", imgThreshV);

    // Remove small holes
    blockSz = blockSize(imgRot.cols, 120);
    elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz*2));
    morphologyEx(imgThreshV, imgThreshV, CV_MOP_CLOSE, elem);
    IMSHOW("SOBEL V thresh mask black", imgThreshV);

    Mat imgThreshVColor;
    cvtColor(imgThreshV, imgThreshVColor, CV_GRAY2BGR);

    /// Cluster analysis
    vector<sPoints4> textBlocks; // list of estimated text blocks
    mycvRowClusters(imgThreshV, imgThreshV, textBlocks);

    Mat imgThreshVColor2, imgRotColor2;
    cvtColor(imgThreshV, imgThreshVColor2, CV_GRAY2BGR);
    cvtColor(imgRot, imgRotColor2, CV_GRAY2BGR);

    // Draw before removing false rects
    for(size_t i = 0 ; i < textBlocks.size() ; i++)
    {
        rectangle(imgThreshVColor2, textBlocks[i].cornerLU, textBlocks[i].cornerPD, Scalar(255, 0, 0));
    }

    // Adjust size of rect according to red color
    mycvRemoveFalseFrames(textBlocks, imgThresh, imgRed);

    // Rotate orig. size image
    Point centerP(imgBGROrigSize.cols/2, imgBGROrigSize.rows/2);
    rotMat = getRotationMatrix2D( centerP, angle, 1 );
    warpAffine( imgBGROrigSize, imgBGROrigSize, rotMat, imgBGROrigSize.size() );
    Mat imgOrigBlack;
    cvtColor(imgBGROrigSize, imgOrigBlack, CV_BGR2GRAY);
//    blockSz = blockSize(imgRot.cols, 40);
//    elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz*2, blockSz));
//    morphologyEx(imgOrigBlack, imgOrigBlack, CV_MOP_CLOSE, elem);
////    IMSHOW("imgOrigBlack to thresh", imgOrigBlack);

    blockSz = blockSize(imgRot.cols, 3);
    adaptiveThreshold(imgOrigBlack, imgOrigBlack, max, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 1);

//    IMSHOW("imgOrigBlack", imgOrigBlack);

    // Image for cascades training
//    mImgNoFrame = imgBGROrigSize.clone();

    // Draw true rects    
    for(size_t i = 0 ; i < textBlocks.size() ; i++)
    {
//      crop&show
        // Apply back scale
        double scaleBack = 1/scaleFactor;
        sPoints4 actP4 = textBlocks[i];
        actP4.cornerLU.x *= scaleBack;
        actP4.cornerLU.y *= scaleBack;
        actP4.cornerPD.x *= scaleBack;
        actP4.cornerPD.y *= scaleBack;

        mycvAdjustFrame(actP4, imgOrigBlack);
        if( textBlocks[i].cornerPD.y - textBlocks[i].cornerLU.y <= 0 || \
            textBlocks[i].cornerPD.x - textBlocks[i].cornerLU.x <= 0)continue;
        Rect roi(actP4.cornerLU.x, actP4.cornerLU.y, actP4.cornerPD.x - actP4.cornerLU.x \
                 , actP4.cornerPD.y - actP4.cornerLU.y);

        Mat cropped = imgBGROrigSize(roi).clone();

        std::ostringstream ostream;
        ostream << "Frame " << i;

        IMSHOW(ostream.str(), cropped);
        mResFrames.push_back(cropped.clone());

        rectangle(imgThreshVColor2, textBlocks[i].cornerLU, textBlocks[i].cornerPD, Scalar(0, 255, 0), 2);
//        rectangle(imgOrigRot, textBlocks[i].cornerLU, textBlocks[i].cornerPD, Scalar(0, 255, 0), 2);


//        // Remove frame - for cascade training
//        if(roi.y + roi.height + 15 < imgBGROrigSize.rows && roi.y - 5 > 0 && \
//           roi.x + roi.width + 10 < imgBGROrigSize.cols && roi.x - 5 > 0)
//        {
//            Rect roi2 = roi;
//            roi2.y += roi.height + 5;
//            roi2.height += 10;
//            roi2.width += 10;

//            roi.y -= 5;
//            roi.x -= 5;
//            roi.height += 10;
//            roi.width += 10;

//            imgBGROrigSize(roi2).copyTo(mImgNoFrame(roi));
//        }
//        else if(roi.y - roi.height - 15 > 0 && roi.y + 5 < imgBGROrigSize.rows && \
//                roi.x - roi.width - 15 > 0 && roi.x + 5 < imgBGROrigSize.cols)
//        {
//            Rect roi2 = roi;
//            roi2.y -= roi.height - 5;

//            roi2.height += 10;
//            roi2.width += 10;

//            roi.y += 5;
//            roi.x += 5;
//            roi.height += 10;
//            roi.width += 10;

//            imgBGROrigSize(roi2).copyTo(mImgNoFrame(roi));
//        }
        rectangle(imgBGROrigSize, actP4.cornerLU, actP4.cornerPD, Scalar(0, 255, 0), 2);
    }

    IMSHOW("Threshold fill", imgThreshVColor2);
//    IMSHOW("Rotated original text founded", imgRotColor2);
    aDst = imgBGROrigSize.clone();
    measureTime("Detect frame");
}

void mycv::mycvRemoveFalseFrames(vector<sPoints4> &aTextBlocks, Mat &aImgBlackMask, const Mat &aImgRedMask)
{
    float correctRatio = 0.18;  // Frame ratio
    float tolerance = 40;       // in %
    int countRedsThresh = 0;

    if(aTextBlocks.size() == 0)return;
    // Adjust size of rect according to red color
    bool bRedFound = false;
    vector<bool> bRed;

    // Remove frames out of image
    for(int i = 0 ; i < (int)aTextBlocks.size() ; i++)
    {
        Point pt1 = aTextBlocks[i].cornerLU;
        Point pt2 = aTextBlocks[i].cornerPD;
        int xMax = aImgRedMask.cols;
        int yMax = aImgRedMask.rows;
        bool bBorderErr = (pt1.x < 0    || pt1.y < 0    || pt2.x < 0    || pt2.y < 0 || //
                           pt1.x > xMax || pt1.y > yMax || pt2.x > xMax || pt2.y > yMax);
        if(bBorderErr)
        {
            aTextBlocks.erase(aTextBlocks.begin()+i);
            i--;
        }
    }

    // Seek for red color from right to left
    for(int i = 0 ; i < (int)aTextBlocks.size() ; i++)
    {
        Point pt1 = aTextBlocks[i].cornerLU;
        Point pt2 = aTextBlocks[i].cornerPD;
        countRedsThresh = 1*(pt2.y-pt1.y)/3;
        bool bActRed = false;
        // Seek mask
        // First from left border to right
        for(int x = pt2.x; x > pt1.x; x--)
        {
            int countReds = 0;
            for(int y = pt1.y ; y < pt2.y ; y++)
            {
                if(aImgRedMask.at<uchar>(y, x) > 0)
                    countReds++;
            }
            if(countReds > countRedsThresh)
            {
                bRedFound = true;
                bActRed = true;
                pt2.x = x;
                aTextBlocks[i].cornerPD = pt2;
                break;
            }
        }

        if(!bActRed)
        {
            int x = pt2.x + (pt2.x-pt1.x)/2;
            if(x >= aImgRedMask.cols)x = aImgRedMask.cols-1;
            for(; x > pt2.x; x--)
            {
                int countReds = 0;
                for(int y = pt1.y ; y < pt2.y ; y++)
                {
                    if(aImgRedMask.at<uchar>(y, x) > 0)
                        countReds++;
                }
                if(countReds > countRedsThresh)
                {
                    bRedFound = true;
                    bActRed = true;
                    pt2.x = x;
                    aTextBlocks[i].cornerPD = pt2;
                    break;
                }
            }
        }
        bRed.push_back(bActRed);
    }

    // If red found -> remove frames without red
    for(int i = 0 ; i < (int)aTextBlocks.size() ; i++)
    {
        // remove without red if prev. found
        bool bAct = bRed[i];
        if(bRedFound && !bAct)
        {
            aTextBlocks.erase(aTextBlocks.begin()+i);
            bRed.erase(bRed.begin()+i);
            i--;
        }
    }

    // Check ratio
    for(int i = 0 ; i < (int)aTextBlocks.size() ; i++)
    {
        float rows = (float)(aTextBlocks[i].cornerPD.x - aTextBlocks[i].cornerLU.x);
        float cols = (float)(aTextBlocks[i].cornerPD.y - aTextBlocks[i].cornerLU.y);
        if(rows/cols < correctRatio-correctRatio*tolerance || rows/cols > correctRatio+correctRatio*tolerance)
        {
            aTextBlocks.erase(aTextBlocks.begin()+i);
            i--;
        }
    }

    // Leave only biggest rects
    uint biggestRect = 0;
    for(int i = 0 ; i < (int)aTextBlocks.size() ; i++)
    {
        uint actSz = (aTextBlocks[i].cornerPD.x-aTextBlocks[i].cornerLU.x)+(aTextBlocks[i].cornerPD.y-aTextBlocks[i].cornerLU.y);
        if(actSz > biggestRect)biggestRect = actSz;
    }

    for(int i = 0 ; i < (int)aTextBlocks.size() ; i++)
    {
        uint actSz = (aTextBlocks[i].cornerPD.x-aTextBlocks[i].cornerLU.x)+(aTextBlocks[i].cornerPD.y-aTextBlocks[i].cornerLU.y);
//        std::ostringstream ostream;
//        ostream /*<< "cols=" << aImgRedMask.cols << ", rows=" << aImgRedMask.rows*/ << "ActSz=" << actSz << ",max=" << biggestRect;
//        putText(aImgBGR, ostream.str(), aTextBlocks[i].cornerLU, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255));
        if(actSz < (biggestRect - biggestRect/5))
        {
            aTextBlocks.erase(aTextBlocks.begin()+i);
            i--;
        }
    }
}

void mycv::mycvAdjustFrame(sPoints4 &aTextBlock, const Mat &aImgBlackMask)
{
    float blackWhiteRatio = 0;
    int thresh = 100;
    float maxRatio = 0.5;
    // Adjust upper site
    int i = aTextBlock.cornerLU.y;
    while(blackWhiteRatio < maxRatio && i < aTextBlock.cornerPD.y-1)
    {
//            p = (uchar*)aImgBlackMask.ptr(i);
        int sum = 0;
        int dif = aTextBlock.cornerPD.x-aTextBlock.cornerLU.x;
        for(int j = 0 ; j < dif ; j++)
        {
            if(aImgBlackMask.at<uchar>(i, j+aTextBlock.cornerLU.x) < thresh)
                sum += 1;
//            sum += aImgBlackMask.at<uchar>(i, j+aTextBlock.cornerLU.x);
        }
        blackWhiteRatio = (float)sum/(float)dif;
        i++;
    }
    aTextBlock.cornerLU.y = i;

    // Adjust lower site
    blackWhiteRatio = 0;
    i = aTextBlock.cornerPD.y-1;
    while(blackWhiteRatio < maxRatio && i > aTextBlock.cornerLU.y)
    {
//            p = (uchar*)aImgBlackMask.ptr(i);
        int sum = 0;
        int dif = aTextBlock.cornerPD.x-aTextBlock.cornerLU.x;
        for(int j = 0 ; j < dif ; j++)
        {
            if(aImgBlackMask.at<uchar>(i, j+aTextBlock.cornerLU.x) < thresh)
                sum += 1;
//            sum += aImgBlackMask.at<uchar>(i, j+aTextBlock.cornerLU.x);
//                aImgBlackMask.at<uchar>(i, j) = 100;
        }
        blackWhiteRatio = (float)sum/(float)dif;
        i--;
    }
    aTextBlock.cornerPD.y = i;

    // Adjust left site
    blackWhiteRatio = 0;
    i = aTextBlock.cornerLU.x;
    while(blackWhiteRatio < maxRatio && i < aTextBlock.cornerPD.x-1)
    {
        int sum = 0;
        int dif = aTextBlock.cornerPD.y-aTextBlock.cornerLU.y;
        for(int j = 0 ; j < dif ; j++)
        {
            if(aImgBlackMask.at<uchar>(j+aTextBlock.cornerLU.y, i) < thresh)
                sum += 1;
//            sum += aImgBlackMask.at<uchar>(j+aTextBlock.cornerLU.y, i);
        }
        blackWhiteRatio = (float)sum/dif;
        i++;
    }
    aTextBlock.cornerLU.x = i;

    // Adjust right site
    blackWhiteRatio = 0;
    i = aTextBlock.cornerPD.x-1;
    while(blackWhiteRatio < maxRatio && i > aTextBlock.cornerLU.x)
    {
        int sum = 0;
        int dif = aTextBlock.cornerPD.y-aTextBlock.cornerLU.y;
        for(int j = 0 ; j < dif ; j++)
        {
            if(aImgBlackMask.at<uchar>(j+aTextBlock.cornerLU.y, i) < thresh)
                sum += 1;
//            sum += aImgBlackMask.at<uchar>(j+aTextBlock.cornerLU.y, i);
        }
        blackWhiteRatio = (float)sum/dif;
        i--;
    }
    aTextBlock.cornerPD.x = i;

}

void mycv::mycvRowClusters(const Mat &aImgThresh, Mat &aDst, vector<sPoints4> &aTxtBlocks)
{
    vector<int> rowFreks;
    mycvRowFreks(aImgThresh, rowFreks);
    int startLine = 0;

    aDst = aImgThresh.clone();

    Mat imgLines;
    cvtColor(aImgThresh, imgLines, CV_GRAY2BGR);

    for(size_t i = 1 ; i < rowFreks.size() ; i++)
    {
//        draw lines
        if(rowFreks[i-1]==1 && rowFreks[i]==0)
        {
            // draw start next text line
            line(imgLines, Point(0, i), Point(imgLines.cols-1, i), Scalar(rowFreks[i]*255, rowFreks[i-1]*255, 0), 2);
            startLine = i;
        }

        if(rowFreks[i-1]==0 && rowFreks[i]==1)
        {
            size_t rowHeigh = i - startLine;
            // draw end of text line
            line(imgLines, Point(0, i), Point(imgLines.cols-1, i), Scalar(rowFreks[i]*255, rowFreks[i-1]*255, 0), 2);
            // process last section

            for(size_t h = 0 ; h < (size_t)aImgThresh.cols ; h++)
            {
                size_t sum = 0; // sum for one collumn of actual text line
                for(size_t j = startLine ; j < startLine + rowHeigh ; j++)
                {
                    if(aImgThresh.at<uchar>(j, h)>0)
                    {
                        sum++;
                    }
                }
                //
                if(sum >= rowHeigh/3)
                {
                    for(size_t j = startLine ; j < startLine + rowHeigh ; j++)
                    {
                        aDst.at<uchar>(j, h) = 255;
                    }
                }
                else
                {
                    for(size_t j = startLine ; j < startLine + rowHeigh ; j++)
                    {
                        aDst.at<uchar>(j, h) = 0;
                    }
                }
            }

            uchar *p = (uchar*)aImgThresh.ptr<uchar>(startLine);
            bool bIsNeighbor = false;
            size_t horizStepB = (size_t)((float)rowHeigh*1.2); // max gap
            size_t horizStepW = (size_t)((float)rowHeigh*2); // max stripe
            size_t stripX1 = 0;
            size_t x1 = 0, x2 = 0; // left right boundaries
            uint numOfV = 0;
            for(size_t h = 1 ; h < (size_t)aDst.cols ; h++)
            {

                if(p[h-1]==0 && p[h]>0)
                {
                    stripX1 = h;
                    if(!bIsNeighbor)
                    {
                        numOfV = 0;
                        x1 = h;
                    }

                }

                if(p[h-1]>0 && p[h]==0)
                {
                    bIsNeighbor = false;
                    if((h-stripX1) > horizStepW)
                    {
                        x2 = stripX1;
                    }
                    else
                    {
                        x2 = h;

                        if(h + horizStepB < (size_t)aDst.cols)
                        {
                            for(size_t k = h ; k < h + horizStepB ; k++)
                            {
                                if(p[k]>0)
                                {
                                    bIsNeighbor=true;
                                    numOfV++;
                                    break;
                                }
                            }
                        }
                    }
                    if(!bIsNeighbor)
                    {
                        if(x2-x1 >= horizStepB)
                        {
                            sPoints4 rect;
                            int growth = rowHeigh/5;
                            rect.cornerLU = Point(x1-growth, startLine-growth);
                            rect.cornerPD = Point(x2+growth, rowHeigh+startLine+growth);
                            if(numOfV > 4 && numOfV < 25)
                                aTxtBlocks.push_back(rect);
                            bIsNeighbor = false;
                        }
                    }
                }
            }
        }

        if(rowFreks[i]==1)
        {
            for(int h = 0 ; h < aImgThresh.cols ; h++)
            {
                aDst.at<uchar>(i, h) = 0;
            }
        }
    }
    IMSHOW("imgLines", imgLines);
}

void mycv::mycvEqualizeIntensity(const Mat& aSrc, Mat &aDst)
{
    if(aSrc.channels() >= 3)
    {
        Mat ycrcb;

        cvtColor(aSrc,ycrcb,CV_BGR2YCrCb);

        vector<Mat> channels;
        split(ycrcb,channels);

        equalizeHist(channels[0], channels[0]);

        merge(channels,ycrcb);

        cvtColor(ycrcb,aDst,CV_YCrCb2BGR);
    }
    aDst = aSrc.clone();
}

void mycv::mycvFindRed(const Mat &aSrc, Mat &aDst)
{
    cvtColor(aSrc, aDst, CV_BGR2GRAY);
//    Mat imgHSV;
//    cvtColor(aSrc, imgHSV, CV_BGR2HSV);

    for(int i = 0 ; i < aSrc.rows ; i++)
    {
        for(int j = 1 ; j < aSrc.cols ; j++)
        {
            // RGB thresh
            uchar cR = (uchar) aSrc.at<Vec3b>(i,j)[2];
            uchar cG = (uchar) aSrc.at<Vec3b>(i,j)[1];
            uchar cB = (uchar) aSrc.at<Vec3b>(i,j)[0];

            uint div = 25, subBG = 40, minT = 30;

            if(cR > minT && (cR > (cG+div)) && (cR > (cB+div)) && (uint)abs(cB-cG) < subBG  \
                    /* && cR > 10 && cG > 10 && cB > 10 \
                     && cR < 230 */&& cG < 200 && cB < 200)
            {
                aDst.at<uchar>(i, j) = 255;
            }
            else aDst.at<uchar>(i, j) = 0;
            // HSV thresh
//            uchar cH = (uchar) imgHSV.at<Vec3b>(i,j)[0];
//            uchar cS = (uchar) imgHSV.at<Vec3b>(i,j)[1];
//            uchar cV = (uchar) imgHSV.at<Vec3b>(i,j)[2];
//            if((cH < 18 || cH > 225) && cS > 40 && cV > 50 && cV < 220)aDst.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
//            else aDst.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
        }
    }
}

QImage mycv::putImage(const Mat& mat)
{
    // 8-bits unsigned, NO. OF CHANNELS=1
    if(mat.type()==CV_8UC1)
    {
        // Set the color table (used to translate colour indexes to qBGR values)
        QVector<QRgb> colorTable;
        for (int i=0; i<256; i++)
            colorTable.push_back(qRgb(i,i,i));
        // Copy input Mat
        const uchar *qImageBuffer = (const uchar*)mat.data;
        // Create QImage with same dimensions as input Mat
        QImage img(qImageBuffer, mat.cols, mat.rows, mat.step, QImage::Format_Indexed8);
        img.setColorTable(colorTable);
        return img;
    }
    // 8-bits unsigned, NO. OF CHANNELS=3
    if(mat.type()==CV_8UC3)
    {
        // Copy input Mat
        const uchar *qImageBuffer = (const uchar*)mat.data;
        // Create QImage with same dimensions as input Mat
        QImage img(qImageBuffer, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
        return img.rgbSwapped();
    }
    else
    {
//        qDebug() << "ERROR: Mat could not be converted to QImage.";
        return QImage();
    }
}

struct myclass1 {
    bool operator() (cv::Vec2f aV1, cv::Vec2f aV2) { return (aV1[0] < aV2[0]);}
} objVect2f;

Mat mycv::mycvRotateAcDominantLines(const Mat &aSrc1Gray, float &aAngle)
{
    Mat imgScaled;
    int width = 800;
    double scaleFactor = (double)width/aSrc1Gray.cols;
    if(aSrc1Gray.cols > width)
        resize(aSrc1Gray, imgScaled, Size(), scaleFactor, scaleFactor, INTER_CUBIC);
    else
        imgScaled = aSrc1Gray.clone();

    Mat rot_mat( 2, 3, CV_32FC1 );
    // Hough lines
    Mat dst, cdst;
    Canny(imgScaled, dst, 30, 100, 3);
    cvtColor(dst, cdst, CV_GRAY2BGR);
//    imshow("Canny", dst);

////     Find linec using Hough transform
//    vector<Vec2f> lines;
//    mycvHoughLines(dst, lines);
//    //HoughLines(dst, lines, 1, CV_PI/180, 120);

//    mycvReduseLines(lines);

//    // Draw lines
//    for( size_t i = 0; i < lines.size(); i++ )
//    {
//        float rho = lines[i][0], theta = lines[i][1];
//        Point pt1, pt2;
//        double a = cos(theta), b = sin(theta);
//        double x0 = a*rho, y0 = b*rho;
//        // Create 2 points for line drawing
//        pt1.x = cvRound(x0 + 1000*(-b));
//        pt1.y = cvRound(y0 + 1000*(a));
//        pt2.x = cvRound(x0 - 1000*(-b));
//        pt2.y = cvRound(y0 - 1000*(a));
//        // Draw line
//        line(cdst, pt1, pt2, Scalar(0,255,0), 1, CV_AA);
//    }
//    imshow("detected lines", cdst);

    vector<Vec4i> lines, redLines;
    double min, max;
    cv::minMaxLoc(imgScaled, &min, &max);
    HoughLinesP(dst, lines, 1, CV_PI/180, (max-min)/3, dst.rows/10, dst.rows/100);
    float angle = 0;
    mycvReduseLines(lines, redLines, angle);
//    redLines = lines;

    if(redLines.size() == 0)return rot_mat;

    for( size_t i = 0; i < lines.size(); i++ )
    {
        Vec4i l = lines[i];
        line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255,0,0), 1, CV_AA);
    }
    for( size_t i = 0; i < redLines.size(); i++ )
    {
        Vec4i l = redLines[i];
        line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,255,0), 1, CV_AA);
    }

    // write angle value to image
    std::ostringstream ostream;
    ostream << angle;
    putText(cdst, "Angle = " + ostream.str(), Point(10, 30), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255));
    IMSHOW("detected lines", cdst);
    aAngle = angle;

/// Rotate acording to angle
    // Get the rotation matrix with the specifications above
    Point center = Point( aSrc1Gray.cols/2, aSrc1Gray.rows/2 );

    rot_mat = getRotationMatrix2D( center, angle, 1 );

    return rot_mat;
}

void mycv::mycvHoughLines(Mat &aImage, vector<Vec2f> &aLines)
{
    // Threshold in parametric space
    int thresh = aImage.cols/6;
//    cout << "Thresh = " << thresh << endl;
    // Find Hough lines
    HoughLines(aImage, aLines, 1, CV_PI/180, thresh);

}

void mycv::mycvRowFreks(const Mat &aImage, vector<int> &avFreks)
{
    int onesCount = 0;
    // for all rows
    uchar *p;
    for(int i = 0 ; i < aImage.rows ; i++)
    {
        // Row pseudo-frek = number of separeted white parts
        int actFrek = 0;
        p = (uchar*)aImage.ptr<uchar>(i);
        for(int j = 1 ; j < aImage.cols ; j++)
        {
            if((p[j-1] == 0 && p[j] != 0))
            {
                actFrek++;
            }
        }
        if(actFrek > 5 && actFrek < 35)
        {
            onesCount++;
        }
        else
        {
            if(onesCount > 0)
            {
                // Write ones only if there are more then ROW_FRES_MIN_ONES
                if(onesCount > ROW_FRES_MIN_ONES)
                {
                    while(onesCount > 0)
                    {
                        avFreks.push_back(0);
                        onesCount--;
                    }
                }
                else
                {
                    while(onesCount > 0)
                    {
                        avFreks.push_back(1);
                        onesCount--;
                    }
                }
            }
            avFreks.push_back(1);
        }
    }

}

void mycv::mycvFloodFill(Mat &aDst, Point aP, uint &aVol, sPoints4 &aSize, uchar aActVal, uchar aNewVal)
{
   if(aDst.at<uchar>(aP.x, aP.y) == aActVal)
   {
       aDst.at<uchar>(aP.x, aP.y) = aNewVal;

       // increase vol
       aVol++;

       // Actualize size of area
       if(aSize.cornerLU.x > aP.x)aSize.cornerLU.x = aP.x;
       if(aSize.cornerLU.y > aP.y)aSize.cornerLU.y = aP.y;
       if(aSize.cornerPD.x < aP.x)aSize.cornerPD.x = aP.x;
       if(aSize.cornerPD.y < aP.y)aSize.cornerPD.y = aP.y;

       aP.x++;
       if(aP.x < aDst.rows)mycvFloodFill(aDst, aP, aVol, aSize, aActVal, aNewVal);
       aP.x-=2;
       if(aP.x >= 0)mycvFloodFill(aDst, aP, aVol, aSize, aActVal, aNewVal);
       aP.x++;
       aP.y++;
       if(aP.y < aDst.cols && aP.x >= 0)mycvFloodFill(aDst, aP, aVol, aSize, aActVal, aNewVal);
       aP.y-=2;
       if(aP.y >= 0 && aP.x >= 0)mycvFloodFill(aDst, aP, aVol, aSize, aActVal, aNewVal);
   }
}

void mycv::mycvRemoveHorizontal(const Mat &aSrc, Mat &aDst)
{
    Mat pom = aSrc.clone();


}

void mycv::mycvRemoveBorderSmalls(const Mat &aSrc, Mat &aDst, int aMaxBorder)
{
    int rowIndx = aSrc.rows-1;
    Mat pom = aSrc.clone();
    for(int i = 0; i < aSrc.cols ; i++)
    {
        // Find white pixel
        if(pom.at<uchar>(0, i) > 1)
        {
            uint vol = 0;
            sPoints4 areaSize;
            areaSize.cornerLU.x = std::numeric_limits<int>::max();
            areaSize.cornerLU.y = std::numeric_limits<int>::max();
            mycvClassFloodFill cFF(pom.at<uchar>(0, i), 1);
            cFF.FloodFill2(pom, Point(0, i), vol, areaSize);
            if((areaSize.cornerPD.x - areaSize.cornerLU.x) < aMaxBorder)
            {
                mycvClassFloodFill cFF2( 1, 0);
                cFF2.FloodFill2(pom, Point(0, i), vol, areaSize);
            }
        }
        // Find white pixel
        if(pom.at<uchar>(rowIndx, i) > 1)
        {
            uint vol = 0;
            sPoints4 areaSize;
            areaSize.cornerLU.x = std::numeric_limits<int>::max();
            areaSize.cornerLU.y = std::numeric_limits<int>::max();
            mycvClassFloodFill cFF(pom.at<uchar>(rowIndx, i), 1);
            cFF.FloodFill2(pom, Point(rowIndx, i), vol, areaSize);
            if((areaSize.cornerPD.x - areaSize.cornerLU.x) < aMaxBorder)
            {
                mycvClassFloodFill cFF2( 1, 0);
                cFF2.FloodFill2(pom, Point(rowIndx, i), vol, areaSize);
            }
        }
    }
    // Ones back to value 255
    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.data;
    p[0] = 0;
    for(int i = 1 ; i < 256 ; i++)
        p[i] = 255;
    LUT(pom, lookUpTable, aDst);
}

void mycv::mycvRemoveHighLowObjects(const Mat &aSrc, Mat &aDst, uint aMaxVol, uint maxX, uint maxY, uint minX, uint minY, uint aMinVol)
{
// 5 times faster implementation than mycvRemoveHigherObjects2
    // Find contours
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(aSrc.clone(), contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);

    // Init output mat
    aDst = Mat::zeros(aSrc.size(), CV_8UC1);

    // Check all coutours
    vector<vector<Point>> holes;
    for(int i = 0 ; i < contours.size() ; i++)
    {
        // Find bounding rect inconvenient
        Rect rect = boundingRect(contours[i]);
        int vol = contourArea(contours[i]);

        // Store holes
        if(hierarchy[i][3] != -1)
        {
            holes.push_back(contours[i]);
        }

        // Check conditions - remove        
        if(vol >= aMaxVol && aMaxVol > 0)
        {
            contours.erase(contours.begin() + i);
            hierarchy.erase(hierarchy.begin() + i);
            i--;
        }
        else if(vol < aMinVol)
        {
            contours.erase(contours.begin() + i);
            hierarchy.erase(hierarchy.begin() + i);
            i--;
        }
        else if(((rect.height > (int)maxY && maxY != 0) || \
                (rect.width > (int)maxX) && maxX != 0))
        {
            contours.erase(contours.begin() + i);
            hierarchy.erase(hierarchy.begin() + i);
            i--;
        }
        else if(rect.height < (int)minY || \
                rect.width < (int)minX)
        {
            contours.erase(contours.begin() + i);
            hierarchy.erase(hierarchy.begin() + i);
            i--;
        }
    }

    // Draw contours
    for(int i = 0 ; i < contours.size() ; i++)
    {
        drawContours(aDst, contours, i, Scalar(255), CV_FILLED);
    }

    // Draw holes
    for(int i = 0 ; i < holes.size() ; i++)
    {
        drawContours(aDst, holes, i, Scalar(0), CV_FILLED);
        // Draw only contour to keep size
        drawContours(aDst, holes, i, Scalar(255));
    }

    // Remove alone contours - holes of deleted objects
    morphologyEx(aDst, aDst, CV_MOP_OPEN, getStructuringElement(MORPH_ELLIPSE, Size(3, 3)));

}

void mycv::mycvRemoveHighLowObjects2(const Mat &aSrc, Mat &aDst, uint aMaxVol, uint maxX, uint maxY, uint minX, uint minY, uint aMinVol)
{
    Mat pom = aSrc.clone();

    for(int i = 1 ; i < aSrc.rows - 1 ; i++)
    {
        for(int j = 1 ; j < aSrc.cols - 1 ; j++)
        {
            // Find white pixel
            if(pom.at<uchar>(i, j) > 125)
            {
                // Fill actual area with 1
                uint vol = 0;
                sPoints4 areaSize;
                areaSize.cornerLU.x = std::numeric_limits<int>::max();
                areaSize.cornerLU.y = std::numeric_limits<int>::max();
                mycvClassFloodFill cFF(pom.at<uchar>(i, j), 1);
                cFF.FloodFill2(pom, Point(i, j), vol, areaSize);
                //mycvFloodFill(pom, Point(i, j), vol, areaSize, pom.at<uchar>(i, j), 1);
                mycvClassFloodFill cFF2( 1, 0);
                // Check conditions
                if(vol >= aMaxVol && aMaxVol > 0)
                {
                    cFF2.FloodFill2(pom, Point(i, j), vol, areaSize);
                }
                else if(vol < aMinVol)
                {
                    cFF2.FloodFill2(pom, Point(i, j), vol, areaSize);
                }
                else if((((areaSize.cornerPD.x - areaSize.cornerLU.x) > (int)maxY && maxY != 0) || \
                        ((areaSize.cornerPD.y - areaSize.cornerLU.y) > (int)maxX) && maxX != 0))
                {
                    cFF2.FloodFill2(pom, Point(i, j), vol, areaSize);
                }
                else if((areaSize.cornerPD.x - areaSize.cornerLU.x) < (int)minY || \
                        (areaSize.cornerPD.y - areaSize.cornerLU.y) < (int)minX)
                {
                    cFF2.FloodFill2(pom, Point(i, j), vol, areaSize);
                }
            }
        }
    }
    // Ones back to value 255
    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.data;
    p[0] = 0;
    p[1] = 255;
    LUT(pom, lookUpTable, aDst);
}


void mycv::mycvReduseLines(vector<Vec4i> &aLinesCartesian, vector<Vec4i> &aLinesCartesianRed, float &aAngle)
{
    vector<Vec2f> linesCart, linesCartSort;
    // convert from Polar system Cartesian
    for( size_t i = 0; i < aLinesCartesian.size(); i++ )
    {
        int x1 = aLinesCartesian[i][0], y1 = aLinesCartesian[i][1];
        int x2 = aLinesCartesian[i][2], y2 = aLinesCartesian[i][3];
        // slope of line
        float k = atan((float)((float)y2-y1)/((float)x2-x1));
        // const term of line
        float q = (float)((float)x1*(y1-y2))/((float)x2-x1) + y1;

        // add to new vect
        Vec2f item;
        item[0] = k;
        item[1] = q;
        linesCart.push_back(item);

//        cout << "k = " << k << " , q = " << q << endl;
    }

    // Sort the slope
    linesCartSort = linesCart;
    int szHalf = linesCartSort.size()/2;
    float rad2Deg = (float)180/3.14;
    std::sort(linesCartSort.begin(), linesCartSort.end(), objVect2f);

    if(linesCartSort.size() == 0)return;

    float medianK = linesCartSort[szHalf][0]*rad2Deg;
//    float medianK = 0;

//    for(int i = -1 ; i <= 1 ; i++)
//        medianK += linesCartSort[szHalf + i][0]*rad2Deg;
//    medianK /= 3;
    aAngle = medianK;

//    for( size_t i = 0; i < linesCartSort.size(); i++ )
//    {
//        float k = linesCartSort[i][0];
//        float q = linesCartSort[i][1];
//        cout << i << " angle = " << k*180/3.14 << " , q = " << q << endl;
//    }
//    cout << " Median angle = " << aAngle << endl;

    for( size_t i = 0; i < aLinesCartesian.size(); i++ )
    {
        float k = linesCart[i][0]*rad2Deg;
        if(k > (-4 + aAngle) && k < (4 + aAngle))
        {
            aLinesCartesianRed.push_back(aLinesCartesian[i]);
        }
    }
}

void mycv::mycvReduseLines(vector<Vec2f> &aLinesCartesian)
{
    vector<Vec2f> linesCart;
    // convert from Polar system Cartesian
    for( size_t i = 0; i < aLinesCartesian.size(); i++ )
    {
        float rho = aLinesCartesian[i][0], theta = aLinesCartesian[i][1];
        float cosA = cos(theta), sinA = sin(theta);
        // slope of line
        float slopeOfLine = -cosA/sinA;
        // const term of line
        float constOfLine = rho/sinA;

        // add to new vect
        Vec2f item;
        item[0] = slopeOfLine;
        item[1] = constOfLine;
        linesCart.push_back(item);

//        cout << "Slope = " << slopeOfLine << " , const = " << constOfLine << endl;
    }

}

int mycv::maxVectInt(const vector<int> &aV)
{
    int max = 0;
    for(size_t i = 0 ; i < aV.size() ; i++)
    {
        if(max < aV[i])max = aV[i];
    }
    return max;
}
void mycv::maskBlackSpots(const Mat &aScr, Mat &aDst)
{
    Mat img = aScr.clone();
    Mat imgGray;
    // Scale to defined width
//    int width = GLOBAL_WIDTH;
//    double scaleFactor = (double)width/img.cols;
//    resize(img, imgGray, Size(), scaleFactor, scaleFactor, INTER_CUBIC);
    imgGray = img;
    Mat imgBGR = imgGray.clone();
    cvtColor(imgGray, imgGray, CV_BGR2GRAY);

    // Gausian filter
    GaussianBlur(imgGray, imgGray, Size(5,5), 4, 4);

    // Apply Histogram Equalization
    equalizeHist( imgGray, imgGray );
//    imshow("EQ. hist", imgGray);

    // Extract vertical edges only
//    Mat maskEdgeVertical = (Mat_<char>(3,3) << -1, 0, 1,-1, 0, 1,-1, 0, 1);
//    Mat imgEdgesV;
//    filter2D(imgGray, imgEdgesV, -1, maskEdgeVertical, Point( -1, -1 ));
//    imshow("SOBEL vertical", imgEdgesV);

    // Threshold
    Mat imgThresh;
    double min, max;
    cv::minMaxLoc(imgGray, &min, &max);
    threshold(imgGray, imgThresh, (max - min)/2, max, THRESH_BINARY_INV);
//    imshow("Thresh", imgThresh);

    // Morphology
    Mat imgMorph;
    cv::Mat element(imgThresh.rows/40,imgThresh.cols/40,CV_8U,cv::Scalar(1));

    // remove small holes
    cv::dilate(imgThresh, imgMorph, element);
    cv::erode(imgMorph, imgMorph, element);
    // remove small objects
    cv::erode(imgMorph, imgMorph, element);
    cv::dilate(imgMorph, imgMorph, element);

//    imshow("Morph", imgMorph);

//    cMeanShift *cMS = new cMeanShift();
//    Mat founded;
//    cMS->setWindSize(40);
//    cMS->findClusters(imgMorph, founded);

//    imshow("Clusters", founded*10);

    // Convert image to YCbCr
    Mat YCbCr;
    cvtColor(imgBGR, YCbCr, COLOR_BGR2YCrCb);
    vector<Mat> ycbcr_planes;
    split(YCbCr, ycbcr_planes);

//    Show Y, Cb and Cr separatly
//    imshow("Y", ycbcr_planes[0]);
//    imshow("Cr", ycbcr_planes[1]);
//    imshow("Cb", ycbcr_planes[2]);
    // Threshold
    Mat imgThresh2, matCr = ycbcr_planes[1].clone();
    equalizeHist( matCr, matCr );
    imshow("Cr eq", matCr);
    cv::minMaxLoc(matCr, &min, &max);
    threshold(matCr, imgThresh2, max - 20, max, THRESH_BINARY);
//    imshow("Thresh2", imgThresh2);
    // remove small objects
    cv::Mat element2(3,3,CV_8U,cv::Scalar(1));
    cv::erode(imgThresh2, imgThresh2, element2);
    cv::dilate(imgThresh2, imgThresh2, element2);

    // compose black and red spots masks
    bitwise_or(imgMorph, imgThresh2, imgMorph);
    imshow("Mask", imgMorph);

    // Binary mask
    bitwise_and(imgMorph, imgGray, imgThresh);
//    imshow("Bin mask", imgThresh);

    aDst = imgThresh.clone();
}
