65.9K
CodeProject 正在变化。 阅读更多。
Home

使用运动检测器和训练过的 DNN 检测害虫

2020年12月23日

CPOL

4分钟阅读

viewsIcon

11792

downloadIcon

176

在本文中,我们将向您展示如何开发一个简单的运动检测器,并将其与训练过的 DNN 模型结合使用,以检测视频中的驼鹿。

引言

不受控制的野生动物对于企业和房主来说都是一个难题。像鹿、驼鹿甚至猫这样的动物都会对花园、庄稼和财产造成破坏。

在本系列文章中,我们将演示如何在树莓派上实时(或接近实时)检测害虫(例如驼鹿),然后采取行动来摆脱害虫。 由于我们不想造成任何伤害,我们将专注于通过播放大声噪音来吓走害虫。

欢迎您下载该项目的源代码。 我们假设您熟悉 Python 并且对神经网络的工作原理有基本的了解。

前一篇文章中,我们开发并训练了一个简单的分类器 DNN 模型,以预测视频帧中驼鹿的出现。 该模型的准确率测试为 97%,这对于我们的目的来说似乎足够好。 在本文中,我们将解释如何开发运动检测器以定位视频流中感兴趣的片段,以及如何将此检测器与分类器结合起来以捕获驼鹿。

检测运动

使用 OpenCV 库创建基本的运动检测器并不是很复杂。 在大多数情况下,我们可以使用背景减除器来实现运动检测。 背景减除算法将帧中的所有像素分成两个子集:背景(静态场景像素)和前景(当物体出现在帧中时动态变化的像素)。 这是一个简单的运动检测器在代码中的样子

class MD:
    def __init__(self, min_w, min_h):
        self.proc_width = 320
        self.proc_height = 240
        self.min_width = min_w
        self.min_height = min_h
        self.motion_objects = []
        self.subtractor = cv2.createBackgroundSubtractorMOG2(history=300)
        self.subtractor.setBackgroundRatio(0.005)
        self.fg_mask = None
        self.frame_count = 0
        
    def process(self, frame):
        p_frame = cv2.resize(frame, (self.proc_width, self.proc_height), cv2.INTER_AREA)
        
        bg_rate = 0.001
        if self.frame_count==0 :
            bg_rate = 1.0
        
        self.fg_mask = self.subtractor.apply(p_frame, bg_rate)
        
        if bg_rate>=1.0 :
            self.fg_mask[:] = 0
            self.motion_objects = {}
        else :
            self.fg_mask = self.cleanup(self.fg_mask)
            self.motion_objects = self.extract_objects(self.fg_mask, self.min_width, self.min_height)
            
        self.frame_count = self.frame_count+1

    def objects(self):
        return self.motion_objects
    
    def foreground(self):
        return self.fg_mask
    
    def cleanup(self, mask):
        (ret,mask) = cv2.threshold(mask,127,255,cv2.THRESH_BINARY)
        mask = cv2.medianBlur(mask, 5)
        (ret,mask) = cv2.threshold(mask,127,255,cv2.THRESH_BINARY)
        return mask
        
    def extract_objects(self, mask, min_w, min_h):
        (contimg, contours, hierarchy) = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        objects = []
        (h, w) = mask.shape
        for (i, contour) in enumerate(contours):
            (rx, ry, rw, rh) = cv2.boundingRect(contour)
            rx = rx/w
            ry = ry/h
            rw = rw/w
            rh = rh/h
            if (rw>=min_w) and (rh>=min_h) :
                rect = (rx, ry, rw, rh)
                objects.append(rect)
        
        return objects

在类初始化时,我们设置要检测的对象的最小尺寸:min_wmin_h。 这将帮助我们拒绝由于风、阳光变化等而出现的虚假“对象”。

初始化例程指定 320 x 240 像素的处理大小。 所有处理过的帧都将被调整大小以适应这些尺寸,以减少要处理的像素数量,从而提高处理速度。 在这里,我们还创建了 MOG2 背景减除器并使用一些参数值对其进行初始化。 这些值可能需要根据您正在使用的视频的分辨率进行更改。

该类的核心方法 process 检测帧中的移动对象。 它首先将帧调整为处理大小,然后使用减除器的 apply 方法获取前景掩码,最后提取运动片段(前景像素)的边界矩形。

请注意,对于第一个处理过的帧,我们将背景更新率参数 bg_rate 的值分配为 1.0,然后将此值更改为 0.001。 选择此低值专门用于测试视频场景。 它会导致缓慢的背景更新,因此检测器有足够的时间专注于移动对象。

实用程序方法 cleanup 清理由前景掩码产生的任何噪声。 extract_objects 方法评估相对坐标中移动对象的边界框。 这是必要的,因为视频帧的大小与处理帧的大小不同。

我们需要一个包装类来在视频文件上运行我们的运动检测器

class VideoMD:
    def __init__(self, md):
        self.md = md
        
    def play(self, file_path):
        capture = cv2.VideoCapture(file_path)
        
        fgd_name = 'Foreground'
        cv2.namedWindow(fgd_name, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(fgd_name, 640, 480)
        
        md_name = 'Motion objects'
        cv2.namedWindow(md_name, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(md_name, 640, 480)
       
        while(True):    
            (ret, frame) = capture.read()
            if frame is None:
                break
            
            self.md.process(frame)
            objects = self.md.objects()
            
            if len(objects)>0:
                Utils.draw_objects(objects, "OBJECT", (255, 0, 0), frame)
            
            # Display foreground
            fgd = self.md.foreground()
            cv2.imshow(fgd_name, fgd)
            
            # Display the resulting frame with object rects
            cv2.imshow(md_name, frame)
            
            time.sleep(0.040)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            
        capture.release()
        cv2.destroyAllWindows()

有了包装器,我们可以按如下方式启动检测器

video_file = r"C:\PI_PEST\video\moose_1.mp4"

md = MD(0.05, 0.1)
v_md = VideoMD(md)
v_md.play(video_file)

对于我们的视频文件,我们得到以下结果

将运动检测器与 DNN 模型结合使用

接下来,我们需要将我们的运动检测算法与训练过的 DNN 模型结合起来。 让我们从创建 PestDetector 类开始

class PestDetector:
    def __init__(self, proto, model, size):
        self.net = cv2.dnn.readNetFromCaffe(proto, model)
        self.size = size
    
    def get_blob(self, frame, obj):
        (h, w, c) = frame.shape
        (rx, ry, rw, rh) = obj
        rx = int(w*rx)
        ry = int(h*ry)
        rw = int(w*rw)
        rh = int(h*rh)
        
        if rh>rw :
            dx = int((rh-rw)/2)
            rx = rx-dx
            rw = rh
            if rx<0 :
                rx = 0
            if (rx+rw)>w :
                vx = w-(rx+rw)
                rw = rw - vx
                rh = rh - vx
        else :
            if rw>rh :
                dy = int((rw-rh)/2)
                ry = ry-dy
                rh = rw
                if ry<0 :
                    ry = 0
                if (ry+rh)>h :
                    vy = h-(ry+rh)
                    rh = rh - vy
                    rw = rw - vy
            
        img = frame[ry:ry+rh, rx:rx+rw]
        roi = (rx/w, ry/h, rw/w, rh/h)
            
        resized = cv2.resize(img, (self.size, self.size), cv2.INTER_AREA)
        blob = cv2.dnn.blobFromImage(resized, 1.0, (self.size, self.size), None, False, False)
        return (roi, blob)
    
    def detect(self, frame, obj):
        (roi, blob) = self.get_blob(frame, obj)
        self.net.setInput(blob)
        detection = self.net.forward()
        
        classes = detection.argmax(axis=1)
        class_num = classes[0]
        class_conf = detection[0][class_num]
        
        return (roi, (class_num, class_conf))

此类与我们在本系列的第二篇文章中讨论的 SSD 类相似。 在初始化时,它会创建一个基于指定的 DNN 模型的神经网络。 detect 方法接收一个帧和一个对象(如果帧是移动对象周围的矩形),并确定该对象的类。

请注意,此类的 get_blob 方法与 SSD 类的相应方法有何不同。 此方法将矩形片段重塑为正方形,以满足 DNN 分类器的输入要求。

接下来,我们将稍微修改 VideoMD 类,使其既可用于运动检测又可用于 DNN 分类

class VideoPD:
    def __init__(self, md, pd, thresh):
        self.md = md
        self.pd = pd
        self.thresh = thresh
        
    def play(self, file_path):
        capture = cv2.VideoCapture(file_path)
        
        md_name = 'Motion objects'
        cv2.namedWindow(md_name, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(md_name, 640, 480)
       
        while(True):    
            (ret, frame) = capture.read()
            if frame is None:
                break
            
            self.md.process(frame)
            objects = self.md.objects()
            
            l = len(objects)
            if l>0:
                Utils.draw_objects(objects, "OBJECT", (255, 0, 0), frame)
            
            pests = []
            if l>0 :
                for (i, obj) in enumerate(objects) :
                    (roi, (class_num, class_conf)) = self.pd.detect(frame, obj)
                    if (class_num>0) and (class_conf>=self.thresh) :
                        pests.append(roi)
            
            k = len(pests)
            if k>0:
                Utils.draw_objects(pests, "PEST", (0, 0, 255), frame)
            
            # Display the resulting frame with object rects
            cv2.imshow(md_name, frame)
            
            time.sleep(0.040)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            
        capture.release()
        cv2.destroyAllWindows()

最后,我们可以使用以下代码启动驼鹿检测器

video_file = r"C:\PI_PEST\video\moose_1.mp4"

md = MD(0.05, 0.1)
proto = r"C:\PI_PEST\net\moose.prototxt"
model = r"C:\PI_PEST\net\moose.caffemodel"
pd = PestDetector(proto, model, 128)

v_pd = VideoPD(md, pd, 0.99)
v_pd.play(video_file)

这是结果视频

正如我们所看到的,我们开发的解决方案已经设法在每次驼鹿出现时都虚拟地检测到它。

后续步骤

下一篇文章中,我们将在 Raspberry Pi 3 设备上测试我们的检测算法,并通过播放大声音来创建我们害虫清除器的“吓走害虫”部分。

© . All rights reserved.