Background subtraction in opencv2

2019-05-25 09:06发布

I am trying to detect foreground motion using opencv2 by removing static (mostly) BG elements. The method I am using is based on taking the mean of a series of images - representing the background. Then calculating one Standard deviation above and below that mean. Using that as a window to detect foreground motion.

This mechanism reportedly works well for moderately noisy environments like waving trees in the BG.

The desired output is a mask that can be used in a subsequent operation so as to minimise further processing. Specifically I am going to use optical flow detection within that region.

cv2 has made this much easier and the code is much simpler to read and understand. Thanks cv2 and numpy.

But I am having difficulty doing the correct FG detection.

Ideally I also want to erode/dilate the BG mean so as to eleminate 1 pixel noise.

The code is all togethr so you have a number of frames at the start (BGsample) to gather the BG data before FG detection starts. the only dependencies are opencv2 (> 2.3.1 ) and numpy (which should be included in > opencv 2.3.1 )

import cv2
import numpy as np


if __name__ == '__main__': 
    cap = cv2.VideoCapture(0) # webcam
    cv2.namedWindow("input")
    cv2.namedWindow("sig2")
    cv2.namedWindow("detect")
    BGsample = 20 # number of frames to gather BG samples from at start of capture
    success, img = cap.read()
    width = cap.get(3)
    height = cap.get(4)
    # can use img.shape(:-1) # cut off extra channels
    if success:
        acc = np.zeros((height, width), np.float32) # 32 bit accumulator
        sqacc = np.zeros((height, width), np.float32) # 32 bit accumulator
        for i in range(20): a = cap.read() # dummy to warm up sensor
        # gather BG samples
        for i in range(BGsample):
            success, img = cap.read()
            frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            cv2.accumulate(frame, acc)
            cv2.accumulateSquare(frame, sqacc)
        #
        M = acc/float(BGsample)
        sqaccM = sqacc/float(BGsample)
        M2 = M*M
        sig2 = sqaccM-M2
        # have BG samples now
        # start FG detection
        key = -1
        while(key < 0):
            success, img = cap.read()
            frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            #Ideally we create a mask for future use that is B/W for FG objects
            # (using erode or dilate to remove noise)
            # this isn't quite right
            level = M+sig2-frame
            grey = cv2.morphologyEx(level, cv2.MORPH_DILATE,
                                    cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)), iterations=2)
            cv2.imshow("input", frame)
            cv2.imshow("sig2", sig2/60)
            cv2.imshow("detect", grey/20)
            key = cv2.waitKey(1)
    cv2.destroyAllWindows()

2条回答
手持菜刀,她持情操
2楼-- · 2019-05-25 09:22

my best guess so far. Using detectmin, max to coerce the fp sigma into grayscale for the cv2.inRange to use. Seems to work OK but was hoping for better... plenty of holes in valid FG data. I suppose it would work better in rgb instead of grayscale. Can't get noise reduction using dilate or erode to work.

Any improvements ?

import cv2
import numpy as np


if __name__ == '__main__': 
    cap = cv2.VideoCapture(1)
    cv2.namedWindow("input")
    #cv2.namedWindow("sig2")
    cv2.namedWindow("detect")
    BGsample = 20 # number of frames to gather BG samples from at start of capture
    success, img = cap.read()
    width = cap.get(3)
    height = cap.get(4)
    if success:
        acc = np.zeros((height, width), np.float32) # 32 bit accumulator
        sqacc = np.zeros((height, width), np.float32) # 32 bit accumulator
        for i in range(20): a = cap.read() # dummy to warm up sensor
        # gather BG samples
        for i in range(BGsample):
            success, img = cap.read()
            frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            cv2.accumulate(frame, acc)
            cv2.accumulateSquare(frame, sqacc)
        #
        M = acc/float(BGsample)
        sqaccM = sqacc/float(BGsample)
        M2 = M*M
        sig2 = sqaccM-M2
        # have BG samples now
        # calculate upper and lower bounds of detection window around mean.
        # coerce into 8bit image space for cv2.inRange compare
        detectmin = cv2.convertScaleAbs(M-sig2)
        detectmax = cv2.convertScaleAbs(M+sig2)
        # start FG detection
        key = -1
        while(key < 0):
            success, img = cap.read()
            frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            level = cv2.inRange(frame, detectmin, detectmax)
            cv2.imshow("input", frame)
            #cv2.imshow("sig2", M/200)
            cv2.imshow("detect", level)
            key = cv2.waitKey(1)
    cv2.destroyAllWindows()
查看更多
Emotional °昔
3楼-- · 2019-05-25 09:42

I don't think you need to manually compute the mean and standard deviation use cv2.meanStdDev instead. In the code below, I'm using your average background matrix computed from

M = acc/float(BGsample) 

So, now we can compute the mean and standard deviation of the average background image, and finally inRange is used to pull out the range that you wanted (i.e., the mean +/- 1 standard deviation).

(mu, sigma) = cv2.meanStdDev(M)
fg = cv2.inRange(M, (mu[0] - sigma[0]), (mu[0] + sigma[0]))
# proceed with morphological clean-up here...

Hope that helps!

查看更多
登录 后发表回答