- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
导读
本篇文章围绕 AI-For-Beginners 课程第 06 课《计算机视觉入门》中的实验任务展开:利用 OpenCV 的**稠密光流(Dense Optical Flow)**技术,从一段手掌在稳定背景上左右上下移动的视频(palm-movement.mp4)中,自动判定视频各部分发生了哪个方向的运动。读完本文后,你将掌握视频帧提取、cv2.calcOpticalFlowFarneback稠密光流计算、极坐标转换与方向直方图分析这一完整方案,并能复刻仓库中 MovementDetection.ipynb 的全部实验步骤。
上图为实验视频中的一个关键帧(原图出自 OpenCV 实验讲义,视频与图片均位于实验目录中),可以看到手掌动作发生在稳定背景之上——这正是光流算法发挥作用的最佳场景。
实验目标与素材
本实验源自 AI for Beginners 课程 定义,任务非常明确:
- 核心目标:使用光流技术确定视频中哪些部分包含上(up)、下(down)、左(left)、右(right)四个方向的运动。
- 进阶目标(Stretch Goal):进一步结合**肤色(skin tone)**对掌/手指进行实际跟踪,可参考 OpenCV + Python 的指尖检测实现。
实验素材:
- 输入视频:palm-movement.mp4(人物手掌在稳定背景上做左右上下运动)
- 起始 Notebook:MovementDetection.ipynb
预备知识:什么是光流
在动手之前,先明确光流(Optical Flow)的核心思想。讲义 OpenCV.ipynb 中指出:仅对比相邻两帧可以知道画面变化的"量",但无法得知画面中"是什么"在动、"往哪里"动。光流正是用来回答这两个问题的技术,它分为两类:
- 稠密光流(Dense Optical Flow):为每一个像素计算一个运动向量,形成向量场,告诉我们在每个像素位置画面正朝哪个方向移动。
- 稀疏光流(Sparse Optical Flow):基于图像中一些显著特征(如边缘、角点),逐帧追踪这些特征的轨迹。
本实验采用的是稠密光流,因为我们需要从全局判断"哪部分画面发生了哪种方向的手掌运动"。
第一步:读取视频并提取帧
实验 Notebook 的第一步要求是"按照讲义中描述的方法获取视频帧"。讲义给出了完整的实现思路:OpenCV 可以直接逐帧读取视频,得到一个 NumPy 数组列表,每个元素是一帧图像。
import cv2 import matplotlib.pyplot as plt import numpy as np # 逐帧读取视频,保存到 frames 列表 frames = [] vid = cv2.VideoCapture('palm-movement.mp4') c = 0 while True: ok, fr = vid.read() if not ok: break frames.append(fr) c += 1 vid.release() print(f"Total frames: {c}")注意事项(色彩空间):OpenCV 出于历史原因以BGR顺序读取彩色图像,而 Matplotlib 使用传统的RGB顺序。因此,无论是显示单帧还是后续可视化,都应立即转换:
im = cv2.cvtColor(frames[0], cv2.COLOR_BGR2RGB) plt.imshow(im)这一细节在 OpenCV.ipynb 中有明确说明:如果跳过转换,显示出的颜色会不正确。
第二步:计算稠密光流帧
读取到所有帧之后,实验要求"按照讲义中的方法计算稠密光流帧"。讲义中给出的核心调用是:
flows = [cv2.calcOpticalFlowFarneback(f1, f2, None, 0.5, 3, 15, 3, 5, 1.2, 0) for f1, f2 in zip(bwframes[:-1], bwframes[1:])] flows[0].shape # 例如输出 (180, 320, 2)其中:
f1, f2:前后两帧的灰度图(cv2.COLOR_BGR2GRAY转换),光流算法基于灰度计算。pyr_scale = 0.5:构建图像金字塔时的缩放比例,标准值为 0.5。levels = 3:金字塔层数。winsize = 15:平均窗口大小,决定每个像素邻域范围。iterations = 3:每层金字塔上迭代求解次数。poly_n = 5:用于估计多项式展开的邻域像素尺寸。poly_sigma = 1.2:平滑多项式展开导数的标准差。flags = 0:算法标志,0 表示使用默认计算模式。
从输出形状(180, 320, 2)可以看出:每个光流帧的尺寸与视频帧相同,且带 2 个通道,分别对应光流向量的x(水平)和y(垂直)分量。这正是后续极坐标转换的数据基础。
第三步:将光流转换为极坐标并可视化
二维光流向量并不直观。讲义介绍了一个巧妙的思路:将光流向量从直角坐标(x, y)转换为极坐标,从而得到每个像素的两个新分量:
- 方向(direction / angle):向量与水平方向的夹角。
- 强度(intensity / magnitude):向量的长度,即运动的剧烈程度。
对应 OpenCV 函数为cv2.cartToPolar,讲义中的参考实现如下:
def flow_to_hsv(flow): hsvImg = np.zeros((flow.shape[0], flow.shape[1], 3), dtype=np.uint8) mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1]) hsvImg[..., 0] = 0.5 * ang * 180 / np.pi # 色相 = 方向 hsvImg[..., 1] = 255 # 饱和度固定为 255 hsvImg[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX) # 亮度 = 强度 return cv2.cvtColor(hsvImg, cv2.COLOR_HSV2BGR)这段代码把光流可视化到HSV 色彩空间:用色相表示运动方向、用亮度表示运动强度。讲义明确指出,在示例输出中"绿色调对应向左移动,蓝色调对应向右移动"。
一个值得注意的推论:当一帧中几乎所有像素都朝同一方向运动时,通常意味着发生了相机移动——利用光流可以检测并尝试补偿相机运动,这正是光流在实际视觉系统中的典型用途。
第四步:构建运动方向直方图
可视化之后,实验进入核心判断环节:为每一个光流帧构建方向直方图。直方图统计落在各个方向区间(bin)内的向量个数,从而把"大量像素各自朝什么方向运动"这一信息,浓缩成可读的分布:
- 对每个像素,根据极坐标中的角度
ang判定其方向所属的直方图区间。 - 关键优化:将所有幅值(magnitude)低于某阈值的向量清零。实验说明特别指出:这一步可以"滤除视频中的微小额外运动,例如眼睛和头部的轻微晃动",只保留手掌这类显著运动。
- 对部分帧绘制方向直方图进行观察。
判断逻辑因此变得非常简单:直方图中,对应于上/下/左/右方向的区间如果明显高于某阈值,即可断定该时间段发生了该方向的运动。
第五步:判定上下左右运动方向
完成直方图分析后,实验要求的收尾步骤是:选择与上、下、左、右四个方向对应、且计数超过设定阈值的直方图区间,据此对每个时间段作出方向判定。这本质上是将"图像级"的光流向量场压缩为"时间级"的简单运动标签,也是本实验与更复杂的动作识别方案之间最直观的区别——一些相对复杂的问题可以仅靠经典计算机视觉解决,而不必立即引入神经网络。
补充:帧差法与事件区间提取
在 OpenCV.ipynb 中,讲义还提供了与"确定哪些帧发生了运动"密切相关的配套技术——帧差法,可作为判定方向前的预处理参考:
bwframes = [cv2.cvtColor(x, cv2.COLOR_BGR2GRAY) for x in frames] diffs = [(p2 - p1) for p1, p2 in zip(bwframes[:-1], bwframes[1:])] diff_amps = np.array([np.linalg.norm(x) for x in diffs])当相机固定时,相邻帧几乎相同,像素差很小;一旦画面中出现显著运动,帧差幅度会迅速升高。讲义进一步用滑动平均平滑该曲线、设定阈值并提取连续"事件"区间:
def moving_average(x, w): return np.convolve(x, np.ones(w), 'valid') / w threshold = 13000 active_frames = np.where(diff_amps > threshold)[0] def subsequence(seq, min_length=30): ss = [] for i, x in enumerate(seq[:-1]): ss.append(x) if x + 1 != seq[i + 1]: if len(ss) > min_length: return ss ss.clear() sub = subsequence(active_frames)得到事件帧区间sub后,选取区间中间帧即可作为该事件的代表画面:frames[(sub[0] + sub[-1]) // 2]。这种"定位事件区间 → 抽取代表帧"的思路,与本实验"定位运动方向区间 → 打标签"的流程异曲同工。
进阶目标:基于肤色的手指跟踪
完成光流方向判定后,实验给出可选进阶方向:利用肤色(skin tone)对掌/手指进行实际跟踪。基本思路是先用颜色空间(如 HSV)将肤色像素从背景中分离,再结合轮廓与几何分析定位手指位置,从而在"方向判定"之上进一步获得"空间位置"信息。这一部分属于开放拓展,适合学有余力的读者自行查阅 OpenCV 肤色分割与指尖检测相关实现。
实验要点总结
- **"复杂问题可以只用计算机视觉解决"**是本次实验最重要的启示:运动检测、指尖检测这类看似复杂的任务,通过光流、阈值、直方图等经典手段即可实现。
- 掌握
cv2.calcOpticalFlowFarneback(稠密光流)、cv2.cartToPolar(极坐标转换)、HSV 可视化(色相=方向、亮度=强度)三大工具是完成本实验的关键。 - 直方图加阈值是判定方向的高性价比方案;帧差法加滑动平均则是定位"何时发生运动"的配套手段。
- 熟悉 OpenCV 能做什么,往往意味着很多传统视觉任务不必等待复杂模型即可落地。
相关资源:完整可运行的实验骨架在 MovementDetection.ipynb,全部算法示例与可视化代码在 OpenCV.ipynb,课程讲义见 06-IntroCV README。
- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
相关推荐
使用 OpenCV 光流法检测手掌运动:AI-For-Beginners 计算机视觉实验指南
使用 OpenCV 光流法检测手掌运动:AI For Beginners 计算机视觉实验指南 本文是 AI For Beginners 课程"计算机视觉导论(0
教程人工智能机器学习深度学习AI-For-Beginners 计算机视觉实验:基于 OpenCV 光流的手掌运动方向检测
AI For Beginners 计算机视觉实验:基于 OpenCV 光流的手掌运动方向检测 本文基于 AI For Beginners 课程"Introduc
教程人工智能机器学习深度学习基于 OpenCV 光流实现视频运动方向检测 —— AI-For-Beginners 第 6 课实验室实战指南
基于 OpenCV 光流实现视频运动方向检测 —— AI For Beginners 第 6 课实验室实战指南 导读 本指南围绕《AI for Beginner
教程人工智能机器学习深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考