运动目标检测ViBe算法的numpy实现

    ViBe算法是一种优秀的运动目标检测,其背景建模是基于像素的邻域来建立,算法的具体原理我就不介绍了,网上可以搜到一大堆。网上提供的实现方法多是遍历图像中每个像素建立背景模型,有用c/c++实现的,也有用python实现的。我实验了一下,基于c/c++实现的基本能满足实时要求,用python遍历像素的方式完全不能用,速度太慢。由于我需要用python实现视频中的烟雾检测,运动目标检测只是第一步,如果用调用库函数的方式又显得太麻烦。既然遍历像素的方式太慢,能不能充分利用numpy批量操作数组的优势,用numpy实现ViBe算法?我试着写了一下,顺便熟悉掌握了numpy中各种数组操作。完成后基本能够满足实时要求,比逐像素遍历的方法要快多了。废话了半天,下面上代码。

 

#! /usr/bin/env python
import numpy as np
import cv2


class ViBe:
    '''
    ViBe运动检测,分割背景和前景运动图像
    '''
    def __init__(self,num_sam=20,min_match=2,radiu=20,rand_sam=16):
        self.defaultNbSamples = num_sam            #每个像素的样本集数量,默认20个
        self.defaultReqMatches = min_match          #前景像素匹配数量,如果超过此值,则认为是背景像素
        self.defaultRadius = radiu                  #匹配半径,即在该半径内则认为是匹配像素
        self.defaultSubsamplingFactor = rand_sam    #随机数因子,如果检测为背景,每个像素有1/defaultSubsamplingFactor几率更新样本集和领域样本集
    
        self.background = 0 
        self.foreground = 255
    
    def __buildNeighborArray(self,img):
        '''
        构建一副图像中每个像素的邻域数组
        参数:输入灰度图像
        返回值:每个像素9邻域数组,保存到self.samples中
        '''
        height,width=img.shape
        self.samples=np.zeros((self.defaultNbSamples,height,width),dtype=np.uint8)
        
        #生成随机偏移数组,用于计算随机选择的邻域坐标
        ramoff_xy=np.random.randint(-1,2,size=(2,self.defaultNbSamples,height,width))
        #ramoff_x=np.random.randint(-1,2,size=(self.defaultNbSamples,2,height,width))
        
        #xr_=np.zeros((height,width))
        xr_=np.tile(np.arange(width),(height,1))
        #yr_=np.zeros((height,width))
        yr_=np.tile(np.arange(height),(width,1)).T
        
        xyr_=np.zeros((2,self.defaultNbSamples,height,width))
        for i in range(self.defaultNbSamples):
            xyr_[1,i]=xr_
            xyr_[0,i]=yr_
            
        xyr_=xyr_+ramoff_xy
        
        xyr_[xyr_<0]=0
        tpr_=xyr_[1,:,:,-1]
        tpr_[tpr_>=width]=width-1
        tpb_=xyr_[0,:,-1,:]
        tpb_[tpb_>=height]=height-1
        xyr_[0,:,-1,:]=tpb_
        xyr_[1,:,:,-1]=tpr_
        
        #xyr=np.transpose(xyr_,(2,3,1,0))
        xyr=xyr_.astype(int)
        self.samples=img[xyr[0,:,:,:],xyr[1,:,:,:]]
    
        
    def ProcessFirstFrame(self,img):
        '''
        处理视频的第一帧
        1、初始化每个像素的样本集矩阵
        2、初始化前景矩阵的mask
        3、初始化前景像素的检测次数矩阵
        参数:
        img: 传入的numpy图像素组,要求灰度图像
        返回值:
        每个像素的样本集numpy数组
        '''
        self.__buildNeighborArray(img)
        self.fgCount=np.zeros(img.shape)    #每个像素被检测为前景的次数
        self.fgMask=np.zeros(img.shape)     #保存前景像素
    
    def Update(self,img):
        '''
        处理每帧视频,更新运动前景,并更新样本集。该函数是本类的主函数
        输入:灰度图像
        '''
        height,width=img.shape
        #计算当前像素值与样本库中值之差小于阀值范围RADIUS的个数,采用numpy的广播方法
        dist=np.abs((self.samples.astype(float)-img.astype(float)).astype(int))
        dist[dist         dist[dist>=self.defaultRadius]=0
        matches=np.sum(dist,axis=0)
        #如果大于匹配数量阀值,则是背景,matches值False,否则为前景,值True
        matches=matches         self.fgMask[matches]=self.foreground
        self.fgMask[~matches]=self.background
        #前景像素计数+1,背景像素的计数设置为0
        self.fgCount[matches]=self.fgCount[matches]+1
        self.fgCount[~matches]=0
        #如果某个像素连续50次被检测为前景,则认为一块静止区域被误判为运动,将其更新为背景点
        fakeFG=self.fgCount>50
        matches[fakeFG]=False
        #此处是该更新函数的关键
        #更新背景像素的样本集,分两个步骤
        #1、每个背景像素有1/self.defaultSubsamplingFactor几率更新自己的样本集
        ##更新样本集方式为随机选取该像素样本集中的一个元素,更新为当前像素的值
        #2、每个背景像素有1/self.defaultSubsamplingFactor几率更新邻域的样本集
        ##更新邻域样本集方式为随机选取一个邻域点,并在该邻域点的样本集中随机选择一个更新为当前像素值        
        #更新自己样本集
        upfactor=np.random.randint(self.defaultSubsamplingFactor,size=img.shape) #生成每个像素的更新几率
        upfactor[matches]=100   #前景像素设置为100,其实可以是任何非零值,表示前景像素不需要更新样本集
        upSelfSamplesInd=np.where(upfactor==0)    #满足更新自己样本集像素的索引
        upSelfSamplesPosition=np.random.randint(self.defaultNbSamples,size=upSelfSamplesInd[0].shape)  #生成随机更新自己样本集的的索引
        samInd=(upSelfSamplesPosition,upSelfSamplesInd[0],upSelfSamplesInd[1])
        self.samples[samInd]=img[upSelfSamplesInd]  #更新自己样本集中的一个样本为本次图像中对应像素值

        #更新邻域样本集
        upfactor=np.random.randint(self.defaultSubsamplingFactor,size=img.shape) #生成每个像素的更新几率
        upfactor[matches]=100   #前景像素设置为100,其实可以是任何非零值,表示前景像素不需要更新样本集
        upNbSamplesInd=np.where(upfactor==0)    #满足更新邻域样本集背景像素的索引
        nbnums=upNbSamplesInd[0].shape[0]
        ramNbOffset=np.random.randint(-1,2,size=(2,nbnums))  #分别是X和Y坐标的偏移
        nbXY=np.stack(upNbSamplesInd)
        nbXY+=ramNbOffset
        nbXY[nbXY<0]=0
        nbXY[0,nbXY[0,:]>=height]=height-1
        nbXY[1,nbXY[1,:]>=width]=width-1
        nbSPos=np.random.randint(self.defaultNbSamples,size=nbnums)
        nbSamInd=(nbSPos,nbXY[0],nbXY[1])
        self.samples[nbSamInd]=img[upNbSamplesInd]
  

    def getFGMask(self):
        '''
        返回前景mask
        '''
        return self.fgMask


def main():
    vc = cv2.VideoCapture("/home/vernon/projects/opencv/背景分割算法/Video/Camera Road 01.avi")
    c = 0
    if vc.isOpened():
        rval, frame = vc.read()
    else:
        rval = False
        
    frame=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    vibe=ViBe()
    vibe.ProcessFirstFrame(frame)
    #samples = np.zeros((frame.shape[0],frame.shape[1], defaultNbSamples))
    cv2.namedWindow("frame",cv2.WINDOW_NORMAL)
    cv2.namedWindow("segMat",cv2.WINDOW_NORMAL)
    
    while rval:
        rval, frame = vc.read()
        
        # 将输入转为灰度图
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        # 输出二值图
        #(segMat, samples) = update(gray, samples)
        vibe.Update(gray)
        segMat=vibe.getFGMask()
        # 转为uint8类型
        segMat = segMat.astype(np.uint8)
        # 形态学处理模板初始化
        #kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
        # 开运算
        #opening = cv2.morphologyEx(segMat, cv2.MORPH_OPEN, kernel1)
        # 形态学处理模板初始化
        #kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
        # 闭运算
        #closed = cv2.morphologyEx(segMat, cv2.MORPH_CLOSE, kernel2)

        # 寻找轮廓
        #contours, hierarchy = cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        #for i in range(0, len(contours)):
        #        x, y, w, h = cv2.boundingRect(contours[i])
        #        print(w * h)
        #        if w * h > 400 and w * h < 10000:
        #            cv2.rectangle(frame, (x, y), (x + w, y + h),(0, 255, 0), 2)
        cv2.imshow("frame", frame)
        cv2.imshow("SegMat",segMat)
        #cv2.imwrite("./result/" + str(c) + ".jpg", frame,[int(cv2.IMWRITE_PNG_STRATEGY)])
        k = cv2.waitKey(1)
        if k == 27:
            vc.release()
            cv2.destroyAllWindows()
            break
        c = c + 1


main()

你可能感兴趣的:(运动目标检测ViBe算法的numpy实现)