如果家中有多个树莓派在吃灰,可以拿出来做个简单的家庭监控系统,比如监控门前的车辆,鱼缸里的鱼儿,养的宠物等。
1.MobileNet-SSD
MobileNet-SSD是MobileNet的改版,mobilenet论文地址:《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》
MobileNet-SSD项目地址:https://github.com/chuanqi305/MobileNet-SSD
这里主要用到了预训练模型deploy,也可以自己重新训练模型。
2.IMAGEZMQ
imagezmq是用于分布式图像处理网络的易于使用的图像传输机制。imagezmq是基于ZMQ开发的,我们先了解一下ZMQ。
2.1 ZMQ
ZMQ(ØMQ、ZeroMQ)看起来像是一套嵌入式的网络链接库,但工作起来更像是一个并发式的框架。它提供的套接字可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。
请求-回复(REQ / REP):
拿Hello World举例,我们会创建一个客户端和一个服务端,客户端发送Hello给服务端,服务端返回World。
发布-订阅(PUB / SUB):
这种消息模式是单向数据分发,服务端将更新事件发送给一组客户端。
分布式处理:
- 任务分发器会生成大量可以并行计算的任务
- 有一组worker会处理这些任务
- 结果收集器会在末端接收所有worker的处理结果,进行汇总
2.2 imagezmq
imagezmq是一组Python类,它们使用PyZMQ消息传递将OpenCV图像从一台计算机传输到另一台计算机。项目地址:https://github.com/jeffbass/imagezmq
3.部署代码
控制流程图:
这里的使用两个树莓派,一个是pi-3,另一个是pi-4。服务端用win10电脑。
3.1 服务端部署
在win10电脑上,python环境下安装必要的包:
# 运行命令,--montageW=2为横向两个屏幕(两列),--montageH=1竖向一行,可根据自己树莓派数量自行调整
# python server.py --prototxt MobileNetSSD_deploy.prototxt --model MobileNetSSD_deploy.caffemodel --montageW 2 --montageH 1
from imutils import build_montages
from datetime import datetime
import numpy as np
import imagezmq
import argparse
import imutils
import cv2
# 构造参数
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--prototxt", required=True,
help="Caffe MobileNetSSD网络路径")
ap.add_argument("-m", "--model", required=True,
help="Caffe MobileNetSSD 预训练模型路径")
ap.add_argument("-c", "--confidence", type=float, default=0.2,
help="置信度阈值,过虑低于此值的检测目标")
ap.add_argument("-mW", "--montageW", required=True, type=int,
help="蒙太奇图像宽度")
ap.add_argument("-mH", "--montageH", required=True, type=int,
help="蒙太奇图像高度")
args = vars(ap.parse_args())
# 初始化 ImageHub对象,接受来自每个Raspberry Pi的连接,
# 它实质上是用socket和ZMQ来接收帧(并发送回确认信息)
imageHub = imagezmq.ImageHub()
# MobileNet-SSD的分类标签,一共21类(算上背景)
CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat",
"bottle", "bus", "car", "cat", "chair", "cow", "diningtable",
"dog", "horse", "motorbike", "person", "pottedplant", "sheep",
"sofa", "train", "tvmonitor"]
# 加载模型
print("模型加载中...")
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
# 设置想要监控的类别,根据自己的需求添加,我这里只监控一类:人
CONSIDER = set(["person"])
objCount = {obj: 0 for obj in CONSIDER}
frameDict = {}
# 初始化一个空字典,存储相关信息
lastActive = {}
lastActiveCheck = datetime.now()
# 存储设备数量等信息
ESTIMATED_NUM_PIS = 4
ACTIVE_CHECK_PERIOD = 10
ACTIVE_CHECK_SECONDS = ESTIMATED_NUM_PIS * ACTIVE_CHECK_PERIOD
#将所有设备传过来的视频帧,做成类似九宫格的监控画面
mW = args["montageW"]
mH = args["montageH"]
print("正在监控: {}...".format(", ".join(obj for obj in
CONSIDER)))
# 处理所有图像帧
while True:
#接受树莓派名字和其传过来的图像,并回复消息
(rpiName, frame) = imageHub.recv_image()
imageHub.send_reply(b'OK')
if rpiName not in lastActive.keys():
print("正在从{}接收数据...".format(rpiName))
# 记录检测时间
lastActive[rpiName] = datetime.now()
# 处理图像大小,构造blob
frame = imutils.resize(frame, width=400)
(h, w) = frame.shape[:2]
blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)),
0.007843, (300, 300), 127.5)
# 检测和预测
net.setInput(blob)
detections = net.forward()
# 目标数量
objCount = {obj: 0 for obj in CONSIDER}
# 循环检测
for i in np.arange(0, detections.shape[2]):
#提取相关概率预测
confidence = detections[0, 0, i, 2]
# 过虑
if confidence > args["confidence"]:
# 类别标签索引
idx = int(detections[0, 0, i, 1])
# 计算数量
if CLASSES[idx] in CONSIDER:
# increment the count of the particular object
# detected in the frame
objCount[CLASSES[idx]] += 1
# 计算目标的bounding box 坐标
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# 绘制图框
cv2.rectangle(frame, (startX, startY), (endX, endY),
(255, 0, 0), 2)
# 显示在帧上
cv2.putText(frame, rpiName, (10, 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 目标数量显示在帧上
label = ", ".join("{}: {}".format(obj, count) for (obj, count) in
objCount.items())
cv2.putText(frame, label, (10, h - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255,0), 2)
# 更新帧
frameDict[rpiName] = frame
# 用在帧字典中的图像构建一个蒙太奇图像
montages = build_montages(frameDict.values(), (w, h), (mW, mH))
# 显示
for (i, montage) in enumerate(montages):
cv2.imshow("Home monitor ({})".format(i), montage)
# 按任意键退出
key = cv2.waitKey(1) & 0xFF
# 当前时间减去上次活动设备检查的时间
# 大于设置的阈值,然后进行检查
if (datetime.now() - lastActiveCheck).seconds > ACTIVE_CHECK_SECONDS:
# 遍历所有先前活动的设备
for (rpiName, ts) in list(lastActive.items()):
# 从最后一个活动帧中删除Pi
# 字典(如果该设备最近未激活)
if (datetime.now() - ts).seconds > ACTIVE_CHECK_SECONDS:
print("最后连接到{}".format(rpiName))
lastActive.pop(rpiName)
frameDict.pop(rpiName)
# 将上次活动检查时间设置为当前时间
lastActiveCheck = datetime.now()
# 退出
if key == ord("q"):
break
# 清理
cv2.destroyAllWindows()
3.2 客户端(树莓派)部署
首先给树莓派创建个虚拟环境,可参考另一篇:https://blog.youkuaiyun.com/y459541195/article/details/102055866
安装必要的包:
pip install opencv-contrib-python
pip install zmq
pip install imutils
记得下载imagezmq:
git clone https://github.com/jeffbass/imagezmq.git
将imagezmq.py
文件放到同意文件夹下面。
主要代码如下:
# 执行命令,SERVER_IP为服务端ip,替换为自己的
# python client.py --server-ip 192.168.1.4
from imutils.video import VideoStream
import imagezmq
import argparse
import socket
import time
# 构造参数
ap = argparse.ArgumentParser()
ap.add_argument("-s", "--server-ip", required=True,
help="客户端连接服务器的IP地址")
args = vars(ap.parse_args())
# 初始化服务器通讯地址
sender = imagezmq.ImageSender(connect_to="tcp://{}:5555".format(
args["server_ip"]))
# 获取主机名,打开相机
rpiName = socket.gethostname()
vs = VideoStream(usePiCamera=True).start()
#vs = VideoStream(src=0).start()
time.sleep(2.0)
while True:
# 读取视频发送给服务器
frame = vs.read()
sender.send_image(rpiName, frame)
4.测试
文件结构:
先开启服务端:
python server.py --prototxt MobileNetSSD_deploy.prototxt --model MobileNetSSD_deploy.caffemodel --montageW 2 --montageH 1
分别在树莓派pi-3和pi-4上开启客户端:
python client.py --server-ip 192.168.1.4
上面的192.168.1.4
替换为自己的ip。
效果如下:
完整项目代码地址:https://download.youkuaiyun.com/download/y459541195/11995013
有兴趣的也可以做个小程序,从小程序里远程查看监控。
Reference:
http://zguide.zeromq.org/page:all
https://github.com/jeffbass/imagezmq
https://www.pyimagesearch.com/2017/09/18/real-time-object-detection-with-deep-learning-and-opencv/
https://arxiv.org/abs/1704.04861
https://github.com/chuanqi305/MobileNet-SSD