案例:运动传感器
差分法原理
运动传感器的实现
基于微信公众平台的开发
代码封装和优化的过程:
面相过程 -> 面相函数 -> 面相对象
需求分析
- 摄像头拍摄的画面中绿色的字是什么?
上面:Motion Undetected:未检测到运动
下面:英文格式的实时时间
- 当探测到动的物体的时候画面发生了什么变化?
上面:Motion detected:检测到运动,字体变红
下面:英文格式的实时时间
画面中:移动的物体被绿框框选
- 当探测到动的物体的时候手机收到了通知,谁发过来的?
如何实现探测+通知?
差分法原理
|图1-图2|:
[[10 50]
[ 0 0]
[10 0]]
如何实现通知
- 准备微信公众平台测试号
-
- 访问微信公众平台网站 https://mp.weixin.qq.com
-
- 查看服务号开发文档
-
- 开发指南 > 概述 >进入微信公众帐号测试号申请系统
-
- 扫码登录即可
- 安装HTTP库 requests:
pip install requests
- 编码实现通知
-
- 获取access_token
- 发送客服文本消息
import json
import requests
# 1.获取access_token
appID = 'wx4ba0ce8848527203'
appsecret = 'b40a9fd619081780e1b52dedadc38647'
url = f'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appID}&secret={appsecret}'
res = requests.get(url).json()
print(res)
access_token = res.get('access_token')
# 2.利用access_token发送通知[这里使用的是发送客服消息接口]
url = f'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={access_token}'
opend_id = 'o8BtAviDQD2upYsoMshhWxb_jz44'
req_data = {
"touser": opend_id,
"msgtype": "text",
"text":
{
"content": "有人闯入!!!"
},
"customservice": {
"kf_account": "test1@kftest"
}
}
# 数据处理,防止传输过程乱码
req_str = json.dumps(req_data, ensure_ascii=False)
req_data = req_str.encode("utf-8")
response = requests.post(url, data=req_data, )
# 手动应用推测的编码,防止乱码
response.encoding = response.apparent_encoding
print(response.text) # 正确解码的内容
{'access_token': '93_OrGX8F_cneWBrV6Vr1SE1lIgP7YleacSf8LiexHr7PrOKFTuKoWsQ1s2I1pkGTpATApY-ReMPu54mFw58cU8Et-dB2QNV8jaAKB9yqfnLvWDWKSNaeIjK63clLoMLDeABADCU', 'expires_in': 7200}
{"errcode":0,"errmsg":"ok"}
实现通知-优化
代码封装优化的过程
-
面向过程
上面的发送通知消息的代码就是面向过程的,用一次写一次,无法被其他模块复用,不实用。
-
面向函数
将功能模块化,可以直接调用:
import json
import requests
# 1.获取access_token
def get_access_token(appID, appsecret):
url = f'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appID}&secret={appsecret}'
res = requests.get(url).json()
print(res)
access_token = res.get('access_token')
return access_token
# 2.利用access_token发送通知[这里使用的是发送客服消息接口]
def sendMSG(access_token, opend_id, MSG='有人闯入'):
url = f'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={access_token}'
req_data = {
"touser": opend_id,
"msgtype": "text",
"text":
{
"content": f"{MSG}"
},
"customservice": {
"kf_account": "test1@kftest"
}
}
# 数据处理,防止传输过程乱码
req_str = json.dumps(req_data, ensure_ascii=False)
req_data = req_str.encode("utf-8")
response = requests.post(url, data=req_data, )
# 手动应用推测的编码,防止乱码
response.encoding = response.apparent_encoding
print(response.text) # 正确解码的内容
return response.text
if __name__ == '__main__':
appID = 'wx4ba0ce8848527203'
appsecret = 'b40a9fd619081780e1b52dedadc38647'
opend_id = 'o8BtAviDQD2upYsoMshhWxb_jz44'
msg = '有人闯入!!!'
access_token = get_access_token(appID, appsecret)
res = sendMSG(access_token, opend_id, msg)
-
面向对象
我们可以接受定义的复杂,但是不能接受调用的复杂
例如上面的发送消息的方法,我们还要传入一个和业务无关的access_token
!!!就很扯淡:
res = sendMSG(access_token, opend_id, msg)
我只关心给谁发送什么消息,我并不要关心什么access_token
!!!
那么此时我们就可以使用面向对象编码。创建文件:SendMSG_LZS.py
:
import json
import requests
class WXTools:
def __init__(self, appID, appsecret):
self.appID = appID
self.appsecret = appsecret
# 1.获取access_token
def get_access_token(self):
url = f'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={self.appID}&secret={self.appsecret}'
res = requests.get(url).json()
print(res)
access_token = res.get('access_token')
return access_token
# 2.利用access_token发送通知[这里使用的是发送客服消息接口]
def sendMSG(self, opend_id, MSG='有人闯入'):
url = f'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={self.get_access_token()}'
req_data = {
"touser": opend_id,
"msgtype": "text",
"text":
{
"content": f"{MSG}"
},
"customservice": {
"kf_account": "test1@kftest"
}
}
# 数据处理,防止传输过程乱码
req_str = json.dumps(req_data, ensure_ascii=False)
req_data = req_str.encode("utf-8")
response = requests.post(url, data=req_data, )
# 手动应用推测的编码,防止乱码
response.encoding = response.apparent_encoding
print(response.text) # 正确解码的内容
return response.text
if __name__ == '__main__':
appID = 'wx4ba0ce8848527203'
appsecret = 'b40a9fd619081780e1b52dedadc38647'
opend_id = 'o8BtAviDQD2upYsoMshhWxb_jz44'
msg = '有人闯入!!!'
res = WXTools(appID, appsecret).sendMSG(opend_id, msg)
实现探测 + 通知
◆ 展示拍摄画面及文字等
import cv2
import datetime
camera=cv2.VideoCapture(0)
while True:
ret, frame = camera.read()
cv2.putText(frame,"Motion:UNdetected",(10,20),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),2)
cv2.putText(frame,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),(10,frame.shape[0]-15),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),2)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
camera.release()
cv2.destroyAllWindows()
◆ 得到灰度图像并展示
略
◆ 得到差分图像并展示
import cv2
import datetime
camera=cv2.VideoCapture(0)
# 指定背景
background=None
# 形态学膨胀:让图像更大(例如让手指变粗)
es=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,4))
while True:
ret, frame = camera.read()
# 转换成灰度图像
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 加入高斯滤波消除噪点
gauss_grey=cv2.GaussianBlur(grey,(5,5),0)
if background is None:
background = gauss_grey
continue
# 差分运算
diff = cv2.absdiff(background,gauss_grey)#获得两张图像差的绝对值
diff=cv2.threshold(diff,50,255,cv2.THRESH_BINARY)[1]#设置差别的阈值,这里设置为50,两个图片差超过50就认为不一样;255:将不同的地方显示为纯白色(单通道255代表白色)
diff=cv2.dilate(diff,es)#进行形态学膨胀:例如有时候有个小飞虫,很小看不清楚,就可以通过膨胀放大
cv2.putText(frame,"Motion:UNdetected",(10,20),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),2)
cv2.putText(frame,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),(10,frame.shape[0]-15),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),2)
cv2.imshow('frame',frame)
cv2.imshow('gauss_grey',gauss_grey)
cv2.imshow('black',diff)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
camera.release()
cv2.destroyAllWindows()
◆ 应用差分法,动的地方,画绿色框框
◆ 如果探测到动的地方则修改文字内容为已探测到,并把文字颜色改为红色
import cv2
import datetime
camera = cv2.VideoCapture(0)
# 指定背景
background = None
# 形态学膨胀:让图像更大(例如让手指变粗)
es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 4))
while True:
ret, frame = camera.read()
# 转换成灰度图像
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 加入高斯滤波消除噪点
gauss_grey = cv2.GaussianBlur(grey, (5, 5), 0)
if background is None:
background = gauss_grey
continue
# 差分运算
diff = cv2.absdiff(background, gauss_grey) # 获得两张图像差的绝对值
diff = cv2.threshold(diff, 50, 255, cv2.THRESH_BINARY)[1] # 设置差别的阈值,这里设置为50,两个图片差超过50就认为不一样;255:将不同的地方显示为纯白色(单通道255代表白色)
diff = cv2.dilate(diff, es) # 进行形态学膨胀:例如有时候有个小飞虫,很小看不清楚,就可以通过膨胀放大
# 发现图像中所有连续的物体
contours,hierarchy=cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#第二个参数:外部轮廓;第三个参数:连续的意思
is_detected = False
for contour in contours:
if cv2.contourArea(contour) < 2000:
# 移动范围小于2000,跳过忽略,进行下一次循环
continue
# 移动范围大于2000,拿到物体的位置坐标
(x, y, w, h) = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
is_detected = True
if is_detected:
show_text="Motion:detected"
text_color=(0, 0, 255)
else:
show_text="Motion:undetected"
text_color = (255, 255, 255)
cv2.putText(frame, show_text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 1, text_color, 2)
cv2.putText(frame, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), (10, frame.shape[0] - 15),
cv2.FONT_HERSHEY_SIMPLEX, 1, text_color, 2)
cv2.imshow('frame', frame)
cv2.imshow('gauss_grey', gauss_grey)
cv2.imshow('black', diff)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
camera.release()
cv2.destroyAllWindows()
最终代码
◆ 将通用配置项使用配置文件settings.py
管理:
appID = 'wx4ba0ce8848527203'
appsecret = 'b40a9fd619081780e1b52dedadc38647'
# 配置发送消息时间间隔,单位秒
send_time_interval=30
◆ 在探测到东西的时候发送微信通知,为防止过多骚扰,设置每一段时间间隔只发送一次。
◆ 增加背景更新机制。
import cv2
import datetime
import time
# 导入全局配置
import settings
# 导入自己封装的发送微信消息工具类
from lzs.SendMSG_LZS import WXTools
# 导入通用配置项
appID = settings.appID
appsecret = settings.appsecret
# 定义公共变量
# 指定背景
background = None
# 上次发送消息时间
last_send_time=None
opend_id = 'o8BtAviDQD2upYsoMshhWxb_jz44'
msg = '有人闯入!!!'
# 形态学膨胀:让图像更大(例如让手指变粗)
es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 4))
camera = cv2.VideoCapture(0)
while True:
ret, frame = camera.read()
# 转换成灰度图像
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 加入高斯滤波消除噪点
gauss_grey = cv2.GaussianBlur(grey, (5, 5), 0)
if background is None:
background = gauss_grey
continue
# 差分运算
diff = cv2.absdiff(background, gauss_grey) # 获得两张图像差的绝对值
diff = cv2.threshold(diff, 50, 255, cv2.THRESH_BINARY)[1] # 设置差别的阈值,这里设置为50,两个图片差超过50就认为不一样;255:将不同的地方显示为纯白色(单通道255代表白色)
diff = cv2.dilate(diff, es) # 进行形态学膨胀:例如有时候有个小飞虫,很小看不清楚,就可以通过膨胀放大
# 添加背景更新机制
# 方案1:把上一帧做为最新的背景
# background=gauss_grey#虽然有效果,但是不实用,物体停止运动时,立即就检测为静态画面,很生硬
# 方案2:让background随时间缓慢适应环境变化(例如每帧更新一部分),避免静态差异被误判为移动:
# gauss_grey权重0.01表示每次只更新1%,这意味着每帧只对背景模型贡献 1% 的更新,旧背景以极慢的速度 “融合” 新环境。例如:如果一个物体在画面中静止 50 帧(约 1-2 秒),它会逐渐被纳入背景(50×1%=50% 的融合度)。
# 但如果物体在移动,它在每帧中的位置不同,每次差分都会产生差异,不会被背景吸收。
background = cv2.addWeighted(background, 0.99, gauss_grey, 0.01, 0) # 缓慢适应新背景
# 发现图像中所有连续的物体
contours,hierarchy=cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#第二个参数:外部轮廓;第三个参数:连续的意思
is_detected = False
for contour in contours:
if cv2.contourArea(contour) < 2000:
# 移动范围小于2000,跳过忽略,进行下一次循环
continue
# 移动范围大于2000,拿到物体的位置坐标
(x, y, w, h) = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
is_detected = True
# 发送通知消息
if last_send_time is None or time.time() - last_send_time > settings.send_time_interval:
WXTools(appID, appsecret).sendMSG(opend_id, msg)
last_send_time=time.time()
print('侦测到物体移动,已发送通知消息!!!')
if is_detected:
show_text="Motion:detected"
text_color=(0, 0, 255)
else:
show_text="Motion:undetected"
text_color = (255, 255, 255)
# 展示拍摄画面及文字
cv2.putText(frame, show_text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 1, text_color, 2)
cv2.putText(frame, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), (10, frame.shape[0] - 15),
cv2.FONT_HERSHEY_SIMPLEX, 1, text_color, 2)
cv2.imshow('frame', frame)
cv2.imshow('gauss_grey', gauss_grey)
cv2.imshow('black', diff)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
camera.release()
cv2.destroyAllWindows()