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





5.00/5 (5投票s)
在本文中,我们将向您展示如何开发一个简单的运动检测器,并将其与训练过的 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_w
和 min_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 设备上测试我们的检测算法,并通过播放大声音来创建我们害虫清除器的“吓走害虫”部分。