全部文章

案例:运动传感器

 

差分法原理

运动传感器的实现

基于微信公众平台的开发

代码封装和优化的过程:

  面相过程 -> 面相函数 -> 面相对象

 

需求分析

  1. 摄像头拍摄的画面中绿色的字是什么?
上面:Motion Undetected:未检测到运动

下面:英文格式的实时时间

  1. 当探测到动的物体的时候画面发生了什么变化?
上面:Motion detected:检测到运动,字体变红

下面:英文格式的实时时间

画面中:移动的物体被绿框框选

 

  1. 当探测到动的物体的时候手机收到了通知,谁发过来的?

如何实现探测+通知?

差分法原理

 

 

 

|图1-图2|:

[[10 50]
 [ 0  0]
 [10  0]]

如何实现通知

  1. 准备微信公众平台测试号

   

    • 查看服务号开发文档

    • 扫码登录即可

 

  1. 安装HTTPrequests:
pip install requests
  1. 编码实现通知
    • 获取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"}

实现通知-优化

代码封装优化的过程

  1. 面向过程

    上面的发送通知消息的代码就是面向过程的,用一次写一次,无法被其他模块复用,不实用。

  1. 面向函数

将功能模块化,可以直接调用:

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)
  1. 面向对象

 我们可以接受定义的复杂,但是不能接受调用的复杂

 例如上面的发送消息的方法,我们还要传入一个和业务无关的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()

 

posted @ 2025-07-08 10:15  指尖下的世界  阅读(0)  评论(0)    收藏  举报