<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Optical Flow on Tech Snippets - 嵌入式技术笔记</title>
    <link>https://tech-snippets.xyz/tags/optical-flow/</link>
    <description>Recent content in Optical Flow on Tech Snippets - 嵌入式技术笔记</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Fri, 22 May 2026 19:00:00 +0800</lastBuildDate>
    <atom:link href="https://tech-snippets.xyz/tags/optical-flow/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>OpenCV 光流法原理与实战指南：从 Lucas-Kanade 到稠密光流</title>
      <link>https://tech-snippets.xyz/posts/opencv-optical-flow-comprehensive-guide/</link>
      <pubDate>Fri, 22 May 2026 19:00:00 +0800</pubDate>
      <guid>https://tech-snippets.xyz/posts/opencv-optical-flow-comprehensive-guide/</guid>
      <description>前言 在计算机视觉的众多技术中，光流法（Optical Flow）可以说是最古老也最具生命力的算法之一。从 1950 年代心理学家 Gibson 首次提出视觉运动感知理论，到 1981 年 Lucas 和 Kanade 发表那篇经典论文，再到今天深度学习时代的 RAFT、GMFlow 等现代光流网络，这项技术已经走过了半个多世纪的历程。
我第一次接触光流法是在大学的计算机视觉课程上。当时教授在黑板上写下那个著名的光流方程 Iₓu + Iᵧv + Iₜ = 0，然后告诉我们：&amp;ldquo;这个简单的方程，蕴含了理解运动的全部秘密。&amp;rdquo; 那时候我还不太理解这句话的含义，直到后来在实际项目中用它实现了一个简单的视频目标跟踪系统，才真正体会到光流法的强大之处。
在今天的边缘计算和嵌入式 AI 场景中，光流法依然占据着不可替代的地位。相比于深度学习的目标跟踪算法，传统光流法具有以下优势：
计算量小：不需要复杂的神经网络，可以在资源受限的嵌入式设备上实时运行 无需训练：不需要标注数据，开箱即用 实时性好：很多优化后的实现可以轻松达到 30 FPS 以上 适用范围广：从无人机的视觉导航，到视频防抖，再到动作识别，光流法无处不在 本文将带您从零开始深入理解光流法的原理，从最基本的亮度恒定假设，到经典的 Lucas-Kanade 算法，再到 OpenCV 中的各种光流法实现。我们会通过大量代码示例，让您不仅理解理论，更能在实际项目中应用这项技术。
一、什么是光流法？ 1.1 光流的定义 简单来说，光流就是空间中运动物体在成像平面上像素运动的瞬时速度。当你盯着窗外行驶的汽车时，视网膜上汽车图像的移动速度就是光流。
更正式的定义是：给定图像序列 I(x, y, t)，光流法的目标是为每个像素点 (x, y) 找到一个速度向量 (u, v)，使得：
I(x, y, t) = I(x + u·dt, y + v·dt, t + dt) 这个等式表达的就是：经过微小的时间间隔 dt 后，像素点 (x, y) 移动到了 (x + u·dt, y + v·dt)，而亮度保持不变。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在计算机视觉的众多技术中，光流法（Optical Flow）可以说是最古老也最具生命力的算法之一。从 1950 年代心理学家 Gibson 首次提出视觉运动感知理论，到 1981 年 Lucas 和 Kanade 发表那篇经典论文，再到今天深度学习时代的 RAFT、GMFlow 等现代光流网络，这项技术已经走过了半个多世纪的历程。</p>
<p>我第一次接触光流法是在大学的计算机视觉课程上。当时教授在黑板上写下那个著名的光流方程 <code>Iₓu + Iᵧv + Iₜ = 0</code>，然后告诉我们：&ldquo;这个简单的方程，蕴含了理解运动的全部秘密。&rdquo; 那时候我还不太理解这句话的含义，直到后来在实际项目中用它实现了一个简单的视频目标跟踪系统，才真正体会到光流法的强大之处。</p>
<p>在今天的边缘计算和嵌入式 AI 场景中，光流法依然占据着不可替代的地位。相比于深度学习的目标跟踪算法，传统光流法具有以下优势：</p>
<ul>
<li><strong>计算量小</strong>：不需要复杂的神经网络，可以在资源受限的嵌入式设备上实时运行</li>
<li><strong>无需训练</strong>：不需要标注数据，开箱即用</li>
<li><strong>实时性好</strong>：很多优化后的实现可以轻松达到 30 FPS 以上</li>
<li><strong>适用范围广</strong>：从无人机的视觉导航，到视频防抖，再到动作识别，光流法无处不在</li>
</ul>
<p>本文将带您从零开始深入理解光流法的原理，从最基本的亮度恒定假设，到经典的 Lucas-Kanade 算法，再到 OpenCV 中的各种光流法实现。我们会通过大量代码示例，让您不仅理解理论，更能在实际项目中应用这项技术。</p>
<p><img alt="光流法基本原理" loading="lazy" src="/images/optical-flow-principle.svg"></p>
<h2 id="一什么是光流法">一、什么是光流法？</h2>
<h3 id="11-光流的定义">1.1 光流的定义</h3>
<p>简单来说，光流就是空间中运动物体在成像平面上像素运动的瞬时速度。当你盯着窗外行驶的汽车时，视网膜上汽车图像的移动速度就是光流。</p>
<p>更正式的定义是：给定图像序列 <code>I(x, y, t)</code>，光流法的目标是为每个像素点 <code>(x, y)</code> 找到一个速度向量 <code>(u, v)</code>，使得：</p>
<pre tabindex="0"><code>I(x, y, t) = I(x + u·dt, y + v·dt, t + dt)
</code></pre><p>这个等式表达的就是：经过微小的时间间隔 <code>dt</code> 后，像素点 <code>(x, y)</code> 移动到了 <code>(x + u·dt, y + v·dt)</code>，而亮度保持不变。</p>
<h3 id="12-光流法的三大假设">1.2 光流法的三大假设</h3>
<p>所有传统光流算法都建立在三个基本假设之上：</p>
<p><strong>假设一：亮度恒定（Brightness Constancy）</strong></p>
<p>同一物体像素点的亮度在连续帧之间不会发生变化。这是最核心的假设，也是光流约束方程的基础。</p>
<pre tabindex="0"><code>I(x, y, t) = I(x + dx, y + dy, t + dt)
</code></pre><p>这个假设在现实中并不总是成立——光照变化、阴影移动、物体旋转都会导致亮度变化。但在大多数场景下，尤其是帧间时间间隔很短时，这个假设是近似成立的。</p>
<p><strong>假设二：时间连续或小运动（Temporal Persistence）</strong></p>
<p>物体的运动是连续平滑的，不会发生突变。换句话说，相邻两帧之间的位移很小。</p>
<p>这就是为什么我们经常需要对图像进行降采样（构建金字塔）——因为当运动较大时，这个假设就不成立了。</p>
<p><strong>假设三：空间一致性（Spatial Coherence）</strong></p>
<p>相邻像素具有相似的运动。如果一个像素属于某个物体，那么它周围的像素也应该属于同一个物体，因此具有相似的运动速度。</p>
<p>这个假设是 Lucas-Kanade 算法能够求解的关键——因为光流约束方程只有一个，但有两个未知数 <code>u</code> 和 <code>v</code>，是病态问题。利用空间一致性，我们可以在一个小邻域内建立多个方程，从而求解出唯一解。</p>
<h3 id="13-光流法的分类">1.3 光流法的分类</h3>
<p>光流算法可以大致分为两类：</p>
<p><strong>稀疏光流（Sparse Optical Flow）</strong></p>
<p>只计算图像中部分特征点的光流（比如角点、边缘点）。计算速度快，但只能得到稀疏的运动向量。</p>
<p>代表算法：Lucas-Kanade</p>
<p><strong>稠密光流（Dense Optical Flow）</strong></p>
<p>计算图像中每一个像素的光流。计算量大，但能得到完整的运动场。</p>
<p>代表算法：Farneback, Horn-Schunck, TV-L1</p>
<p>在实际应用中，稀疏光流通常用于目标跟踪、视觉里程计等任务，而稠密光流则用于视频插帧、动作识别、视频分割等需要完整运动信息的场景。</p>
<h2 id="二光流约束方程的数学推导">二、光流约束方程的数学推导</h2>
<p>理解光流约束方程的推导过程，是深入理解光流法的关键。让我们从亮度恒定假设开始。</p>
<h3 id="21-泰勒展开">2.1 泰勒展开</h3>
<p>我们从亮度恒定假设出发：</p>
<pre tabindex="0"><code>I(x, y, t) = I(x + dx, y + dy, t + dt)
</code></pre><p>对右边进行一阶泰勒展开：</p>
<pre tabindex="0"><code>I(x + dx, y + dy, t + dt) = I(x, y, t) + (∂I/∂x)dx + (∂I/∂y)dy + (∂I/∂t)dt + ε
</code></pre><p>其中 <code>ε</code> 是高阶无穷小，可以忽略。</p>
<h3 id="22-光流约束方程">2.2 光流约束方程</h3>
<p>将两式相减，两边除以 <code>dt</code>，得到：</p>
<pre tabindex="0"><code>(∂I/∂x)(dx/dt) + (∂I/∂y)(dy/dt) + (∂I/∂t) = 0
</code></pre><p>令：</p>
<ul>
<li><code>Iₓ = ∂I/∂x</code>，图像在 x 方向的空间梯度</li>
<li><code>Iᵧ = ∂I/∂y</code>，图像在 y 方向的空间梯度</li>
<li><code>Iₜ = ∂I/∂t</code>，图像的时间梯度</li>
<li><code>u = dx/dt</code>，x 方向的光流速度</li>
<li><code>v = dy/dt</code>，y 方向的光流速度</li>
</ul>
<p>就得到了著名的光流约束方程：</p>
<pre tabindex="0"><code>Iₓu + Iᵧv + Iₜ = 0
</code></pre><h3 id="23-孔径问题">2.3 孔径问题</h3>
<p>现在问题来了：一个方程，两个未知数 <code>u</code> 和 <code>v</code>，怎么解？</p>
<p>这就是光流法中的<strong>孔径问题（Aperture Problem）</strong>。</p>
<p>想象一下，你透过一个小孔看一个运动的物体——你只能看到边缘的运动，却无法确定完整的运动方向。比如一条倾斜的直线向右运动，透过小孔你看到的可能只是直线沿着垂直方向移动。</p>
<p>数学上说，光流约束方程只能约束沿着梯度方向的运动分量，但垂直于梯度方向的运动分量是无法确定的：</p>
<pre tabindex="0"><code>∇I · (u, v) = -Iₜ
</code></pre><p>这说明光流向量 <code>(u, v)</code> 在梯度方向 <code>∇I</code> 上的投影等于 <code>-Iₜ / |∇I|</code>，但垂直于梯度方向的分量可以是任意值。</p>
<p>如何解决这个问题？这就是 Lucas-Kanade 算法的精髓所在——利用空间一致性假设，在一个小窗口内建立多个方程，从而求解出唯一的光流向量。</p>
<h2 id="三lucas-kanade-算法详解">三、Lucas-Kanade 算法详解</h2>
<p>Lucas-Kanade 算法由 Bruce D. Lucas 和 Takeo Kanade 于 1981 年提出，是最经典的稀疏光流算法，也是今天 OpenCV 中 <code>calcOpticalFlowPyrLK</code> 函数的理论基础。</p>
<h3 id="31-核心思想">3.1 核心思想</h3>
<p>Lucas-Kanade 的核心思想就是利用<strong>空间一致性假设</strong>：在一个小的邻域窗口内（比如 3×3 或 5×5），所有像素都具有相同的光流向量 <code>(u, v)</code>。</p>
<p>如果窗口内有 n 个像素，我们就可以建立 n 个方程：</p>
<pre tabindex="0"><code>Iₓ₁u + Iᵧ₁v = -Iₜ₁
Iₓ₂u + Iᵧ₂v = -Iₜ₂
...
Iₓₙu + Iᵧₙv = -Iₜₙ
</code></pre><p>写成矩阵形式就是：</p>
<pre tabindex="0"><code>[Iₓ₁ Iᵧ₁]   [u]   [-Iₜ₁]
[Iₓ₂ Iᵧ₂]   [v]   [-Iₜ₂]
...    =   ...
[Iₓₙ Iᵧₙ]       [-Iₜₙ]
</code></pre><p>简记为：</p>
<pre tabindex="0"><code>A · v = b
</code></pre><p>其中：</p>
<pre tabindex="0"><code>    [Iₓ₁ Iᵧ₁]
A = [Iₓ₂ Iᵧ₂]
    [...   ...]
    [Iₓₙ Iᵧₙ]

    [u]
v = [v]

    [-Iₜ₁]
b = [-Iₜ₂]
    [...  ]
    [-Iₜₙ]
</code></pre><h3 id="32-最小二乘解">3.2 最小二乘解</h3>
<p>这是一个超定方程组，我们用最小二乘法求解。两边乘以 <code>Aᵀ</code>：</p>
<pre tabindex="0"><code>AᵀA · v = Aᵀb
</code></pre><p>得到：</p>
<pre tabindex="0"><code>[ΣIₓ²   ΣIₓIᵧ] [u]   [-ΣIₓIₜ]
[ΣIₓIᵧ  ΣIᵧ²] [v] = [-ΣIᵧIₜ]
</code></pre><p>记 <code>M = AᵀA</code>，则：</p>
<pre tabindex="0"><code>v = M⁻¹ · Aᵀb
</code></pre><p>只要矩阵 <code>M</code> 可逆，我们就能求出唯一解。什么时候 <code>M</code> 可逆呢？当 <code>M</code> 的两个特征值都比较大的时候——换句话说，当这个窗口内包含至少两个不同方向的边缘时，这通常就是角点的特征。</p>
<p>这就是为什么 Lucas-Kanade 光流通常只对角点进行计算——只有角点区域的 <code>M</code> 矩阵才是良态的，才能得到稳定的光流解。</p>
<h3 id="33-算法步骤">3.3 算法步骤</h3>
<p>Lucas-Kanade 算法的完整步骤如下：</p>
<ol>
<li><strong>特征点检测</strong>：在第一帧图像中检测角点（通常用 Shi-Tomasi 算法）</li>
<li><strong>图像梯度计算</strong>：计算当前帧和下一帧图像的空间梯度 <code>Iₓ, Iᵧ</code> 和时间梯度 <code>Iₜ</code></li>
<li><strong>窗口内方程构建</strong>：对每个特征点，在其邻域窗口内构建超定方程组</li>
<li><strong>最小二乘求解</strong>：计算 <code>M = AᵀA</code>，求解光流向量 <code>v = M⁻¹Aᵀb</code></li>
<li><strong>迭代优化</strong>：使用牛顿法或高斯-牛顿法进行迭代求精</li>
</ol>
<p>（第一部分完，约2200字）</p>
<h2 id="四金字塔光流解决大位移问题">四、金字塔光流：解决大位移问题</h2>
<p>标准的 Lucas-Kanade 算法有一个严重的局限：它只能处理小运动。还记得小运动假设吗？如果物体在两帧之间移动了超过几个像素，泰勒展开的一阶近似就不再成立，光流计算就会失效。</p>
<p>金字塔光流（Pyramidal Lucas-Kanade）就是为了解决这个问题而提出的。</p>
<h3 id="41-图像金字塔">4.1 图像金字塔</h3>
<p>图像金字塔就是对原始图像进行多次降采样得到的一系列图像。最顶层是分辨率最低的图像，最底层是原始图像。</p>
<p>比如一个 4 层金字塔：</p>
<ul>
<li>第 0 层（原始）：640×480</li>
<li>第 1 层：320×240</li>
<li>第 2 层：160×120</li>
<li>第 3 层：80×60</li>
</ul>
<p>这样做的好处是：在低分辨率图像上，原来 10 个像素的位移可能就变成了 1-2 个像素，满足小运动假设。</p>
<h3 id="42-由粗到精coarse-to-fine策略">4.2 由粗到精（Coarse-to-Fine）策略</h3>
<p>金字塔光流采用&quot;由粗到精&quot;的计算策略：</p>
<ol>
<li><strong>顶层计算</strong>：在最顶层（分辨率最低）计算光流，此时位移很小，计算结果准确</li>
<li><strong>结果传播</strong>：将顶层的光流结果作为下一层的初始估计</li>
<li><strong>逐层求精</strong>：在下一层图像上，以之前的估计为起点进行迭代求精</li>
<li><strong>直到底层</strong>：重复上述过程直到达到原始图像层</li>
</ol>
<p>这样即使原始图像中有较大的位移，在顶层金字塔中也会被压缩成小位移，从而可以被准确计算。</p>
<h3 id="43-数学原理">4.3 数学原理</h3>
<p>在金字塔的每一层 L，我们计算光流增量 <code>(dLx, dLy)</code>，然后将累计的光流传递到下一层：</p>
<pre tabindex="0"><code>gL-1 = 2 × (gL + (dLx, dLy))
</code></pre><p>其中 <code>gL</code> 是第 L 层的累计光流估计。</p>
<p>这个过程一直进行到第 0 层（原始图像），最终得到完整的光流向量。</p>
<p>在 OpenCV 中，<code>calcOpticalFlowPyrLK</code> 函数默认使用 3-4 层金字塔，这也是实践中的最佳配置。</p>
<h2 id="五opencv-稀疏光流实战">五、OpenCV 稀疏光流实战</h2>
<p>现在让我们进入实战环节，用 OpenCV 实现 Lucas-Kanade 光流跟踪。</p>
<h3 id="51-环境准备">5.1 环境准备</h3>
<p>首先确保安装了正确版本的 OpenCV：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install opencv-python numpy matplotlib
</span></span></code></pre></div><h3 id="52-基本代码框架">5.2 基本代码框架</h3>
<p>这是一个最基本的 Lucas-Kanade 光流跟踪示例：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">cv2</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 视频捕获</span>
</span></span><span class="line"><span class="cl"><span class="n">cap</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">VideoCapture</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>  <span class="c1"># 0 表示默认摄像头</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Shi-Tomasi 角点检测参数</span>
</span></span><span class="line"><span class="cl"><span class="n">feature_params</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">maxCorners</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span>       <span class="c1"># 最多检测多少个角点</span>
</span></span><span class="line"><span class="cl">    <span class="n">qualityLevel</span><span class="o">=</span><span class="mf">0.3</span><span class="p">,</span>     <span class="c1"># 角点质量阈值</span>
</span></span><span class="line"><span class="cl">    <span class="n">minDistance</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span>        <span class="c1"># 角点之间的最小距离</span>
</span></span><span class="line"><span class="cl">    <span class="n">blockSize</span><span class="o">=</span><span class="mi">7</span>           <span class="c1"># 计算协方差矩阵的窗口大小</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Lucas-Kanade 光流参数</span>
</span></span><span class="line"><span class="cl"><span class="n">lk_params</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">winSize</span><span class="o">=</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">15</span><span class="p">),</span>     <span class="c1"># 搜索窗口大小</span>
</span></span><span class="line"><span class="cl">    <span class="n">maxLevel</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>           <span class="c1"># 金字塔层数</span>
</span></span><span class="line"><span class="cl">    <span class="n">criteria</span><span class="o">=</span><span class="p">(</span><span class="n">cv2</span><span class="o">.</span><span class="n">TERM_CRITERIA_EPS</span> <span class="o">|</span> <span class="n">cv2</span><span class="o">.</span><span class="n">TERM_CRITERIA_COUNT</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mf">0.03</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 随机颜色（用于绘制轨迹）</span>
</span></span><span class="line"><span class="cl"><span class="n">color</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 读取第一帧并检测角点</span>
</span></span><span class="line"><span class="cl"><span class="n">ret</span><span class="p">,</span> <span class="n">old_frame</span> <span class="o">=</span> <span class="n">cap</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">old_gray</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">old_frame</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2GRAY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">p0</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">goodFeaturesToTrack</span><span class="p">(</span><span class="n">old_gray</span><span class="p">,</span> <span class="n">mask</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">feature_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 创建一个 mask 用于绘制轨迹</span>
</span></span><span class="line"><span class="cl"><span class="n">mask</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">old_frame</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">ret</span><span class="p">,</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">cap</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">ret</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">frame_gray</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2GRAY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 计算光流</span>
</span></span><span class="line"><span class="cl">    <span class="n">p1</span><span class="p">,</span> <span class="n">st</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">calcOpticalFlowPyrLK</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">old_gray</span><span class="p">,</span> <span class="n">frame_gray</span><span class="p">,</span> <span class="n">p0</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">lk_params</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 筛选出跟踪成功的点</span>
</span></span><span class="line"><span class="cl">    <span class="n">good_new</span> <span class="o">=</span> <span class="n">p1</span><span class="p">[</span><span class="n">st</span> <span class="o">==</span> <span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">good_old</span> <span class="o">=</span> <span class="n">p0</span><span class="p">[</span><span class="n">st</span> <span class="o">==</span> <span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 绘制轨迹</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">new</span><span class="p">,</span> <span class="n">old</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">good_new</span><span class="p">,</span> <span class="n">good_old</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">new</span><span class="o">.</span><span class="n">ravel</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">c</span><span class="p">,</span> <span class="n">d</span> <span class="o">=</span> <span class="n">old</span><span class="o">.</span><span class="n">ravel</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">mask</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">line</span><span class="p">(</span><span class="n">mask</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">b</span><span class="p">)),</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">c</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">d</span><span class="p">)),</span> <span class="n">color</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">tolist</span><span class="p">(),</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">frame</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">b</span><span class="p">)),</span> <span class="mi">5</span><span class="p">,</span> <span class="n">color</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">tolist</span><span class="p">(),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">mask</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">cv2</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span><span class="s1">&#39;Optical Flow&#39;</span><span class="p">,</span> <span class="n">img</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">cv2</span><span class="o">.</span><span class="n">waitKey</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span> <span class="o">==</span> <span class="mi">27</span><span class="p">:</span>  <span class="c1"># ESC 退出</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 更新上一帧和特征点</span>
</span></span><span class="line"><span class="cl">    <span class="n">old_gray</span> <span class="o">=</span> <span class="n">frame_gray</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">p0</span> <span class="o">=</span> <span class="n">good_new</span><span class="o">.</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cap</span><span class="o">.</span><span class="n">release</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">cv2</span><span class="o">.</span><span class="n">destroyAllWindows</span><span class="p">()</span>
</span></span></code></pre></div><h3 id="53-参数详解">5.3 参数详解</h3>
<p>让我们详细解释一下关键参数：</p>
<p><strong>Shi-Tomasi 角点参数：</strong></p>
<ul>
<li><code>maxCorners</code>：检测的最大角点数。数量越多跟踪越稳定，但计算越慢。通常 50-200 是合理范围。</li>
<li><code>qualityLevel</code>：角点质量阈值，0-1 之间。值越高，检测到的角点越少但质量越好。0.3 是经验值。</li>
<li><code>minDistance</code>：角点之间的最小像素距离。避免在同一区域检测到大量重叠的角点。</li>
<li><code>blockSize</code>：计算角点响应时的窗口大小。</li>
</ul>
<p><strong>Lucas-Kanade 光流参数：</strong></p>
<ul>
<li><code>winSize</code>：搜索窗口大小。窗口越大，对噪声的鲁棒性越好，但计算量也越大。(15, 15) 或 (21, 21) 是常用值。</li>
<li><code>maxLevel</code>：金字塔层数。层数越多能处理的运动越大，但计算时间也越长。通常 3-4 层就足够了。</li>
<li><code>criteria</code>：迭代停止条件。这里设置最多迭代 10 次，或者当光流变化小于 0.03 时停止。</li>
</ul>
<h3 id="54-返回值解析">5.4 返回值解析</h3>
<p><code>calcOpticalFlowPyrLK</code> 返回三个值：</p>
<ol>
<li><strong>p1</strong>：下一帧中对应特征点的位置</li>
<li><strong>st</strong>：状态向量，1 表示该点跟踪成功，0 表示跟踪失败</li>
<li><strong>err</strong>：每个点的跟踪误差</li>
</ol>
<p><code>st</code> 向量非常重要——我们可以通过它筛选出跟踪成功的点，避免绘制错误的轨迹。在实际应用中，每隔一段时间就应该重新检测一次特征点，因为随着时间推移，很多点会逐渐丢失。</p>
<h2 id="六稠密光流farneback-算法">六、稠密光流：Farneback 算法</h2>
<p>稀疏光流只跟踪少数特征点，但很多应用场景需要知道每一个像素的运动——这就是稠密光流。</p>
<h3 id="61-farneback-算法简介">6.1 Farneback 算法简介</h3>
<p>Farneback 算法由 Gunnar Farneback 于 2003 年提出，是基于多项式展开的稠密光流算法。它的核心思想是：</p>
<ol>
<li>对每个像素周围的邻域进行二次多项式拟合</li>
<li>在邻域变换下，观察多项式系数如何变化</li>
<li>通过这些变化来估计光流</li>
</ol>
<p>相比于其他稠密光流算法，Farneback 的优势在于：</p>
<ul>
<li>计算速度相对较快</li>
<li>可以得到稠密的光流场</li>
<li>对光照变化有一定的鲁棒性</li>
</ul>
<h3 id="62-farneback-基本原理">6.2 Farneback 基本原理</h3>
<p>Farneback 算法假设图像在局部可以近似为二次多项式：</p>
<pre tabindex="0"><code>f(x) = xᵀAx + bᵀx + c
</code></pre><p>其中 <code>A</code> 是 2×2 对称矩阵，<code>b</code> 是 2 维向量，<code>c</code> 是标量。</p>
<p>当发生全局位移 <code>d</code> 时，多项式变换为：</p>
<pre tabindex="0"><code>f₂(x) = f₁(x - d) = (x - d)ᵀA(x - d) + bᵀ(x - d) + c
</code></pre><p>通过比较变换前后的多项式系数，我们可以解出位移 <code>d</code>。</p>
<p>在实际实现中，Farneback 算法同样使用金字塔策略，由粗到精地计算光流。</p>
<p>（第二部分完，约2100字）</p>
<h3 id="63-farneback-光流代码实现">6.3 Farneback 光流代码实现</h3>
<p>下面是使用 Farneback 算法计算稠密光流并可视化的完整代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">cv2</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cap</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">VideoCapture</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ret</span><span class="p">,</span> <span class="n">frame1</span> <span class="o">=</span> <span class="n">cap</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">prvs</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">frame1</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2GRAY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 创建 HSV 图像用于可视化光流</span>
</span></span><span class="line"><span class="cl"><span class="n">hsv</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">frame1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">hsv</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">255</span>  <span class="c1"># 饱和度设为最大值</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">ret</span><span class="p">,</span> <span class="n">frame2</span> <span class="o">=</span> <span class="n">cap</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">ret</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nb">next</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">frame2</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2GRAY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 计算 Farneback 稠密光流</span>
</span></span><span class="line"><span class="cl">    <span class="n">flow</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">calcOpticalFlowFarneback</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">prvs</span><span class="p">,</span> <span class="nb">next</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">pyr_scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span>    <span class="c1"># 金字塔缩放比例</span>
</span></span><span class="line"><span class="cl">        <span class="n">levels</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>         <span class="c1"># 金字塔层数</span>
</span></span><span class="line"><span class="cl">        <span class="n">winsize</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span>       <span class="c1"># 平均窗口大小</span>
</span></span><span class="line"><span class="cl">        <span class="n">iterations</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>     <span class="c1"># 每层迭代次数</span>
</span></span><span class="line"><span class="cl">        <span class="n">poly_n</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>         <span class="c1"># 多项式邻域大小</span>
</span></span><span class="line"><span class="cl">        <span class="n">poly_sigma</span><span class="o">=</span><span class="mf">1.2</span><span class="p">,</span>   <span class="c1"># 多项式标准差</span>
</span></span><span class="line"><span class="cl">        <span class="n">flags</span><span class="o">=</span><span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 将光流转换为极坐标 (幅度, 角度)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mag</span><span class="p">,</span> <span class="n">ang</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cartToPolar</span><span class="p">(</span><span class="n">flow</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="n">flow</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 角度对应 Hue 通道，幅度对应 Value 通道</span>
</span></span><span class="line"><span class="cl">    <span class="n">hsv</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">ang</span> <span class="o">*</span> <span class="mi">180</span> <span class="o">/</span> <span class="n">np</span><span class="o">.</span><span class="n">pi</span> <span class="o">/</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="n">hsv</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">normalize</span><span class="p">(</span><span class="n">mag</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">NORM_MINMAX</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># HSV 转 BGR 显示</span>
</span></span><span class="line"><span class="cl">    <span class="n">bgr</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">hsv</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_HSV2BGR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">cv2</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span><span class="s1">&#39;Dense Optical Flow&#39;</span><span class="p">,</span> <span class="n">bgr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">cv2</span><span class="o">.</span><span class="n">waitKey</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span> <span class="o">==</span> <span class="mi">27</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">prvs</span> <span class="o">=</span> <span class="nb">next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cap</span><span class="o">.</span><span class="n">release</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">cv2</span><span class="o">.</span><span class="n">destroyAllWindows</span><span class="p">()</span>
</span></span></code></pre></div><h3 id="64-farneback-参数调优">6.4 Farneback 参数调优</h3>
<p>Farneback 算法的参数对结果影响很大：</p>
<ul>
<li><strong>pyr_scale=0.5</strong>：经典值，每层金字塔分辨率减半。设为 0.8 可以保留更多细节但计算量增加。</li>
<li><strong>levels=3</strong>：金字塔层数，越多能处理越大的运动。</li>
<li><strong>winsize=15</strong>：平均窗口大小，越大越平滑但会丢失细小运动。</li>
<li><strong>iterations=3</strong>：每层迭代次数，越多越精确但越慢。</li>
<li><strong>poly_n=5</strong>：多项式邻域大小，通常 5 或 7。</li>
<li><strong>poly_sigma=1.2</strong>：高斯平滑标准差，对应 poly_n=5 时是 1.1，poly_n=7 时是 1.5。</li>
</ul>
<h2 id="七其他光流算法对比">七、其他光流算法对比</h2>
<p>OpenCV 中还实现了多种其他光流算法，让我们对比一下它们的特点：</p>
<h3 id="71-horn-schunck-算法">7.1 Horn-Schunck 算法</h3>
<p>Horn-Schunck 是最早的稠密光流算法之一（1981年），引入了全局平滑约束：</p>
<pre tabindex="0"><code>E = ∫∫(Iₓu + Iᵧv + Iₜ)² dxdy + α ∫∫[(∇u)² + (∇v)²] dxdy
</code></pre><p>优点是理论优美，缺点是对噪声敏感，计算量大，且容易过度平滑。</p>
<h3 id="72-tv-l1-算法">7.2 TV-L1 算法</h3>
<p>TV-L1 使用总变分（Total Variation）正则化，是目前质量最好的传统光流算法之一：</p>
<pre tabindex="0"><code>E = ||Iₓu + Iᵧv + Iₜ||₁ + λ(||∇u||₁ + ||∇v||₁)
</code></pre><p>L1 范数使得算法对异常值和噪声更加鲁棒。OpenCV 中通过 <code>createOptFlow_DualTVL1()</code> 创建。</p>
<h3 id="73-simpleflow-算法">7.3 SimpleFlow 算法</h3>
<p>SimpleFlow 是 2012 年提出的算法，核心思想是非局部滤波，计算效率高且效果不错。OpenCV 中通过 <code>calcOpticalFlowSF()</code> 调用。</p>
<h3 id="74-算法性能对比">7.4 算法性能对比</h3>
<table>
<thead>
<tr>
<th>算法</th>
<th>速度</th>
<th>精度</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lucas-Kanade</td>
<td>⭐⭐⭐⭐⭐</td>
<td>⭐⭐⭐</td>
<td>稀疏特征点跟踪</td>
</tr>
<tr>
<td>Farneback</td>
<td>⭐⭐⭐</td>
<td>⭐⭐⭐</td>
<td>一般稠密光流</td>
</tr>
<tr>
<td>TV-L1</td>
<td>⭐</td>
<td>⭐⭐⭐⭐</td>
<td>高精度稠密光流</td>
</tr>
<tr>
<td>SimpleFlow</td>
<td>⭐⭐⭐⭐</td>
<td>⭐⭐⭐</td>
<td>实时稠密光流</td>
</tr>
</tbody>
</table>
<h2 id="八实战项目基于光流的运动目标检测">八、实战项目：基于光流的运动目标检测</h2>
<p>现在让我们实现一个更实用的项目：使用光流法进行运动目标检测和跟踪。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">cv2</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">OpticalFlowTracker</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_corners</span><span class="o">=</span><span class="mi">200</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_corners</span> <span class="o">=</span> <span class="n">max_corners</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">feature_params</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">maxCorners</span><span class="o">=</span><span class="n">max_corners</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">qualityLevel</span><span class="o">=</span><span class="mf">0.01</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">minDistance</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">blockSize</span><span class="o">=</span><span class="mi">7</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">lk_params</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">winSize</span><span class="o">=</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">15</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">maxLevel</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">criteria</span><span class="o">=</span><span class="p">(</span><span class="n">cv2</span><span class="o">.</span><span class="n">TERM_CRITERIA_EPS</span> <span class="o">|</span> <span class="n">cv2</span><span class="o">.</span><span class="n">TERM_CRITERIA_COUNT</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mf">0.03</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">track_len</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detect_interval</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">frame_idx</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">process_frame</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">frame</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">frame_gray</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2GRAY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">vis</span> <span class="o">=</span> <span class="n">frame</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># 计算光流</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">img0</span><span class="p">,</span> <span class="n">img1</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">prev_gray</span><span class="p">,</span> <span class="n">frame_gray</span>
</span></span><span class="line"><span class="cl">            <span class="n">p0</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">float32</span><span class="p">([</span><span class="n">tr</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">tr</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">])</span><span class="o">.</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">p1</span><span class="p">,</span> <span class="n">st</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">calcOpticalFlowPyrLK</span><span class="p">(</span><span class="n">img0</span><span class="p">,</span> <span class="n">img1</span><span class="p">,</span> <span class="n">p0</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">lk_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">p0r</span><span class="p">,</span> <span class="n">st</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">calcOpticalFlowPyrLK</span><span class="p">(</span><span class="n">img1</span><span class="p">,</span> <span class="n">img0</span><span class="p">,</span> <span class="n">p1</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">lk_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="c1"># 双向验证：正反向计算的误差应该很小</span>
</span></span><span class="line"><span class="cl">            <span class="n">d</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">p0</span> <span class="o">-</span> <span class="n">p0r</span><span class="p">)</span><span class="o">.</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">max</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">good</span> <span class="o">=</span> <span class="n">d</span> <span class="o">&lt;</span> <span class="mf">1.0</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="n">new_tracks</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">tr</span><span class="p">,</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span> <span class="n">good_flag</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">,</span> <span class="n">p1</span><span class="o">.</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="n">good</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="ow">not</span> <span class="n">good_flag</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="cl">                <span class="n">tr</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">tr</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">track_len</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="k">del</span> <span class="n">tr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                <span class="n">new_tracks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">tr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">vis</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">y</span><span class="p">)),</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span> <span class="o">=</span> <span class="n">new_tracks</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="c1"># 绘制轨迹</span>
</span></span><span class="line"><span class="cl">            <span class="n">cv2</span><span class="o">.</span><span class="n">polylines</span><span class="p">(</span><span class="n">vis</span><span class="p">,</span> <span class="p">[</span><span class="n">np</span><span class="o">.</span><span class="n">int32</span><span class="p">(</span><span class="n">tr</span><span class="p">)</span> <span class="k">for</span> <span class="n">tr</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">],</span> <span class="kc">False</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="c1"># 计算运动统计</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">5</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">motions</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="n">tr</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="n">tr</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">tr</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">                <span class="n">mean_motion</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">motions</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">cv2</span><span class="o">.</span><span class="n">putText</span><span class="p">(</span><span class="n">vis</span><span class="p">,</span> <span class="sa">f</span><span class="s1">&#39;Mean motion: </span><span class="si">{</span><span class="n">mean_motion</span><span class="si">:</span><span class="s1">.2f</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                           <span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">30</span><span class="p">),</span> <span class="n">cv2</span><span class="o">.</span><span class="n">FONT_HERSHEY_SIMPLEX</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># 每隔几帧重新检测特征点</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">frame_idx</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">detect_interval</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">mask</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">frame_gray</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">mask</span><span class="p">[:]</span> <span class="o">=</span> <span class="mi">255</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="ow">in</span> <span class="p">[</span><span class="n">np</span><span class="o">.</span><span class="n">int32</span><span class="p">(</span><span class="n">tr</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="k">for</span> <span class="n">tr</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                <span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">mask</span><span class="p">,</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="n">p</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">goodFeaturesToTrack</span><span class="p">(</span><span class="n">frame_gray</span><span class="p">,</span> <span class="n">mask</span><span class="o">=</span><span class="n">mask</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">feature_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">p</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">np</span><span class="o">.</span><span class="n">float32</span><span class="p">(</span><span class="n">p</span><span class="p">)</span><span class="o">.</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                    <span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="o">.</span><span class="n">append</span><span class="p">([(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">frame_idx</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">prev_gray</span> <span class="o">=</span> <span class="n">frame_gray</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="n">cv2</span><span class="o">.</span><span class="n">putText</span><span class="p">(</span><span class="n">vis</span><span class="p">,</span> <span class="sa">f</span><span class="s1">&#39;Track count: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">tracks</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                   <span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">60</span><span class="p">),</span> <span class="n">cv2</span><span class="o">.</span><span class="n">FONT_HERSHEY_SIMPLEX</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">vis</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 主程序</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">cap</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">VideoCapture</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">tracker</span> <span class="o">=</span> <span class="n">OpticalFlowTracker</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">ret</span><span class="p">,</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">cap</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">ret</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="n">vis</span> <span class="o">=</span> <span class="n">tracker</span><span class="o">.</span><span class="n">process_frame</span><span class="p">(</span><span class="n">frame</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">cv2</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span><span class="s1">&#39;Optical Flow Tracking&#39;</span><span class="p">,</span> <span class="n">vis</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">cv2</span><span class="o">.</span><span class="n">waitKey</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span> <span class="o">==</span> <span class="mi">27</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">cap</span><span class="o">.</span><span class="n">release</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">cv2</span><span class="o">.</span><span class="n">destroyAllWindows</span><span class="p">()</span>
</span></span></code></pre></div><p>这个改进版的跟踪器加入了双向验证（正反向计算光流，误差小的点才保留），大大提高了跟踪的稳定性。</p>
<h2 id="九参数调优指南">九、参数调优指南</h2>
<h3 id="91-提高跟踪稳定性">9.1 提高跟踪稳定性</h3>
<p><strong>问题</strong>：跟踪点经常丢失，轨迹断断续续</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li>增大 <code>winSize</code> 到 (21, 21) 或 (31, 31)</li>
<li>增加 <code>maxLevel</code> 金字塔层数到 4-5</li>
<li>降低 <code>qualityLevel</code> 到 0.01，保留更多特征点</li>
<li>增加 <code>minDistance</code> 避免特征点过于密集</li>
</ul>
<h3 id="92-提高实时性能">9.2 提高实时性能</h3>
<p><strong>问题</strong>：在嵌入式设备上帧率太低</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li>减小 <code>maxCorners</code> 到 50-100</li>
<li>减小 <code>winSize</code> 到 (11, 11)</li>
<li>减小 <code>maxLevel</code> 到 2-3</li>
<li>对输入图像进行降采样（如 640×480 → 320×240）</li>
</ul>
<h3 id="93-处理快速运动">9.3 处理快速运动</h3>
<p><strong>问题</strong>：快速移动时跟踪失效</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li>增加 <code>maxLevel</code> 到 4-5</li>
<li>增大 <code>winSize</code></li>
<li>提高摄像头帧率（如果支持）</li>
<li>使用更高的相机快门速度减少运动模糊</li>
</ul>
<h2 id="十常见问题与解决方案">十、常见问题与解决方案</h2>
<h3 id="101-为什么跟踪点会漂移">10.1 为什么跟踪点会漂移？</h3>
<p><strong>原因</strong>：</p>
<ul>
<li>小的计算误差会累积</li>
<li>亮度恒定假设不成立</li>
<li>物体旋转或尺度变化</li>
</ul>
<p><strong>解决方案</strong>：</p>
<ul>
<li>定期重新检测特征点</li>
<li>使用更鲁棒的特征描述子（如 ORB）</li>
<li>加入双向验证机制</li>
</ul>
<h3 id="102-为什么光照变化时光流失效">10.2 为什么光照变化时光流失效？</h3>
<p><strong>原因</strong>：亮度恒定假设被破坏</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li>使用图像归一化（直方图均衡化）</li>
<li>改用基于梯度的特征（如 HOG）</li>
<li>使用对光照更鲁棒的光流变种（如 Census 变换）</li>
</ul>
<h3 id="103-如何处理大位移运动">10.3 如何处理大位移运动？</h3>
<p><strong>原因</strong>：小运动假设不成立</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li>增加金字塔层数</li>
<li>使用由粗到精的策略</li>
<li>考虑使用深度学习光流算法（如 RAFT）</li>
</ul>
<h2 id="十一进阶方向深度学习光流">十一、进阶方向：深度学习光流</h2>
<p>传统光流算法虽然经典，但在复杂场景下的精度已经被深度学习方法超越。</p>
<h3 id="111-现代深度学习光流网络">11.1 现代深度学习光流网络</h3>
<ul>
<li><strong>FlowNet / FlowNet2</strong>（2015/2016）：端到端的光流网络鼻祖</li>
<li><strong>PWC-Net</strong>（2018）：基于金字塔、变形、代价体的轻量网络</li>
<li><strong>RAFT</strong>（2020）：基于循环优化的光流网络，目前的 SOTA</li>
<li><strong>GMFlow / GMFlowNet</strong>（2022/2023）：基于全局匹配的高速高精度光流</li>
</ul>
<h3 id="112-在边缘设备上部署">11.2 在边缘设备上部署</h3>
<p>很多轻量型的光流网络已经可以在边缘设备上实时运行：</p>
<ul>
<li>PWC-Net 在 Jetson Nano 上可以达到 10-15 FPS</li>
<li>量化后的小型网络在 RK3588 上可以达到 30+ FPS</li>
</ul>
<p>如果你对精度要求很高，且硬件资源充足，深度学习光流是更好的选择。</p>
<h2 id="总结">总结</h2>
<p>本文从零开始深入讲解了光流法的原理和实战应用。我们从最基本的亮度恒定假设出发，推导了光流约束方程，理解了孔径问题的本质，然后详细介绍了 Lucas-Kanade 算法和金字塔策略。</p>
<p>在实战部分，我们不仅给出了稀疏光流和稠密光流的基本代码，还实现了一个带有双向验证的稳定目标跟踪器。我们对比了各种光流算法的优缺点，给出了详细的参数调优指南和常见问题的解决方案。</p>
<p>光流法作为计算机视觉中最古老也最基础的技术之一，至今仍然在边缘计算、嵌入式 AI 等领域发挥着不可替代的作用。理解光流法的原理，不仅能帮助你解决实际问题，更能为你打开理解视觉运动的大门。</p>
<p>从最早的 Gibson 视觉运动理论，到 Lucas-Kanade 算法，再到今天的深度学习光流网络，人类对视觉运动的理解已经走过了半个多世纪。但光流法的核心思想——从亮度变化中推断运动——始终没有改变。这也许就是计算机视觉最迷人的地方：简单的原理，却蕴含着理解世界运动规律的钥匙。</p>
<p>（全文完，约7000字）</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
