用30行Python代码实现实时运动检测!OpenCV+MOG2+开运算,摄像头下无所遁形(万字详解可复制)

CSDN 原创首发 | 作者:你的名字
标签:OpenCV、运动检测、MOG2、背景建模、开运算、形态学、轮廓检测
阅读时间:约 60 分钟
核心代码:30行(不含注释)实现摄像头实时运动检测,精准框出移动物体!


目录

  1. 引言:为什么你需要这个项目?
  2. 最终效果与项目概览
  3. 环境配置与依赖安装
  4. 核心算法原理速览
    • 4.1 背景减除的前世今生
    • 4.2 MOG2 混合高斯模型详解
    • 4.3 开运算:先腐蚀后膨胀的魔法
    • 4.4 轮廓周长过滤策略
  5. 完整源代码(不修改版)
  6. 逐行代码深度解析(万字核心)
    • 6.1 摄像头初始化
    • 6.2 十字形卷积核的选择
    • 6.3 背景建模器创建
    • 6.4 主循环:读取与显示原始帧
    • 6.5 前景提取:fgmask.apply
    • 6.6 开运算去噪:fgmask_new
    • 6.7 轮廓检测:findContours
    • 6.8 周长过滤与矩形框绘制
    • 6.9 结果展示与按键退出
  7. 运行效果展示与实时画面分析
  8. 参数调优与避坑指南
    • 8.1 卷积核形状与大小
    • 8.2 MOG2 高级参数
    • 8.3 周长阈值的选择
    • 8.4 阴影检测的取舍
  9. 拓展实战:从运动检测到目标跟踪、入侵报警
  10. 总结与感悟

1. 引言:为什么你需要这个项目?

想象一下,你仅需几行 Python 代码,就能让电脑摄像头自动识别画面中移动的人或物体,并用醒目的绿框实时圈出它们——这并非科幻,而是计算机视觉中最经典、最实用的技术之一:运动检测

无论你是想做一个低成本的家庭安防系统,还是为机器人赋予感知移动障碍的能力,抑或是在课堂上向学生展示图像处理的魅力,这个项目都将是你踏入视频分析世界的绝佳起点。本文将以一段 完全可运行、极其精炼的 Python 代码 为核心,逐字逐句拆解其实现细节,并深入探讨背后的算法原理。全文超过 1 万字,不惜篇幅只为让你彻底弄懂 MOG2 背景建模、开运算去噪、轮廓周长过滤 三大核心环节。而且,本人郑重承诺:绝不修改你提供的源代码,所有讲解都将围绕原汁原味的脚本展开。

读完它,你不仅能够复现一个酷炫的实时运动检测器,更能收获一整套可以迁移到其他视觉任务的“内功心法”。


2. 最终效果与项目概览

运行代码后,你的电脑将调用默认摄像头,屏幕上同时显示四个窗口:

  • frame:原始彩色视频流,没有任何处理,保留真实画面。
  • fgmask:MOG2 算法直接输出的前景掩膜(二值图),白色区域代表运动像素,黑色代表静止背景。
  • fgmask1:对前景掩膜执行开运算后的结果,噪点明显减少,前景目标更干净。
  • fgmask_new_rect:在原始帧上绘制绿色矩形框的最终画面,每个移动物体都被框出。

当有行人走过、手臂挥动或物体移动时,相应的区域会立即被绿色框标记。按下键盘左上角的 ESC 键即可退出程序。

整个过程流畅且实时,代码仅用了不到 40 行(含注释),展现了 OpenCV 强大的高层封装能力。下面,让我们先搭建好环境,再一头扎进代码的海洋。


3. 环境配置与依赖安装

本实现完全基于 Python 和 OpenCV,没有任何其他框架的要求。你可以按照以下步骤快速配置:

第一步:安装 Python
确保你的系统安装有 Python 3.6 或更高版本。可通过命令行输入 python --version 检查。

第二步:安装 OpenCV
使用 pip 安装 opencv-python 包:

pip install opencv-python

如果你希望使用更轻量的 headless 版本(无 GUI 功能),可安装 opencv-python-headless,但本例需要显示窗口,故使用完整版。

第三步:检验安装
打开 Python 交互环境,输入 import cv2,若无报错即表示安装成功。

注意:默认摄像头索引为 0(通常指笔记本内置摄像头)。如果你使用外接 USB 摄像头,可能需要修改为 12,具体设备编号可通过 cv2.VideoCapture 多次尝试。


4. 核心算法原理速览

在深入代码之前,我们先建立算法层面的宏观认知,这将帮助你理解为何这几行代码如此有效。

4.1 背景减除的前世今生

运动检测最直接的方法是帧差法:将当前帧与前一帧相减,变化大于阈值的像素判为前景。但这会带来大量噪声,也无法处理暂时静止的物体重新运动的情况。

更先进的方案是背景减除:维护一个背景模型,将当前帧与其比较,差异大的区域视为前景。关键挑战在于背景模型需要不断更新以适应光照变化、树叶摇曳等动态场景。

4.2 MOG2 混合高斯模型详解

OpenCV 提供的 cv2.createBackgroundSubtractorMOG2 正是基于 自适应混合高斯背景建模 的算法。原理如下:

  • 为图像中每个像素点建立 K 个高斯分布(通常 K=3~5),每个分布都有权重、均值和方差。
  • 当新的一帧到来时,对于每个像素点的新值,将其与已有的 K 个高斯分布逐一比较,若其像素值落在某个分布的标准差范围内,则认为该像素与该分布“匹配”。
  • 匹配的分布参数会进行更新(均值、方差、权重增大),未匹配的分布权重衰减。若没有任何分布匹配,则用当前值新建一个分布,替代权重最小的那个分布。
  • 最终,将这些高斯分布按权重与方差的比值从大到小排序,排名靠前的分布(通常取前 B 个)被视作背景模型。如果当前像素值匹配背景模型中的某个分布,则判定为背景;否则则为前景。

MOG2 相较于原始的 MOG 改进之处在于:可以自动选取每个像素的最佳 K 值(不需固定),且对阴影检测有特殊处理(本例中我们使用了默认参数,未关闭阴影检测,但依然能良好工作)。

4.3 开运算:先腐蚀后膨胀的魔法

直接从 MOG2 获得的前景掩膜常包含 孤立的白点噪声前景物体内部的空洞。形态学操作用来处理这类问题:

  • 腐蚀 (Erosion):用核在图像上滑动,只有当核内所有像素都为 1 时,中心点才为 1;否则为 0。效果:白色区域(前景)被“蚕食”,细小白点消失,物体边界收缩。
  • 膨胀 (Dilation):只要核内有一个像素为 1,中心点就为 1。效果:白色区域扩张,可以连接邻近区域,填充小孔。
  • 开运算 (Opening):先腐蚀,再膨胀。组合效果:消除小噪点、断开细长连接,同时大致恢复物体原来的大小。这正是本代码采用的操作,cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel) 完美扣合去噪需求。

代码中使用了 cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3)),一个 3x3 的十字形结构元素,对去除孤立点特别高效。

4.4 轮廓周长过滤策略

经过开运算后,掩膜上仍可能存在一些面积极小的残存噪点。这时,我们通过 cv2.findContours 找到所有白色区域的轮廓,然后根据周长进行筛选:只保留周长大于 188 像素的轮廓,并在原图上绘制其外接矩形。这个阈值有效地过滤掉了随机的背景闪烁或细微的树叶晃动,只留下真正有意义的运动物体。


5. 完整源代码

以下为本文讲解的完整可运行代码。你可以直接复制到本地 Python 文件中,连接摄像头后运行。

import cv2

# 调用摄像头,参数 0 表示默认摄像头
cap = cv2.VideoCapture(0)

# 定义形态学操作的卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, ksize=(3, 3))

# 创建混合高斯模型用于背景建模
fgbg = cv2.createBackgroundSubtractorMOG2()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # 显示原始摄像头画面
    cv2.imshow('frame', frame)

    # 背景建模,提取前景(运动物体)
    fgmask = fgbg.apply(frame)
    cv2.imshow('fgmask', fgmask)

    # 开运算去噪点(先腐蚀后膨胀)
    fgmask_new = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
    cv2.imshow('fgmask1', fgmask_new)

    # 寻找前景轮廓
    contours = cv2.findContours(fgmask_new, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

    # 复制一份原始帧,用于画矩形框
    frame_copy = frame.copy()
    for c in contours:
        perimeter = cv2.arcLength(c, closed=True)
        # 过滤掉周长过小的轮廓,只保留较大的运动物体
        if perimeter > 188:
            x, y, w, h = cv2.boundingRect(c)
            # 在复制的帧上画矩形框,避免直接修改原始帧
            cv2.rectangle(frame_copy, (x, y), (x + w, y + h), (0, 255, 0), 2)

    # 显示带矩形框的结果
    cv2.imshow('fgmask_new_rect', frame_copy)

    # 按 ESC 键退出
    k = cv2.waitKey(30)
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

6. 逐行代码深度解析(万字核心)

现在,我们进入万字核心环节,以“行级粒度”解读每一行代码的设计思想与实现细节。请坐稳扶好,这将是一场知识密度极高的旅程。

6.1 摄像头初始化

import cv2
cap = cv2.VideoCapture(0)

cv2.VideoCapture(0) 创建一个视频捕获对象,参数 0 代表默认摄像头索引。操作系统会为已连接的摄像头分配 ID,多数笔记本内置摄像头 ID 为 0。若你有多个摄像头,可尝试 1、2 等,或者用循环测试 cap.isOpened() 来找到可用的那个。

cap 对象随后会通过 read() 方法连续读入视频帧。若设备无法打开,cap.isOpened() 会返回 False,但在本例中遇到读取失败时直接 break 退出。

6.2 十字形卷积核的选择

kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, ksize=(3, 3))

cv2.getStructuringElement 返回指定形状和大小的结构元素(核),用于后续的形态学操作。参数解释:

  • 形状cv2.MORPH_CROSS 是十字形核,其中心像素和上下左右四个像素为 1,四角为 0。形状像“╋”。相比于矩形核(全是 1)或椭圆核(圆形区域为 1),十字形核对孤立噪声点的去除特别敏感:因为一个孤立的白色像素(1)在腐蚀时,需要核内所有像素为 1 才能保留,而十字形核只有 5 个点的限制,比 9 个点的矩形核更容易将微小噪点腐蚀掉。因此擅长清除椒盐状的随机噪声。
  • 尺寸(3, 3) 是很小的核,适合去除微细噪声而不严重破坏前景物体的形状。如果视频分辨率较高(如 1080p),可考虑增大到 (5, 5) 或 (7, 7),但本例保持轻量,31 行代码中的设计已经足够。

为什么不用矩形核?
矩形核在腐蚀时会将所有方向同等程度蚕食,可能把物体边缘的细节也抹掉。十字形核保留了斜向的结构,对前景目标的完整性更友好。

6.3 背景建模器创建

fgbg = cv2.createBackgroundSubtractorMOG2()

我们创建了一个默认参数的 MOG2 背景减除器。没有显式设置参数,说明作者信任 OpenCV 的默认值。其默认参数如下(了解这些值有助于调参):

  • history=500:用于背景建模的历史帧数。过去 500 帧的信息会影响背景模型。
  • varThreshold=16:方差阈值,用于判断一个像素是否与某个高斯分布匹配。数值越小,更多像素会被判为前景(更敏感);越大则越严格。
  • detectShadows=True阴影检测默认开启。这意味着阴影像素不会被标记为纯白(255),而是被标记为灰色(127)。这个设计非常重要,因为在后续的开运算和轮廓检测中,灰色像素既不是完全的前景,也不是背景,会参与二值化吗?实际上,fgmask 是一幅灰度图像,当执行形态学操作时,OpenCV 将非零像素视为前景(即包括了 127 的阴影)。但我们并没有特别排除阴影,只是依赖开运算和周长过滤来规避阴影造成的细长轮廓。尽管阴影检测开启,但最终效果依然不错,因为阴影往往面积大但周长相对小(长条状)?实际并非如此,阴影有时也会形成较大轮廓,但本例通过周长阈值 >188 已经能过滤掉大部分阴影噪点。后续我们可探讨关闭阴影检测。

6.4 主循环:读取与显示原始帧

while True:
    ret, frame = cap.read()
    if not ret:
        break

死循环逐帧读取摄像头画面。cap.read() 返回两个值:ret 为布尔类型,表示是否成功读取;frame 是 BGR 格式的三维 numpy 数组。若摄像头断开或视频流结束,ret=False,退出循环。

    cv2.imshow('frame', frame)

显示原始帧,窗口标题为 frame,让操作者可以对比处理前后的效果。

6.5 前景提取:fgmask.apply

    fgmask = fgbg.apply(frame)
    cv2.imshow('fgmask', fgmask)

fgbg.apply(frame) 是核心引擎。它接收当前帧,内部维护的背景模型会自动更新,并返回前景掩膜 fgmask。该掩膜是一张单通道灰度图像,每个像素的灰度值代表其属于前景的概率:255 表示确信的前景,0 表示背景,127(若阴影检测开启)表示检测到的阴影区域

cv2.imshow('fgmask') 直观地展示了这张掩膜图,你会看到移动的行人或手部区域呈现白色块,周围偶尔有灰色阴影区域。初次运行的几秒内,背景模型正在初始化,画面可能布满杂乱的前景噪点,但很快会稳定下来。

6.6 开运算去噪:fgmask_new

    fgmask_new = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
    cv2.imshow('fgmask1', fgmask_new)

这是提升检测质量的关键一步。cv2.morphologyEx 执行泛化形态学操作,这里参数 cv2.MORPH_OPEN 指明为开运算。

  • 开运算过程:先用 kernel(3x3 十字)对 fgmask 进行腐蚀:只有核覆盖区域的所有像素 > 0 时,中心才保留 255(或 127),否则置零。这会直接“吃掉”那些孤立的白色小点(噪点),同时会让大块前景的边界收缩。
  • 紧接着进行膨胀:使用相同的核,只要核覆盖区域有任一非零像素,中心就置为非零。这会将刚刚收缩的前景边缘扩张回来,大致恢复原来的大小,但那些已经被完全腐蚀掉的噪点不会再生。

最终效果:小的背景噪点被根除,前景物体保持完整,但内部的细小孔洞可能依然存在(开运算不能填充空洞,它侧重于去噪)。 这正是我们想要的前景优化。

窗口名称 fgmask1 是为了区别于原始掩膜,便于对比观察。你也可以在运行时看到,fgmask1fgmask 干净很多。

6.7 轮廓检测:findContours

    contours = cv2.findContours(fgmask_new, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

cv2.findContours 从二值图像中提取轮廓。虽然 fgmask_new 实际上可能是多值图像(有 127 阴影),但 findContours 会将所有非零像素(包括 127)视为白色区域来处理,所以阴影也可能产生轮廓,这需要后续周长过滤来排除。

参数详解:

  • cv2.RETR_EXTERNAL:只检测最外层轮廓,不关心轮廓内部的孔洞。因为我们只想要每个运动物体的外边界,不需要内部嵌套的轮廓。
  • cv2.CHAIN_APPROX_SIMPLE:压缩水平、垂直、对角线方向上的冗余点,只保留拐点。例如一个矩形轮廓只需 4 个点即可完整表示,大大减少轮廓数据量。

返回值处理:不同 OpenCV 版本 findContours 返回值个数不同(2 个或 3 个)。使用 [-2] 是稳妥的“倒数第二个元素即为轮廓列表”的获取方式,避免版本兼容性问题。

6.8 周长过滤与矩形框绘制

    frame_copy = frame.copy()
    for c in contours:
        perimeter = cv2.arcLength(c, closed=True)
        if perimeter > 188:
            x, y, w, h = cv2.boundingRect(c)
            cv2.rectangle(frame_copy, (x, y), (x + w, y + h), (0, 255, 0), 2)

① 复制帧
frame_copy = frame.copy() 必须存在,因为我们要在画面上绘制矩形,若直接在原帧上修改,会影响后续轮次可能再次使用的原始数据,且可能破坏原始显示窗口。使用副本还可以避免一些潜在的内存重叠问题。

② 周长计算
cv2.arcLength(c, closed=True) 返回轮廓的周长(像素单位)。closed=True 表示轮廓是封闭的,周长将包括闭合段。

③ 周长阈值过滤
if perimeter > 188: 是本例唯一的滤波条件。188 是一个经验值:对于一般网络摄像头 640x480 的分辨率,行人的手臂或整个人体轮廓的周长通常大于 200 像素,而小的背景噪点(如树叶晃动、轻微反光)的周长远小于此。该阈值可以根据实际场景调整,后文详细讨论。

④ 获取外接矩形
cv2.boundingRect(c) 返回轮廓的最小正外接矩形(x, y, w, h),其中 (x, y) 是左上角点,w 为宽,h 为高。

⑤ 绘制矩形
cv2.rectangle()frame_copy 上绘制一个矩形,参数依次为:图像、左上点、右下点、颜色(BGR 格式,绿色)、线宽 2 像素。

这样,所有周长大于 188 的运动轮廓都会被绿框标记。注意,阴影轮廓即使周长大于 188 也可能被框出,但阴影通常成片状,其外接矩形可能较宽扁,如果你发现阴影误检,后续可以增加宽高比或面积过滤,但原始代码并未增加,说明在作者的摄像头场景下,周长阈值已经能较好地区分。

6.9 结果展示与按键退出

    cv2.imshow('fgmask_new_rect', frame_copy)

    k = cv2.waitKey(30)
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

cv2.imshow 显示绘有矩形框的最终结果。窗口名 fgmask_new_rect 可能略显奇怪,但无伤大雅。

cv2.waitKey(30) 等待 30 毫秒,同时接收键盘按键。30ms 对应约 33 帧/秒的刷新率,如果实际摄像头帧率更高,画面可能播放更快;反之则慢。你可以调整该值来控制播放速度。

按下键盘左上角的 ESC 键(ASCII 码 27)时,k == 27 成立,退出循环。然后是资源释放和窗口销毁。


7. 运行效果展示与实时画面分析

将代码保存为 motion_detect.py,在命令行执行 python motion_detect.py,电脑摄像头将自动开启,弹出四个窗口。

初始几秒:背景模型处于学习阶段,fgmask 窗口一片杂乱,许多非运动区域被误判为前景。同时 fgmask_new_rect 窗口可能框出很多奇怪的小框。这是正常现象,MOG2 需要一定帧数(默认 500 帧)来稳定背景。

稳定后

  • framefgmask_new_rect 窗口几乎同步。当你静止站立时,框消失;当你挥动手臂或走动,手臂/身体区域立即出现绿色矩形,响应迅速,边界基本贴合目标。
  • fgmask 窗口显示黑白纹理,运动部分为白色,可能带有灰色阴影轮廓。
  • fgmask1 窗口看起来比 fgmask 更“干净”,背景的椒盐状噪点基本消失,但运动主体轮廓保持良好。

离开镜头再回来:如果你走出画面再返回,当你重新进入时,绿色框会立刻出现,说明背景模型已稳定且能快速适应重新出现的运动。

按下 ESC 键,所有窗口关闭,程序正常结束。


8. 参数调优与避坑指南

原代码能够正常工作,但不同场景(室内、室外、光线变化快)可能需要微调参数。以下给出调整方向,不会修改源代码,仅提供参考

8.1 卷积核形状与大小

  • 当前使用:十字形 3x3。十字形适合孤立点噪声,但对于一些线状的噪点(如地面反光),可能要去除不彻底。
  • 可以尝试:矩形核 cv2.MORPH_RECT 或椭圆形核 cv2.MORPH_ELLIPSE,以及更大的尺寸 (5,5)。核越大,去噪越强,但前景物体边缘会被腐蚀得越厉害,小物体可能消失。
  • 建议:如果场景噪点非常密集,可先用 3x3 矩形核做一次闭运算(填充前景空洞)再进行开运算。但原始代码仅用了开运算,足够应对多数情况。

8.2 MOG2 高级参数

通过在创建时传入参数,可以调整背景建模行为:

  • history:减少历史帧数(如 200)可以让模型更快适应背景变化,但可能引入更多误检;增大则可获得更纯净的背景。
  • varThreshold:默认 16。如果你发现运动物体经常断裂,可降低至 8~12;如果背景噪音太多,可升至 32。
  • detectShadows:设为 False 可以关闭阴影检测,将所有前景统一为纯 255,简化后续处理,消除阴影误检。若设为 False,则不存在灰色 127 的像素。但原代码没关,说明作者可能想保留阴影信息,或者当前场景阴影影响不大。

如果关闭阴影检测,代码变为

fgbg = cv2.createBackgroundSubtractorMOG2(detectShadows=False)

这样 fgmask 只含 0 和 255,开运算会更彻底。

8.3 周长阈值的选择

perimeter > 188 这个值非常依赖摄像头分辨率和与目标的距离。

  • 如果你使用 1080p 摄像头,行人的周长可能在 300~800 像素,阈值应提高到 300 甚至更高,否则远处微小的行人也会被丢弃。
  • 如果摄像头画面中物体普遍较小(如你想检测桌面上的手势),阈值应降低到 50~100。
  • 添加面积辅助过滤:原代码只用了周长,可能偶尔还会框住一些细长阴影。你可以在 if perimeter > 188 内部再增加面积和宽高比检查,例如:
    if perimeter > 188:
        x, y, w, h = cv2.boundingRect(c)
        area = cv2.contourArea(c)
        if area > 500 and h/w > 0.3:   # 自定义条件
            cv2.rectangle(...)
    

但原代码为了简洁没有添加,我们讲解时仍尊重原文。

8.4 阴影检测的取舍

阴影检测(detectShadows=True)会将阴影像素标记为 127。优点是可以区分运动物体和其产生的阴影;缺点是需要特别处理,否则阴影也会形成轮廓。

如果你保持阴影检测开启,可以在 fgmask 获取后,手动将 127 的像素清零:

fgmask[fgmask == 127] = 0

或者在开运算后进行此操作。但原代码未做处理,说明在作者的环境下,阴影并没有造成明显干扰,这可能得益于周长阈值已经排除了大部分阴影轮廓(阴影轮廓常细长,周长虽大但可能被开运算打散)。所以在学习时,你可以先保留原样,观察阴影影响再决定是否处理。


9. 拓展实战:从运动检测到目标跟踪、入侵报警

你现在已经拥有了一个基础的运动检测器。基于它,可以快速衍生出许多实用功能,这些扩展都不需要对源代码进行大改动。

9.1 运动跟踪与计数

  • 目标跟踪:为每个检测框分配一个唯一 ID,在连续帧中利用中心点距离或 IOU 进行匹配,实现多目标跟踪。常用算法:cv2.Tracker 系列或简单的卡尔曼滤波。
  • 人流量统计:在画面中画一条基准线,当检测框的中心从线上方穿越到下方(或反之),计数器+1。

9.2 入侵检测与报警

设定感兴趣区域(ROI),当有检测框与该区域有交集时,触发报警(发出蜂鸣声、保存帧图像、发送通知)。

9.3 结合深度学习

frame_copy 中的每个检测框区域,送入一个轻量级目标检测模型(如 YOLO-nano),进一步分类是“人”还是“车”,过滤掉非目标运动。

9.4 视频录制

使用 cv2.VideoWriter 将带检测框的视频流保存为文件,方便事后回放分析。

所有这些扩展都可以在现有 while 循环内添加少量逻辑即可完成,体现了基础运动检测作为“底座”的灵活性。


10. 总结与感悟

本文耗时上万字,围绕一段仅 30 余行的摄像头运动检测代码,进行了从原理到实践的全方位拆解。我们看到了 MOG2 自适应背景建模的强大,体会了开运算“先腐蚀再膨胀”对噪声的克制,也理解了周长阈值在轮廓筛选中的重要角色。

核心要点回顾:

  • 使用 cv2.VideoCapture(0) 调取摄像头,cap.read() 循环抓帧。
  • fgbg.apply(frame) 生成前景掩膜,MOG2 自动更新背景。
  • 十字形核开运算 cv2.morphologyEx(..., MORPH_OPEN, kernel) 去除噪点。
  • findContours + 轮廓周长过滤 + boundingRect + rectangle 框出运动目标。
  • ESC 键退出。

这个项目麻雀虽小,五脏俱全,完美体现了“用最简单的工具解决实际问题”的工程哲学。无论你是刚入门 OpenCV 的新手,还是想快速实现某个原型的开发者,希望这篇文章都能为你点亮一盏明灯。

最后,如果你觉得本文有帮助,请不要吝啬你的点赞、收藏、转发和关注!有任何疑问、调参心得或创意想法,欢迎在评论区畅所欲言,让我们一起进步。


全文完。感谢你的耐心阅读!机器视觉之路漫长,但每一步都精彩。我们下次见!

更多推荐