<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Brown1729</title>
        <link>https://brown1729.github.io/</link>
        <description>Recent content on Brown1729</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <copyright>Brown1729</copyright>
        <lastBuildDate>Fri, 08 May 2026 21:39:00 +0800</lastBuildDate><atom:link href="https://brown1729.github.io/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>SIFT特征提取</title>
        <link>https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/</link>
        <pubDate>Fri, 08 May 2026 21:39:00 +0800</pubDate>
        
        <guid>https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/</guid>
        <description>&lt;img src="https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/panorama.jpg" alt="Featured image of post SIFT特征提取" /&gt;&lt;h1 id=&#34;选题背景&#34;&gt;选题背景
&lt;/h1&gt;&lt;p&gt;图像配准、目标识别、三维重建与全景拼接都依赖稳定可靠的局部特征。
对于同一场景的两张图像，拍摄时往往会出现尺度变化、视角变化、旋转以及亮度差异，
如果只使用原始像素进行匹配，算法通常对这些变化非常敏感。SIFT 是经典的局部不变特征方法，
它通过在尺度空间中寻找稳定极值点，并为每个关键点构建具有旋转与一定光照鲁棒性的描述子，
从而在复杂场景下仍能保持较高的匹配成功率&lt;/p&gt;
\[1\]&lt;p&gt;。&lt;/p&gt;
&lt;p&gt;本次作业选择 SIFT 作为实现对象，目标有两个。第一，完整实现 SIFT 的核心流程，
而不是直接调用 OpenCV 的现成算子；第二，将提取到的特征用于两幅图像的拼接，
验证算法在真实图像上的有效性。围绕这两个目标，本文不仅给出算法原理，
也重点说明与代码 &lt;code&gt;sift.py&lt;/code&gt; 对应的实现细节、参数选择与实验结果。&lt;/p&gt;
&lt;h1 id=&#34;算法描述重点&#34;&gt;算法描述（重点）
&lt;/h1&gt;&lt;h2 id=&#34;算法原理&#34;&gt;算法原理
&lt;/h2&gt;&lt;h3 id=&#34;尺度空间与高斯差分&#34;&gt;尺度空间与高斯差分
&lt;/h3&gt;&lt;p&gt;要让计算机在不同缩放比例下提取到相同的特征，就必须建立连续的&amp;quot;尺度空间&amp;quot;。Koenderink (1984) and Lindeberg (1994)的研究指出，
在各种合理假设下，唯一可能的尺度空间核是高斯核。&lt;/p&gt;
&lt;p&gt;因此，尺度空间函数 $L(x,y,\sigma)$ 定义为变化尺度的高斯函数 $G(x,y,\sigma)$ 与输入图像 $I(x,y)$ 的卷积：
&lt;/p&gt;
$$\begin{equation}
    L(x,y,\sigma) = G(x,y,\sigma) * I(x,y)
\end{equation}$$&lt;p&gt;其中，二维高斯函数为：
&lt;/p&gt;
$$G(x,y,\sigma) = \frac{1}{2\pi\sigma^2} e^{-(x^2+y^2)/2\sigma^2}$$&lt;p&gt;为了高效寻找尺度空间中稳定关键点的位置，Lowe 提出使用相邻尺度（即尺度之间相差一个常数因子 $k$）的高斯平滑图像相减，得到高斯差分函数 $D(x,y,\sigma)$：
&lt;/p&gt;
$$\begin{align*}
    D(x,y,\sigma) &amp; = (G(x,y,k\sigma) - G(x,y,\sigma)) * I(x,y) \\
                  &amp; = L(x,y,k\sigma) - L(x,y,\sigma)
\end{align*}$$&lt;p&gt;这么做的原因有很多，首先，DoG是一个计算效率很高的办法，因为在任何情况下进行尺度空间描述都需要平滑图像$L$，而$D$可以通过简单的图像减法得到。&lt;/p&gt;
&lt;p&gt;其次，DoG 是对尺度归一化的高斯拉普拉斯算子（$\sigma^2\nabla^2G$）（LoG）的绝佳近似。Lindeberg（1994）表明，为了真正的尺度不变性，需要使用因子
$\sigma^2$ 对拉普拉斯算子进行归一化。Mikolajczyk（2002）发现，与其他一系列可能的图像函数相比，$\sigma^2\nabla^2G$的最大值和最小值提取的图像特征
最为稳定。DoG 与 LoG 的关系可以用热传导方程来解释（用$\sigma$ 进行参数化）：
&lt;/p&gt;
$$\frac{\partial G}{\partial \sigma} = \sigma\nabla^2G$$&lt;p&gt;
$\nabla^2G$ 可以通过对$\partial G / \partial \sigma$的有限差分近似来获得，计算在相邻尺度$k\sigma$和$\sigma$之间的差值：
&lt;/p&gt;
$$\sigma\nabla^2G = \frac{\partial G}{\partial \sigma} \approx \frac{G(x,y,k\sigma) - G(x,y,\sigma)}{k\sigma - \sigma}$$&lt;p&gt;
整理后可得：
&lt;/p&gt;
$$\begin{equation}
    G(x,y,k\sigma) - G(x,y,\sigma) \approx (k-1)\sigma^2\nabla^2G
\end{equation}$$&lt;p&gt;
这证明了相差常数因子 $k$ 的高斯差分函数已经隐式地包含了实现尺度不变性所必需的 $\sigma^2$ 尺度归一化操作。
式中因子 $(k-1)$ 在任何尺度上都为常数，因此不会影响极值位置。当 $k$ 趋近于 $1$ 时，误差将趋于零，但实际上，即使在尺度上差异很大，
近似几乎不会对极值检测产生影响。&lt;/p&gt;
&lt;p&gt;一种有效地构造尺度空间的方法如&lt;a class=&#34;link&#34; href=&#34;#fig:dog_theory&#34; &gt;1&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:dog_theory&amp;rdquo;}所示。初始图像与不同尺度的高斯核重复卷积，以生成在尺度空间中由常数因子 $k$ 相差的图像序列在左列。
右侧未相邻图像相减得到的高斯差分图像。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/dog_theory.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;对于尺度空间的每个 octave，初始图像与高斯核重复卷积产生左侧所示的一组尺度空间图像。相邻高斯图像被减去以产生右侧的 高斯差分图像。在每个octave之后，高斯图像被下采样 2 倍，再重复该过程。&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;由于要在相邻尺度进行比较，那么一个每 octave 含4层的高斯差分金子塔，只能在中间两层中进行两个尺度的极值点检测，其它尺度则只能在不同组中进行。
为了在每 octave 中检测 $s$ 个尺度的极值点，则DoG金字塔每 octave 需 $s+2$ 层图像，而DoG金字塔由高斯金字塔相邻两层相减得到，所以高斯金字塔每 octave 需 $s+3$ 层图像。
Lowe 经过实验发现，当 $s = 3$ 时，特征点的稳定性达到最高。&lt;/p&gt;
&lt;p&gt;接下来需要计算每层图像对应的尺寸和尺度。Lowe 规定每过一个 octave，图像的长和宽就需要缩小一半。也就是说，面积会变成上一组的四分之一。
每个 octave 内，图像的尺度应该递增，之前已经定义 $k$ 是相邻两层图像之间的尺度比例常数。每个 octave 需要进行 $s$ 个尺度区间的划分，
即在这个 octave 中我们要检测出 $s$ 个尺度的极值点，因此设定一个 octave 内的 $k$ 应该按照 $k = 2^{1/s}$ 变化。假设初始层的尺度为 $\sigma_0$，
例如，在第一个 octave 的 6 层高斯图像中，$s = 6-3 = 3$，尺度的分布如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;第 1 层：$\sigma_0$ (即 $1.6$)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第 2 层：$k\sigma_0$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第 3 层：$k^2\sigma_0$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第 4 层：$k^3\sigma_0 = 2^{3/3}\sigma_0 = 2\sigma_0$ (即 $3.2$)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第 5 层：$k^4\sigma_0$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第 6 层：$k^5\sigma_0$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个 octave 的定义是绝对尺度 $\sigma$ 翻倍（变成 $2\sigma$），所以选择第4层（即 $2\sigma_0$）下采样后作为下一 octave 的初始图像。
$2\sigma_0$ 图像下采样后相对于本层未进行模糊处理的图像的尺度为 $\sigma_0$。然后不断乘新的 $k$，得到该层其他图像。&lt;/p&gt;
&lt;p&gt;Lowe 假设相机的原始图像已经自带了 $\sigma=0.5$ 的轻微模糊。为了保留更多高频细节，他先通过双线性插值把原图的长宽放大一倍（此时相对新像素间距 $\sigma=1.0$）
，然后再进行一次高斯平滑，达到基础尺度 $\sigma_0 = 1.6$。论文实验证明 $1.6$ 能提供接近最优的重复率。所以一般选择 $\sigma_0 = 1.6$ 作为初始尺度。&lt;/p&gt;
&lt;h3 id=&#34;关键点的精确定位与筛选&#34;&gt;关键点的精确定位与筛选
&lt;/h3&gt;&lt;p&gt;接下来就是提取极值点，如&lt;a class=&#34;link&#34; href=&#34;#fig:minia_maxima_theory&#34; &gt;2&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:minia_maxima_theory&amp;rdquo;}所示。离散空间中的局部极值检测在数学上定义了 $D(x,y,\sigma)$ 之后，由于计算机处理的是数字图像，
我们必须在离散的像素网格和有限的尺度层级中去寻找这个函数的极值点 。为了检测 $D(x,y,\sigma)$ 的局部最大值和最小值，Lowe 采用了一种三维的邻域比较法：
以当前正在考察的采样点为中心。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;把它与当前尺度（同一张图像）周围的 8 个相邻像素点进行比较。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把它与上一尺度图像对应位置的 9 个相邻像素点进行比较。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把它与下一尺度图像对应位置的 9 个相邻像素点进行比较。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一共需要与周围的 $8 + 9 + 9 = 26$ 个邻居进行比较。只有当该中心点的值严格大于所有 26 个邻居，
或者严格小于所有 26 个邻居时，它才会被选中作为&amp;quot;候选关键点&amp;quot; 。虽然听起来每个点都要比 26 次计算量很大，但 Lowe 在论文中特别指出，这个操作的实际计算成本相当低。
因为在实际代码执行时，绝大多数点在和前几个邻居比较后，就会因为不满足&amp;quot;严格大于或小于&amp;quot;的条件而被迅速淘汰，根本不需要完成全部 26 次比较。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/minia_maxima_theory.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;在尺度空间中，候选关键点的提取过程。&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;通过比较相邻像素找到离散的极值点后，接下来利用泰勒展开进行亚像素级别的精确定位，并剔除不稳定的点。
将原点平移至采样点，对尺度空间函数 $D(x,y,\sigma)$ 进行三元（$x, y, \sigma$）二阶泰勒展开：
&lt;/p&gt;
$$\begin{equation}
    D(\mathbf{v}) = D(0) + \frac{\partial D}{\partial \mathbf{v}}^T \bigg |_{\mathbf{v}=0} \mathbf{v} + \frac{1}{2} \mathbf{v}^T \frac{\partial^2 D}{\partial \mathbf{v}^2} \bigg |_{\mathbf{v}=0} \mathbf{v}
\end{equation}$$&lt;p&gt;
这里的参数 $\sigma$ 更确切的说应该是 $s$，即尺度空间的坐标。 $\mathbf{v} = (x, y, \sigma)^T$ 是相对于采样点的偏移量向量。对该式求导并令其为零，即可得到极值的精确偏移量估计 $\hat{\mathbf{v}}$：&lt;/p&gt;
$$\begin{equation}
    \hat{\mathbf{v}} = -\frac{\partial^2 D}{\partial \mathbf{v}^2}^{-1} \frac{\partial D}{\partial \mathbf{v}}
\end{equation}$$&lt;p&gt;
将求得的 $\hat{\mathbf{v}}$ 代回泰勒展开式，计算极值点处的函数值：
&lt;/p&gt;
$$\begin{equation}
    D(\hat{\mathbf{v}}) = D + \frac{1}{2} \frac{\partial D}{\partial \mathbf{v}}^T \hat{\mathbf{v}}
\end{equation}$$&lt;p&gt;
如果 $|D(\hat{\mathbf{v}})| &amp;lt; 0.03$，则说明该点对比度过低，容易受噪声干扰，予以剔除。&lt;/p&gt;
&lt;p&gt;DoG 函数在图像边缘会产生强烈的响应，但边缘点在垂直于边缘的方向上定位非常不稳定。Lowe 借用了 Harris 角点检测的思路&lt;/p&gt;
\[2\]&lt;p&gt;，通过计算关键点所在位置和尺度下的 $2 \times 2$ Hessian 矩阵 $H$ 来分析主曲率：
&lt;/p&gt;
$$\begin{equation}
    H = \begin{bmatrix} D_{xx} &amp; D_{xy} \\ D_{xy} &amp; D_{yy} \end{bmatrix}
\end{equation}$$&lt;p&gt;
矩阵特征值 $\alpha$（较大值）和 $\beta$（较小值）与主曲率成正比。为了避免直接计算特征值，算法通过计算矩阵的迹和行列式来评估主曲率比值 $r = \alpha / \beta$：
&lt;/p&gt;
$$Tr(H) = D_{xx} + D_{yy} = \alpha + \beta$$&lt;p&gt;
&lt;/p&gt;
$$Det(H) = D_{xx}D_{yy} - (D_{xy})^2 = \alpha\beta$$&lt;p&gt;
曲率比值关系可化简为：
&lt;/p&gt;
$$\begin{equation}
    \frac{Tr(H)^2}{Det(H)} = \frac{(r+1)^2}{r}
\end{equation}$$&lt;p&gt;
若 $\frac{Tr(H)^2}{Det(H)} \ge \frac{(r+1)^2}{r} \bigg |_{r=10}$
（Lowe 在论文中默认主曲率比值上限 $r=10$）则说明该点为边缘响应点，予以剔除。&lt;/p&gt;
&lt;h3 id=&#34;方向分配&#34;&gt;方向分配
&lt;/h3&gt;&lt;p&gt;为了实现旋转不变性，算法需要为每个关键点分配一个基准方向。
在关键点所在的特定尺度$\sigma&amp;rsquo;$下，得到对应的尺度图像$L(x,y, \sigma&amp;rsquo;)$。
计算以特征点为中心、以 $3 \times 1.5 \sigma&amp;rsquo;$ 为半径的窗口内每个像素点的梯度幅值 $m(x,y)$ 和梯度方向 $\theta(x,y)$。
&lt;/p&gt;
$$\begin{equation}
    m(x,y) = \sqrt{(L(x+1,y) - L(x-1,y))^2 + (L(x,y+1) - L(x,y-1))^2}
\end{equation}$$&lt;p&gt;
&lt;/p&gt;
$$\begin{equation}
    \theta(x,y) = \tan^{-1}\left(\frac{L(x,y+1) - L(x,y-1)}{L(x+1,y) - L(x-1,y)}\right)
\end{equation}$$&lt;p&gt;随后，使用直方图统计邻域内像素的梯度和方向，形成一个包含 36 个方向区间（bin）的直方图（覆盖 360 度）。
直方图的区间通常被定义为&amp;quot;左闭右开&amp;quot;$[a, b)$，角度映射到对应 Bin 的索引通常是通过向下取整操作来实现的。即
&lt;/p&gt;
$$Bin\_Index = \lfloor \frac{Angle}{10} \rfloor \pmod{36}$$&lt;p&gt;
在统计完所有梯度后，通常会使用一维高斯核对这 36 个 Bin 进行一次滑动窗口平滑，均衡临界位置的梯度。
其次找到最高峰所在的 Bin 时，会取该 Bin 及其左右相邻的两个 Bin（共 3 个点），在这 3 个点上拟合一条开口向下的抛物线。
抛物线的顶点横坐标，就是该关键点最终的主方向角度。剩下达到峰值 80% 以上的局部极值，同样对它进行 3 点抛物线拟合求出精确角度。
然后，在完全相同的 $(x, y)$ 坐标和 $\sigma$ 尺度下，克隆出一个新的关键点，并赋予它这个辅方向角度。&lt;/p&gt;
&lt;p&gt;至此，将检测出的含有位置、尺度和方向的关键点，即是该图像的SIFT特征点。&lt;/p&gt;
&lt;h3 id=&#34;局部图像描述符&#34;&gt;局部图像描述符
&lt;/h3&gt;&lt;p&gt;Lowe 对这一部分的设计深受 Edelman 等人关于初级视觉皮层中&amp;quot;复杂神经元（Complex neurons）&amp;ldquo;模型的启发。复杂细胞对特定方向和频率的梯度有响应，
但允许梯度在较小的感受野内发生空间位置的偏移。从某种程度上讲，SIFT 特征描述符就是对复杂细胞响应的模拟。&lt;/p&gt;
&lt;p&gt;通过以上的步骤已经找到了SIFT特征点位置、尺度和方向信息，下面就需要使用一组向量来描述关键点，也就是生成特征点描述子，这个描述子不但包括关键点，
也包含关键点周围对其有贡献的像素点，并且描述符应该有较高的独特性，以便于提高特征点正确匹配的概率。&lt;/p&gt;
&lt;p&gt;确定计算描述子所需的图像区域特征描述子与特征点所在的尺度有关，因此，对梯度的求取应在特征点对应的高斯图像上进行。
将关键点附近的邻域划分为$d×d$（Lowe 建议$d=4$）个子区域，每个子区域作为一个种子点，每个种子点有8个方向。&lt;/p&gt;
&lt;p&gt;每个子区域的物理尺寸与关键点的尺度 $\sigma$ 挂钩。具体而言，为每个子区域分配的实际边长为 $3\sigma$。
由于整个描述符由 $d \times d$ 个子区域构成，为了兼顾边界插值所需的外扩（$d+1$）以及最极端 $45^\circ$ 旋转时的对角线冗余
（$\sqrt{2}$），实际计算所需图像区域半径 $R$ 为：
&lt;/p&gt;
$$\begin{equation}
    R = \frac{3\sigma \times \sqrt{2} \times (d+1)}{2}
\end{equation}$$&lt;p&gt;
其中 $\sigma$ 为关键点的当前尺度，$d=4$ 为子区域划分数量。该半径保证了在旋转 $45^\circ$ 的最极端情况下，采样窗口仍然能够完整覆盖所有子区域，
为后续的双线性插值预留了足够的边界余量。&lt;/p&gt;
&lt;h4 id=&#34;坐标旋转与梯度重采样&#34;&gt;坐标旋转与梯度重采样
&lt;/h4&gt;&lt;p&gt;以关键点为中心，在最接近该关键点尺度的平滑图像 $L(x,y)$ 上划定一个半径为 $R$ 的圆形采样窗口。为了实现绝对的旋转不变性，
必须将采样窗口的坐标轴根据关键点的主方向 $\theta$ 进行反向旋转。对于窗口内的任意采样点 $(x,y)$（以关键点为中心），
旋转后的坐标 $(x&amp;rsquo;, y&amp;rsquo;)$ 为：
&lt;/p&gt;
$$\begin{equation}
    \begin{bmatrix} x&#39; \\ y&#39; \end{bmatrix} =
    \begin{bmatrix} \cos\theta &amp; -\sin\theta \\ \sin\theta &amp; \cos\theta \end{bmatrix}
    \begin{bmatrix} x \\ y \end{bmatrix}
\end{equation}$$&lt;p&gt;
旋转后，所有的梯度数据都被映射到了一个与图像绝对旋转无关的局部坐标系中，&lt;a class=&#34;link&#34; href=&#34;#fig:descriptor_theory&#34; &gt;3&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:descriptor_theory&amp;rdquo;} 左半部分展示了这一过程。&lt;/p&gt;
&lt;h4 id=&#34;梯度幅值与方向的计算&#34;&gt;梯度幅值与方向的计算
&lt;/h4&gt;&lt;p&gt;在旋转后的坐标系中，计算每个采样点的梯度幅值 $m(x&amp;rsquo;,y&amp;rsquo;)$ 和梯度方向 $\phi(x&amp;rsquo;,y&amp;rsquo;)$（注意此处 $\phi$ 是相对于旋转后坐标系的局部方向）。
由于旋转后的坐标 $(x&amp;rsquo;, y&amp;rsquo;)$ 通常是浮点数，无法直接索引图像像素，因此需要采用双线性插值从原始图像 $L$ 中获取该点的像素值，
再通过有限差分计算梯度：
&lt;/p&gt;
$$\begin{align}
    m(x&#39;,y&#39;)    &amp; = \sqrt{(L(x&#39;+1,y&#39;) - L(x&#39;-1,y&#39;))^2 + (L(x&#39;,y&#39;+1) - L(x&#39;,y&#39;-1))^2}              \\[4pt]
    \phi(x&#39;,y&#39;) &amp; = \tan^{-1}\left(\frac{L(x&#39;,y&#39;+1) - L(x&#39;,y&#39;-1)}{L(x&#39;+1,y&#39;) - L(x&#39;-1,y&#39;)}\right)
\end{align}$$&lt;p&gt;
其中 $L(x&amp;rsquo;,y&amp;rsquo;)$ 表示通过双线性插值在浮点位置 $(x&amp;rsquo;,y&amp;rsquo;)$ 处获得的灰度值。&lt;/p&gt;
&lt;h4 id=&#34;高斯加权&#34;&gt;高斯加权
&lt;/h4&gt;&lt;p&gt;为了让距离关键点中心较远的梯度对描述符的贡献减小（以提高对位置偏移的鲁棒性），算法使用一个以关键点为中心的高斯权重函数对梯度幅值进行加权。
该高斯窗口的尺度 $\sigma_w$ 取子区域宽度的一半，即 $\sigma_w = 0.5 \times 3\sigma = 1.5\sigma$。
对于位置 $(x&amp;rsquo;,y&amp;rsquo;)$ 处的采样点，其加权后的梯度幅值为：
&lt;/p&gt;
$$\begin{equation}
    m_w(x&#39;,y&#39;) = m(x&#39;,y&#39;) \times \exp\left(-\frac{x&#39;^2 + y&#39;^2}{2\sigma_w^2}\right)
\end{equation}$$&lt;p&gt;
&lt;a class=&#34;link&#34; href=&#34;#fig:descriptor_theory&#34; &gt;3&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:descriptor_theory&amp;rdquo;} 中以重叠的圆形示意了高斯权重的分布范围。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/descriptor_theory.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;左图：将关键点周围 16 \times 16 的采样窗口划分为 4 \times 4 个子区域，每个子区域统计 8 个方向的梯度直方图， 最终形成 4 \times 4 \times 8 = 128 维描述符向量。高斯窗口（重叠圆圈）用于降低远离中心像素的权重。 右图：每个子区域的 8 方向梯度直方图，箭头长度表示该方向上的梯度累加和。&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;三线性插值分配&#34;&gt;三线性插值分配
&lt;/h4&gt;&lt;p&gt;将旋转后的采样窗口划分为 $d \times d = 4 \times 4$ 个子区域，每个子区域的大小为 $3\sigma \times 3\sigma$。
每个采样点的加权梯度幅值 $m_w(x&amp;rsquo;,y&amp;rsquo;)$ 需要按照三线性插值的方式分配到相邻的子区域和方向区间中。&lt;/p&gt;
&lt;p&gt;具体而言，对于每个采样点，根据其相对于 $4 \times 4$ 子区域网格的浮点坐标 $(x_r, y_r)$ 以及梯度方向 $\phi$，
将其幅值按距离权重分配到相邻的 2 个子区域行、2 个子区域列以及 2 个方向区间（共 $2 \times 2 \times 2 = 8$ 个 bin）中。
分配给某个特定 bin 的权重为：
&lt;/p&gt;
$$\begin{equation}
    w = m_w \times (1 - d_x)(1 - d_y)(1 - d_\phi)
\end{equation}$$&lt;p&gt;
其中 $d_x, d_y, d_\phi$ 分别为采样点在该维度上与相邻 bin 中心之间的距离。这种平滑分配机制有效避免了直方图的突变，
提高了描述符的稳定性。&lt;/p&gt;
&lt;h4 id=&#34;生成描述符向量&#34;&gt;生成描述符向量
&lt;/h4&gt;&lt;p&gt;经过三线性插值后，每个子区域生成一个包含 8 个方向区间（每 $45^\circ$ 一个区间）的梯度直方图。将这些直方图按顺序排列，
形成一个 $4 \times 4 \times 8 = 128$ 维的特征向量 $\mathbf{f} = (f_1, f_2, \dots, f_{128})$。
这个向量即为 SIFT 特征点的最终描述符。&lt;/p&gt;
&lt;h4 id=&#34;归一化与抗光照变化处理&#34;&gt;归一化与抗光照变化处理
&lt;/h4&gt;&lt;p&gt;为了减少光照变化（如亮度偏移和对比度变化）对描述符的影响，需要对 128 维向量进行归一化处理。
首先将向量归一化为单位长度：
&lt;/p&gt;
$$\begin{equation}
    \mathbf{f}&#39; = \frac{\mathbf{f}}{\|\mathbf{f}\|_2} = \frac{\mathbf{f}}{\sqrt{f_1^2 + f_2^2 + \cdots + f_{128}^2}}
\end{equation}$$&lt;p&gt;但单位归一化只能消除线性光照变化（如对比度线性拉伸）的影响。对于非线性的光照变化（如局部阴影导致的某些梯度幅值饱和），
算法还需对归一化后的向量进行阈值截断：将每个分量限制不超过 $0.2$，即 $f&amp;rsquo;_i = \min(f&amp;rsquo;_i, 0.2)$。
截断后，向量的长度不再是 1，因此需再次进行归一化：
&lt;/p&gt;
$$\begin{equation}
    \mathbf{f}&#39;&#39; = \frac{\mathbf{f}&#39;}{\|\mathbf{f}&#39;\|_2}
\end{equation}$$&lt;p&gt;$0.2$ 的阈值意味着如果一个描述符的某个方向分量特别大，说明该处的梯度幅值受到了光照饱和的影响，
将其截断后重新归一化可以使描述符更加关注梯度的整体分布模式而非绝对的灰度强度，从而大幅提升对光照变化的鲁棒性。&lt;/p&gt;
&lt;p&gt;至此，每个 SIFT 关键点都拥有了一个 128 维的描述符向量。该描述符综合了关键点的位置 $(x,y)$、尺度 $\sigma$、
主方向 $\theta$ 以及周围区域详细的梯度分布信息。&lt;/p&gt;
&lt;h3 id=&#34;特征匹配&#34;&gt;特征匹配
&lt;/h3&gt;&lt;p&gt;假设我们有基准图像 A 和待匹配图像 B。对于图像 A 中的每一个 128 维特征描述符向量，
我们需要在图像 B 的特征点数据库中寻找它的对应点。评判两个特征点相似度的可以计算它们 128 维向量之间的欧氏距离。
距离越小，说明这两个局部区域的纹理结构越相似。&lt;/p&gt;
&lt;p&gt;但在高维空间（128维）中，仅靠全局的距离阈值来判断是不够的。因为不同的特征点本身的区分度不同，有些特征则容易和背景噪声混淆。&lt;/p&gt;
&lt;h4 id=&#34;最近邻与次近邻距离比&#34;&gt;最近邻与次近邻距离比
&lt;/h4&gt;&lt;p&gt;Lowe 提出了一种相对比较法：对于图像 A 中的特征点 $P$，在图像 B 中找到距离它最近的特征点 $N_1$（距离为 $D_1$）。
继续在图像 B 中找到距离它第二近的特征点 $N_2$（距离为 $D_2$）。计算两者的距离比值：$Ratio = \frac{D_1}{D_2}$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果这是一个正确的匹配，那么最近邻 $N_1$ 应该是目标在另一张图上的真实投影，它的距离 $D_1$ 会非常小；而次近邻 $N_2$
通常只是背景杂波中的一个相似干扰项，距离 $D_2$ 会相对较大。因此，比值 $D_1/D_2$ 会很小。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果这是一个错误的匹配（比如特征点 $P$ 实际上是背景噪声，在图 B 中根本没有对应点），
那么图 B 中会有很多相似的干扰项聚集在相似的距离上，
导致 $D_1$ 和 $D_2$ 差不多大，比值 $D_1/D_2$ 会接近 1 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lowe 在大量真实图像上统计了概率密度函数（PDF），发现将比值阈值设定为 0.8 时，可以剔除掉 90% 的错误匹配，
只会误剔除不到 5% 的正确匹配 。即：
&lt;/p&gt;
$$\text{If } \frac{D_1}{D_2} &lt; 0.8 \implies \text{接受该匹配，否则剔除}$$&lt;h2 id=&#34;图像拼接&#34;&gt;图像拼接
&lt;/h2&gt;&lt;p&gt;得到匹配点后，想要进一步将两个图像拼接起来，需要估计单应矩阵 $H$，使第二幅图像映射到第一幅图像坐标系。
本次作业使用 RANSAC&lt;/p&gt;
\[3\]&lt;p&gt; 求解该矩阵，以降低误匹配对结果的影响。
最后将第二幅图像透视变换到第一幅图像坐标系，并在重叠区域做简单平均融合，从而得到最终的全景图像。&lt;/p&gt;
&lt;h1 id=&#34;软件实现&#34;&gt;软件实现
&lt;/h1&gt;&lt;p&gt;本文程序实现位于 &lt;code&gt;code/sift.py&lt;/code&gt;，整体由&amp;quot;特征提取&amp;quot;与&amp;quot;图像拼接&amp;quot;两部分组成。前者对应标准 SIFT 流程，后者使用自实现 SIFT 的输出完成两图配准和融合。为了减少重复 I/O，程序既支持直接传入图像路径，也支持传入灰度数组。&lt;/p&gt;
&lt;h2 id=&#34;尺度空间与高斯差分-1&#34;&gt;尺度空间与高斯差分
&lt;/h2&gt;&lt;p&gt;在代码层面，尺度空间部分由 &lt;code&gt;gaussian_blur_image&lt;/code&gt;、&lt;code&gt;create_initial_image&lt;/code&gt;、&lt;br&gt;
&lt;code&gt;build_gaussian_pyramid&lt;/code&gt; 与 &lt;code&gt;build_dog_pyramid&lt;/code&gt; 四个函数完成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;gaussian_blur_image&lt;/code&gt; 根据 $\sigma$ 动态生成核大小，近似遵循 $6\sigma+1$ 的经验规则，
因为用 Python 实现 for 循环效率实在过于缓慢，所以这里直接调用 OpenCV 的函数 &lt;code&gt;GaussianBlur&lt;/code&gt; 完成卷积。
&lt;a class=&#34;link&#34; href=&#34;#fig:gaussian_blur_code&#34; &gt;4&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:gaussian_blur_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/gaussian_blur_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;高斯模糊代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;create_initial_image&lt;/code&gt; 先将输入图像放大 $2$ 倍，再利用高斯函数的半群性质补足到初始模糊尺度 $\sigma_0=1.6$，
与经典 SIFT 的做法保持一致。
&lt;a class=&#34;link&#34; href=&#34;#fig:create_initial_image_code&#34; &gt;5&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:create_initial_image_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/create_initial_image_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;创建初始图像代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;build_gaussian_pyramid&lt;/code&gt; 在每个 octave 内同样使用高斯函数的半群性质递推计算相邻层所需补充模糊量，
而不是每次从原图重新卷积，从而保证尺度关系正确。
&lt;a class=&#34;link&#34; href=&#34;#fig:build_gaussian_pyramid_code&#34; &gt;6&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:build_gaussian_pyramid_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/build_gaussian_pyramid_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;构建高斯金字塔代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;build_dog_pyramid&lt;/code&gt; 直接对同一 octave 内相邻高斯层做减法，得到 DoG 图像序列。
&lt;a class=&#34;link&#34; href=&#34;#fig:build_dog_pyramid_code&#34; &gt;7&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:build_dog_pyramid_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/build_dog_pyramid_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;构建 DoG 金字塔代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当前实现固定参数为 &lt;code&gt;num_octaves = 4&lt;/code&gt;、&lt;code&gt;num_intervals = 3&lt;/code&gt;、&lt;code&gt;sigma_init = 1.6&lt;/code&gt;。
这一设置在图像上能够稳定提取较多特征，同时控制住运行时间。&lt;/p&gt;
&lt;h2 id=&#34;关键点的精确定位与筛选-1&#34;&gt;关键点的精确定位与筛选
&lt;/h2&gt;&lt;p&gt;关键点检测由 &lt;code&gt;is_pixel_an_extremum&lt;/code&gt;、&lt;code&gt;localize_extremum_via_taylor&lt;/code&gt; 与&lt;/p&gt;
&lt;p&gt;&lt;code&gt;compute_keypoints&lt;/code&gt; 完成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;is_pixel_an_extremum&lt;/code&gt; 对每个候选点做严格的 26 邻域比较，避免平台值进入候选集合。
注意，中心点可能为负数，此时需要看是否比周围的像素都小。
&lt;a class=&#34;link&#34; href=&#34;#fig:is_pixel_an_extremum_code&#34; &gt;8&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:is_pixel_an_extremum_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/is_pixel_an_extremum_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;检查像素是否为极值点代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;localize_extremum_via_taylor&lt;/code&gt; 负责计算梯度向量和 Hessian 矩阵，
并通过伪逆求解三维偏移量，因为 Hessian 矩阵可能非正定，所以需要使用伪逆而不是直接求逆。
对 $\sigma$ 方向求导并不是按照真实的 $\sigma$ 作为变量，而是以这个区域中的序号，即 $s$ 作为变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在精定位之后，代码按照论文思路先做低对比度剔除，再用二维 Hessian 主曲率比值剔除边缘响应点。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一部分决定了最终关键点是否稳定，也是后续方向分配和描述子生成的基础。&lt;/p&gt;
&lt;h2 id=&#34;方向分配-1&#34;&gt;方向分配
&lt;/h2&gt;&lt;p&gt;方向分配由 &lt;code&gt;assign_orientations&lt;/code&gt; 完成。实现时有两个关键点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;梯度方向采用 &lt;code&gt;atan2(dy, dx)&lt;/code&gt; 的结果并映射到 $[0,360)$，与图像坐标系保持一致。
&lt;a class=&#34;link&#34; href=&#34;#fig:compute_gradient_and_orientation_code&#34; &gt;9&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:compute_gradient_and_orientation_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/compute_gradient_and_orientation_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;计算梯度代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在方向直方图形成后，先做一次高斯滤波。再做一次五点平滑，然后寻找局部峰值，
并使用抛物线插值计算更精确的峰值角度。
&lt;a class=&#34;link&#34; href=&#34;#fig:five-point_smoothing_code&#34; &gt;10&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:five-point_smoothing_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/five-point_smoothing_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;五点平滑代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;#fig:parabolic_interpolation_code&#34; &gt;11&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:parabolic_interpolation_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/parabolic_interpolation_code.png&#34;&gt;&lt;figcaption&gt;
        &lt;h4&gt;抛物线插值代码实现&lt;/h4&gt;
      &lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果某些次峰超过主峰的 $80%$，程序会为同一位置生成额外方向，这也解释了&amp;quot;稳定关键点个数&amp;quot;与&amp;quot;最终描述子个数&amp;quot;不相同。&lt;/p&gt;
&lt;h2 id=&#34;局部图像描述符-1&#34;&gt;局部图像描述符
&lt;/h2&gt;&lt;p&gt;描述子生成对应 &lt;code&gt;generate_descriptor&lt;/code&gt;。该函数先根据关键点方向将局部窗口旋转到统一坐标系，
再将采样点投影到 $4\times 4$ 网格以及 8 个方向 bin 中。
程序使用三线性插值将每个采样点的贡献分配给最多 8 个相邻 bin，并采用&amp;quot;两次归一化 + 一次截断&amp;quot;的标准处理流程。
最终输出的每个描述子都是 128 维 &lt;code&gt;float32&lt;/code&gt; 向量。&lt;/p&gt;
&lt;p&gt;另外，程序内部关键点坐标是按放大后的基准图像计算的，因此在 &lt;code&gt;compute_sift&lt;/code&gt; 返回结果前，
又调用 &lt;code&gt;convert_keypoint_to_input_image_size&lt;/code&gt; 将坐标和尺度除以 2，
保证输出关键点与输入图像坐标保持一致。&lt;/p&gt;
&lt;h2 id=&#34;特征匹配-1&#34;&gt;特征匹配
&lt;/h2&gt;&lt;p&gt;描述子匹配由 &lt;code&gt;match_descriptors&lt;/code&gt; 实现。为了避免依赖额外的 SciPy 距离函数，
代码采用 NumPy 矩阵运算直接构造平方欧氏距离矩阵：
&lt;/p&gt;
$$\begin{equation}
    \|f_i-g_j\|_2^2 = \|f_i\|_2^2 + \|g_j\|_2^2 - 2 f_i^T g_j
\end{equation}$$&lt;p&gt;
然后对每一行找出最近邻和次近邻，再用比率测试决定是否保留该匹配，这里设置的比率阈阈值为 $0.75$。
相比双重循环逐点求距离，这样的实现更加紧凑，也便于后续扩展到更多特征点。
&lt;a class=&#34;link&#34; href=&#34;#fig:match_descriptors_code&#34; &gt;12&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:match_descriptors_code&amp;rdquo;}&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/match_descriptors_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;比率测试匹配描述子代码实现&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;图像拼接-1&#34;&gt;图像拼接
&lt;/h2&gt;&lt;p&gt;拼接部分由 &lt;code&gt;find_homography_ransac&lt;/code&gt;、&lt;code&gt;warp_and_merge&lt;/code&gt; 和 &lt;code&gt;stitch_images&lt;/code&gt; 组成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;find_homography_ransac&lt;/code&gt; 调用 OpenCV 的 &lt;code&gt;cv2.findHomography&lt;/code&gt; 函数，
根据匹配点估计从第二幅图像到第一幅图像的单应矩阵，确保矩阵方向与后续透视变换一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;warp_and_merge&lt;/code&gt; 先计算变换后四个角点的位置，确定最终画布大小，再对两幅图像进行透视映射，
并对重叠区域做均值融合。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;stitch_images&lt;/code&gt; 封装完整流程：读取彩色图像、转灰度提取 SIFT、比率测试、RANSAC、图像融合，
并将结果保存为 &lt;code&gt;code/figures/panorama.jpg&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这部分虽然只处理两张图像，但已经具备了从特征提取到几何估计再到输出全景图的完整闭环。&lt;/p&gt;
&lt;h1 id=&#34;数据分析和可视化&#34;&gt;数据分析和可视化
&lt;/h1&gt;&lt;h2 id=&#34;关键点可视化&#34;&gt;关键点可视化
&lt;/h2&gt;&lt;p&gt;采用像素较小的图片 &lt;code&gt;camera_man.png&lt;/code&gt; 进行关键点提取的调试。
结果如 &lt;a class=&#34;link&#34; href=&#34;#fig:keypoints_camera_man&#34; &gt;13&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:keypoints_camera_man&amp;rdquo;} 所示。
可以看到，SIFT 提取的关键点数量很多，大部分是角点和边缘点，也有部分在草坪上突出的黑色板块周围。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/camera_man.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;原始图像&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/camera_man_sift_keypoints.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;SIFT关键点可视化&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;关键点与匹配统计&#34;&gt;关键点与匹配统计
&lt;/h2&gt;&lt;p&gt;实验使用两张已经缩放过的风景图像：&lt;code&gt;YuejiangTowerLeft_resized.jpg&lt;/code&gt; 与&lt;br&gt;
&lt;code&gt;YuejiangTowerCenter_resized.jpg&lt;/code&gt;。
缩放版本的选择主要是考虑到当前 SIFT 实现中仍包含较多 Python 层循环，若直接使用原始大图，
运行时间会明显增长。拼接阶段采用的主要参数为：比率测试阈值 &lt;code&gt;ratio_thresh = 0.75&lt;/code&gt;，
RANSAC 重投影阈值 &lt;code&gt;reproj_thresh = 4.0&lt;/code&gt;，最大迭代次数 &lt;code&gt;max_iters = 2000&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;程序运行结果如下：第一张图像在方向分配前共有 694 个稳定关键点，经方向复制后生成 841 个最终特征；第二张图像分别为 704 个和 843 个。随后经过 Lowe 比率测试保留 30 对匹配点，经 RANSAC 筛选后得到 24 个内点。统计结果如 &lt;a class=&#34;link&#34; href=&#34;#tab:keypoints&#34; &gt;1&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;tab:keypoints&amp;rdquo;} 与 &lt;a class=&#34;link&#34; href=&#34;#tab:matching&#34; &gt;2&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;tab:matching&amp;rdquo;} 所示。&lt;/p&gt;
&lt;p&gt;::: {#tab:keypoints}
图像        稳定关键点数   最终带方向特征数&lt;/p&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code&gt;Left_resized        694              841
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Center_resized       704              843&lt;/p&gt;
&lt;p&gt;: 两幅输入图像的 SIFT 特征统计
:::&lt;/p&gt;
&lt;p&gt;::: {#tab:matching}
指标            数值&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Lowe 比率测试后匹配数    30
RANSAC 内点数        24
内点率           80.0%&lt;/p&gt;
&lt;p&gt;: 匹配与几何估计结果统计
:::&lt;/p&gt;
&lt;p&gt;从统计上看，RANSAC 内点率达到 $24/30=80%$，说明比率测试保留下来的匹配中大部分都具备合理几何一致性，也说明当前实现提取到的特征具有较好的区分能力。&lt;/p&gt;
&lt;h2 id=&#34;拼接结果可视化&#34;&gt;拼接结果可视化
&lt;/h2&gt;&lt;p&gt;最终全景图如 &lt;a class=&#34;link&#34; href=&#34;#fig:panorama_result&#34; &gt;14&lt;/a&gt;{reference-type=&amp;ldquo;ref+label&amp;rdquo; reference=&amp;ldquo;fig:panorama_result&amp;rdquo;} 所示。可以看到，两幅图像在右侧主体、
树木边缘以及背景建筑等区域基本完成了基本对齐，但高楼却被错误的歪斜了，而且在建筑群和近处树木周围还有明显的重影现象。
说明基于 SIFT 的关键点匹配和单应矩阵估计是有效的，但照片的拍摄本身存在变焦、旋转、视差等干扰，
单纯的单应矩阵估计无法完全补偿这些因素。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/YuejiangTowerLeft_resized.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;原始左图&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/YuejiangTowerCenter_resized.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;原始右图&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/sift%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96/figures/panorama.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;使用自实现 SIFT、Lowe 比率测试与 RANSAC 得到的两图拼接结果&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;结果分析&#34;&gt;结果分析
&lt;/h2&gt;&lt;p&gt;结合特征统计与可视化结果，可以得到以下几点结论。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;双倍基准图像与规范的初始模糊有助于提升小尺度特征的可检测性，因此最终关键点数量明显多于最初未修正版本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主方向分配与描述子旋转坐标保持了统一的角度约定，使得特征在旋转情况下仍能稳定匹配，这是拼接成功的关键。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 NumPy 直接计算距离矩阵可以摆脱额外依赖，同时保证比率测试阶段的实现简洁可靠。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;尽管结果已经能够成功拼接，但当前实现的主要瓶颈仍在描述子生成阶段的大量 Python 循环，因此运行速度偏慢。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;融合阶段只做了平均叠加，没有使用羽化、多频段融合或曝光补偿，因此视觉质量仍有提升空间。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;结论&#34;&gt;结论
&lt;/h1&gt;&lt;p&gt;本次作业基于 &lt;code&gt;sift.py&lt;/code&gt; 完整实现了 SIFT 特征提取与两幅图像拼接流程。
程序按照经典方法完成了尺度空间构建、关键点精确定位与筛选、关键点方向分配、128 维描述子生成、
Lowe 比率测试以及 RANSAC 单应估计，并最终在风景图像上成功生成全景图。&lt;/p&gt;
&lt;p&gt;从实验结果看，该实现已经具备较好的尺度和旋转鲁棒性，能够在两幅存在视角差异的图像之间建立可靠匹配关系，
并输出正确的拼接结果。与此同时，当前版本仍有两个明显不足：一是纯 Python 循环带来的运行速度较慢，
二是图像融合方式较为简单。后续若继续改进，可以考虑使用向量化方式重写局部梯度统计与描述子分配过程，
或使用 OpenCV 的更高效接口进行对照；在拼接部分，还可以加入双向一致性匹配、圆柱投影、羽化融合或多频段融合，
以进一步提升全景图的视觉效果。&lt;/p&gt;
&lt;p&gt;:::::: {#refs .references .csl-bib-body entry-spacing=&amp;ldquo;0&amp;rdquo;}
::: {#ref-Lowe2004 .csl-entry}
[&lt;/p&gt;
\[1\]&lt;p&gt; ]{.csl-left-margin}[D. G. Lowe, &amp;ldquo;Distinctive image features from scale-invariant keypoints,&amp;rdquo; &lt;em&gt;International Journal of Computer Vision&lt;/em&gt;, vol. 60, no. 2, pp. 91&amp;ndash;110, 2004]{.csl-right-inline}
:::&lt;/p&gt;
&lt;p&gt;::: {#ref-Harris1988ACC .csl-entry}
[&lt;/p&gt;
\[2\]&lt;p&gt; ]{.csl-left-margin}[C. Harris and M. Stephens, &amp;ldquo;A combined corner and edge detector,&amp;rdquo; in &lt;em&gt;Proceedings of the 4th alvey vision conference&lt;/em&gt;, 1988, pp. 147&amp;ndash;151.]{.csl-right-inline}
:::&lt;/p&gt;
&lt;p&gt;::: {#ref-Fischler1981 .csl-entry}
[&lt;/p&gt;
\[3\]&lt;p&gt; ]{.csl-left-margin}[M. A. Fischler and R. C. Bolles, &amp;ldquo;Random sample consensus: A paradigm for model fitting with applications to image analysis and automated cartography,&amp;rdquo; &lt;em&gt;Communications of the ACM&lt;/em&gt;, vol. 24, no. 6, pp. 381&amp;ndash;395, 1981]{.csl-right-inline}
:::
::::::&lt;/p&gt;
</description>
        </item>
        <item>
        <title>匈牙利算法</title>
        <link>https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/</link>
        <pubDate>Fri, 01 May 2026 20:41:00 +0800</pubDate>
        
        <guid>https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/</guid>
        <description>&lt;img src="https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/%E7%94%9F%E4%B9%8B%E5%90%91%E5%BE%80.png" alt="Featured image of post 匈牙利算法" /&gt;&lt;p&gt;匈牙利算法（Hungarian Algorithm）是一种组合优化算法，用于在多项式时间内解决指派问题（Assignment Problem）。它的核心思想是在二分图中不断寻找“增广路径”来优化匹配，最终找到总成本最小（或总收益最大）的最优指派方案。&lt;/p&gt;
&lt;h2 id=&#34;数学模型指派问题&#34;&gt;数学模型（指派问题）
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;决策变量：定义二元决策变量 \( x_{ij} \)
$$x_{ij} =
\begin{cases} 
1, &amp; \text{如果第 } i \text{ 个人分配给第 } j \text{ 个任务} \\
0, &amp; \text{否则}
\end{cases}$$&lt;/li&gt;
&lt;li&gt;目标函数：
我们的目标是最大化总成本指派问题，目标函数可以写为：
$$\min Z = \sum_{i=1}^n \sum_{j=1}^n c_{ij}x_{ij}$$&lt;/li&gt;
&lt;li&gt;约束条件：&lt;/li&gt;
&lt;li&gt;每个人只能分配给一个任务：
每个 \( i \) 只能选择一个 \( j \)，即：
$$\sum_{j=1}^n x_{ij} = 1, \quad \forall i = 1, 2, \dots, n$$&lt;/li&gt;
&lt;li&gt;每个任务只能被一个人分配：
每个任务 \( j \) 只能分配给一个人 \( i \)，即：
$$\sum_{i=1}^n x_{ij} = 1, \quad \forall j = 1, 2, \dots, n$$&lt;/li&gt;
&lt;li&gt;二进制变量约束：
每个 \( x_{ij} \) 都是二进制变量，表示任务是否被分配：
$$x_{ij} \in \{0, 1\}, \quad \forall i, j$$&lt;/li&gt;
&lt;li&gt;数学模型
$$\min \sum_{i \in W} \sum_{j \in J} x_{ij}c_{ij}$$
$$\text{s.t.}$$
$$\sum_{j \in J} x_{ij} = 1, \quad \forall i \in \{1, 2, \dots, n\}$$
$$\sum_{i \in W} x_{ij} = 1, \quad \forall j \in \{1, 2, \dots, n\}$$
$$x_{ij} \in \{0, 1\}, \quad \forall i \in \{1, 2, \dots, n\}, \forall j \in \{1, 2, \dots, n\}$$&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;匈牙利算法&#34;&gt;匈牙利算法
&lt;/h2&gt;&lt;h3 id=&#34;核心概念图论视角&#34;&gt;核心概念：图论视角
&lt;/h3&gt;&lt;p&gt;理解匈牙利算法，首先需要了解其背后的几个关键图论概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;二分图 (Bipartite Graph)：图中的顶点可以分成两个互不相交的集合U和V，且所有的边都连接着U中的一个顶点和V中的一个顶点。在指派问题中，这两个集合可以理解为工人和任务。&lt;/li&gt;
&lt;li&gt;匹配 (Matching)：一个边的集合，其中任意两条边都没有公共顶点。你可以理解为给部分工人和任务的配对关系，一个工人最多只能做一个任务。&lt;/li&gt;
&lt;li&gt;完美匹配 (Perfect Matching)：如果一个匹配包含了图中所有的顶点，那么它就是一个完美匹配。&lt;/li&gt;
&lt;li&gt;最大匹配 (Maximum Matching)：在所有可能的匹配中，包含边数最多的那个匹配。&lt;/li&gt;
&lt;li&gt;增广路径 (Augmenting Path)：从一个未匹配的顶点出发，经过一系列未匹配边、匹配边、未匹配边&amp;hellip;最终到达另一个未匹配顶点所构成的路径。匈牙利算法的核心就是不断寻找增广路径来“腾挪”位置，从而增加匹配的边数，直到无法再找到，此时便得到了最大匹配。&lt;/li&gt;
&lt;li&gt;饱和：点集均匹配
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/hungarian.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;匈牙利算法&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;a class=&#34;link&#34; href=&#34;https://www.bilibili.com/video/BV1jT411R7vQ/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.bilibili.com/video/BV1jT411R7vQ/&lt;/a&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/hungarian_example.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;匈牙利算法例子&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

S为起点，T为终点，N(S)为邻集，N(S)-T为可选点，M为路径&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;算法流程与实例这里是带权匹配的匈牙利算法&#34;&gt;算法流程与实例（这里是带权匹配的匈牙利算法）
&lt;/h3&gt;&lt;p&gt;建议看https://www.hungarianalgorithm.com/examplehungarianalgorithm.php给的例子&lt;/p&gt;
&lt;h3 id=&#34;如何判断用最小的线覆盖所有的零&#34;&gt;如何判断用最小的线覆盖所有的零
&lt;/h3&gt;&lt;h4 id=&#34;柯尼希定理&#34;&gt;柯尼希定理
&lt;/h4&gt;&lt;p&gt;用代码实现“最少线条覆盖所有零”的过程，本质上是将这个矩阵问题转化为图论中的经典问题：二分图的最小顶点覆盖（Minimum Vertex Cover）。
在图论中有一个非常著名的柯尼希定理（König&amp;rsquo;s Theorem），它指出：在一个二分图中，最小顶点覆盖数等于最大匹配数。
假设你面对一个包含多个 0 的矩阵。
前置准备：寻找独立零，行有独立划掉列，列有独立划掉行&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找出行中唯一的 0，给它画上圈（表示已分配）。画圈后，将该圈所在的列中的其他 0 划掉（表示被排除）。&lt;/li&gt;
&lt;li&gt;找出列中唯一的 0，给它画上圈。画圈后，将该圈所在的行中的其他 0 划掉（表示被排除）。&lt;/li&gt;
&lt;li&gt;重复上述操作，直到不能再画圈为止。
正式开始画线（打勾法）：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;步骤 1： 对所有没有圈的行打勾（✓）。&lt;/li&gt;
&lt;li&gt;步骤 2： 观察所有刚刚打勾的行，如果这些行里面有 0（不管是被圈住的还是被划掉的），就对这些 0 所在的列打勾。&lt;/li&gt;
&lt;li&gt;步骤 3： 观察所有刚刚打勾的列，如果这些列里面有被圈住的 0，就对这个 0 所在的行打勾。&lt;/li&gt;
&lt;li&gt;步骤 4： 重复步骤 2 和步骤 3，直到不能再增加任何新的勾为止。&lt;/li&gt;
&lt;li&gt;步骤 5：画线！ 对所有没有打勾的行画一条横线，对所有已经打勾的列画一条竖线。
如果画出的线数 等于 矩阵的阶数，则算法结束，否则
找到未被线覆盖元素中最小的，将未被线覆盖的元素都减去这个最小值，并将它加到被线覆盖了两次的元素上。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;dfs柯尼希定理&#34;&gt;DFS+柯尼希定理
&lt;/h4&gt;&lt;p&gt;DFS 其实就是解决无权二部图的匹配问题。可以把 0 看作结点之间有边，然后先行再列的进行搜索，这样就能找到每列的 独立0。
再利用柯尼希定理进行 BFS，没有 独立 0 的行若有 0，则该列划线，若该列有 独立0，则对 独立0 所在行进行列搜索，若有 0 则该列划线，再看该列有没有 独立0，依次进行。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;  1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;  9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 42
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 43
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 44
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 45
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 46
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 47
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 48
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 49
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 50
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 51
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 52
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 53
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 54
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 55
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 56
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 57
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 58
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 59
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 60
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 61
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 62
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 63
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 64
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 65
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 66
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 67
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 68
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 69
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 70
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 71
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 72
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 73
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 74
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 75
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 76
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 77
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 78
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 79
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 80
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 81
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 82
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 83
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 84
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 85
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 86
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 87
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 88
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 89
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 90
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 91
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 92
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 93
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 94
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 95
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 96
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 97
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 98
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 99
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;100
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;101
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;102
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;103
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;104
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;105
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;106
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;107
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;108
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;109
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;110
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;111
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;112
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;113
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;114
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;115
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nn&#34;&gt;java.util.*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MinimumZeroCover&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * 计算并返回覆盖矩阵中所有0的最少线条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @param matrix 经过规约后的代价矩阵（关注里面的0）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     */&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;findMinimumLines&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;double&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[][]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cols&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 记录列匹配到的行索引&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cols&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Arrays&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;fill&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 记录行匹配到的列索引 (方便后续找未匹配的行)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Arrays&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;fill&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ==========================================&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 阶段 1：寻找二分图的最大匹配 (寻找相互独立的 0)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ==========================================&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cols&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dfsMatch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ==========================================&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 阶段 2：利用柯尼希定理寻找最小顶点覆盖 (程序化打勾法)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ==========================================&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedRow&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cols&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Queue&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Integer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LinkedList&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 步骤 1: 找出所有【未匹配的行】，将它们作为交替路的起点&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;offer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedRow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 标记为已访问 (相当于打勾)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 步骤 2 &amp;amp; 3: 顺藤摸瓜，遍历交替路 (BFS)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;isEmpty&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;poll&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 遍历该行所有的 0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cols&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 遇到了 0 所在的列，标记该列 (对列打勾)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 如果这列已经分配给了某个行，就把那个行拉进来继续找 (对行打勾)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchedR&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchedR&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedRow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchedR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedRow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchedR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;offer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchedR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ==========================================&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 阶段 3：输出结果 (划线规则)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ==========================================&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;List&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Integer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineRows&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ArrayList&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;List&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Integer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineCols&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ArrayList&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 规则：对【未访问】的行划线&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedRow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineRows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cols&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 规则：对【已访问】的列划线&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineCols&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;System&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;最少需要 &amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineRows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineCols&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34; 条线覆盖所有的 0。&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;System&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;划横线的行 (Row index): &amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineRows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;System&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;划竖线的列 (Col index): &amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lineCols&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * 基础版匈牙利算法的 DFS 寻轨
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     */&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;dfsMatch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;double&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[][]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 寻找包含 0 且在当前寻轨中未被访问过的列&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 如果该列尚未匹配，或者原本匹配的行能腾出位置 (找到增广路)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dfsMatch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;visitedCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchCol&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matchRow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 测试用例&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 假设这是一个经过行列规约后的矩阵，里面的 0 需要被覆盖&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;double&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[][]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;System&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;开始计算...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;findMinimumLines&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这段代码中最精妙的部分在于 阶段 2 和 阶段 3，它完全抛弃了人类视觉上的“尝试和试错”，转而利用状态机的流转来达成目的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;matchRow[i] == -1： 对应打勾法的“找出所有没有圈的行”。我们将这些行加入 BFS 的队列作为起点。&lt;/li&gt;
&lt;li&gt;!visitedRow[i] 划横线： 为什么是对未访问的行划线？因为如果一个行被访问了，说明它是从一个未匹配点出发的交替路的一部分；如果它未被访问，说明它包含的那个独立 0 的列没有别的选择了，必须用一条横线把它封死。&lt;/li&gt;
&lt;li&gt;visitedCol[j] 划竖线： 为什么是对已访问的列划线？因为在这个交替树里的列，包含了导致冲突的 0，我们无法用横线去盖住它对应的未匹配行，所以只能退而求其次，用竖线把这列的 0 全盖住。
这段代码的时间复杂度主要是求最大匹配的 $O(V \cdot E)$，在矩阵中也就是 $O(N^3)$。这是一种非常纯粹、无歧义的寻找矩阵最小覆盖的方法，在编写诸如分配优化系统、运筹学仿真器时非常实用。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;最大流&#34;&gt;最大流
&lt;/h4&gt;&lt;p&gt;待补充&lt;/p&gt;
&lt;h2 id=&#34;km算法&#34;&gt;KM算法
&lt;/h2&gt;&lt;p&gt;参考 [https://www.bilibili.com/video/BV1fT411977g/]&lt;/p&gt;
&lt;h3 id=&#34;数学表示&#34;&gt;数学表示
&lt;/h3&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/km_vertex.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;顶标定义&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/km.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;KM算法定义&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;这里 S 是 冲突的 x结点，T 是 冲突 y结点&lt;/p&gt;
&lt;h3 id=&#34;例子&#34;&gt;例子
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/km_example.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;KM算法例子&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/km_example_1.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;KM算法例子&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;我们用刚才代码里的那个 $3 \times 3$ 矩阵来做一个详细的手工推演。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;假设我们有 3 个工人（$X_1, X_2, X_3$）和 3 个任务（$Y_1, Y_2, Y_3$），他们完成不同任务的产出（权重）如下表：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;$Y1$​ (任务1)&lt;/th&gt;
          &lt;th&gt;$Y2$​ (任务2)&lt;/th&gt;
          &lt;th&gt;$Y3$​ (任务3)&lt;/th&gt;
          &lt;th&gt;&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;$X_1$(工人1)&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;$X_2$ (工人2)&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;$X_3$ (工人3)&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;5&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;我们的目标是找出一个匹配方案，让总产出最大。&lt;/p&gt;
&lt;p&gt;第一步：初始化“期望”与“要求”（顶标）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;左侧 $X$(工人) 的初始期望 $L(x)$： 设为自己能产生的最大权重。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$L(X_1) = \max(3, 0, 4) = 4$&lt;/li&gt;
&lt;li&gt;$L(X_2) = \max(2, 1, 3) = 3$&lt;/li&gt;
&lt;li&gt;$L(X_3) = \max(0, 0, 5) = 5$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;右侧 $Y$(任务) 的初始要求 $L(y)$： 全部设为 0。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$L(Y_1) = L(Y_2) = L(Y_3) = 0$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;寻找相等边： 此时，只有满足 $L(x) + L(y) = W(x, y)$ 的边才能进入“相等子图”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$X_1 - Y_3$: $4 + 0 = 4$ (进入子图)&lt;/li&gt;
&lt;li&gt;$X_2 - Y_3$: $3 + 0 = 3$ (进入子图)&lt;/li&gt;
&lt;li&gt;$X_3 - Y_3$: $5 + 0 = 5$ (进入子图)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其他边都不满足，暂时被忽略。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二步：开始匹配（冲突与降薪）&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;为 $X_1$ 找匹配：
$X_1$ 在相等子图中只连了 $Y_3$，且 $Y_3$ 没人占，直接匹配：$X_1 \rightarrow Y_3$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为 $X_2$ 找匹配：
$X_2$ 在相等子图中也只连了 $Y_3$，但 $Y_3$ 被 $X_1$ 占了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;触发“腾位置”协商：我们问 $X_1$ 能不能换个任务？$X_1$ 看了看自己所在的相等子图，发现自己除了 $Y_3$ 没别的边了，协商失败。
此时，参与协商的左侧节点是 ${X_1, X_2}$，右侧节点是 ${Y_3}$。&lt;/p&gt;
&lt;p&gt;触发“降薪”机制 (调整顶标)：
我们需要让新的边加入进来。计算最小妥协量 $\Delta$：
看那些参与协商的工人（$X_1, X_2$）与未参与协商的任务（$Y_1, Y_2$）之间的差值 $L(x) + L(y) - W(x,y)$：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$X_1$ 连 $Y_1: 4 + 0 - 3 = 1$&lt;/li&gt;
&lt;li&gt;$X_1$ 连 $Y_2: 4 + 0 - 0 = 4$&lt;/li&gt;
&lt;li&gt;$X_2$ 连 $Y_1: 3 + 0 - 2 = 1$&lt;/li&gt;
&lt;li&gt;$X_2$ 连 $Y_2: 3 + 0 - 1 = 2$&lt;/li&gt;
&lt;li&gt;最小的差值 $\Delta = 1$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更新顶标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参与协商的工人降薪：$L(X_1)$ 变成 $3$，$$L(X_2)$ 变成 $2$。&lt;/li&gt;
&lt;li&gt;参与协商的任务提要求：$L(Y_3)$ 变成 $1$。&lt;/li&gt;
&lt;li&gt;(没参与的 $X_3, Y_1, Y_2$ 顶标不变)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重新检查相等子图：
此时 $L(x) = [3, 2, 5]$，$L(y) = [0, 0, 1]$。
新加入的相等边：$X_1-Y_1$ ($3+0=3$)， $X_2 - Y_1$ ($2+0=2$)。
现在 $X_2$ 可以匹配 $Y_1$ 了。
当前匹配：$X_1 \rightarrow Y_3$, $X_2 \rightarrow Y_1$。&lt;/p&gt;
&lt;p&gt;第三步：最后的连锁反应&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为 $X_3$ 找匹配：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$X_3$ 想匹配 $Y_3$（因为 $L(X_3) + L(Y_3) = 5 + 1 = 6 \neq 5$），$X_3$ 找不到任何相等边。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;此时 $S={x_3},,N_{G_2}(S)=T=\emptyset$，计算
$\alpha_2=\min{l(x) + l(y) -\omega(x,y)|x\in S,,y\in V_2-T}=1$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更新顶标：$L(X_3)$ 降为 $4$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;此时 $L(X_3) + L(Y_3) = 4 + 1 = 5$，$X_3-Y_3$ 重新成为相等边。
$X_3$ 尝试匹配 $Y_3$。$Y_3$ 被 $X_1$ 占了。
$X_1$ 尝试腾位置：$X_1$ 发现自己还有相等边连着 $Y_1$。但 $Y_1$ 被 $X_2$ 占了。
$X_2$ 尝试腾位置：$X_2$ 发现自己没有其他相等边了，协商再次失败！&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再次大规模调整顶标：
参与协商的集合：$X = {X_1, X_2, X_3}$, $Y = {Y_1, Y_3}$。
计算出新的 $\Delta = 1$（通过 $X_2$ 连 $Y_2$ 的差值算得）。&lt;/p&gt;
&lt;p&gt;更新顶标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$L(X_1)$ 降为 $2$, $L(X_2)$ 降为 $1$, $L(X_3)$ 降为 $3$。&lt;/li&gt;
&lt;li&gt;$L(Y_1)$ 升为 $1$, $L(Y_3)$ 升为 $2$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;奇迹时刻（重新找增广路）：
此时的相等边有：$X_1-Y_1$, $X_1-Y_3$, $X_2-Y_1$, $X_2-Y_2$ (新加入), $X_3-Y_3$。&lt;/p&gt;
&lt;p&gt;连锁腾位置成功：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$X_3$ 拿走 $Y_3$。&lt;/li&gt;
&lt;li&gt;被挤掉的 $X_1$ 去拿 $Y_1$。&lt;/li&gt;
&lt;li&gt;被挤掉的 $X_2$ 去拿 $Y_2$。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终匹配方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$X_1 \rightarrow Y_1$ (权重 3)&lt;/li&gt;
&lt;li&gt;$X_2 \rightarrow Y_2$ (权重 1)&lt;/li&gt;
&lt;li&gt;$X_3 \rightarrow Y_3$ (权重 5)&lt;/li&gt;
&lt;li&gt;最大总权重 = $3 + 1 + 5 = 9$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;手写过程&#34;&gt;手写过程
&lt;/h3&gt;&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95/figures/km_handwrite.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;KM算法手写过程&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
        </item>
        <item>
        <title>Canny边缘检测</title>
        <link>https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/</link>
        <pubDate>Sun, 12 Apr 2026 19:23:00 +0800</pubDate>
        
        <guid>https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/</guid>
        <description>&lt;img src="https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/137175467.jpg" alt="Featured image of post Canny边缘检测" /&gt;&lt;h1 id=&#34;canny-边缘检测&#34;&gt;Canny 边缘检测
&lt;/h1&gt;&lt;h2 id=&#34;选题背景&#34;&gt;选题背景
&lt;/h2&gt;&lt;p&gt;Canny 边缘检测算法[1][2]在计算机视觉领域享有&amp;quot;不可动摇&amp;quot;的历史地位，被广泛誉为边缘检测的&amp;quot;黄金标准&amp;quot;。&lt;/p&gt;
&lt;p&gt;它的核心意义可以总结为以下四个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;从&amp;quot;经验启发&amp;quot;走向&amp;quot;严格数学最优化&amp;quot;
在 Canny 之前，大多数边缘检测算子（如 Sobel、Prewitt、Roberts）都是基于研究者的直觉和经验设计的（例如用简单的差分模板去寻找像素突变）。
Canny 第一次将边缘检测定义为一个数学最优化问题。他提出了三大严格的量化指标（信噪比、定位精度、单一响应），并运用变分法求出了理论上的最优解。
这种&amp;quot;定义目标 -&amp;gt; 数学建模 -&amp;gt; 求解最优&amp;quot;的科学范式，深刻影响了后续数十年的计算机视觉研究。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发现了边缘检测的&amp;quot;不确定性原理&amp;quot;
Canny 在推导中证明了一个极其深刻的物理规律：检测率（抗噪性）和定位精度是一对不可调和的矛盾。
算子越宽（平滑越多），越能抵抗噪声，但边缘的位置就越模糊；
算子越窄，定位越精准，但极易把噪声当成边缘。
这一发现彻底打破了寻找&amp;quot;完美单一算子&amp;quot;的幻想，为后来的多尺度图像分析奠定了理论基础。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;滞后双阈值完美解决&amp;quot;边缘断裂&amp;quot;行业痛点
早期的算法通常只使用一个固定阈值，只要图像中有一点点噪声波动，提取出来的边缘就像虚线一样断断续续。
Canny 创造性地引入了&amp;quot;滞后双阈值（Hysteresis Thresholding）&amp;ldquo;配合连通性分析，极大地提升了提取出完整、干净、连续边缘的概率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;强大的工程适应性与长久的生命力
虽然 Canny 推导出了复杂的理论最优算子，但他非常务实地证明了高斯一阶导数是对该算子的极佳近似。这使得算法不仅效果好，
而且计算效率极高。时至今日，距论文发表将近 40 年，Canny 算法依然是 OpenCV 等各大计算机视觉库中最常用、最底层的核心函数，
也是任何新提出的边缘检测算法必须对比的&amp;quot;基准线&amp;rdquo;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;算法描述重点&#34;&gt;算法描述（重点）
&lt;/h2&gt;&lt;h3 id=&#34;数学模型&#34;&gt;数学模型
&lt;/h3&gt;&lt;h4 id=&#34;问题定义&#34;&gt;问题定义
&lt;/h4&gt;&lt;p&gt;Canny 认为，一个优秀的边缘检测器必须满足以下三个量化的数学准则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;良好的检测（Good Detection）：最大化输出信噪比（SNR）。&lt;/p&gt;
&lt;p&gt;算子对边缘中心的响应为 $H_G$，对噪声的均方根响应为 $H_n$ 。其比值为：
&lt;/p&gt;
$$
  SNR = \frac{H_G}{H_n} = \frac{|\int_{-W}^{+W} G(-x)f(x)dx|}{n_0 \sqrt{\int_{-W}^{+W} f^2(x)dx}}
  $$&lt;p&gt;
其中，假设边缘模型为$G(x)$， 算子为$f(x)$，加性高斯白噪声的均方幅度为$n_0$，滤波器宽度为$W$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;良好的定位（Good Localization）：检测到的边缘点应尽可能靠近真实边缘的中心。&lt;/p&gt;
&lt;p&gt;定位性能被定义为边缘位置偏差均方根的倒数，通过Taylor级数和随机过程分析可得，定位算式为：
&lt;/p&gt;
$$
  Localization = \frac{|\int_{-W}^{+W} G&#39;(-x)f&#39;(x)dx|}{n_0 \sqrt{\int_{-W}^{+W} f&#39;^2(x)dx}}
  $$&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单一响应（Low Multiple Responses）：对于单条边缘，算子应该只产生一个响应峰值，避免因噪声干扰而在边缘附近产生多个假边缘。&lt;/p&gt;
&lt;p&gt;Canny 利用 Rice 噪声公式 来预测噪声引起的平均峰值间距 $x_{max}$
&lt;/p&gt;
$$
  x_{max}(f) = 2 \pi \left( \frac{\int f&#39;^2(x) dx}{\int f&#39;&#39;^2(x) dx} \right)^{1/2}
  $$&lt;p&gt;
为了保证单一边缘只被标记一次，他引入了一个约束条件：将噪声响应的平均间距设定为算子宽度的某个比例 $k$（即 $x_{max} = kW$）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假设输入为 $G(x) = Au(x)$，其中$u(x)$为阶跃函数，$A$为幅值。
要满足这三个准则，定义泛函
&lt;/p&gt;
$$
\Sigma(f) =  \Sigma(f) = \frac{|\int_{-W}^{0} f(x)dx|}{\sqrt{\int_{-W}^{W} f^2(x)dx}} \quad
\Lambda(f&#39;) = \frac{|f&#39;(0)|}{\sqrt{\int_{-W}^{W} f&#39;^2(x)dx}}
$$&lt;p&gt;现在，问题变成了一个经典的变分学问题：寻找一个函数 $f(x)$，使得目标泛函 $\Sigma(f) \cdot \Lambda(f&amp;rsquo;)$ 取得极大值，
同时满足单一响应约束和边界条件。&lt;/p&gt;
&lt;h4 id=&#34;变分法求解泛函极值&#34;&gt;变分法求解泛函极值
&lt;/h4&gt;&lt;p&gt;由于 $\Sigma$ 和 $\Lambda$ 中的积分形式复杂，Canny 使用了等周问题（Isoperimetric Problem）的技巧。
因为尺度变换不改变目标函数的极值结构，他将分母中的积分设定为常数，从而将问题转化为最小化分母之一，同时受其他积分常数约束的形式。&lt;/p&gt;
&lt;p&gt;他构造了带有拉格朗日乘子（$\lambda_1, \lambda_2, \lambda_3$）的复合泛函
&lt;/p&gt;
$$
\Psi(f, f&#39;, f&#39;&#39;) = f^2 + \lambda_1 f&#39;^2 + \lambda_2 f&#39;&#39;^2 + \lambda_3 f
$$&lt;p&gt;
根据变分法求极值的必要条件，函数 $f(x)$ 必须满足欧拉-拉格朗日方程。
对于包含高阶导数的泛函，其欧拉方程为：
&lt;/p&gt;
$$
\frac{\partial \Psi}{\partial f} - \frac{d}{dx}\left(\frac{\partial \Psi}{\partial f&#39;}\right) +
\frac{d^2}{dx^2}\left(\frac{\partial \Psi}{\partial f&#39;&#39;}\right) = 0
$$&lt;p&gt;
将 $\Psi$ 代入，得到一个常系数四阶常微分方程：
&lt;/p&gt;
$$
2f(x) - 2\lambda_1 f&#39;&#39;(x) + 2\lambda_2 f&#39;&#39;&#39;&#39;(x) + \lambda_3 = 0
$$&lt;p&gt;
求解特征方程：通过求二次变分 $\delta^2 J$，Canny 证明了为了使系统存在最小值，方程的特征根必定是四个复数根，
形式为 $\gamma = \pm \alpha \pm i\omega$。因此，该微分方程在区间 $[-W, 0]$ 内的通解是一个有阻尼的振荡系统，
即指数函数与三角函数的乘积叠加：
&lt;/p&gt;
$$
f(x) = a_1 e^{\alpha x} \sin(\omega x) + a_2 e^{\alpha x} \cos(\omega x) + a_3 e^{-\alpha x} \sin(\omega x)+ a_4 e^{-\alpha x} \cos(\omega x) + c
$$&lt;p&gt;
利用边界条件$f(0)=0, f(-W)=0$，可以解出待定系数。经过数值优化后，Canny 发现这个四阶微分方程的解在波形上极其逼近高斯函数的一阶导数
$G&amp;rsquo;(x) = -\frac{x}{\sigma^2} \exp(-\frac{x^2}{2\sigma^2})$。&lt;/p&gt;
&lt;h4 id=&#34;扩展至二维&#34;&gt;扩展至二维
&lt;/h4&gt;&lt;p&gt;上述推导是在一维切面上完成的。在真实的二维图像 $I(x,y)$ 中，边缘的方向是未知的。Canny 利用了多变量微积分将算法扩展到二维空间。
设二维平滑函数为对称高斯 $G(x,y)$。图像被平滑后，任意一点的法向向量（即梯度方向 $\vec{n}$）可以由下式确定：
&lt;/p&gt;
$$
\vec{n} = \frac{\nabla (G * I)}{|\nabla (G * I)|}
$$&lt;p&gt;在二维平面上寻找边缘点，等价于在梯度方向 $\vec{n}$ 上，寻找方向导数 $G_{\vec{n}}$ 的局部最大值。
在微积分中，方向导数的极值点对应于二阶方向导数的零交叉点。因此，非极大值抑制的数学本质就是求解以下偏微分方程：
&lt;/p&gt;
$$
\frac{\partial^2}{\partial \vec{n}^2} (G * I) = 0
$$&lt;h3 id=&#34;算法流程&#34;&gt;算法流程
&lt;/h3&gt;&lt;h4 id=&#34;高斯平滑滤波&#34;&gt;高斯平滑滤波
&lt;/h4&gt;&lt;p&gt;由于图像在获取和传输过程中往往会受到噪声干扰，而边缘检测本质上是求导操作，导数对噪声极为敏感（噪声会导致局部强烈的假边缘）。
因此，第一步必须使用高斯滤波器对图像进行平滑降噪。实际上，根据卷积的性质（由于偶函数卷积和互相关等价，所以这里直接用卷积代替）
高斯滤波与梯度计算的结合就相当于用高斯核的一阶导数对图像进行滤波。
&lt;/p&gt;
$$
\frac{d}{dx} \left(f * g\right) = f * \frac{d}{dx}g
$$&lt;p&gt;用一个二维高斯核与原始图像进行卷积。高斯核的大小（例如 $3\times3$、$5\times5$）和标准差 $\sigma$ 决定了平滑的程度。$\sigma$ 越大，平滑越强，抗噪越好，
但边缘细节也会丢失更多。这一点 Canny 也在论文中提到了，就是所谓的&amp;quot;不确定性原理&amp;quot;，将泛函在空间上缩放，即令
&lt;/p&gt;
$$
\Sigma(f_\omega)=\sqrt{\omega}\Sigma(f),\quad\Lambda(f_\omega&#39;)=\frac{1}{\sqrt{\omega}}\Lambda(f&#39;)
$$&lt;p&gt;
$\omega$对两个指标的作用是相反的。&lt;/p&gt;
&lt;h4 id=&#34;计算梯度幅值和方向&#34;&gt;计算梯度幅值和方向
&lt;/h4&gt;&lt;p&gt;找到图像中像素亮度变化最剧烈的地方，即潜在的边缘。边缘通常发生在亮度突变的地方，数学上表现为一阶导数的极值。
使用差分算子（最常用的是 Sobel 算子，也有用 Prewitt 或 Roberts 算子的）分别计算图像在水平方向（X 轴）和垂直方向（Y 轴）的梯度幅值 $G_x$ 和 $G_y$。
然后计算每个像素点的综合梯度幅值和梯度方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;梯度幅值: $G = \sqrt{G_x^2 + G_y^2}$（代表边缘的强度）&lt;/li&gt;
&lt;li&gt;梯度方向: $\theta = \arctan\left(\frac{G_y}{G_x}\right)$ （代表边缘的走向，垂直于边缘）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;非极大值抑制&#34;&gt;非极大值抑制
&lt;/h4&gt;&lt;p&gt;将模糊、较宽的边缘&amp;quot;瘦身&amp;quot;为单像素宽的细线条，精确定位边缘中心。经过平滑和梯度计算后，得到的边缘往往比较粗。我们要找出真正的边缘点，
也就是在局部梯度方向上变化最剧烈的那个点。&lt;/p&gt;
&lt;p&gt;遍历图像中的每一个像素,查看该像素的梯度方向。为了简化计算，通常将梯度方向近似为四个主要方向之一：0°（水平）、45°（对角）、90°（垂直）或 135°（反对角）。
沿着这个梯度方向，比较该像素与其前后相邻两个像素的梯度幅值。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果该像素的梯度幅值不是这三个点中最大的，则将其梯度值设为 0。&lt;/li&gt;
&lt;li&gt;如果它是最大的，则保留其梯度值，作为候选边缘点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;双阈值检测&#34;&gt;双阈值检测
&lt;/h4&gt;&lt;p&gt;区分真实的强边缘和可能由噪声引起的弱边缘。经过非极大值抑制后，剩下的像素点都是局部的梯度极大值，
但其中有些极大值可能是由微小的噪声波动引起的。Canny 引入了高、低两个阈值。设定一个高阈值和一个低阈值&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;强边缘：梯度幅值 &amp;gt; 高阈值。这些被认为是极其可靠的真实边缘。&lt;/li&gt;
&lt;li&gt;弱边缘：低阈值 &amp;lt; 梯度幅值 $\le$ 高阈值。这些点待定，可能是真实边缘的延伸，也可能是噪声。&lt;/li&gt;
&lt;li&gt;被抑制：梯度幅值 $\le$ 低阈值。直接当作背景或噪声剔除，值设为 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;滞后边界跟踪&#34;&gt;滞后边界跟踪
&lt;/h4&gt;&lt;p&gt;解决边缘断裂问题，将有效的弱边缘连接成完整的线条。真实的边缘通常是连续的。如果一个弱边缘点与一个强边缘点相连，
那么这个弱边缘很大概率也是真实边缘的一部分；如果一个弱边缘孤立存在，则很可能是噪声。&lt;/p&gt;
&lt;p&gt;遍历所有的&amp;quot;弱边缘&amp;quot;像素：检查其周围的连通邻域。如果在这个邻域内存在至少一个强边缘像素，那么这个弱边缘就被保留，
并升级为强边缘，正式成为最终输出的边缘点。如果周围没有任何强边缘相连，则将该弱边缘抑制，设为 0。&lt;/p&gt;
&lt;h3 id=&#34;尺度讨论&#34;&gt;尺度讨论
&lt;/h3&gt;&lt;p&gt;根据上述公式，$\omega$对两个指标的作用是相反的，而高斯核$\sigma$正比于$\omega$，因此我们可以得出以下定性的分析&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\sigma$ 越小，平滑越弱，抗噪越差，但边缘细节也会保留更多。&lt;/li&gt;
&lt;li&gt;$\sigma$ 越大，平滑越强，抗噪越好，但边缘细节也会丢失更多。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;软件实现&#34;&gt;软件实现
&lt;/h2&gt;&lt;p&gt;编程语言为python 3.11.0，开发环境为PyCharm 2025.3.3。&lt;/p&gt;
&lt;h3 id=&#34;高斯平滑滤波-1&#34;&gt;高斯平滑滤波
&lt;/h3&gt;&lt;p&gt;采用上次作业中的高斯平滑滤波函数。&lt;/p&gt;
&lt;h3 id=&#34;计算梯度幅值和方向-1&#34;&gt;计算梯度幅值和方向
&lt;/h3&gt;&lt;p&gt;使用 Sobel 算子计算图像在水平方向（X 轴）和垂直方向（Y 轴）的梯度幅值 $G_x$ 和 $G_y$，
再通过式计算每个像素点的综合梯度幅值和梯度方向。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：这里进行滤波的时候不能使类型为uint8，Sobel 算子的计算结果可以为负数，
若使用uint8类型会导致计算值溢出。所以我采用了float32类型。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/sobel_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;Sobel 算子代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;非极大值抑制-1&#34;&gt;非极大值抑制
&lt;/h3&gt;&lt;p&gt;由于圆周上下对称，搜索也是前后都进行的，所以只需要半圆的四个方向即可。
将半圆周四等分，分别对应0°、45°、90°、135°。判断前后两个像素的梯度幅值是否大于当前像素的梯度幅值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：遍历范围需要往内部缩减一格，否则会溢出。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/nonmax_spress_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;非极大值抑制代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;双阈值检测-1&#34;&gt;双阈值检测
&lt;/h3&gt;&lt;p&gt;按照理论的实现即可，我这里采用的是取相对比例抑制，opencv中的Canny函数是直接设定高阈值和低阈值。
由于不同图像中的阈值范围不同，设置比例可能更加通用一点。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/threshold_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;双阈值检测代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;滞后边界跟踪-1&#34;&gt;滞后边界跟踪
&lt;/h3&gt;&lt;p&gt;此处搜索范围为$3\times3$&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/hysteresis_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;滞后边界跟踪代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;数据分析和可视化&#34;&gt;数据分析和可视化
&lt;/h2&gt;&lt;p&gt;图片采用经典的cameraman图像，包含多个不同的边缘和噪声。&lt;/p&gt;
&lt;h3 id=&#34;每步拆分&#34;&gt;每步拆分
&lt;/h3&gt;&lt;p&gt;高斯核采用$\sigma = 1.4$，$ksize = (5, 5)$，高阈值比例为$0.12$，低阈值比例为$0.05$。处理结果如下所示。
可以看到，非极大值抑制很好地将边缘精确定位下来，滞后阈值检测则能够将边缘连接起来，但由于探测范围比较小，连接的边有限。
后方的建筑物就因为缺少足够的强边缘而被略去了。&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/camera_man.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;原始图像&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/gaussian_blur.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;高斯滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/sobel_filters.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;Sobel 算子滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/non_max_suppression.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;非极大值抑制&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/threshold.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;双阈值检测&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/hysteresis.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;滞后边界跟踪&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&#34;尺度讨论-1&#34;&gt;尺度讨论
&lt;/h3&gt;&lt;p&gt;分别对 $\sigma=0.5, 1.0, 1.5, 2.0$ 进行测试。下图展示了不同高斯平滑尺度对边缘检测结果的影响。&lt;/p&gt;
&lt;p&gt;可以看到，当 $\sigma=0.5$ 时，平滑程度较低，边缘检测结果保留了极高的细节，
但同时对噪声极其敏感。前方草地极细碎的纹理以及图像固有的高频噪点都被当作边缘提取了出来，使得画面显得较为杂乱。&lt;/p&gt;
&lt;p&gt;当 $\sigma=1.0$ 时，高斯滤波的平滑作用开始显现，草地区域的细碎噪点被大量抑制，
同时人物主体和背景建筑的轮廓依然保持清晰，取得了较好的信噪比与定位精度折中。&lt;/p&gt;
&lt;p&gt;当 $\sigma=1.5$ 时，图像中的高频噪点已基本被完全滤除，画面十分干净。但由于平滑尺度的增大，
部分真实弱边缘的梯度幅值下降，导致后方远景建筑群的微弱轮廓开始被阈值抑制并逐渐消失。&lt;/p&gt;
&lt;p&gt;当 $\sigma=2.0$ 时，高频信息被进一步过度平滑。不仅更多细节边缘（如摄影师大衣的褶皱、远处的塔楼结构）因梯度跌破阈值而彻底丢失，
且定位精度（Localization）的显著下降导致部分相邻边缘发生移位或粘连，造成局部线条结构失真。
总体而言，有效边缘的数量随着 $\sigma$ 的增大呈现减少趋势。&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/edges_0.5.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=0.5&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/edges_1.0.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=1.0&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/edges_1.5.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=1.5&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/figures/edges_2.0.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=2.0&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&#34;结论&#34;&gt;结论
&lt;/h2&gt;&lt;p&gt;本次作业从底层数学原理出发，深入剖析并完整复现了 Canny 边缘检测算法。主要成果包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;理论验证&lt;/strong&gt;：系统梳理了算法背后的变分法最优化模型与三大评价准则，深刻理解了边缘检测中抗噪性与定位精度之间不可调和的&amp;quot;不确定性原理&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;底层算法实现&lt;/strong&gt;：脱离高度封装的计算机视觉库，利用 Python 与 NumPy 库从零构建了高斯平滑、Sobel 梯度计算、非极大值抑制（NMS）以及滞后双阈值连通等核心模块，成功实现了将模糊的图像梯度细化为单像素宽连续轮廓的完整处理链路。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尺度效应分析&lt;/strong&gt;：通过对经典 Cameraman 测试图像的参数对比实验，直观且定量地验证了高斯核标准差 $\sigma$ 对特征提取结果的影响。实验结果与理论推导高度吻合，证实了 $\sigma$ 增大时高频细节丢失及边缘定位精度下降的物理现象。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;参考文献&#34;&gt;参考文献
&lt;/h2&gt;&lt;p&gt;[1] Canny, J. (1986). A Computational Approach to Edge Detection. IEEE Transactions on Pattern Analysis and Machine Intelligence, PAMI-8(6), 679-698.&lt;/p&gt;
&lt;p&gt;[2] Gonzalez, R. C., &amp;amp; Woods, R. E. (2011). Digital Image Processing (3rd ed.). Prentice Hall.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>高斯滤波</title>
        <link>https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/</link>
        <pubDate>Sun, 12 Apr 2026 19:23:00 +0800</pubDate>
        
        <guid>https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/</guid>
        <description>&lt;img src="https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/illust_77235380_20191124_002921.jpg" alt="Featured image of post 高斯滤波" /&gt;&lt;h1 id=&#34;高斯滤波算法实现&#34;&gt;高斯滤波算法实现
&lt;/h1&gt;&lt;h2 id=&#34;选题背景&#34;&gt;选题背景
&lt;/h2&gt;&lt;p&gt;数字图像在获取、传输与存储过程中不可避免地会受到噪声干扰，其中高斯噪声因符合传感器热噪声、光照波动等物理过程的统计特性，成为图像中最常见的噪声类型之一。如何有效抑制高斯噪声的同时最大程度保留图像的边缘与细节结构，是图像预处理阶段的核心问题。高斯滤波作为一种经典的线性平滑滤波技术，凭借其数学上的严谨性（高斯核为尺度空间理论中的唯一线性核[1]）、物理上的直观性（邻域像素权重随距离递减）以及计算上的高效性（二维高斯核可分解为两个一维核的级联），在数字图像处理领域占据了基础而重要的地位。&lt;/p&gt;
&lt;p&gt;在应用层面，高斯滤波已广泛渗透至众多关键领域。在医学图像分析中，CT、MRI等影像常受高斯噪声污染，高斯滤波作为预处理步骤可提升信噪比，为后续的病灶分割与三维重建提供更可靠的数据基础；在自动驾驶视觉感知中，摄像头采集的图像在弱光或高速运动场景下常伴随噪声，高斯滤波被用于构建尺度空间，支撑SIFT、ORB等特征提取算法的稳定性，从而保障目标检测与跟踪的准确性；在工业缺陷检测中，对金属表面或电子元件的图像进行高斯平滑，可抑制纹理噪声，突显裂纹、划痕等关键缺陷；此外，在图像增强与摄影后期中，高斯模糊被广泛用于背景虚化、光晕模拟及隐私保护，成为图形图像软件中的标准工具。&lt;/p&gt;
&lt;p&gt;针对高斯噪声的抑制问题，传统解决思路主要分为空间域与频域两类。空间域方法以高斯滤波为核心，通过构造高斯卷积核直接对图像进行加权平均；频域方法则利用高斯函数的傅里叶变换仍为高斯函数的特性，在频域实现低通滤波。由于二维高斯核具有可分离性，实际实现中常将二维卷积分解为先后执行的行、列一维卷积，将计算复杂度从$O(n^2m^2)$降至$O(n^2m)$（其中，n是图像大小，m是核大小），大幅提升了处理效率。近年来，随着深度学习的发展，可学习的高斯滤波、将高斯先验嵌入卷积神经网络等方法进一步拓展了其应用边界，但传统高斯滤波因其可解释性强、无需训练数据、计算资源消耗低等优势，仍广泛应用于实时系统与资源受限场景。&lt;/p&gt;
&lt;p&gt;本次作业选择实现基于可分离卷积的高斯滤波算法，以经典的高斯核生成与快速互相关为核心技术路线。具体而言，我们将：
（1）分析高斯核的性质和高斯滤波的去噪原理；
（2）利用可分离性将二维滤波转化为行、列两个一维滤波的串联实现，分析其加速效果；
（3）在不同滤波参数的组合下，评估高斯滤波的模糊效果；
（4）与均值滤波、中值滤波进行对比实验，定量评估滤波后图像的PSNR与SSIM，展示高斯滤波在平滑噪声与保护边缘之间的平衡能力。
通过上述算法设计与实验分析，旨在深入理解高斯滤波的工作原理、参数影响机制及其在实际应用中的适用边界，为后续更复杂的图像处理任务奠定理论与实践基础。&lt;/p&gt;
&lt;h2 id=&#34;算法描述重点&#34;&gt;算法描述（重点）
&lt;/h2&gt;&lt;h3 id=&#34;高斯核&#34;&gt;高斯核
&lt;/h3&gt;&lt;p&gt;二维圆对称的高斯分布如公式所示：&lt;/p&gt;
$$G\left(x, y, \sigma\right) = \frac{1}{2\pi\sigma^2}e^{-\left(x^2+y^2\right)/2\sigma^2}$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(x, y)$ 是离核中心的偏置，在连续域中$(x, y) \in \mathbb{R}^2$&lt;/li&gt;
&lt;li&gt;$\sigma$ 是标准差&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图像处理领域的高斯核与连续域较为不同，由于离散域对高斯函数进行了截断，需要进行归一化处理，一般表示为：&lt;/p&gt;
$$G\left(x, y, \sigma\right) = Ke^{-\left(x^2+y^2\right)/2\sigma^2}$$&lt;p&gt;在离散域中，$(x, y) \in \mathbb{Z}^2$。假设高斯核的宽度为 $m$ 则：&lt;/p&gt;
$$K = \frac{1}{\sum^{\left \lfloor m/2 \right \rfloor}_{x = -\left \lfloor m/2 \right \rfloor }\sum^{\left \lfloor m/2 \right \rfloor}_{y = -\left \lfloor m/2 \right \rfloor } e^{-\left(x^2+y^2\right)/2\sigma^2}}$$&lt;p&gt;做这种归一化的目的有二[2]：&lt;/p&gt;
&lt;p&gt;（1）恒定灰度区域的平均值保持不变。对于一幅图像中灰度均匀的区域（例如纯色背景），滤波后的结果应保持原有的灰度值不变。若核未归一化（即系数之和不等于 1），则对恒定区域进行卷积后，输出灰度值会被放大或缩小，导致图像整体亮度偏移。归一化保证了当核与恒定灰度区域卷积时，输出等于原灰度值，从而维持了图像的亮度一致性。&lt;/p&gt;
&lt;p&gt;（2）防止引入偏差，使原图像和滤波后图像像素和相同。从信号处理的角度，滤波过程可视为对图像像素的加权求和。若核系数之和不为 1，则输出图像的像素总和会发生变化，相当于引入了直流分量的增益或衰减。归一化后，在忽略边界效应的情况下原图像与滤波后图像的像素总和保持不变，即图像的总体能量或平均亮度得以守恒。这保证了滤波操作不会人为地改变图像的整体明暗程度，使结果更符合物理直观。&lt;/p&gt;
&lt;p&gt;但同时也导致离散高斯核与实际的高斯函数存在一定的误差。&lt;/p&gt;
&lt;h3 id=&#34;参数影响&#34;&gt;参数影响
&lt;/h3&gt;&lt;p&gt;以 5 × 5 大小的高斯核为例：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$\sigma$=0.5（小 $\sigma$）&lt;/strong&gt;：曲线陡峭尖锐，峰值极高，能量几乎全部集中在中心原点。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/sigma0.5.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=0.5&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;$\sigma$=1.0（中 $\sigma$）&lt;/strong&gt;：曲线平缓度适中，分布范围与峰值高度均衡。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/sigma1.0.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=1.0&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;$\sigma$=3.0（大 $\sigma$）&lt;/strong&gt;：曲线非常平缓宽阔，峰值极低，能量向两侧大范围扩散。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/sigma3.0.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;σ=3.0&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;基于概率论的&amp;quot;$3\sigma$&amp;ldquo;原则，高斯函数在 $±3\sigma$ 之外的累积概率已接近99.7%。取半宽为 $3\sigma$，总宽度为 $6\sigma$，再加 1 保证中心对称且核尺寸为奇数，即核宽$k_{max} = 2\left\lfloor 3 \sigma\right\rfloor + 1$。此时离散截断带来的误差很小，也意味着在 $±3\sigma$ 之外的取值都接近零。因此，$k_{max}$大小的高斯核处理的结果与任意大小的高斯核得到的结果相同[2]。&lt;/p&gt;
&lt;h3 id=&#34;性质&#34;&gt;性质
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;可分离性&lt;/strong&gt;：高斯核是唯一可分离的圆对称核，也是高斯核最重要的计算性质：&lt;/p&gt;
$$G(x, y) = G(x) \cdot G(y)$$&lt;p&gt;其中 $ G(x) = \frac{1}{\sqrt{2\pi} \sigma} e^{-\frac{x^2}{2\sigma^2}} $ 是一维高斯函数。
这意味着二维卷积可以分解为两次一维卷积：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先对图像每一行进行一维高斯卷积（水平方向）；&lt;/li&gt;
&lt;li&gt;再对结果每一列进行一维高斯卷积（垂直方向）。
若图像尺寸为$n \times n$，核尺寸为 $m \times m$，直接二维卷积的时间复杂度为$O(n^2m^2)$；而分离实现为$O(2n^2m)=O(n^2m)$，当$m$较大时加速效果显著。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;各向同性&lt;/strong&gt;：二维高斯核仅依赖于到中心的距离$r = \sqrt{x^2 + y^2}$，具有各向同性。这一性质保证了滤波效果于方向无关，不会引入方向性伪影，适用于大多数无方向先验的图像处理任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;卷积与互相关等价&lt;/strong&gt;：高斯核是偶函数$G(x) = G(-x)$，所以高斯核的卷积与互相关等价，即：&lt;/p&gt;
$$G_1 * G_2(x) = G_1 ** G_2(x)$$&lt;p&gt;&lt;strong&gt;高斯核对卷积的半群性质&lt;/strong&gt;：高斯核在卷积运算下封闭，且满足方差相加的规律：&lt;/p&gt;
$$G_{\sigma_1} * G_{\sigma_2} = G_{\sqrt{\sigma_1^2 + \sigma_2 ^ 2}}$$&lt;p&gt;即连续两次高斯模糊（标准差分别为$\sigma_1$和$\sigma_2$）等价于一次标准差为$\sqrt{\sigma_1^2 + \sigma_2 ^ 2}$的高斯模糊。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;频域特性&lt;/strong&gt;：高斯函数的傅里叶变换仍为高斯函数，说明在频域范畴属于低通滤波，并且具有平滑的过渡。&lt;/p&gt;
&lt;h3 id=&#34;算法流程&#34;&gt;算法流程
&lt;/h3&gt;&lt;h4 id=&#34;构建高斯核&#34;&gt;构建高斯核
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;步骤 1：选择核尺寸&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核尺寸通常取奇数，这样核存在一个明确的中心像素，便于定位。根据&amp;quot;3 $\sigma$&amp;ldquo;原则，取$size = 2\left\lfloor 3 \sigma\right\rfloor + 1$，这一步决定了核的空间支撑范围。如果 $\sigma$ 较大而核尺寸过小，会截断高斯尾部，导致归一化后核形状失真，影响滤波效果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 2：计算偏移量&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以 $size = 5$ 为例。偏移量范围为 $-2, -1, 0, 1, 2$，分别对应核内各位置相对于中心的距离。偏移量是计算高斯权重的自变量。对于二维核，需要生成 x 和 y 两个方向上的偏移网格。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 3：应用高斯公式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于每个位置 $(x, y)$（x 和 y 为偏移量），计算未归一化的权重：&lt;/p&gt;
$$G&#39;(x, y) = e^{-\frac{x^2 + y^2}{2\sigma^2}}$$&lt;p&gt;对于一维核（可分离实现），则只需计算 $G&amp;rsquo;(x)$。此时得到的系数之和不等于 1，且 $\sigma$ 越大，截断损失的面积比例越大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 4：归一化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将所有系数相加得到总和 $sum$，然后将每个系数除以 $sum$，得到最终的高斯核：&lt;/p&gt;
$$w\_{\text{normalized}}(x, y) = \frac{G&#39;(x, y)}{\sum_{i,j} G&#39;(i, j)}$$&lt;h4 id=&#34;与可分离性的结合&#34;&gt;与可分离性的结合
&lt;/h4&gt;&lt;p&gt;实际实现中，通常不会直接构建二维核再卷积，而是利用可分离性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先构建一维高斯核（同样经过上述步骤，只是在一维偏移上计算并归一化）&lt;/li&gt;
&lt;li&gt;先对图像每一行进行一维卷积（水平方向）&lt;/li&gt;
&lt;li&gt;再对结果每一列进行一维卷积（垂直方向）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;对边界的处理&#34;&gt;对边界的处理
&lt;/h4&gt;&lt;p&gt;滤波通常需要对边界进行扩展，如补零、反射、复制边缘等，以保证输出尺寸与原图一致，然后才从图像的第一格开始作互相关运算。&lt;/p&gt;
&lt;h3 id=&#34;高斯滤波去噪原理&#34;&gt;高斯滤波去噪原理
&lt;/h3&gt;&lt;h4 id=&#34;空间域&#34;&gt;空间域
&lt;/h4&gt;&lt;p&gt;高斯噪声是均值为零、独立同分布的随机扰动，对于图像中任一像素，其观测值为真实值加上噪声：&lt;/p&gt;
$$I\_{noise}(x, y) = I\_{true}(x,y) + n(x,y), \quad n \sim \mathcal{N}(0, \sigma^2)$$&lt;p&gt;高斯滤波对邻域内像素进行加权平均，权重由高斯核决定。由于噪声的均值为零，且各像素噪声相互独立，加权求和后噪声分量的期望值趋近于零：&lt;/p&gt;
$$\mathbb{E}\left[\sum_{i,j}\omega_{i,j}n(i,j)\right] = 0$$&lt;p&gt;其中，$\omega_{i,j}$为高斯核的权重。因此，噪声在加权平均过程中被互相抵消，而图像真实信号因邻域内变化平缓，加权平均后得以保留。&lt;/p&gt;
&lt;h4 id=&#34;频域&#34;&gt;频域
&lt;/h4&gt;&lt;p&gt;高斯噪声的功率谱在频域中分布较为平坦，能量覆盖所有频率，但因其变化快的特性，主要集中在高频区域。高斯滤波器的频率响应仍然是高斯函数，是一个平滑的低通滤波器，所以能抑制以高频为主的高斯噪声。&lt;/p&gt;
&lt;h3 id=&#34;与其他滤波器相比&#34;&gt;与其他滤波器相比
&lt;/h3&gt;&lt;p&gt;高斯滤波：在平滑效果与边缘保留之间取得最佳平衡，且具有优良的数学性质，是大多数图像处理任务的首选。
均值滤波：实现简单，借助积分图可达到极快速度，但视觉质量较差，适用于对速度要求极高、对模糊质量不敏感的场合。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;对比维度&lt;/th&gt;
          &lt;th&gt;高斯滤波&lt;/th&gt;
          &lt;th&gt;均值滤波&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;核形状&lt;/td&gt;
          &lt;td&gt;钟形曲面，中心权重最大，四周平滑衰减&lt;/td&gt;
          &lt;td&gt;矩形均匀分布，所有权重相等&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;滤波效果&lt;/td&gt;
          &lt;td&gt;自然平滑模糊，边缘保留较好，无振铃现象&lt;/td&gt;
          &lt;td&gt;均匀模糊，边缘处易产生块状伪影&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;边缘保留能力&lt;/td&gt;
          &lt;td&gt;较强&lt;/td&gt;
          &lt;td&gt;较弱&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;噪声抑制&lt;/td&gt;
          &lt;td&gt;有效抑制高斯噪声&lt;/td&gt;
          &lt;td&gt;抑制高斯噪声效果略差于高斯滤波&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;频域特性&lt;/td&gt;
          &lt;td&gt;高斯函数，低通&lt;/td&gt;
          &lt;td&gt;sinc函数，低通&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;可分离性&lt;/td&gt;
          &lt;td&gt;可分解为两个一维滤波&lt;/td&gt;
          &lt;td&gt;同样可分离&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;计算复杂度&lt;/td&gt;
          &lt;td&gt;通过可分离性实现O(k)每像素&lt;/td&gt;
          &lt;td&gt;同样O(k)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;卷积封闭性&lt;/td&gt;
          &lt;td&gt;多次高斯滤波等价于一次&lt;/td&gt;
          &lt;td&gt;多次均值滤波不等价于单次&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;软件实现&#34;&gt;软件实现
&lt;/h2&gt;&lt;p&gt;本次作业基于Python3.11.0进行实现。高斯滤波分为两个部分，一是构建高斯核，二是将高斯核与原图像进行互相关算法。图片读取依赖python-opencv库的cv2.imread，互相关算法参考了python-opencv的处理逻辑[3]。&lt;/p&gt;
&lt;h3 id=&#34;构建高斯核-1&#34;&gt;构建高斯核
&lt;/h3&gt;&lt;p&gt;此处考虑更加一般的情况，即高斯核水平和竖直方向的大小和方差可能不一致，所以需要单独进行计算。具体代码如：&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/gaussian_kernel_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;高斯核构建代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;cv2filter2d&#34;&gt;cv2.filter2d
&lt;/h3&gt;&lt;p&gt;原图像可以有多个通道，对于每一个通道都要进行一次滤波，所以需要将图像转为(height, width, channel)形式。&lt;/p&gt;
&lt;p&gt;为了滤波后不改变图像的大小，所以需要在图像周围进行填充，一般有常量填充、复制边缘、反射边缘等方法，这里均进行了实现。在实际中（参考opencv），核并不总是以核中心为锚点（即核的作用点）。锚点的选取也会影响到滤波的效果，锚点位置影响图像周围填充的大小。填充采用numpy.pad函数：&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/padding_code.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;周围填充代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;在opencv中，计算方式如下[3]：&lt;/p&gt;
$$\text{dst}(x, y) = \sum\_{x&#39;, y&#39;} \text{kernel}(x&#39;, y&#39;) \cdot \text{src}(x+x&#39;-\text{anchor}.x,, y+y&#39;-\text{anchor}.y)$$$$\quad x&#39;\in \left\[0, \text{kernel}.cols\right], \quad y&#39; \in \left\[0, \text{kernel}.rows\right]$$&lt;p&gt;opencv中，还会引入一个 delta，为滤波后的图像加上偏置，互相关运算代码：&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/filter2d_conv.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;互相关运算代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;利用高斯滤波的可分离性&#34;&gt;利用高斯滤波的可分离性
&lt;/h3&gt;&lt;p&gt;在先前我们已多次提到可分离性，这里给出代码实现，其中manual_filter2d为上述提到的参考opencv实现的互相关方法。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/separate_blur.png&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;核分离的高斯模糊代码&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;数据分析和可视化&#34;&gt;数据分析和可视化
&lt;/h2&gt;&lt;p&gt;本节测试图片来自画师JW作品《且慢听说前面风很大》，因为其同时包含细节较多和较少的区域，图片分辨率适中，所以作为实验对象。&lt;/p&gt;
&lt;h3 id=&#34;处理速度&#34;&gt;处理速度
&lt;/h3&gt;&lt;p&gt;本部分测试了将核整体进行滤波与将核分离进行滤波的处理时间，图片分辨率为$2800\times1600$，采用$7 \times 7,\quad \sigma=1$的核与$31 \times 31,\quad \sigma=5$的核。从表中我们可以看到，当核的体积较小时，未分离方式所花时间比分离方式少，这是因为分离会多新建一次图像带来耗时，而核体积本身就很小，提速不明显，新建图像的时间开销更大；当核的体积较大时，未分离方式所花时间比分离方式多，此时互相关带来的时间开销超过新建图像的耗时。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;计算方式&lt;/th&gt;
          &lt;th&gt;未分离&lt;/th&gt;
          &lt;th&gt;分离&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;$7 \times 7,\quad \sigma=1$&lt;/td&gt;
          &lt;td&gt;21.96s&lt;/td&gt;
          &lt;td&gt;37.06s&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;$31 \times 31,\quad \sigma=5$&lt;/td&gt;
          &lt;td&gt;94.24s&lt;/td&gt;
          &lt;td&gt;41.11s&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;模糊&#34;&gt;模糊
&lt;/h3&gt;&lt;p&gt;本部分按照$size = 2\left\lfloor 3 \sigma\right\rfloor + 1$取了三个不同方差的高斯核。可以看到，当方差越大时，高斯滤波造成的结果越模糊。将$7 \times 7,\quad \sigma=1$与$19 \times 19,\quad \sigma=1$进行做差，得到的差图全为黑色，进一步证明了最大有效核宽的理论。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;原始图像&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/blur_7x7_1.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;7×7, σ=1&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/blur_9x9_1.5.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;9×9, σ=1.5&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/blur_19x19_3.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;19×19, σ=3&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/blur_19x19_1.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;19×19, σ=1&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/diff_1.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;7×7与19×19作差&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;去噪&#34;&gt;去噪
&lt;/h3&gt;&lt;h4 id=&#34;指标&#34;&gt;指标
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;1. PSNR (Peak Signal-to-Noise Ratio，峰值信噪比)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PSNR是最常用的图像质量评价指标之一，基于均方误差(MSE)计算，用于衡量去噪后图像与原始图像之间的差异。其数学定义为：&lt;/p&gt;
$$\text{MSE} = \frac{1}{MN} \sum_{i=1}^{M} \sum_{j=1}^{N} [I(i,j) - K(i,j)]^2$$$$\text{PSNR} = 10 \cdot \log_{10} \left( \frac{\text{MAX}_I^2}{\text{MSE}} \right)$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$M$和$N$分别为图像的高度和宽度&lt;/li&gt;
&lt;li&gt;$I(i,j)$为原始图像在位置$(i,j)$的像素值&lt;/li&gt;
&lt;li&gt;$K(i,j)$为去噪后图像在位置$(i,j)$的像素值&lt;/li&gt;
&lt;li&gt;$\text{MAX}_I$为图像的最大可能像素值（对于8位图像为255）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PSNR值越高，表示图像质量越好。通常认为PSNR大于30dB时图像质量较好，20-30dB为可接受范围，低于20dB则质量较差。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. SSIM (Structural Similarity Index Measure，结构相似性指数)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SSIM是一种更符合人类视觉感知的图像质量评价指标，从亮度、对比度和结构三个方面综合评估图像相似性。其定义为：&lt;/p&gt;
$$
\text{SSIM}(x,y) = [l(x,y)]^\alpha \cdot [c(x,y)]^\beta \cdot [s(x,y)]^\gamma
$$&lt;p&gt;其中亮度比较函数：
&lt;/p&gt;
$$l(x,y) = \frac{2\mu_x\mu_y + C_1}{\mu_x^2 + \mu_y^2 + C_1}$$&lt;p&gt;对比度比较函数：
&lt;/p&gt;
$$c(x,y) = \frac{2\sigma_x\sigma_y + C_2}{\sigma_x^2 + \sigma_y^2 + C_2}$$&lt;p&gt;结构比较函数：
&lt;/p&gt;
$$s(x,y) = \frac{\sigma_{xy} + C_3}{\sigma_x\sigma_y + C_3}$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mu_x, \mu_y$分别为图像$x$和$y$的均值&lt;/li&gt;
&lt;li&gt;$\sigma_x, \sigma_y$分别为图像$x$和$y$的标准差&lt;/li&gt;
&lt;li&gt;$\sigma_{xy}$为图像$x$和$y$的协方差&lt;/li&gt;
&lt;li&gt;$C_1, C_2, C_3$为小的正常数，避免分母为零&lt;/li&gt;
&lt;li&gt;通常取$\alpha = \beta = \gamma = 1$，$C_3 = C_2/2$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SSIM的取值范围为[0,1]，值越接近1表示图像质量越好。相比PSNR，SSIM能更好地反映人类视觉对图像结构信息的感知。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指标应用说明&lt;/strong&gt;：
在本次实验中，PSNR用于定量评估去噪算法对噪声的抑制能力，而SSIM则用于评估算法在保持图像结构信息方面的表现。两者结合使用能够更全面地评价高斯滤波算法的性能。&lt;/p&gt;
&lt;h4 id=&#34;高斯噪声&#34;&gt;高斯噪声
&lt;/h4&gt;&lt;p&gt;本部分测试了不同大小的高斯核对高斯噪声的处理效果，同时与均值滤波进行比对。原图添加了$\mu=0,\sigma=20$的高斯噪声，分别进行$5 \times 5,\quad \sigma=1$和$9 \times 9,\quad \sigma=1.5$的高斯滤波，以及$5 \times 5$的均值滤波。&lt;/p&gt;
&lt;p&gt;对比可知，高斯核越大，对噪声的抑制效果越好，但对图像的模糊程度越高。同等大小下，均值滤波的模糊程度比高斯滤波高，但去噪的效果几乎相同，从指标上可得，高斯滤波的去噪效果较好。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;滤波方式&lt;/th&gt;
          &lt;th&gt;PSNR(dB)&lt;/th&gt;
          &lt;th&gt;SSIM&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;添加噪声&lt;/td&gt;
          &lt;td&gt;25.19&lt;/td&gt;
          &lt;td&gt;0.45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;高斯滤波$（5 \times 5,\quad \sigma=1）$&lt;/td&gt;
          &lt;td&gt;30.02&lt;/td&gt;
          &lt;td&gt;0.78&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;高斯滤波$（9 \times 9,\quad \sigma=1.5）$&lt;/td&gt;
          &lt;td&gt;29.19&lt;/td&gt;
          &lt;td&gt;0.79&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;均值滤波$（5 \times 5）$&lt;/td&gt;
          &lt;td&gt;28.85&lt;/td&gt;
          &lt;td&gt;0.76&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_gaussian_20.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;添加了μ=0,σ=20的高斯噪声&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_gaussian_20_blur_5x5_1.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;进行5×5,σ=1的高斯滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_gaussian_20_blur_9x9_1.5.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;进行9×9,σ=1.5的高斯滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_gaussian_20_box_blur_5x5.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;进行5×5的均值滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;椒盐噪声&#34;&gt;椒盐噪声
&lt;/h4&gt;&lt;p&gt;椒盐噪声是图像处理中常见的脉冲噪声类型，表现为图像中随机出现的黑白像素点。其数学模型可表示为：&lt;/p&gt;
$$I\_{noise}(x,y) =
\begin{cases}
0      &amp; \text{以概率 } p\_1       \\
255    &amp; \text{以概率 } p\_2       \\
I(x,y) &amp; \text{以概率 } 1-p\_1-p\_2
\end{cases}$$&lt;p&gt;其中$p_1,p_2$为噪声密度，控制噪声强度。&lt;/p&gt;
&lt;p&gt;高斯滤波对椒盐噪声的去噪效果有限，主要原因在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;极值敏感性&lt;/strong&gt;：椒盐噪声为极值噪声（0或255），高斯滤波的加权平均无法有效消除这种极端值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权重污染&lt;/strong&gt;：椒盐噪声点会污染高斯核的权重计算，影响邻域像素的平滑效果&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;边缘模糊&lt;/strong&gt;：为消除椒盐噪声需要较大的$\sigma$值，但会导致图像过度模糊，丢失细节信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非线性特性&lt;/strong&gt;：椒盐噪声具有非线性特性，而高斯滤波是线性滤波器，处理效果有限&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本部分在测试图像上添加$p_1=0.2,p_2=0.2$的椒盐噪声，分别使用$9\times9$高斯核（$\sigma=1.5$）和$5\times5$中值滤波进行对比实验。中值滤波作为非线性滤波器，对椒盐噪声具有更好的处理效果。高斯滤波效果较差。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_salt_pepper.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;添加了椒盐噪声&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_salt_pepper_blur_9x9_1.5.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;进行9×9,σ=1.5的高斯滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img src=&#34;https://brown1729.github.io/p/%E9%AB%98%E6%96%AF%E6%BB%A4%E6%B3%A2/figures/cloud_salt_pepper_median_blur_5x5.jpg&#34;&gt;&lt;figcaption&gt;
      &lt;h4&gt;进行5×5的中值滤波&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论
&lt;/h2&gt;&lt;p&gt;本次数字图像处理作业成功实现了基于可分离卷积的高斯滤波算法，并对其在图像模糊和去噪中的应用进行了系统研究。通过理论分析、算法实现和实验验证，达到了预期目标，主要成果总结如下：&lt;/p&gt;
&lt;h3 id=&#34;主要成果&#34;&gt;主要成果
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;算法实现&lt;/strong&gt;：成功构建了可分离卷积的高斯滤波算法，实现了高斯核的自动生成、归一化处理以及高效的二维卷积运算。算法充分利用了高斯核的可分离性，将计算复杂度从$O(n^2m^2)$降低到$O(n^2m)$，显著提升了处理效率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理论验证&lt;/strong&gt;：通过数学推导验证了高斯核的卷积半群性质和频域特性，证明了连续两次高斯模糊等价于一次标准差为$\sqrt{\sigma_1^2 + \sigma_2^2}$的高斯模糊，为参数选择提供了理论依据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实验分析&lt;/strong&gt;：系统评估了高斯滤波在不同参数设置下的性能表现&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模糊程度与参数 $\sigma$ 的关系&lt;/strong&gt;：
实验验证了标准差 $\sigma$ 对模糊程度的控制作用。当 $\sigma$ 较小时（如 $\sigma = 1$），高斯核能量集中，滤波后图像细节保留较好，仅产生轻微平滑；随着 $\sigma$ 增大（如 $\sigma = 1.5$、$\sigma = 3$），核的支撑范围扩大，权重分布更平坦，图像模糊程度明显增加。同时，依据&amp;rdquo;$\text{size} = 2\lfloor 3\sigma\rfloor + 1$&amp;ldquo;的经验公式，当核尺寸达到理论有效宽度后，继续增加核尺寸不再改变滤波结果（如 $7 \times 7, \sigma = 1$ 与 $19 \times 19, \sigma = 1$ 的差图为全黑），验证了截断误差可忽略的结论。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;计算效率与可分性&lt;/strong&gt;：
对比了直接二维卷积与分离卷积的实现效率。对于小核（$7 \times 7$），未分离方式耗时 21.96 秒，而分离方式因需额外图像分配反而耗时 37.06 秒；对于大核（$31 \times 31$），未分离方式耗时 94.24 秒，分离方式仅耗时 41.11 秒，加速比超过 2 倍。结果表明，可分性在大核滤波时优势显著，而在小核场景下可直接使用二维卷积以避免内存开销。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;去噪性能&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高斯噪声：&lt;/strong&gt;
在添加 $\mu = 0, \sigma = 20$ 的高斯噪声图像上，采用 $5 \times 5, \sigma = 1$ 的高斯滤波后，PSNR 从 25.19 dB 提升至 30.02 dB，SSIM 从 0.45 提升至 0.78；同尺寸均值滤波的 PSNR 为 28.85 dB，SSIM 为 0.76。高斯滤波在两项指标上均略优于均值滤波，且当 $\sigma$ 增至 1.5（核尺寸 $9 \times 9$）时，PSNR 虽略有下降（29.19 dB），但 SSIM 仍保持较高水平（0.79），说明在保持结构相似性方面高斯滤波更具优势。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;椒盐噪声：&lt;/strong&gt;
在添加密度为 0.2 的椒盐噪声图像上，高斯滤波（$9 \times 9, \sigma = 1.5$）无法有效去除极值点，图像中仍残留大量黑白斑点，而中值滤波（$5 \times 5$）能基本恢复图像结构。实验表明，高斯滤波对脉冲噪声效果有限，体现了其作为线性滤波器的局限性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;未来改进方向&#34;&gt;未来改进方向
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;深度学习应用&lt;/strong&gt;：将高斯先验嵌入神经网络，实现可学习的高斯滤波。研究基于图像内容的自适应$\sigma$选择策略，实现参数自动化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多种滤波混合使用&lt;/strong&gt;：识别不同的噪声特征，采用对应最有效的滤波方式。&lt;/p&gt;
&lt;p&gt;通过本次作业，不仅深入理解了高斯滤波的理论基础和实现技术，也为后续更复杂的图像处理任务奠定了坚实基础。高斯滤波作为经典的数字图像处理技术，在保持其基础地位的同时，仍有广阔的研究和应用空间。&lt;/p&gt;
&lt;h2 id=&#34;参考文献&#34;&gt;参考文献
&lt;/h2&gt;&lt;p&gt;[1] Lowe, D. G. (2004). Distinctive image features from scale-invariant keypoints. International journal of computer vision, 60(2), 91-110.&lt;/p&gt;
&lt;p&gt;[2] Gonzalez, R. C., &amp;amp; Woods, R. E. (2011). Digital image processing. Pearson Education.&lt;/p&gt;
&lt;p&gt;[3] OpenCV GaussianBlur Documentation. &lt;a class=&#34;link&#34; href=&#34;https://docs.opencv.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://docs.opencv.org/&lt;/a&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>ResNet 瓶颈架构</title>
        <link>https://brown1729.github.io/p/resnet-%E7%93%B6%E9%A2%88%E6%9E%B6%E6%9E%84/</link>
        <pubDate>Thu, 26 Mar 2026 19:37:00 +0800</pubDate>
        
        <guid>https://brown1729.github.io/p/resnet-%E7%93%B6%E9%A2%88%E6%9E%B6%E6%9E%84/</guid>
        <description>&lt;img src="https://brown1729.github.io/p/resnet-%E7%93%B6%E9%A2%88%E6%9E%B6%E6%9E%84/130852450_p0.jpg" alt="Featured image of post ResNet 瓶颈架构" /&gt;&lt;h1 id=&#34;resnet---瓶颈架构&#34;&gt;ResNet - 瓶颈架构
&lt;/h1&gt;&lt;h2 id=&#34;深度网络的计算挑战&#34;&gt;深度网络的计算挑战
&lt;/h2&gt;&lt;p&gt;随着网络变得更深，每个残差块的计算成本成为一个关键问题。一个包含两个$ 3 \times 3 $卷积的基本块在256通道时的成本为：
&lt;/p&gt;
$$
 \text{FLOPs}_{\text{basic}} = 2 \times (256 \times 256 \times 3 \times 3) \times H \times W = 1{,}179{,}648 \times H \times W 
$$&lt;p&gt;对于在$ 14 \times 14 $特征图上有16个256通道块的ResNet-50，仅一个阶段就需要大约37亿FLOPs。使用基本块扩展到更深的网络（101、152层）在计算上是不可行的。&lt;/p&gt;
&lt;p&gt;瓶颈块通过引入&amp;quot;压缩、处理、扩展&amp;quot;模式解决了这个问题，将计算量减少约8倍，同时保持甚至提高了表示能力。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;瓶颈架构&#34;&gt;瓶颈架构
&lt;/h2&gt;&lt;p&gt;瓶颈块使用三个卷积而不是两个，中间进行通道压缩：
&lt;/p&gt;
$$
 y = F_{\text{bottleneck}}(x) + x 
$$&lt;p&gt;其中$ F_{\text{bottleneck}} $包含：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;层1：&lt;/strong&gt; $ 1 \times 1 $卷积（压缩）
&lt;/p&gt;
$$
 h_1 = \text{ReLU}(\text{BN}(W_1 \cdot x)) 
$$&lt;p&gt;
将通道从$ C $减少到$ C/4 $。例如，256通道减少到64。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;层2：&lt;/strong&gt; $ 3 \times 3 $卷积（处理）
&lt;/p&gt;
$$
 h_2 = \text{ReLU}(\text{BN}(W_2 * h_1)) 
$$&lt;p&gt;
在减少的通道数下执行空间处理。$ 3 \times 3 $核捕获空间模式，但在$ C/4 $通道而不是$ C $通道下。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;层3：&lt;/strong&gt; $ 1 \times 1 $卷积（扩展）
&lt;/p&gt;
$$
 h_3 = \text{BN}(W_3 \cdot h_2) 
$$&lt;p&gt;
将通道从$ C/4 $扩展回$ C $。注意：此层后没有ReLU（ReLU在加上跳跃连接后应用）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;跳跃连接和激活：&lt;/strong&gt;
&lt;/p&gt;
$$
 y = \text{ReLU}(h_3 + x) 
$$&lt;hr&gt;
&lt;h2 id=&#34;瓶颈为何节省计算&#34;&gt;瓶颈为何节省计算
&lt;/h2&gt;&lt;p&gt;关键洞察是昂贵的$ 3 \times 3 $卷积在压缩表示上运行，使用$ C/4 $通道而不是$ C $通道。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本块FLOPs&lt;/strong&gt;（两个$ 3 \times 3 $卷积在$ C $通道）：
&lt;/p&gt;
$$ \text{FLOPs}_{\text{basic}} = 2 \times C^2 \times 9 \times H \times W = 18C^2 HW $$&lt;p&gt;&lt;strong&gt;瓶颈块FLOPs：&lt;/strong&gt;
• $ 1 \times 1 $压缩（$ C \rightarrow C/4 $）：$ C \times C/4 \times 1 \times HW = C^2 HW/4 $
• $ 3 \times 3 $处理（$ C/4 \rightarrow C/4 $）：$ (C/4)^2 \times 9 \times HW = 9C^2 HW/16 $
• $ 1 \times 1 $扩展（$ C/4 \rightarrow C $）：$ C/4 \times C \times 1 \times HW = C^2 HW/4 $&lt;/p&gt;
$$ \text{FLOPs}_{\text{bottleneck}} = C^2 HW \left(\frac{1}{4} + \frac{9}{16} + \frac{1}{4}\right) = C^2 HW \times \frac{17}{16} \approx 1.06 C^2 HW $$&lt;p&gt;&lt;strong&gt;比率：&lt;/strong&gt;
&lt;/p&gt;
$$
 \frac{\text{FLOPs}_{\text{basic}}}{\text{FLOPs}_{\text{bottleneck}}} = \frac{18}{1.06} \approx 17
$$&lt;p&gt;瓶颈块在相同通道数下比基本块大约便宜17倍。这种巨大的节省使得ResNet-50、101和152变得实用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;计算节省的实际例子&#34;&gt;计算节省的实际例子
&lt;/h2&gt;&lt;p&gt;考虑阶段3的瓶颈块，$ C = 256 $通道，$ H \times W = 14 \times 14 $：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $压缩（$ 256 \rightarrow 64 $）：&lt;/strong&gt;
&lt;/p&gt;
$$ 256 \times 64 \times 1 \times 196 = 3{,}211{,}264 \text{ FLOPs} $$&lt;p&gt;&lt;strong&gt;$ 3 \times 3 $处理（$ 64 \rightarrow 64 $）：&lt;/strong&gt;
&lt;/p&gt;
$$ 64 \times 64 \times 9 \times 196 = 7{,}225{,}344 \text{ FLOPs} $$&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $扩展（$ 64 \rightarrow 256 $）：&lt;/strong&gt;
&lt;/p&gt;
$$ 64 \times 256 \times 1 \times 196 = 3{,}211{,}264 \text{ FLOPs} $$&lt;p&gt;&lt;strong&gt;总瓶颈：&lt;/strong&gt; $ 13{,}647{,}872 $ FLOPs&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;等效基本块&lt;/strong&gt;（两个$ 3 \times 3 $在256通道）：
&lt;/p&gt;
$$ 2 \times 256 \times 256 \times 9 \times 196 = 231{,}211{,}008 \text{ FLOPs} $$&lt;p&gt;&lt;strong&gt;节省：&lt;/strong&gt; $ 231M / 13.6M \approx 17\times $&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;-1-times-1-卷积&#34;&gt;$ 1 \times 1 $卷积
&lt;/h2&gt;&lt;p&gt;$ 1 \times 1 $卷积（点卷积）是瓶颈设计的关键推动者。理解它至关重要。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $卷积的作用：&lt;/strong&gt;
在每个空间位置$ (h, w) $，$ 1 \times 1 $卷积对通道向量应用线性变换：
&lt;/p&gt;
$$ y_{hw} = W \cdot x_{hw} + b $$&lt;p&gt;
其中$ x_{hw} \in \mathbb{R}^{C_{in}} $是位置$ (h, w) $的输入特征向量，$ W \in \mathbb{R}^{C_{out} \times C_{in}} $是权重矩阵，$ y_{hw} \in \mathbb{R}^{C_{out}} $是输出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它不做什么：&lt;/strong&gt;
• 不查看相邻空间位置（无空间感受野）
• 不捕获空间模式（这是$ 3 \times 3 $卷积的工作）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它擅长什么：&lt;/strong&gt;
• 改变通道数（廉价地）
• 学习特征通道的线性组合
• 通道混合：每个输出通道是所有输入通道的加权和
• 维度减少：将高维通道向量压缩到低维&lt;/p&gt;
&lt;p&gt;Lin等人（2013）在Network-in-Network论文中引入了$ 1 \times 1 $卷积，GoogLeNet/Inception将其推广用于维度减少。ResNet的瓶颈设计使其成为架构的核心。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;压缩-扩展模式&#34;&gt;压缩-扩展模式
&lt;/h2&gt;&lt;p&gt;瓶颈的&amp;quot;压缩、处理、扩展&amp;quot;模式在整个深度学习中出现：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在ResNet瓶颈块中：&lt;/strong&gt;
&lt;/p&gt;
$$ C\xrightarrow{1 \times 1} C/4 \xrightarrow{3 \times 3} C/4 \xrightarrow{1 \times 1} C $$&lt;p&gt;
将通道压缩4倍，廉价地进行空间处理，扩展回来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在MobileNet反向瓶颈中：&lt;/strong&gt;
&lt;/p&gt;
$$ C \xrightarrow{1 \times 1} 6C \xrightarrow{3 \times 3\, \text{深度}} 6C \xrightarrow{1 \times 1} C $$&lt;p&gt;
相反方向：先扩展，处理，然后压缩。称为&amp;quot;反向&amp;quot;是因为宽的部分在中间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在Transformer FFN中：&lt;/strong&gt;
&lt;/p&gt;
$$ 
d_{model} \xrightarrow{W_1} 4 \times d_{model} \xrightarrow{\text{ReLU}} 4 \times d_{model} \xrightarrow{W_2} d_{model}
$$&lt;p&gt;
扩展4倍，应用非线性，压缩回来。&lt;/p&gt;
&lt;p&gt;**共同原则：**移动到不同的维度进行处理，然后返回原始维度。&amp;ldquo;工作维度&amp;quot;可以根据设计目标（计算节省vs.表示丰富度）更大或更小。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;瓶颈vs基本何时使用哪种&#34;&gt;瓶颈vs.基本：何时使用哪种
&lt;/h2&gt;&lt;p&gt;ResNet论文根据模型深度使用两种不同的块类型：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本块&lt;/strong&gt;（用于ResNet-18和ResNet-34）：
• 两个$ 3 \times 3 $卷积
• 更简单的架构
• 总层数较少但每块FLOPs更多
• 适用于计算预算允许的较小模型&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;瓶颈块&lt;/strong&gt;（用于ResNet-50、101和152）：
• 三个卷积（$ 1 \times 1 $, $ 3 \times 3 $, $ 1 \times 1 $）
• 更多层但每块FLOPs更少
• 适用于计算效率关键的深层模型&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交叉点：&lt;/strong&gt;
ResNet-34（基本块）：2180万参数，36亿FLOPs
ResNet-50（瓶颈块）：2560万参数，38亿FLOPs&lt;/p&gt;
&lt;p&gt;尽管有50层vs.34层，ResNet-50只有稍微更多的参数和FLOPs。这是因为瓶颈设计效率更高，50个瓶颈层的成本与34个基本层大致相同。&lt;/p&gt;
&lt;p&gt;ResNet-50也表现明显更好（ImageNet上76.0% vs. 73.3% top-1准确率），表明瓶颈效率启用的额外深度直接转化为更好的性能。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;实际中的通道计数&#34;&gt;实际中的通道计数
&lt;/h2&gt;&lt;p&gt;标准ResNet瓶颈使用4:1压缩比：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阶段1&lt;/strong&gt;（$ 56 \times 56 $）：
• 输入/输出通道：256
• 瓶颈通道：64
• 压缩：$ 256 \rightarrow 64 \rightarrow 64 \rightarrow 256 $&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阶段2&lt;/strong&gt;（$ 28 \times 28 $）：
• 输入/输出通道：512
• 瓶颈通道：128
• 压缩：$ 512 \rightarrow 128 \rightarrow 128 \rightarrow 512 $&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阶段3&lt;/strong&gt;（$ 14 \times 14 $）：
• 输入/输出通道：1024
• 瓶颈通道：256
• 压缩：$ 1024 \rightarrow 256 \rightarrow 256 \rightarrow 1024 $&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阶段4&lt;/strong&gt;（$ 7 \times 7 $）：
• 输入/输出通道：2048
• 瓶颈通道：512
• 压缩：$ 2048 \rightarrow 512 \rightarrow 512 \rightarrow 2048 $&lt;/p&gt;
&lt;p&gt;注意瓶颈块的输入/输出通道是瓶颈通道的4倍。这就是为什么ResNet-50在其最终阶段有2048通道，而ResNet-34（使用基本块）只有512通道。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;扩展因子&#34;&gt;扩展因子
&lt;/h2&gt;&lt;p&gt;比率$ C_{out} / C_{bn} $称为&lt;strong&gt;扩展因子&lt;/strong&gt;。在标准ResNet瓶颈中，这个因子是4：
&lt;/p&gt;
$$ C_{out} = 4 \times C_{bn} $$&lt;p&gt;&lt;strong&gt;为什么是4？&lt;/strong&gt;
这是一个经验选择，提供了良好的权衡：
• 太小（例如，扩展=1）：没有压缩优势，基本上是一个带额外层的基本块
• 太大（例如，扩展=16）：激进的压缩可能在瓶颈中丢失太多信息
• 扩展=4：$ 1 \times 1 $层廉价，$ 3 \times 3 $层可负担，压缩不会显著损害表示能力&lt;/p&gt;
&lt;p&gt;后来的架构尝试了不同的扩展因子：
• ResNeXt：使用分组卷积增加有效宽度，同时保持FLOPs不变
• EfficientNet：使用可变扩展因子（1到6之间）通过神经架构搜索优化
• RegNet：系统地研究设计空间，发现2-4左右的扩展因子是最优的&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;通过瓶颈的信息流&#34;&gt;通过瓶颈的信息流
&lt;/h2&gt;&lt;p&gt;信息通过三层时会发生什么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $压缩（信息瓶颈）：&lt;/strong&gt;
256维特征向量投影到64维。这是一个有损压缩：网络必须学习256个特征中哪些64个线性组合最有信息量。投影学习保留与任务最相关的特征。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$ 3 \times 3 $处理（空间推理）：&lt;/strong&gt;
在压缩空间中，$ 3 \times 3 $卷积查看相邻空间位置。它可以检测压缩特征空间中的边缘、纹理和模式。因为通道数小（64），这个操作很便宜。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $扩展（信息恢复）：&lt;/strong&gt;
64维处理特征投影回256维。这种扩展允许网络将处理信息&amp;quot;扩散&amp;quot;到整个通道空间，创建丰富的输出表示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;跳跃连接确保没有丢失：&lt;/strong&gt;
即使瓶颈的压缩过于激进并丢弃了有用信息，跳跃连接也保留了完整的256维输入。输出$ y = F(x) + x $可以访问原始信息（$ x $）和瓶颈的处理贡献（$ F(x) $）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参数数量分析&#34;&gt;参数数量分析
&lt;/h2&gt;&lt;p&gt;对于输入/输出通道$ C $和瓶颈通道$ C/4 $的瓶颈块：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $压缩：&lt;/strong&gt;
&lt;/p&gt;
$$ P_1 = C \times C/4 = C^2/4 $$&lt;p&gt;&lt;strong&gt;$ 3 \times 3 $处理：&lt;/strong&gt;
&lt;/p&gt;
$$ P_2 = C/4 \times C/4 \times 9 = 9C^2/16 $$&lt;p&gt;&lt;strong&gt;$ 1 \times 1 $扩展：&lt;/strong&gt;
&lt;/p&gt;
$$ P_3 = C/4 \times C = C^2/4 $$&lt;p&gt;&lt;strong&gt;总计：&lt;/strong&gt;
&lt;/p&gt;
$$ P_{\text{bottleneck}} = C^2/4 + 9C^2/16 + C^2/4 = 17C^2/16 \approx 1.06C^2 $$&lt;p&gt;&lt;strong&gt;基本块（两个$ 3 \times 3 $）：&lt;/strong&gt;
&lt;/p&gt;
$$ P_{\text{basic}} = 2 \times C^2 \times 9 = 18C^2 $$&lt;p&gt;&lt;strong&gt;比率：&lt;/strong&gt;
&lt;/p&gt;
$$ \frac{P_{\text{basic}}}{P_{\text{bottleneck}}} = \frac{18}{1.06} \approx 17 $$&lt;p&gt;瓶颈比基本块少17倍参数。这与计算节省的比率相同，因为FLOPs和参数都随通道维度和核大小的相同乘积缩放。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;resnet变体中的瓶颈块&#34;&gt;ResNet变体中的瓶颈块
&lt;/h2&gt;&lt;p&gt;瓶颈块的数量在ResNet配置中不同：
ResNet-50：3 + 4 + 6 + 3 = 16个瓶颈块
ResNet-101：3 + 4 + 23 + 3 = 33个瓶颈块
ResNet-152：3 + 8 + 36 + 3 = 50个瓶颈块&lt;/p&gt;
&lt;p&gt;深度主要在阶段3（$ 14 \times 14 $分辨率阶段）增加。这是因为阶段3具有良好的空间分辨率平衡（足以捕获有意义的空间模式）和可管理的计算（空间上不太大，通道不太多）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;超越标准瓶颈&#34;&gt;超越标准瓶颈
&lt;/h2&gt;&lt;p&gt;几个重要架构修改了瓶颈设计：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ResNeXt（2017）：&lt;/strong&gt;
用$ G $个并行分组卷积替换单个$ 3 \times 3 $卷积：
&lt;/p&gt;
$$ F(x) = \sum_{i=1}^{G} W_3^{(i)} \cdot \text{ReLU}(\text{BN}(W_2^{(i)} * \text{ReLU}(\text{BN}(W_1^{(i)} \cdot x)))) $$&lt;p&gt;
这增加了&amp;quot;基数&amp;rdquo;（并行路径数），同时保持FLOPs不变。ResNeXt-50在相同计算预算下优于ResNet-50。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SE-ResNet（Squeeze-and-Excitation，2018）：&lt;/strong&gt;
在瓶颈后添加通道注意力机制：
&lt;/p&gt;
$$ y = F(x) \cdot \text{SE}(F(x)) + x $$&lt;p&gt;
SE模块学习根据重要性重新加权通道，添加很少参数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EfficientNet（2019）：&lt;/strong&gt;
使用神经架构搜索为网络中的每个块找到最优瓶颈配置（扩展比、核大小、通道数）。&lt;/p&gt;
&lt;p&gt;这些扩展都建立在瓶颈的压缩-处理-扩展核心思想上，显示了这个设计模式的基本性和多功能性。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;通过瓶颈的梯度流&#34;&gt;通过瓶颈的梯度流
&lt;/h2&gt;&lt;p&gt;瓶颈块中的梯度流遵循与基本块相同的残差原则，但在残差分支中有一个额外的层：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前向：&lt;/strong&gt;
&lt;/p&gt;
$$ 
y = h_3 + x = (\text{BN}(W_3 \cdot \text{ReLU}(\text{BN}(W_2 * \text{ReLU}(\text{BN}(W_1 \cdot x)))))) + x 
$$&lt;p&gt;&lt;strong&gt;反向（通过跳跃连接）：&lt;/strong&gt;
&lt;/p&gt;
$$ 
\frac{\partial \mathcal{L}}{\partial x} = \frac{\partial \mathcal{L}}{\partial y} \cdot \left(\frac{\partial h_3}{\partial x} + I\right) 
$$&lt;p&gt;恒等项$ I $提供梯度高速公路，就像在基本块中一样。残差分支的梯度现在通过三层而不是两层，但跳跃连接确保即使这个三层梯度很小，整体梯度仍然健康。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三层梯度路径：&lt;/strong&gt;
&lt;/p&gt;
$$
 \frac{\partial h_3}{\partial x} = \frac{\partial h_3}{\partial h_2} \cdot \frac{\partial h_2}{\partial h_1} \cdot \frac{\partial h_1}{\partial x} 
$$&lt;p&gt;每个因子涉及卷积雅可比和ReLU掩码。有三个乘法因子而不是两个（如基本块），通过瓶颈分支的残差梯度可能更小。然而，跳跃连接补偿：恒等梯度独立于分支中的层数。&lt;/p&gt;
&lt;p&gt;这正是瓶颈设计工作的原因：跳跃连接的梯度总是1，无论残差分支中堆叠多少层。瓶颈的计算节省不会带来梯度流的任何成本。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;批归一化在瓶颈块中的作用&#34;&gt;批归一化在瓶颈块中的作用
&lt;/h2&gt;&lt;p&gt;批归一化在每个瓶颈块中出现三次，每次卷积后一次：
&lt;/p&gt;
$$ h_1 = \text{ReLU}(\text{BN}_1(W_1 \cdot x)) $$&lt;p&gt;
&lt;/p&gt;
$$ h_2 = \text{ReLU}(\text{BN}_2(W_2 * h_1)) $$&lt;p&gt;
&lt;/p&gt;
$$ h_3 = \text{BN}_3(W_3 \cdot h_2) $$&lt;p&gt;&lt;strong&gt;每个BatchNorm有特定目的：&lt;/strong&gt;
• $ \text{BN}_1 $（在$ 1 \times 1 $压缩后）：在ReLU之前归一化压缩表示。没有这个，通道减少的分布偏移可能导致大多数值为负（被ReLU归零），浪费容量。
• $ \text{BN}_2 $（在$ 3 \times 3 $处理后）：归一化空间处理特征。$ 3 \times 3 $卷积聚合相邻位置，可能创建具有更大方差的特征。BatchNorm重置尺度。
• $ \text{BN}_3 $（在$ 1 \times 1 $扩展后）：在添加到跳跃连接之前归一化扩展表示。这很关键：如果$ h_3 $与$ x $的尺度非常不同，加法$ h_3 + x $将被较大的项主导。BatchNorm确保两个项都有意义地贡献。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关于最终ReLU的说明：&lt;/strong&gt;
加法后的ReLU（$ y = \text{ReLU}(h_3 + x) $）应用于组合输出。这意味着在加法和最终激活之间没有BatchNorm，保持跳跃连接尽可能干净（快捷路径上只有一个非线性）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;为什么更深网络需要瓶颈块&#34;&gt;为什么更深网络需要瓶颈块
&lt;/h2&gt;&lt;p&gt;网络深度和块类型之间的关系不是任意的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;浅层网络（18-34层）使用基本块因为：&lt;/strong&gt;
• 块数足够少，较高的每块成本可负担
• 通过仅两层的更简单梯度流
• 没有通道扩展的足够容量（$ 4 \times $）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;深层网络（50+层）使用瓶颈块因为：&lt;/strong&gt;
• 许多块需要低每块成本以实现可处理性
• 每块$ 17 \times $计算节省在50+层上显著累积
• $ 4 \times $通道扩展提供每参数更多容量
• 当跳跃连接处理梯度流时，三层结构不是劣势&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;深度-效率权衡：&lt;/strong&gt;
具有基本块的ResNet-152需要：
&lt;/p&gt;
$$
 \text{FLOPs} \approx 17 \times 11.3\text{B} = 192\text{B FLOPs} 
$$&lt;p&gt;这将不实用。瓶颈设计使152层网络在仅113亿FLOPs下可行，在每图像墙钟训练时间方面与34层基本网络（36亿FLOPs）相当。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>三角形中一个恒等式的证明</title>
        <link>https://brown1729.github.io/p/%E4%B8%89%E8%A7%92%E5%BD%A2%E4%B8%AD%E4%B8%80%E4%B8%AA%E6%81%92%E7%AD%89%E5%BC%8F%E7%9A%84%E8%AF%81%E6%98%8E/</link>
        <pubDate>Wed, 10 Sep 2025 10:02:37 +0800</pubDate>
        
        <guid>https://brown1729.github.io/p/%E4%B8%89%E8%A7%92%E5%BD%A2%E4%B8%AD%E4%B8%80%E4%B8%AA%E6%81%92%E7%AD%89%E5%BC%8F%E7%9A%84%E8%AF%81%E6%98%8E/</guid>
        <description>&lt;img src="https://brown1729.github.io/p/%E4%B8%89%E8%A7%92%E5%BD%A2%E4%B8%AD%E4%B8%80%E4%B8%AA%E6%81%92%E7%AD%89%E5%BC%8F%E7%9A%84%E8%AF%81%E6%98%8E/illust_76510277_20191124_002755.jpg" alt="Featured image of post 三角形中一个恒等式的证明" /&gt;&lt;h2 id=&#34;定理&#34;&gt;定理
&lt;/h2&gt;&lt;p&gt;三角形$\triangle ABC$中，设角$A$、$B$、$C$所对的边分别为$a$、$b$、$c$。于是有
&lt;/p&gt;
$$
    \frac{\cos A}{\sin B \sin C} + \frac{\cos B}{\sin A \sin C} + \frac{\cos C}{\sin B \sin A} = 2
$$&lt;h2 id=&#34;证明&#34;&gt;证明
&lt;/h2&gt;&lt;p&gt;左式通分，得到
&lt;/p&gt;
$$
    LHS = \frac{\cos A \sin A + \cos B \sin B + \cos C \sin C}{\sin A \sin B \sin C}
$$&lt;p&gt;
上下同乘$2$
&lt;/p&gt;
$$
        = \frac{2 \cos A \sin A + 2 \cos B \sin B + 2 \cos C \sin C}{2 \sin A \sin B \sin C}
$$&lt;p&gt;
利用二倍角公式，将前两项转换
&lt;/p&gt;
$$
        = \frac{\sin 2A + \sin 2B + 2 \sin C \cos C}{2 \sin A \sin B \sin C}
$$&lt;p&gt;
然后和差化积
&lt;/p&gt;
$$
        = \frac{2 \sin(A+B) \cos(A-B) + 2 \cos C \sin C}{2 \sin A \sin B \sin C}
$$&lt;p&gt;
&lt;/p&gt;
$$
        \because A + B + C = \pi,   \therefore \sin(A+B) = \sin(\pi - C) = \sin C
$$&lt;p&gt;
&lt;/p&gt;
$$
        LHS = \frac{2 \sin C \cos(A-B) + 2 \cos C \sin C}{2 \sin A \sin B \sin C}
$$&lt;p&gt;
&lt;/p&gt;
$$
        = \frac{\sout {2 \sin C} \cos(A-B) + \sout{2} \cos C \sout{\sin C}}{\sout{2} \sin A \sin B \sout{\sin C}}
$$&lt;p&gt;
&lt;/p&gt;
$$
        = \frac{\cos(A-B) + \cos C}{\sin A \sin B}
$$&lt;p&gt;
然后积化和差
&lt;/p&gt;
$$
        = \cfrac{\cos(A-B) + \cos C}{-\cfrac{1}{2}[\cos(A+B) - \cos(A-B)]}
$$&lt;p&gt;
&lt;/p&gt;
$$
        \because \cos(A+B) = \cos(\pi - C) = - \cos C
$$&lt;p&gt;
&lt;/p&gt;
$$
        \therefore LHS = \cfrac{\cos(A-B) + \cos C}{-\cfrac{1}{2}[-\cos C - \cos(A-B)]}
$$&lt;p&gt;
&lt;/p&gt;
$$
        = 2\frac{\cos(A-B) + \cos C}{\cos C + \cos(A-B)}
$$&lt;p&gt;
&lt;/p&gt;
$$
        = 2 = RHS
$$&lt;p align=&#34;right&#34;&gt;$\square$&lt;/p&gt;</description>
        </item>
        <item>
        <title>关于</title>
        <link>https://brown1729.github.io/%E5%85%B3%E4%BA%8E/</link>
        <pubDate>Tue, 09 Sep 2025 00:00:00 +0000</pubDate>
        
        <guid>https://brown1729.github.io/%E5%85%B3%E4%BA%8E/</guid>
        <description>&lt;h2 id=&#34;介绍&#34;&gt;介绍
&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;这是我的个人博客，用于记录一些学习笔记和心得体会。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;成就&#34;&gt;成就
&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;无
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;教育经历&#34;&gt;教育经历
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;东南大学 | 南京 本科在读，自动化专业   2023 - 至今&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>文章</title>
        <link>https://brown1729.github.io/archives/</link>
        <pubDate>Tue, 28 May 2019 00:00:00 +0000</pubDate>
        
        <guid>https://brown1729.github.io/archives/</guid>
        <description></description>
        </item>
        <item>
        <title>查找</title>
        <link>https://brown1729.github.io/search/</link>
        <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
        
        <guid>https://brown1729.github.io/search/</guid>
        <description></description>
        </item>
        <item>
        <title>链接</title>
        <link>https://brown1729.github.io/%E9%93%BE%E6%8E%A5/</link>
        <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
        
        <guid>https://brown1729.github.io/%E9%93%BE%E6%8E%A5/</guid>
        <description></description>
        </item>
        
    </channel>
</rss>
