☰
AI-For-Beginners 计算机视觉实验:用 OpenCV 光流识别手掌视频中的上下左右运动方向
2026/10/10 12:01:25 网站建设 项目流程
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

本文基于 AI-For-Beginners 课程第 4 周"计算机视觉入门"的官方实验(Lab)文档,完整讲解如何仅用 OpenCV(不借助任何神经网络)从一段手掌在稳定背景上左右上下移动的视频中,利用稠密光流(Dense Optical Flow)加方向直方图,自动判断视频中哪些片段包含上、下、左、右四个方向的运动。读完后,你可以独立完成实验笔记 MovementDetection.ipynb 的全部四个步骤,并理解"纯计算机视觉算法"与深度学习方法各自的适用边界。

1. 实验任务:本 Lab 到底要做什么

实验文档 lab/README.md(该课程讲义 Introduction to Computer Vision 末尾的 "Assignment" 小节直接链接到此实验)给出了明确的任务设定:

  • 素材:一段视频 palm-movement.mp4,其中一只手掌在稳定(固定)背景前依次做左移、右移、上移、下移四个动作。
  • 主要目标(Goal):使用Optical Flow(光流)技术,判断视频的哪些片段(帧区间)分别包含向上、向下、向左、向右的运动。
  • 进阶目标(Stretch goal):进一步结合**肤色(skin tone)**信息,真正对手掌/手指做定位与跟踪,即把光流给出的"全画面运动"收敛到"手本身在往哪走"。

文档在 "Takeaway" 一节总结了本实验的核心认知:像运动检测、指尖检测这类相对复杂的任务,有时完全可以用纯计算机视觉手段解决,不需要深度学习。因此了解 OpenCV 这类库的能力边界非常重要。这一点与课程讲义 06-IntroCV/README.md 的结论一致——先用算法把图像"整理"好,能显著降低后续神经网络对训练数据量的要求。

2. 环境准备与实验材料

2.1 Python 环境

仓库根目录提供统一的 conda 环境定义 environment.yml(环境名ai4beg),与本实验直接相关的依赖有:

依赖作用
conda-forge::opencv本实验的核心库,提供视频读取、光流、色彩空间变换等全部算法
matplotlib=3.9可视化帧、光流图、方向直方图
numpy=1.26帧与光流的数组化表示、直方图与归一化运算
jupyter/ipykernel/ipywidgets运行.ipynb实验笔记

环境创建方式(在本仓库外执行,仓库本身只读):

conda env create -f environment.yml conda activate ai4beg

适用前提:光流函数cv2.calcOpticalFlowFarneback属于 OpenCV 基础模块(cv2),不依赖 GPU;视频读取依赖系统具备 ffmpeg 后端,conda-forge 版 opencv 已自带该依赖。

2.2 实验文件清单

文件说明
lab/README.md实验任务书(本文的主体来源)
lab/palm-movement.mp4输入视频:手掌四方向移动素材
lab/MovementDetection.ipynb起始笔记:四个 Markdown 步骤 + 四个# Code here代码格,需自行补全
OpenCV.ipynb课程讲义配套笔记,演示了视频读帧、帧差法、calcOpticalFlowFarneback光流与 HSV 可视化的完整参考实现

起始笔记 MovementDetection.ipynb 的结构是:按顺序给出 4 个带说明的 Markdown 单元,每个后面跟一个空代码格。下面按笔记的原始步骤顺序逐步展开,并结合 OpenCV.ipynb 中的已验证代码给出可直接运行的参考实现。

3. 步骤一:读取视频帧

笔记要求:"Start by getting video frames as described in the lecture"(按讲义所述获取视频帧)。讲义 OpenCV.ipynb 中给出的标准读帧代码是:

import cv2 import numpy as np vid = cv2.VideoCapture('palm-movement.mp4') c = 0 frames = [] while vid.isOpened(): ret, frame = vid.read() if not ret: # 读取失败即视频结束 break frames.append(frame) c += 1 vid.release() print(f"Total frames: {c}")

要点:

  • 每个frame是一个 NumPy 数组,形状为(H, W, 3),且 OpenCV 传统上以BGR通道序读取;
  • 本实验只做方向分析、不关心颜色,讲义的通用做法是先把所有帧转灰度:
bwframes = [cv2.cvtColor(x, cv2.COLOR_BGR2GRAY) for x in frames]

讲义中还展示了如何用帧间数组差值(frame difference)检测"画面里有没有东西在动":固定相机下相邻帧几乎相同,灰度帧相减后的范数在静止时很低、有显著运动时升高。但 OpenCV.ipynb 明确指出,帧差法只能告诉你变化量有多大,无法回答哪些像素在朝哪个方向移动——这正是本实验要改用光流的原因。

4. 步骤二:计算稠密光流并转换到极坐标

笔记原文要求:"calculate dense optical flow frames as described in the lecture, and convert dense optical flow to polar coordinates"。

4.1 稠密光流(Farneback 算法)

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:])]

cv2.calcOpticalFlowFarneback的参数与本课程取值对应如下(Farneback 是基于多项式展开的稠密光流算法,输入必须为灰度图):

位置参数本实验取值含义
prev/next相邻两灰度帧上一帧与当前帧
flowNone输出缓冲区(由 OpenCV 自动分配)
pyr_scale0.5图像金字塔中相邻层间的缩放系数
levels3金字塔层数,越大越能捕捉大范围运动
winsize15每个金字塔层的平滑窗口大小
iterations3每层金字塔的光流估计迭代次数
poly_n5局部像素邻域的多项式阶数(宽)
poly_sigma1.2多项式拟合的高斯核标准差
flags0默认行为(OPTFLOW_DEFAULT)

输出的每个flow形状为(H, W, 2):与帧同尺寸的两个通道,分别是每个像素运动向量的x、y 分量(y 轴向下,故 y 分量为正表示像素在向下移动)。

课程同时介绍了光流的两种类型(见 06-IntroCV/README.md):

  • Dense Optical Flow(稠密):为画面中每个像素都计算运动向量,得到完整向量场——本实验需要逐像素方向做直方图,因此选稠密光流;
  • Sparse Optical Flow(稀疏):只跟踪特征点(如边缘)在帧间的轨迹,计算量更小,适合特征明显的场景。

4.2 极坐标转换与 HSV 可视化

二维向量难以直接"看"方向。OpenCV.ipynb 用一个巧妙的思路:把光流向量转成极坐标,得到每像素的方向(角度)与强度(模长),再映射到 HSV 色彩空间——色相(Hue)编码方向、明度(Value)编码强度、饱和度固定为 255:

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 # 角度 [0,2π) -> 色相 [0,180) hsvImg[..., 1] = 255 # 饱和固定 hsvImg[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX) # 模长归一化为明度 return cv2.cvtColor(hsvImg, cv2.COLOR_HSV2BGR)

cv2.cartToPolar一次性输出模长mag与角度ang(弧度,范围[0, 2π)),后一步的直方图统计正是直接基于这个角度数组。讲义笔记中描述,将事件片段内的光流逐帧转 HSV 后,偏绿色对应向左移动、偏蓝色对应向右移动,可直观读出整体运动趋势;此外笔记还提示了一个实用技巧——若整帧像素几乎朝同一方向流动,通常是相机自身在移动,可做反向补偿。

5. 步骤三:对每帧光流构建方向直方图

笔记要求:"Build histogram of directions for each of the optical flow frame",并给出两条关键提示:

  1. 直方图的作用:统计各方向区间(bin)内落了多少运动向量,不同运动方向会在不同 bin 上形成峰值,从而把帧内的运动方向分离出来;
  2. 阈值去噪:"You may also want to zero out all vectors whose magnitude is below certain threshold"——把模长小于阈值的向量置零,以滤除眼睛眨动、头部微晃等与手势无关的微小运动(这正是"稳定背景"假设下光流法最容易受到的干扰源)。

基于笔记描述与讲义实现风格,可写出如下参考实现(n_bins取值可自行调整,bin 越宽方向分辨率越低、抗噪越好):

def direction_histogram(flow, mag_thresh=1.0, n_bins=36): mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1]) ang = mag >= mag_thresh # 弱向量置零(布尔掩码),抑制眼部/头部等微动 hist, _ = np.histogram(ang[mag >= mag_thresh], bins=n_bins, range=(0, 2 * np.pi)) return hist

对每一帧光流调用一次,并把部分帧的直方图画出来对比(笔记原文要求 "Plot the histograms for some of the frames")。

6. 步骤四:从直方图判断上/下/左/右方向

笔记最后一步:"Looking at histograms, it should be pretty straightforward how to determine direction of movement. You need to select those bins that correspond to up/down/left/right directions, and that are above certain threshold."

结合图像坐标系约定(y 轴向下,见 4.1 节),四个方向在角度域上的中心位置可以推断为:

方向向量特征角度中心(弧度)
向右(right)x>0, y≈00(含2π边界)
向下(down)x≈0, y>0π/2
向左(left)x<0, y≈0π
向上(up)x≈0, y<03π/2

参考实现:统计落在每个方向"扇区"内的向量占比,超过判定阈值(例如占有效向量的 25%~30%)才认定该方向成立;若四个方向都不显著,则判为"无明确方向"(例如手掌停顿的帧)。对整段视频逐帧执行后,即可输出形如"第 40~75 帧:左移;第 80~110 帧:上移……"的区间结果,完成任务书要求——找出视频中分别包含 up/down/left/right 运动的部分。

SECTORS = {'right': (0.0, 2*np.pi), 'down': (0, np.pi/2), 'left': (np.pi/2, np.pi), 'up': (np.pi, 3*np.pi/2)} def judge_direction(hist, min_share=0.30): total = hist.sum() if total == 0: return None centers = {'right': 0, 'down': np.pi/2, 'left': np.pi, 'up': 3*np.pi/2} # 简化策略:找最大峰值 bin,检查其是否落在某方向邻域且占比超阈值 peak = int(np.argmax(hist)) ang = (peak + 0.5) * (2 * np.pi / len(hist)) % (2 * np.pi) for name, c in centers.items(): if abs(ang - c) < (np.pi / len(hist)) * 3 and hist[peak] / total >= min_share: return name return None

以上两段代码是依据 MovementDetection.ipynb 各步骤的文字描述,并沿用 OpenCV.ipynb 中已验证的cartToPolar/光流用法整理出的参考实现;实验笔记本身只保留# Code here,具体参数(mag_thresh、n_bins、min_share)需按视频帧率与运动幅度自行调参。

7. 进阶目标:用肤色掩码把"全画面光流"收敛到"手掌光流"

任务书的 Stretch goal 是基于肤色(skin tone)真正跟踪手掌/手指运动。其核心思路可以概括为三步(对应任务书引用的两篇社区教程所讲的 OpenCV + Python 肤色跟踪方案,此处仅描述方法本身):

  1. 把每帧转 HSV 后,用肤色在 HSV 空间的常见取值范围做掩码(skin mask),得到"手掌区域"二值图;
  2. 用形态学操作(cv2.morphologyEx开/闭运算)清理掩码,取最大连通域作为手掌候选区域;
  3. 只对掩码内的光流向量做方向直方图(即第 5 节函数中把ang掩码与肤色掩码相与),并可用掩码质心(cv2.moments)逐帧追踪手掌位置变化作为方向判定的交叉验证。

这样做的好处是:直方图不再受"手掌移动导致画面其余部分产生反向背景响应"等干扰,方向判定更稳定,也能进一步支持逐帧的指尖/手掌位置输出。

8. 实验总结与延伸

  • 本实验的完整技术链路是:读帧 → 灰度化 → Farneback 稠密光流 →cartToPolar极坐标 → 阈值去弱向量 → 方向直方图 → 扇区阈值判定,全程只使用 OpenCV + NumPy,没有任何神经网络参与。
  • 实验印证了课程文档的结论:运动检测这类"相对复杂的任务"完全可以由经典计算机视觉算法解决;知道 OpenCV 能做什么、在什么场景下替代模型,是 AI 工程师的重要基础能力。
  • 想回顾光流的完整演示(包括帧差法动量曲线、flow_to_hsv可视化、事件片段裁剪),可运行课程配套笔记 OpenCV.ipynb;讲义 06-IntroCV/README.md 中另有关于 OpenCV 图像预处理(阈值化、透视变换、特征点提取)的配套示例,可作为"先用 CV 降低 NN 负担"这一思想的更多实例。
  • 注意本方法的前提假设:相机固定、背景稳定、光照基本不变——若背景大幅变化或光照剧烈波动,弱向量阈值与方向扇区阈值都需重新校准,必要时改用稀疏光流加特征点方案。
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询