Python与OpenCV(一)——基于帧差法的运动目标检测程序分析

2023-11-07

OpenCV提供了强大的图像处理功能,与Python的结合堪称完美。。。
这一次,我们试一下用帧差法来完成对运动目标的检测与跟踪。
帧差法的原理是这样的:由于摄像机采集的视频序列具有连续性的特点,所以如果所采集场景内没有运动目标的时候,连续帧的变化很小,如果存在有运动的目标,则连续的帧和帧之间会有明显地变化。我们将连续的两帧或三帧图像进行差分运算,取其的灰度差的绝对值,如果该值超过我们所定的阈值时,就判定为运行目标。其原理如图所示。
在这里插入图片描述
先上源码,这个是两帧法,就是把前一帧定义为变量background,后一帧与其对比的差值:

源码呈现

源码是从网上找的,原作者写的很精炼,分析的也好,就是没有逐句分析,所以我们先上源码,再分析:

import cv2
import numpy as np
 
camera = cv2.VideoCapture(0) # 参数0表示第一个摄像头
# 判断视频是否打开
if (camera.isOpened()):
  print('摄像头成功打开')
else:
  print('摄像头未打开')
 
# 测试用,查看视频size
size = (int(camera.get(cv2.CAP_PROP_FRAME_WIDTH)),
    int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print('size:'+repr(size))
 
es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 4))
background = None
 
while True:
  # 读取视频流
  grabbed, frame_lwpCV = camera.read()
  # 对帧进行预处理,先转灰度图,再进行高斯滤波。
  # 用高斯滤波进行模糊处理,进行处理的原因:每个输入的视频都会因自然震动、光照变化或者摄像头本身等原因而产生噪声。对噪声进行平滑是为了避免在运动和跟踪时将其检测出来。
  gray_lwpCV = cv2.cvtColor(frame_lwpCV, cv2.COLOR_BGR2GRAY)
  gray_lwpCV = cv2.GaussianBlur(gray_lwpCV, (21, 21), 0)
 
  # 将第一帧设置为整个输入的背景
  if background is None:
    background = gray_lwpCV
    continue
  # 对于每个从背景之后读取的帧都会计算其与北京之间的差异,并得到一个差分图(different map)。
  # 还需要应用阈值来得到一幅黑白图像,并通过下面代码来膨胀(dilate)图像,从而对孔(hole)和缺陷(imperfection)进行归一化处理
  diff = cv2.absdiff(background, gray_lwpCV)
  diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1] # 二值化阈值处理
  diff = cv2.dilate(diff, es, iterations=2) # 形态学膨胀
 
  # 显示矩形框
  contours, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 该函数计算一幅图像中目标的轮廓
  for c in contours:
    if cv2.contourArea(c) < 1500: # 对于矩形区域,只显示大于给定阈值的轮廓,所以一些微小的变化不会显示。对于光照不变和噪声低的摄像头可不设定轮廓最小尺寸的阈值
      continue
    (x, y, w, h) = cv2.boundingRect(c) # 该函数计算矩形的边界框
    cv2.rectangle(frame_lwpCV, (x, y), (x+w, y+h), (0, 255, 0), 2)
 
  cv2.imshow('contours', frame_lwpCV)
  cv2.imshow('dis', diff)
 
  key = cv2.waitKey(1) & 0xFF
  # 按'q'健退出循环
  if key == ord('q'):
    break
# When everything done, release the capture
camera.release()
cv2.destroyAllWindows()

源码分析

1.导入模块
import cv2
import numpy as np

这两句是导入两个所需要的模块,第一个是opencv,这个肯定是要的,另一个是NumPy,NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。import numpy as np是指在程序中用np来代替numpy的调用,所以只要是看到np地方,就是采用了numpy的库运算。例如:
kernel = np.ones((5, 5), np.uint8)。
而同理,程序中出现了cv2的地方,就是调用了opencv模块。

2.视频信息初处理

下面的五行是摄像头进行处理。。。

camera = cv2.VideoCapture(0)
if (camera.isOpened()):
  print('摄像头成功打开')
else:
  print('摄像头未打开')
size = (int(camera.get(cv2.CAP_PROP_FRAME_WIDTH)),int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print('size:'+repr(size))

第一行:camera = cv2.VideoCapture(0)
创建一个视频捕获对象camera。cv2.VideoCapture()中的参数如果是0,表示打开本机,也就是运行该程序的笔记本的内置摄像头;如果不需要监测摄像头,而是对已有视频进行监测,那么在括号中引入视频文件的路径就可以打开视频,如cap = cv2.VideoCapture(“E:/test.avi”)
第二至五行:如果视频捕获对象camera创建成功,也就是相应的摄像头或视频存在,可以形成视频流,那么camera.isOpened()返回为真,输出“摄像头成功打开”,表示摄像头已打开。否则输出“摄像头未打开”
第六到七行:cv2.CAP_PROP_FRAME_WIDTH为视频信息的宽度属性;cv2.CAP_PROP_FRAME_HEIGHT为视频信息的高度属性,size = (int(camera.get(cv2.CAP_PROP_FRAME_WIDTH)),int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT)))的意思就是将获取到的视频信息的宽度和高度属性转换为整型值,赋给size变量,然后将其用repr() 函数转换成string格式进行输出。
与CAP_PROP_FRAME_WIDTH类似的视频属性有很多,下面列几个,这些参数有get来进行获取,用set进行设置。
cv2.CAP_PROP_FRAME_WIDTH, 1080 # 宽度
cv2.CAP_PROP_FRAME_HEIGHT, 960 # 高度
cv2.CAP_PROP_FPS, 30 # 帧数
cv2.CAP_PROP_BRIGHTNESS, 1 # 亮度 1
cv2.CAP_PROP_CONTRAST,40 # 对比度 40
cv2.CAP_PROP_SATURATION, 50 # 饱和度 50
cv2.CAP_PROP_HUE, 50 # 色调 50
cv2.CAP_PROP_EXPOSURE, 50 # 曝光 50

3.图像预处理

在介绍这一部分内容之前,我们先来了解一下形态学。形态学原本是生物学中研究动物和植物结构的一个学科分支,后来被应用到数学中,形成了数学形态学。
数学形态学是以形态为基础对图像进行分析的数学工具,其基础是集合论,思路是用具有一定形态的结构元素去度量和提取图像中的对应形状以达到对图像分析和识别的目的。
而将数学形态学采用一定的方法呈现出来,就形成了OpenCV的图像处理的基础。

es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 4))
background = None

在OpenCV当中,定义结构元素用到的命令是cv2.getStructuringElement(c1,c2),其中c1参数表示核的形状,主要有三种选择:
矩形:MORPH_RECT;
交叉形:MORPH_CROSS;
椭圆形:MORPH_ELLIPSE;
在本例中,选择的是椭圆形。
第二个参数c2是核的尺寸,我们在原代码中插一句print(es),将它打印出来,如下所示:
[[0 0 0 0 1 0 0 0 0]
[0 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 1 1]
[0 1 1 1 1 1 1 1 0]]
可以看出它是一个矩阵,内含4个小矩阵,每一个小矩阵都只有一行元素,每行元素是9个。
当然我们也可以不用cv2.getStructuringElement(c1,c2)来定义结构元素,例如我们需要在图象中提取数字2,那么就定义一个“2”这样的形态,再利用形态学中的膨胀、腐蚀、开运算、闭运算将它提取出来。
在图像识别中,定义结构元素是提取特定图像的基础,也关系到最终提取的结果,理论上讲,几乎没有利用形态学提取不了的特征,如果一次不够,那就多用几次。
后面的一句:background = None,先定义前一帧为空。

4.定义结构化元素

接着就进入了一个永真循环,不停地从摄像头或视频中截取图像进行处理,先来看循环当中的图像预处理。

  grabbed, frame_lwpCV = camera.read()
  gray_lwpCV = cv2.cvtColor(frame_lwpCV, cv2.COLOR_BGR2GRAY)
  gray_lwpCV = cv2.GaussianBlur(gray_lwpCV, (21, 21), 0)
  if background is None:
    background = gray_lwpCV
    continue

第一句grabbed, frame_lwpCV = camera.read(),camera.read()是按帧读取视频,它的返回值有两个,第一个grabbed是一个布尔值,如果读取帧是正确的则返回True,如果文件读取到结尾,它的返回值就为False。后面的 frame_lwpCV 返回的是当前循环所提取的图像,是一个三维矩阵。
第二句gray_lwpCV = cv2.cvtColor(frame_lwpCV, cv2.COLOR_BGR2GRAY),将提取的图像转为灰度,为进一步处理作准备。
第三句gray_lwpCV = cv2.GaussianBlur(gray_lwpCV, (21, 21), 0),用高斯滤波进行模糊处理,进行处理的原因:每个输入的视频都会因自然震动、光照变化或者摄像头本身等原因而产生噪声。对噪声进行平滑是为了避免在运动和跟踪时将其检测出来。在cv2.GaussianBlur(gray_lwpCV, (21, 21), 0)函数中,其中gray_lwpCV是要进行滤波的原图像,(21, 21)是高斯核的大小,blur1和blur2的选取一般是奇数,blur1和blur2的值可以不同。最后的参数0表示标准差取0。
第四至六句定义前一帧作为背景帧,后面会用当前帧与背景帧相对比,以确定运动目标。

5.图像处理
  diff = cv2.absdiff(background, gray_lwpCV)
  diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1] 
  diff = cv2.dilate(diff, es, iterations=2) 

第一句:diff = cv2.absdiff(background, gray_lwpCV),这句话表示将得到的gray_lwpCV与background进行对比,就是将两幅图像作差,获取差分图,返回的结果代表他们的差异之处,这里用的是灰度图,类型是uint8,在 OpenCV单通道使用的数据类型是 uint8,两个uint8的数相减得不到负数,会得到差的补码。
第二句:diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1],对得到的差分图diff进行二值化处理。
cv2.threshold()有四个参数,第一个原图像,在本例中就是差分图;第二个25是定义了进行分类的阈值,第三个是高于(低于)阈值时赋予的新值255,第四个是一个方法选择参数,常用的有:
• cv2.THRESH_BINARY(黑白二值)
• cv2.THRESH_BINARY_INV(黑白二值反转)
• cv2.THRESH_TRUNC (得到的图像为多像素值)
• cv2.THRESH_TOZERO
• cv2.THRESH_TOZERO_INV
threshold函数有两个返回值,第一个是得到的阈值,用cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[0]可以得到,第二个就是阈值化后的图像,用cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1]来得到。
总之,这句话是把diff图像当中阈值高于25的部分转为255。
第三句:diff = cv2.dilate(diff, es, iterations=2) ,cv2.dilate( ) 是对图像进行膨胀操作。
膨胀操作是取核中像素值的最大值代替锚点位置的像素值,这样会使图像中较亮的区域增大,较暗的区域减小。如果是一张黑底,白色前景的二值图,就会使白色的前景物体颜色面积变大,就像膨胀了一样。示例如图1为正常图片,图2为灰度图片,图3为膨胀的图片。
图1正常图片
在这里插入图片描述

图2灰度图片
在这里插入图片描述

图3膨胀图片
在这里插入图片描述
cv2.dilate( )的格式是 cv2.dilate(src, kernel, iteration),其中src表示输入的图片, kernel表示所引用的结构元素, iteration表示迭代的次数。

6.追踪目标,作出标记并显示

准备工作作完,就该找目标了,

contours, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 
  for c in contours:
    if cv2.contourArea(c) < 1500:
      continue
    (x, y, w, h) = cv2.boundingRect(c) 
    cv2.rectangle(frame_lwpCV, (x, y), (x+w, y+h), (0, 255, 0), 2)
   cv2.imshow('contours', frame_lwpCV)
   cv2.imshow('dis', diff) 

第一行:contours, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ,这句是用来找出目标的轮廓值,参数有三个,第一个为寻找轮廓的图像,这里用了处理后的图片的拷贝,第二个参数表示轮廓的检索模式,有四种:
cv2.RETR_EXTERNAL 只检测外轮廓
cv2.RETR_LIST 检测的轮廓不建立等级关系
cv2.RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息
cv2.RETR_TREE 建立一个等级树结构的轮廓
第三个为轮廓的近似算法:
cv2.CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标(矩形只需四顶点)
cv2.CHAIN_APPROX_TC89_L1 使用teh-Chinl chain 近似算法的一种
CV_CHAIN_APPROX_TC89_KCOS 使用teh-Chinl chain 近似算法的一种
cv2.CHAIN_APPROX_NONE 存储所有的轮廓点,相邻的两个点的像素位置差不超过1
cv2.findContours( )的返回值:
OpenCV2和OpenCV4中,findContours这个轮廓提取函数会返回两个值:
①轮廓的点集(contours) ②各层轮廓的索引(hierarchy)
OpenCV3中,则会返回三个值:①处理的图像(image) ②轮廓的点集(contours) ③各层轮廓的索引(hierarchy)
如果运行时出现ValueError: not enough values to unpack (expected 3, got 2),意思是值错误:没有足够的值解包(应为3,得到2),那么解决的办法就是,它要几个就给他几个就可以了。
第二至五行:为找到的目标绘制矩形。for循环是对所找到的所有轮廓逐一进行绘制,每一个c值对应一个,如果当前c值对应的轮廓面积太小,则直接找下一个。如果不小的话,开始绘制矩形框。
(x, y, w, h) = cv2.boundingRect© :矩形边框(Bounding Rectangle)是说,用一个最小的矩形,把找到的形状包起来。返回四个值,分别是x,y,w,h;x,y是矩阵左上点的坐标,w,h是矩阵的宽和高
cv2.rectangle(frame_lwpCV, (x, y), (x+w, y+h), (0, 255, 0), 2):绘制矩形,参数说明:frame_lwpCV要绘制图片,(x, y)表示矩阵左上角的位置,(x+w, y+h)表示矩阵右下角的位置, (0, 255, 0)表示颜色,2表示线条
注意,现在绘制的对象是原图,也就是找到目标的那个彩色的图。显示用下一句完成cv2.imshow(‘contours’, frame_lwpCV),对话框上显示的名称为contours,图像对象为frame_lwpCV,如图:
在这里插入图片描述
最后一句cv2.imshow(‘dis’, diff),显示处理后图像:
在这里插入图片描述

7.总结

最后还有一点程序,主要完成一些边界性操作:

  key = cv2.waitKey(1) & 0xFF
  if key == ord('q'):
    break
camera.release()
cv2.destroyAllWindows()

key = cv2.waitKey(1) :waitKey()方法本身表示等待键盘输入,参数是1,表示延时1ms切换到下一帧图像,而cv2.waitKey(1) 与0xFF作“&”运算,是只取waitKey()返回整数值当中的所“&”的8位,其他位都取0。
最后调用release()释放摄像头,调用destroyAllWindows()关闭所有图像窗口。

本文代码是做一个基于帧差法的运动检测,主要考虑的是“背景帧”与其它帧之间的差异。这种方法检测需要提前设置背景帧,如果是在室外,光线的变化就会引起误检测,如果光线变化不大,识别率还是可以的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python与OpenCV(一)——基于帧差法的运动目标检测程序分析 的相关文章

随机推荐

  • 浅谈STL

    一 函数对象概念 概念 重载函数调用操作符的类 其对象常称为函数对象 函数对象使用重载的 时 行为类似函数调用 也叫仿函数 本质 函数对象 仿函数 是一个类 不是一个函数 特点 函数对象在使用时 可以像普通函数那样调用 可以有参数 可以有返
  • CentOS中安装Docker

    Docker 要求 CentOS 系统的内核版本高于 3 10 通过 uname r 命令查看你当前的内核版本 uname r 确保 yum 包更新到最新 sudo yum update 移除旧的版本 sudo yum remove doc
  • 【人物访谈·03期】关于对数据分析前辈的一次行业探讨

    小飞象 人物访谈 做一个独立的人 不要依赖别人 独立会给你的魅力加分 出品 小飞象 人物访谈 嘉宾 冰冰老师 采访 整理 木兮 排版 木兮 背景 本期03 访谈邀请到了小飞象社群特别嘉宾 冰冰 个人公众号 冰云数据 作为访谈对象 而且通过此
  • Uncaught TypeError: Cannot read properties of undefined (reading ‘MethodInfo‘)

    VUE前端项目问题记录 vue grpc web 错误描述 user grpc web pb js 95 Uncaught TypeError Cannot read properties of undefined reading Meth
  • 多任务学习中的数据分布问题(一)

    今天这个专题源于我在做分布式多任务学习实验时在选取数据集的时候的疑惑 以下我们讨论多任务学习中 尤其是在分布式的环境下 如何选择数据集和定义任务 多任务学习最初的定义是 多任务学习是一种归纳迁移机制 基本目标是提高泛化性能 多任务学习通过相
  • 打通大前端最后一公里之前端埋点与日志系统架构设计

    什么是埋点 所谓 埋点 是数据采集领域 尤其是用户行为数据采集领域 的术语 指的是针对特定用户行为或事件进行捕获 处理和发送的相关技术及其实施过程 埋点的技术实质 是先监听软件应用运行过程中的事件 当需要关注的事件发生时进行判断和捕获 埋点
  • C51单片机 串口通信RX&TX---适合初学

    原料 keil 仿真软件 虚拟串口工具 群文件里的tools vspdctl dll记得覆盖 说明 串口中断允许位ES 1时 单片机一接收到数据或者发送数据就会进入串口中断函数 我认为是因为SBUF一有动静 便会触发到中断 所以单片机发送数
  • 2022年高考都结束了,还有人真觉得程序员下班后不需要学习吗?

    一转眼 高考已经过去十来天了 近期 各省市也陆续公布了高考成绩 还有今年的各批次录取日程 最近一些有转行想法的 刚毕业想进入这个行业的人 甚至还有刚高考完的小朋友私信问我以后想当去大厂当程序员的话需要提前学些什么 属实 卷 得可以 哈哈前浪
  • windows常见后门隐藏和权限维持方法及排查技术

    https xz aliyun com t 4842 这片文章中隐藏webshell我觉得很nice 进程注入以前试过 wmi 和 bitsadmin 可以了解下 常见backdoor和persistence方式方法 系统工具替换后门 Im
  • linux嵌入式常见面试问题(持续更新)

    1 采用DMA方式进行串口通信 如何解析数据才能提高吞吐量 DMA 直接内存访问 是一种通过硬件控制器直接访问内存的方式 而不需要CPU的干预 在串口通信中 采用DMA方式可以减少CPU的负担 提高数据传输的效率 为了提高吞吐量 需要对DM
  • Python错误:ModuleNotFoundError: No module named ‘keras‘(已解决)

    出现错误 进入 Settings Project interpreter 点击右边 在左边出现的组件中找到keras 点击下方的Install Pakage 安装成功
  • Java 格式化字符串

    7 4 1 常规类型的格式化 String类的format 方法用于创建格式化的字符串以及连接多个字符串对象 熟悉C语言的读者应该记得C语言的sprintf 方法 两者有类似之处 format 方法有两种重载形式 l format Stri
  • java通过redis实现排行榜功能

    1 数据存储规划 在排行榜中 每个用户都会有一个唯一的标识 ID 同时需要记录该用户的积分 Score 因此 我们可以将每个用户的ID作为有序集合中的一个元素 将用户的积分作为该元素的分数 ZADD rank 1000 user1 ZADD
  • Qt操作表格

    文章目录 一 使用QAxObject 1 环境配置 二 使用QXlsx 一 使用QAxObject 1 环境配置 首先在Qt Modules中引入axserver axcontainer 代码中包含相关头文件 include
  • 计算机内存是ram,电脑ram内存不足怎么办

    电脑ram内存不足怎么办 电脑用久了就会经常出现内存不足的情况 今天小编要给大家介绍的便是电脑ram内存不足怎么办 欢迎阅读 电脑ram内存不足怎么办 设置虚拟内存按照微软认证系统工程师刘岸松的方法设置方法如下 设置虚拟内存方法 右击我的电
  • A Simple RGB ISP

    设计一个最简单的RGB ISP需要多少个模块呢 答案可能是4个 分别是CFA CCM GAMMA和RGB2YUV 有了这4个模块 我们就可以将Sensor输出的RAW data转化为在显示器上可以观看的画面了 CFA Color Filte
  • 基于组合双向拍卖的共享储能机制研究(Matlab代码实现)

    目录 1 概述 2 运行结果 2 1 算例数据 2 2 买家中标 2 3 卖家中标 3 文献来源 4 Matlab代码实现 1 概述 文献来源 摘要 为满足共享储能中储能用户的互补性和替代性需求 解决常规单向拍卖中可能存在的垄断竞争问题 提
  • 怎样才算一个好的产品经理?

    虽然是小公司全能型人员 但是随着实际写代码变得越来越少 产品的规划和运营越来越多 自己的主要职能也在向产品经理上转变 所以最近几年比较关注产品经理方面的功能 那么怎么才能算是一个好的产品经理呢 周鸿伟的观点 这个名字相信让很多人又爱有恨 但
  • Vue.js模板语法

    模板语法 Vue js使用了基于HTML的模板语法 允许开发者声明式地将DOM绑定至底层Vue实例的数据 所有vue js的模板都是合法的HTML 所以能被遵循规范的浏览器和HTML解析器解析 在底层的实现上 Vue将模板编译成虚拟DOM渲
  • Python与OpenCV(一)——基于帧差法的运动目标检测程序分析

    OpenCV提供了强大的图像处理功能 与Python的结合堪称完美 这一次 我们试一下用帧差法来完成对运动目标的检测与跟踪 帧差法的原理是这样的 由于摄像机采集的视频序列具有连续性的特点 所以如果所采集场景内没有运动目标的时候 连续帧的变化