/*
 * mycv.cpp
 *
 *  Created on: 3. 5. 2015
 *      Author: Jirka
 */

#include "mycv.h"


mycv::mycv()
{

}

mycv::~mycv()
{

}

void mycv::segmentDigits(const Mat &aSrc, Mat &aDst, vector<Mat> &aDigits, Mat &aPatt)
{
	mycvDigitsSegm cDigitsSegm;
//	cDigitsSegm.loadPatterns(aPatt);

	// Segment digits
	cDigitsSegm.segmentNum(aSrc, aDst, aDigits, MODE_DETECT_CONTOURS);
}

void mycv::adjustFrame(const Mat &aSrc, Mat &aDst, vector<Rect> &aRects)
{
	Mat imgOrigBlack;
	cvtColor(aSrc, imgOrigBlack, CV_BGR2GRAY);
    int blockSz = blockSize(imgOrigBlack.cols, 3);
    adaptiveThreshold(imgOrigBlack, imgOrigBlack, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 1);

    // Adjust frame
    sPoints4 actP4;
    actP4.cornerLU.x = aRects[0].x;
    actP4.cornerLU.y = aRects[0].y;
    actP4.cornerPD.x = aRects[0].x + aRects[0].width;
    actP4.cornerPD.y = aRects[0].y + aRects[0].height;
    mycvAdjustFrame(actP4, imgOrigBlack);

    Rect roi(actP4.cornerLU.x, actP4.cornerLU.y, actP4.cornerPD.x - actP4.cornerLU.x \
                     , actP4.cornerPD.y - actP4.cornerLU.y);
    aDst = aSrc(roi).clone();
}

void mycv::detectFrame(const Mat &aScr, Mat &aDst, vector<Rect> &aRects)
{
	/// Load image
	Mat imgBGR = aScr.clone();

	// 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);

	// 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 );
	// 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);

	// 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);

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

	// 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);


	// 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);

	// Remove too small dark objects
//    mycvRemoveHighLowObjects(imgThresh, imgThresh, 0, 0, 0, imgThresh.cols/15, imgThresh.rows/30);


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


	/// Extract vertical edges
	Mat imgEdgesV;
	Sobel(imgRot, imgEdgesV, CV_8U, 1, 0, 3);

	// Mask vertical image
	bitwise_and(imgThresh,imgEdgesV, 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);


	// 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);
	// Remove too large objects
	int szMaxX = imgThreshV.cols/4;
	int szMaxY = imgThreshV.cols/4;
	mycvRemoveHighLowObjects(imgThreshV, imgThreshV, imgThreshV.cols*4, szMaxX, szMaxY);

	// Remove small holes
	blockSz = blockSize(imgRot.cols, 120);
	elem = getStructuringElement(MORPH_ELLIPSE, Size(blockSz, blockSz*2));
	morphologyEx(imgThreshV, imgThreshV, CV_MOP_CLOSE, elem);

	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
	Mat imgBGROrigSize = aScr.clone();
//	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, 3);
	adaptiveThreshold(imgOrigBlack, imgOrigBlack, max, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSz, 1);

	// 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;

		Rect roi(actP4.cornerLU.x, actP4.cornerLU.y, actP4.cornerPD.x - actP4.cornerLU.x \
				 , actP4.cornerPD.y - actP4.cornerLU.y);

		aRects.push_back(roi);

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

	aDst = imgBGROrigSize;
}

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);

    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));

    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::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;

    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::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();

    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(imgThreshVColor, Point(0, i), Point(imgThreshVColor.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(imgThreshVColor, Point(0, i), Point(imgThreshVColor.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;
            }
        }
    }
}

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::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);
        }
    }
}

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

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::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);
        }
    }
}

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;
}

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);
}
