def evaluateCurrentClassifier(adaBoostClassifierList, newAdaboostClassifier, vaildImageBundle, vaildSizeBundle):
    currentCascadeClassifier = CascadeClassifier(copy.deepcopy(adaBoostClassifierList))

    posSampleList = vaildImageBundle[0]
    negSampleList = vaildImageBundle[1]
    numOfPosSample = vaildSizeBundle[0]
    numOfNegSample = vaildSizeBundle[1]

    detectionCounter = 0
    falsePositiveCounter = 0

    if len(adaBoostClassifierList) == 0:
        for posSample in posSampleList:
            image = posSample.getImage()
            integralImage = Util.getIntegralImage(image)

            if newAdaboostClassifier.isFace(integralImage):
                detectionCounter += 1

        for negSample in negSampleList:
            image = negSample.getImage()
            integralImage = Util.getIntegralImage(image)

            if newAdaboostClassifier.isFace(integralImage):
                falsePositiveCounter += 1
    else:
        for posSample in posSampleList:
            image = posSample.getImage()
            integralImage = Util.getIntegralImage(image)

            if currentCascadeClassifier.isFace(integralImage):
                if newAdaboostClassifier.isFace(integralImage):
                    detectionCounter += 1

        for negSample in negSampleList:
            image = negSample.getImage()
            integralImage = Util.getIntegralImage(image)

            if currentCascadeClassifier.isFace(integralImage):
                if newAdaboostClassifier.isFace(integralImage):
                    falsePositiveCounter += 1

    return tuple([float(falsePositiveCounter) / numOfNegSample, float(detectionCounter) / numOfPosSample])
def getBestDecisionStump(feature, posSampleList, negSampleList, sumOfPosWeight, sumOfNegWeight):
    sampleImageList = posSampleList + negSampleList
    length = len(sampleImageList)
    sumOfLeftPosWeight = 0.0  # just represent the sum of positive weights on the left of threshold
    sumOfLeftNegWeight = 0.0  # just represent the sum of negative weights on the left of threshold
    threshold = (sampleImageList[0].getFeatureValue() + sampleImageList[1].getFeatureValue()) / 2
    polarity = POS_POLARITY
    error = sumOfPosWeight + sumOfNegWeight

    # calculate feature values for all training samples based on given feature
    for sampleImage in sampleImageList:
        image = sampleImage.getImage()
        integralImage = Util.getIntegralImage(image)
        featureValue = Util.featureExtracion(integralImage, feature)
        sampleImage.setFeatureValue(featureValue)

    # sort feature bundle list (quick sort in util)
    Util.quickSort(sampleImageList, 0, length - 1)

    # (for loop) get the threshold and polarity with lowest error of weight sum
    for i in xrange(length - 1):
        sampleImage = sampleImageList[i]
        sampleType = sampleImage.getSampleType()
        featureValue = sampleImage.getFeatureValue()
        nextFeatureValue = sampleImageList[i + 1].getFeatureValue()
        weight = sampleImage.getWeight()

        newThreshold = (featureValue + nextFeatureValue) / 2.0
        newPolarity = POS_POLARITY
        newError = error

        if sampleType == Util.FACE:
            sumOfLeftPosWeight += weight
        else:
            sumOfLeftNegWeight += weight

        error1 = sumOfLeftPosWeight + (sumOfNegWeight - sumOfLeftNegWeight)
        error2 = (sumOfPosWeight - sumOfLeftPosWeight) + sumOfLeftNegWeight
        if error1 > error2:
            newError = error2
        else:
            newPolarity = NEG_POLARITY
            newError = error1

        if newError < error:
            threshold = newThreshold
            polarity = newPolarity
            error = newError

    return DecisionStump(feature, threshold, polarity, error)
def getCascadeClassifier(maxFPR, minDR, targetFPR, sampleImageBundle, sampleSizeBundle):
    trainImageBundle = sampleImageBundle[0]
    vaildImageBundle = sampleImageBundle[1]
    trainSizeBundle = sampleSizeBundle[0]
    vaildSizeBundle = sampleSizeBundle[1]

    posTrainSampleList = trainImageBundle[0]
    negTrainSampleList = trainImageBundle[1]
    posTrainSampleSize = trainSizeBundle[0]
    negTrainSampleSize = trainSizeBundle[1]
    negTrainSampleSizeO = negTrainSampleSize

    adaBoostClassifierList = list()

    # initialise the false positive rate, detection rate and layer index
    fPR = 1.0
    dR = 1.0
    layerIndex = 0
    numOfFeature = 0  # for test only

    # generate each layer / stage
    print "===== start training cascade classifier ====="

    while (fPR > targetFPR) and (len(negTrainSampleList) > 0):
        layerIndex += 1
        # numOfFeature = 0  # original version
        newFRP = fPR
        newDR = dR

        print "===== ", layerIndex, " stage / adaboost classifier ====="

        # generate new stage via adaboost
        newAdaboostClassifier = None
        while newFRP > (maxFPR * fPR):
            numOfFeature += 1
            featureList = Util.getRandomFeatureSet(numOfFeature)
            trainImageBundle = tuple([posTrainSampleList, negTrainSampleList])
            trainSizeBundle = tuple([posTrainSampleSize, negTrainSampleSize])

            newAdaboostClassifier = getAdaboostClassifier(featureList, trainImageBundle, trainSizeBundle)

            # reduce the stage threshold via minus fix step size
            # (newFRP, newDR) = evaluateCurrentClassifier(adaBoostClassifierList,
            #                                             newAdaboostClassifier,
            #                                             vaildImageBundle,
            #                                             vaildSizeBundle)
            #
            # print '===== newFRP: ', newFRP, ', newDR: ', newDR, ', sT: ', newAdaboostClassifier.getStageThreshold(), ' ====='
            #
            # newStageThreshold = newAdaboostClassifier.getStageThreshold()
            # stepSize = abs(newStageThreshold) * DEFAULT_STEP_SIZE_RATIO
            # lowerBoundary = newStageThreshold - abs(newStageThreshold)
            # while (newDR < (minDR * dR)) and (newStageThreshold > lowerBoundary):
            #     # reduce the stage threshold
            #     newStageThreshold -= stepSize
            #     newAdaboostClassifier.setStageThreshold(newStageThreshold)
            #     (newFRP, newDR) = evaluateCurrentClassifier(adaBoostClassifierList,
            #                                                 newAdaboostClassifier,
            #                                                 vaildImageBundle,
            #                                                 vaildSizeBundle)
            #
            #     print '===== newFRP: ', newFRP, ', newDR: ', newDR, ', sT: ', newAdaboostClassifier.getStageThreshold(), ' ====='

            # TODO: correct this part to get correct result
            # reduce the stage threshold via binary search
            newStageThreshold = newAdaboostClassifier.getStageThreshold()
            highStageThreshold = newStageThreshold
            midStageThreshold = newStageThreshold
            lowStageThreshold = newStageThreshold - abs(newStageThreshold) - DEFAULT_STEP_SIZE_RATIO
            tolerantError = abs(newStageThreshold) * DEFAULT_TOLERANT_ERROR_RATIO
            if tolerantError <= 0.0:
                tolerantError = DEFAULT_EPSILON

            (newFRP, newDR) = evaluateCurrentClassifier(
                adaBoostClassifierList, newAdaboostClassifier, vaildImageBundle, vaildSizeBundle
            )

            print "===== newFRP: ", newFRP, ", newDR: ", newDR, ", highST: ", highStageThreshold, " ====="

            if newDR < (minDR * dR):
                newAdaboostClassifier.setStageThreshold(lowStageThreshold)
                (newFRP, newDR) = evaluateCurrentClassifier(
                    adaBoostClassifierList, newAdaboostClassifier, vaildImageBundle, vaildSizeBundle
                )

                print "===== newFRP: ", newFRP, ", newDR: ", newDR, ", lowST: ", lowStageThreshold, " ====="

                if newDR >= (minDR * dR):
                    while (highStageThreshold - lowStageThreshold) > tolerantError:
                        midStageThreshold = (highStageThreshold + lowStageThreshold) / 2.0
                        newAdaboostClassifier.setStageThreshold(midStageThreshold)
                        (newFRP, newDR) = evaluateCurrentClassifier(
                            adaBoostClassifierList, newAdaboostClassifier, vaildImageBundle, vaildSizeBundle
                        )

                        print "===== newFRP: ", newFRP, ", newDR: ", newDR, ", midST: ", midStageThreshold, " ====="

                        if newDR < (minDR * dR):
                            highStageThreshold = midStageThreshold
                        else:
                            lowStageThreshold = midStageThreshold

                    if newDR >= (minDR * dR):
                        newAdaboostClassifier.setStageThreshold(lowStageThreshold)
                    else:
                        newFRP = 1.0
                        newDR = 1.0

        fPR = newFRP
        dR = newDR
        adaBoostClassifierList.append(newAdaboostClassifier)
        print "===== get a new eligible adaboost classifier with fPR ", fPR, " dR ", dR, " ====="

        # clear negTrainSampleList
        # if newFRP > targetFPR, add new negative samples into negTrainSampleList, which satisfy
        # for current cascade classifier, the negative samples will be detected as faces
        # update negTrainSampleSize
        newNegTrainSampleList = list()
        if newFRP > targetFPR:
            currentCascadeClassifier = CascadeClassifier(copy.deepcopy(adaBoostClassifierList))
            for negTrainSample in negTrainSampleList:
                image = negTrainSample.getImage()
                integralImage = Util.getIntegralImage(image)
                if currentCascadeClassifier.isFace(integralImage):
                    newNegTrainSampleList.append(negTrainSample)
        negTrainSampleList = newNegTrainSampleList
        negTrainSampleSize = len(negTrainSampleList)

    return CascadeClassifier(adaBoostClassifierList, maxFPR, minDR, targetFPR, posTrainSampleSize, negTrainSampleSizeO)
def detectFaceFromCamera(cascadeClassifierFile=Util.DEFAULT_JSON_FILE,
                         scaleRatio=Util.DEFAULT_SCALE_RATIO):
    cascadeClassifier = getCascadeClassifierFromFile(cascadeClassifierFile)

    cap = cv2.VideoCapture(0)
    cv2.namedWindow('Face Detection', cv2.WINDOW_NORMAL)

    while (True):
        startTime = time.time()
        ret, inputImage = cap.read()

        height = len(inputImage)
        width = len(inputImage[0])

        # TODO: change the size of input image
        if height > 96 or width > 96:
            heightRatio = height / 96
            widthRatio = width / 96
            if heightRatio == 0:
                heightRatio = 1
            if widthRatio == 0:
                widthRatio = 1
            resizeRatio = 1.0 / min(heightRatio, widthRatio)
            inputImage = cv2.resize(inputImage, (0, 0), fx=resizeRatio, fy=resizeRatio)

        # inputImage = cv2.GaussianBlur(inputImage, (5, 5), 0)
        grayImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
        height = len(grayImage)
        width = len(grayImage[0])

        # retrieve all the scales to generate windows
        faceBundleList = list()
        winSize = Util.WIN_SIZE
        while (winSize <= height) and (winSize <= width):
            winSize *= scaleRatio
            trueWinSize = int(winSize)
            trueStepSize = trueWinSize / 5

            # slide window on each scale to detect face via cascade classifier
            for x in xrange(0, height - trueWinSize + 1, trueStepSize):
                for y in xrange(0, width - trueWinSize + 1, trueStepSize):
                    newWindow = grayImage[x:x + trueWinSize, y:y + trueWinSize]
                    scaledNewWindow = cv2.resize(newWindow, (Util.WIN_SIZE, Util.WIN_SIZE))
                    scaledIntegralImage = Util.getIntegralImage(scaledNewWindow)

                    # collect face area (rectangle), and rescale them back to original size
                    if cascadeClassifier.isFace(scaledIntegralImage):
                        rectangle = Util.Rectangle(x, y, trueWinSize, trueWinSize)
                        rectangleSize = rectangle.getSize()
                        faceBundleList.append(tuple([rectangle, rectangleSize]))

        # # TODO: sort the face in face bundle list (desc order)
        # redundancyIndexList = set()
        # length = len(faceBundleList)
        # Util.rectangleQuickSort(faceBundleList, 0, length - 1)

        # # TODO: check if larger one contains smaller one
        # for i in xrange(length - 1):
        #     for ii in xrange(i + 1, length):
        #         if faceBundleList[i][0].isContain(faceBundleList[ii][0]):
        #             redundancyIndexList.add(ii)
        #
        # redundancyIndexList = list(redundancyIndexList)
        # redundancyIndexList = sorted(redundancyIndexList, reverse=True)

        # # TODO: delete redundant rectangle
        # for index in redundancyIndexList:
        #     del faceBundleList[index]

        # TODO: draw rectangles
        # TODO: (optional) just draw large ones
        length = len(faceBundleList)
        # for faceBundle in faceBundleList:
        print 'face #: ', length
        startInex = 0
        endIndex = length
        # if length < 5:
        #     endIndex = length / 2 + 1
        # elif length < 10:
        #     endIndex = length / 5 + 1
        # else:
        #     endIndex = length / 10 + 1
        for faceBundle in faceBundleList[startInex:endIndex]:
            x = faceBundle[0].getX()
            y = faceBundle[0].getY()
            h = faceBundle[0].getH()
            w = faceBundle[0].getW()
            cv2.rectangle(inputImage, (y, x), (y + h, x + w), (255, 0, 0), 1)

        endTime = time.time()
        print 'FPS: ', 60.0 / (endTime - startTime)
        cv2.imshow('Face Detection', inputImage)
        if cv2.waitKey(40) & 0xFF == ord('q'):
            break

    return None