void CascadeClassifierImpl::detectMultiScaleNoGrouping( InputArray _image, std::vector<Rect>& candidates,
                                                    std::vector<int>& rejectLevels, std::vector<double>& levelWeights,
                                                    double scaleFactor, Size minObjectSize, Size maxObjectSize,
                                                    bool outputRejectLevels )
{
    CV_INSTRUMENT_REGION();

    Size imgsz = _image.size();
    Size originalWindowSize = getOriginalWindowSize();

    if( maxObjectSize.height == 0 || maxObjectSize.width == 0 )
        maxObjectSize = imgsz;

    // If a too small image patch is entering the function, break early before any processing
    if( (imgsz.height < originalWindowSize.height) || (imgsz.width < originalWindowSize.width) )
        return;

    std::vector<float> all_scales, scales;
    all_scales.reserve(1024);
    scales.reserve(1024);

    // First calculate all possible scales for the given image and model, then remove undesired scales
    // This allows us to cope with single scale detections (minSize == maxSize) that do not fall on precalculated scale
    for( double factor = 1; ; factor *= scaleFactor )
    {
        Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) );
        if( windowSize.width > imgsz.width || windowSize.height > imgsz.height )
            break;
        all_scales.push_back((float)factor);
    }

    // This will capture allowed scales and a minSize==maxSize scale, if it is in the precalculated scales
    for( size_t index = 0; index < all_scales.size(); index++){
        Size windowSize( cvRound(originalWindowSize.width*all_scales[index]), cvRound(originalWindowSize.height*all_scales[index]) );
        if( windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height)
            break;
        if( windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height )
            continue;
        scales.push_back(all_scales[index]);
    }

    // If minSize and maxSize parameter are equal and scales is not filled yet, then the scale was not available in the precalculated scales
    // In that case we want to return the most fitting scale (closest corresponding scale using L2 distance)
    if( scales.empty() && !all_scales.empty() ){
        std::vector<double> distances;
        // Calculate distances
        for(size_t v = 0; v < all_scales.size(); v++){
            Size windowSize( cvRound(originalWindowSize.width*all_scales[v]), cvRound(originalWindowSize.height*all_scales[v]) );
            double d = (minObjectSize.width - windowSize.width) * (minObjectSize.width - windowSize.width)
                       + (minObjectSize.height - windowSize.height) * (minObjectSize.height - windowSize.height);
            distances.push_back(d);
        }
        // Take the index of lowest value
        // Use that index to push the correct scale parameter
        size_t iMin=0;
        for(size_t i = 0; i < distances.size(); ++i){
            if(distances[iMin] > distances[i])
                    iMin=i;
        }
        scales.push_back(all_scales[iMin]);
    }

    candidates.clear();
    rejectLevels.clear();
    levelWeights.clear();

#ifdef HAVE_OPENCL
    bool use_ocl = tryOpenCL && ocl::isOpenCLActivated() &&
         OCL_FORCE_CHECK(_image.isUMat()) &&
         !featureEvaluator->getLocalSize().empty() &&
         (data.minNodesPerTree == data.maxNodesPerTree) &&
         !isOldFormatCascade() &&
         maskGenerator.empty() &&
         !outputRejectLevels;
#endif

    Mat grayImage;
    _InputArray gray;

    if (_image.channels() > 1)
        cvtColor(_image, grayImage, COLOR_BGR2GRAY);
    else if (_image.isMat())
        grayImage = _image.getMat();
    else
        _image.copyTo(grayImage);
    gray = grayImage;

    if( !featureEvaluator->setImage(gray, scales) )
        return;

#ifdef HAVE_OPENCL
    // OpenCL code
    CV_OCL_RUN(use_ocl, ocl_detectMultiScaleNoGrouping( scales, candidates ))

    if (use_ocl)
        tryOpenCL = false;
#endif

    // CPU code
    featureEvaluator->getMats();
    {
        Mat currentMask;
        if (maskGenerator)
            currentMask = maskGenerator->generateMask(gray.getMat());

        size_t i, nscales = scales.size();
        cv::AutoBuffer<int> stripeSizeBuf(nscales);
        int* stripeSizes = stripeSizeBuf.data();
        const FeatureEvaluator::ScaleData* s = &featureEvaluator->getScaleData(0);
        Size szw = s->getWorkingSize(data.origWinSize);
        int nstripes = cvCeil(szw.width/32.);
        for( i = 0; i < nscales; i++ )
        {
            szw = s[i].getWorkingSize(data.origWinSize);
            stripeSizes[i] = std::max((szw.height/s[i].ystep + nstripes-1)/nstripes, 1)*s[i].ystep;
        }

        CascadeClassifierInvoker invoker(*this, (int)nscales, nstripes, s, stripeSizes,
                                         candidates, rejectLevels, levelWeights,
                                         outputRejectLevels, currentMask, &mtx);
        parallel_for_(Range(0, nstripes), invoker);
    }
}