上一篇文章中,我们已经讨论了图像的矩阵表示、色彩空间以及空间域中的逐像素操作,例如亮度调整、Gamma 校正和掩膜处理。这些方法的共同特点是:某个像素的新值只依赖于它自己,而不依赖周围像素的分布。这样的处理足以完成基础增强,但它仍然停留在“像素编辑”的层面,还没有真正进入“结构分析”的范畴。

而计算机视觉真正关心的,恰恰不是单个像素本身,而是像素与像素之间如何组织成边缘、纹理、轮廓和局部模式。一个像素是否属于边界,不取决于它单独有多亮,而取决于它与邻域相比是否发生了突变;一块区域是否具有纹理,也不是由单点数值决定,而是由局部重复关系决定。只要把问题这样重述一遍,就会发现:逐像素操作终究是不够的,图像分析必须走向邻域操作。

本文真正想回答的问题也由此明确下来:当逐像素操作无法表达结构时,图像处理究竟如何利用局部邻域来表达“平滑”“锐化”和“边缘”这三类现象?我的目的不是把卷积写成一组零散的 API 或卷积核清单,而是把它还原成一套统一的局部分析框架。

沿着这个问题往下走,文章的逻辑其实很自然:先解释为什么必须引入邻域;再说明二维卷积如何成为统一的数学形式;接着讨论同一卷积框架下为什么会分化出平滑、锐化与边缘检测这三类任务;最后再回到更大的问题,说明传统手工卷积核为什么会自然通向卷积神经网络。

本文会在关键节点穿插必要的示意图与实验图,用来辅助说明卷积的局部分析过程、不同滤波器的响应差异,以及从传统算子到 CNN 的过渡关系。

1. 从逐像素操作到邻域操作

在上一篇文章中,无论是线性变换

还是 Gamma 校正,本质上都属于 Point Operation。这类操作只改变像素值本身,并不关心局部结构,因此很难回答下面这些更“视觉”的问题:

  • 某个位置是不是边缘?
  • 这片区域是否存在纹理?
  • 图像中是否包含某种重复模式?

这些问题都要求我们不再孤立地看待单个像素,而是把一个像素放回到它所在的局部邻域中去理解。也就是说,新图像中某一点的值,不再仅由原图像中对应位置的像素决定,而是由它周围一小块区域共同决定。

这就是邻域操作(Neighborhood Operation)的核心思想,也是卷积滤波的出发点。换句话说,逐像素操作回答的是“这个点该变成什么值”,而邻域操作回答的是“这个点所在的局部区域呈现出什么结构”。前者更接近数值映射,后者才真正开始接近视觉分析。

如下图所示,逐像素操作只关心单点输入与单点输出之间的映射关系,而邻域操作则要求结合局部窗口的信息来决定当前位置的响应值。

一旦接受了“像素必须放回邻域中理解”这个前提,接下来的问题就变成了:我们该如何把“邻域信息”写成统一、可计算、可重复应用的数学形式?这正是二维卷积要解决的问题。

2. 二维卷积的数学定义与滑窗机制

2.1 从一维卷积到二维卷积

在一维离散信号中,卷积的定义为:

其中, 是输入信号, 是卷积核, 是输出信号。这个公式的本质是:让一个局部模板沿着输入信号滑动,在每个位置做加权求和。换句话说,卷积不是在问“某个点自身有多大”,而是在问“某个点附近这一小段局部模式,与当前模板有多匹配”。

图像是二维离散信号,因此二维卷积可以写为:

如图所示,二维卷积的计算过程可以理解为:卷积核在输入图像上按窗口滑动,在每个位置与局部像素逐元素相乘并求和,最终得到输出图像中对应位置的响应值。

这里:

  • 表示原图像;
  • 表示卷积核;
  • 表示卷积后的输出图像。

从实现角度看,二维卷积做的事情很直观:取出图像上的一个局部窗口,让它与卷积核对应元素相乘,再把结果累加,最终得到输出图像中当前位置的值。

但真正决定结果的并不是“滑动”这个动作,而是卷积核中的权重分布。滑动只是让同一种局部分析方式在整张图像上重复执行;卷积核才真正决定我们在局部区域里关注什么。权重如果近似平均,它更像在做平滑;如果强调中心与邻域的差异,它就更像在做差分;如果具有方向性,它就可能对某个方向上的结构更敏感。

因此,卷积最重要的意义并不只是“提供一种计算方法”,而是提供了一种统一描述局部结构的语言。后面我们会看到,平滑、锐化和边缘检测之所以可以放在同一篇文章里讨论,正是因为它们本质上都在使用这套语言,只是“提问方式”不同。

2.2 局部感受野与滑窗

假设我们使用一个 的卷积核,那么输出图像中每个像素的计算都只依赖输入图像的一个 邻域。这一小块区域就叫做 局部感受野(Local Receptive Field)

例如,设原图像局部区域为:

卷积核为:

则输出像素值可以写成:

它本质上就是局部区域与卷积核之间的一次内积。卷积核决定了“我们希望从这块局部区域中提取什么模式”,而滑窗机制则让这种模式检测过程在整张图像上重复执行。

到这里就能得到一个很重要的结论:平滑、锐化和边缘检测虽然表面上属于三类不同任务,但它们并不是三套彼此割裂的方法,而是同一个局部算子框架在不同权重设计下的不同表现。这个结论是全文的核心支点,后面所有章节其实都只是围绕它展开。

2.3 严格卷积与工程中的互相关

严格意义上的卷积要求在计算前先对卷积核进行翻转,而图像处理库和深度学习框架中更常见的其实是 互相关(Cross-Correlation)

也就是说,工程实现中常常直接拿卷积核去滑动,而不显式翻转。对于很多对称卷积核,例如高斯核,这种区别并不影响结果;而对于梯度核、方向核等非对称算子,则需要明确它的方向性含义。

这也是为什么在写代码时,大家习惯上仍然把它称为“卷积”,但在数学上要知道它与严格定义之间的差别。

明确了卷积的统一形式之后,接下来就可以按“卷积核究竟想保留什么、抑制什么”来理解后面的几类典型任务。最先出现的是平滑,因为它最直接体现了局部加权平均的思想。

3. 平滑滤波:去噪与细节损失

平滑滤波(Smoothing)本质上是在抑制图像中的高频成分。噪声通常表现为局部的剧烈波动,因此适当的平滑可以降低噪声;但代价是图像中的边缘、纹理和细节也会被一并削弱。

这一点看似矛盾,其实非常统一。无论是随机噪声,还是物体边界,它们在局部上都表现为“变化很快”。当卷积核开始对局部区域做平均时,它并不会先判断哪些变化是噪声、哪些变化是结构,而是先把所有高频变化统一压低。也正因如此,平滑的收益和代价总是同时出现:去噪越明显,细节损失往往也越明显。

3.1 均值滤波

最简单的平滑方法是均值滤波。对于一个 的核,其形式为:

它把邻域内所有像素等权平均。这样做的结果很直接:局部波动被压平,噪声降低,但边缘也会明显变钝。

均值滤波的优点是实现简单、计算代价低,但它的问题也正来自“等权”这件事本身。核内每个像素的影响被视为同等重要,这意味着卷积核并不区分谁是中心、谁更靠近边界,也不关心局部区域是否跨越了一条真实轮廓。因此,一旦核窗口跨过边界,两侧原本差异很大的像素就会被强行平均,最终表现为轮廓被抹平、细节被稀释。

3.2 高斯滤波

高斯滤波比均值滤波更符合自然图像的局部统计规律。其权重满足二维高斯分布:

这里的核心思想是:距离中心越近的像素,对当前输出值的影响越大;距离越远,权重越小。相比均值滤波,高斯滤波更加平滑自然,能更好地抑制噪声,同时减少块状伪影。

这背后的逻辑是,高斯核默认“当前像素的解释应更多依赖附近区域,而不是远处区域”。这种中心偏好的局部加权,比均值滤波更接近自然图像中局部相关性的分布方式。因此它并不是简单地“比均值更高级”,而是在相同的平滑目标下,用更合理的权重分布减少不必要的模糊。

3.3 中值滤波

中值滤波不属于线性卷积,但它在滤波章节中必须单独讨论。它的做法不是求加权平均,而是取邻域内像素值排序后的中位数。

这种方式对 椒盐噪声(Salt-and-Pepper Noise) 特别有效,因为极端异常值在排序后不会主导最终结果。与均值或高斯滤波相比,中值滤波在保留边缘方面往往更有优势,因为它不是把一个突变边界直接“摊平”,而是尝试在局部样本中选出更具有代表性的数值。

也正因为如此,中值滤波提醒我们一件很重要的事:去噪并不只有“做平均”这一种思路。不同滤波器的差异,根本上来自它们对“局部代表值”这件事的定义不同。

3.4 去噪与模糊是一体两面

这一节最重要的理解是:去噪与模糊本质上是同一个过程的两个侧面。噪声之所以被抑制,是因为图像中的局部快速变化被平均掉了;而细节之所以丢失,也是因为这些变化被平均掉了。

因此,选择何种平滑滤波器,并不是“哪个更强”这么简单,而是要结合噪声类型、图像内容和任务目标去做权衡。很多时候,真正的问题不是“能不能去噪”,而是“愿意为去噪付出多少结构损失”。这一判断,也决定了后面锐化和边缘检测为什么总要和去噪联动出现。

如下图所示,不同平滑方法虽然都能抑制噪声,但它们对边缘和纹理的保留能力并不相同,这也是滤波器选择必须结合具体任务目标的原因。

而一旦我们开始反过来思考问题,不再问“如何压低局部变化”,而是问“如何突出局部变化”,卷积核的角色就会从平滑转向锐化。

4. 锐化增强:高频信息的重新强调

如果说平滑是在抑制高频,那么锐化(Sharpening)则是在强调高频。边缘和细节通常对应图像中的强烈局部变化,因此从本质上看,锐化是在突出这些变化。

这一节和上一节并不是对立关系,而是同一套局部运算框架下的两个方向:平滑尝试保留低频、压制突变;锐化尝试重新强调那些被平滑削弱的结构变化。如下图所示,平滑核、锐化核和边缘检测核虽然形式不同,但本质上都在通过不同的权重设计,对局部区域提出不同的“问题”。

4.1 拉普拉斯算子与二阶导数

拉普拉斯算子是最经典的锐化算子之一,其连续形式为:

在离散图像中,常见的卷积核形式之一为:

它衡量的是像素与周围邻域之间的二阶变化。当某个位置灰度变化剧烈时,拉普拉斯响应会很强,因此它可以突出边缘和细节。

如果说均值核是在问“周围平均起来长什么样”,那么拉普拉斯核问的就是“当前位置与周围相比偏离了多少”。这种从“局部平均”到“局部偏差”的视角切换,正是锐化和边缘增强的核心。

4.2 Unsharp Mask 的思想

一个更有工程意义的锐化思路是:

其中, 可以理解为图像中的高频残差。也就是说,我们先通过平滑得到低频部分,再用原图减去低频,提取出高频,再把这部分加回去。

这个思路的好处在于,它把“锐化”解释成一个非常清楚的分解过程:原图 = 低频结构 + 高频细节,而锐化就是有选择地把高频细节重新加重。相比直接套一个锐化核,这样的理解更容易把平滑、锐化和频率成分之间的关系串起来。

4.3 锐化为什么容易放大噪声

噪声本身也是高频成分,因此锐化在强调细节的同时,也可能把噪声一起放大。这就是为什么工程实践中,锐化通常不会孤立使用,而是常常与预平滑、阈值控制或边缘保护策略一起出现。

从这里已经可以看出,滤波器并不是“万能增强器”,它们只是针对特定频率特征做选择性强调或抑制。

不过,锐化的目标仍然比较宽泛。它强调的是“细节”和“结构”,却不一定明确告诉我们“哪里是边界”。如果我们想进一步提取轮廓、分界线和显著结构,就需要更直接地研究局部变化率本身,这就进入了边缘检测的问题。

5. 边缘检测:导数、梯度与结构变化

边缘是图像中最重要的结构之一。它通常对应物体轮廓、纹理边界或者亮度突变区域。从数学上讲,边缘就是灰度函数在某个方向上的变化率突然变大。

因此,边缘检测可以被看作锐化思想的进一步精确化:锐化只是在强调高频,边缘检测则是在进一步追问,哪些高频响应对应真正的结构边界,哪些只是噪声或局部纹理。也就是说,边缘检测不是单纯“把图像弄得更锐”,而是在局部变化中筛出最值得保留的结构信息。

5.1 一阶导数与梯度

在二维图像中,一阶导数由梯度向量表示:

梯度幅值定义为:

梯度方向定义为:

其中,梯度幅值反映变化强度,梯度方向反映变化最剧烈的方向。边缘检测并不是简单地“把轮廓画出来”,而是在估计图像局部变化率。

这里有一个很重要的认识:边缘本身不是一个像素值,而是一种变化关系。只有把图像看成一个二维标量场,“导数”“梯度”“方向”这些概念才真正有意义。如下图所示,横向梯度、纵向梯度和梯度幅值分别强调了局部变化的不同侧面,这也说明边缘检测关注的是变化率本身,而不是某个像素的绝对取值。

5.2 Sobel、Prewitt 与 Scharr

为了在离散图像中近似求导,我们需要使用差分卷积核。

Sobel 在 方向的典型核为:

它可以看作“求导 + 平滑”的组合,因此相比最简单的差分算子更稳健。Prewitt 与 Scharr 也属于同类思想,只是在权重设计上不同。

这些算子的核心不是记住核,而是理解:它们都在尝试估计某个方向上的局部灰度变化率。不同算子之间的差异,更多体现在“如何在求导的同时保持一定稳健性”,而不在于它们是否属于完全不同的思想体系。

5.3 二阶导数与零交叉

一阶导数强调灰度变化最明显的位置,而二阶导数则更关注变化趋势本身。拉普拉斯算子不仅能用于锐化,也可用于边缘检测,尤其是在分析零交叉(Zero Crossing)时很有意义。

不过二阶导数对噪声更加敏感,因此通常需要先做平滑,再做二阶导数分析。换句话说,二阶导数虽然能提供更锋利的结构判据,但代价是稳定性更差,对前置去噪的依赖也更强。

到这里为止,我们已经具备了构造边缘检测器的几个基本部件:先通过局部卷积估计变化率,再根据响应强弱和方向信息判断哪里可能存在结构边界。但如果直接把梯度图当作最终结果,往往会得到粗糙、断裂、对噪声敏感的边缘响应。问题并不在于导数思想错了,而在于“求出了变化”并不等于“得到了稳定边缘”。Canny 的意义正是在于,它把这些零散步骤组织成了一条更完整、更稳定的处理流程。

6. Canny:经典边缘检测的工程化方案

Canny 边缘检测之所以经典,不是因为它只是“效果好”,而是因为它把边缘检测从一个单一算子提升为一条完整的处理流水线。它并没有否定前面讲过的梯度和卷积,恰恰相反,它是在这些基本算子的基础上进一步解决“边缘太粗、太碎、太噪”的问题。

如果把这条流程拆开来看,Canny 实际上是在依次回答四个问题:如何先压低噪声、如何估计局部变化、如何把宽响应压成细边、以及如何在断裂与伪边之间做最后筛选。

第一步,高斯平滑。 直接对原图求梯度会对噪声非常敏感,因此 Canny 通常先使用高斯滤波做预处理。这里体现的是一个非常重要的工程原则:先降低局部随机波动,再做导数运算。如果不先去噪,后续梯度响应里混入的大量随机高频,很容易把噪声误判成边缘。

第二步,梯度计算。 在经过平滑的图像上,使用 Sobel 等算子分别求出 方向的梯度,再进一步计算梯度幅值和方向。到这一步,我们已经知道哪些位置“变化剧烈”,以及这种变化主要沿着哪个方向发生。换句话说,此时得到的还不是最终边缘,而是一张关于局部变化率的响应图。

第三步,非极大值抑制。 仅有梯度幅值还不够,因为真实边缘在响应图中往往会表现成一条较宽的亮带。非极大值抑制(Non-Maximum Suppression)的作用,就是沿着梯度方向只保留局部最大值,把宽边缘压细成近似单像素宽度。它解决的不是“有没有边缘”,而是“边缘能否被定位得足够准确”。

第四步,双阈值与滞后连接。 最后,通过高阈值与低阈值把像素划分为强边缘、弱边缘和非边缘。弱边缘是否保留,不仅看它自己的响应值,还要看它是否与强边缘连通。这个过程通常叫作滞后阈值连接(Hysteresis Thresholding)。这样做的好处是:既能避免单一阈值过于激进,也能提升边缘的连续性和鲁棒性。

从算法设计的角度看,Canny 的价值在于它把“去噪、求导、细化、连接”串成了一条逻辑完整的 pipeline。这种思路对后来的计算机视觉算法设计影响深远,也让我们更清楚地看到:边缘检测真正难的并不是“求一个导数”,而是如何从噪声和局部纹理中稳定地筛出结构边界。

如下图所示,Canny 并不是一个单步算子,而是一条从平滑、求梯度到边缘细化与连接的连续处理流程。

7. OpenCV 实现细节与常见陷阱

前面的内容偏数学与原理,这一节更偏工程实践。很多时候,理论上理解了卷积,但一旦真正写代码,问题反而不出在公式上,而出在边界、数据类型和显示方式上。

这类问题之所以值得单独拿出来讲,不是因为它们只是“实现细节”,而是因为它们会直接改变实验结论。很多看起来像算法效果差异的现象,最后其实都能追溯到 padding 策略、数据类型截断或者可视化方式不当。

7.1 常用 API

在 OpenCV 中,常见相关函数包括:

  • cv2.filter2D:通用卷积接口;
  • cv2.blur:均值滤波;
  • cv2.GaussianBlur:高斯滤波;
  • cv2.medianBlur:中值滤波;
  • cv2.Sobel:一阶梯度;
  • cv2.Laplacian:二阶导数;
  • cv2.Canny:完整边缘检测流程。

这说明 API 只是封装,底层思想仍然是局部模板与邻域之间的运算。真正重要的不是记住函数名,而是知道自己到底是在做局部平均、局部差分,还是一整条边缘提取流程。

下面这段代码给出一个最小示例,把平滑、梯度和边缘检测串在同一张图像上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cv2
import numpy as np

img = cv2.imread("sample.png", cv2.IMREAD_GRAYSCALE)

# 1) 通用卷积:手写一个 3x3 均值核
mean_kernel = np.ones((3, 3), dtype=np.float32) / 9.0
mean_img = cv2.filter2D(img, ddepth=-1, kernel=mean_kernel)

# 2) 高斯滤波:更自然的局部加权平均
gaussian_img = cv2.GaussianBlur(img, (5, 5), 1.0)

# 3) Sobel 梯度:分别估计 x / y 方向变化率
grad_x = cv2.Sobel(gaussian_img, cv2.CV_32F, 1, 0, ksize=3)
grad_y = cv2.Sobel(gaussian_img, cv2.CV_32F, 0, 1, ksize=3)

# 4) Canny:完整边缘检测流程
edges = cv2.Canny(gaussian_img, 80, 160)

7.2 Padding 与边界处理

卷积核滑到图像边缘时,窗口会越界,因此必须定义边界策略。常见做法包括零填充(Zero Padding)、复制边界(Replicate)和镜像填充(Reflect)。

不同的 padding 方式会直接影响边缘区域的响应结果。在做梯度和边缘检测时,这一点尤其值得注意,因为边界伪影很容易被误判为真实结构。

换句话说,padding 从来不是一个无关紧要的默认参数。它实际上在回答一个隐含问题:当卷积核滑到图像边缘、观测信息不完整时,你打算如何“虚构”图像之外的世界。不同的回答方式,会直接改变边界附近的卷积结果。

在 OpenCV 中,这个选择通常由 borderType 控制,例如:

1
2
3
4
5
6
7
import cv2

img = cv2.imread("sample.png", cv2.IMREAD_GRAYSCALE)

replicate = cv2.GaussianBlur(img, (5, 5), 1.0, borderType=cv2.BORDER_REPLICATE)
reflect = cv2.GaussianBlur(img, (5, 5), 1.0, borderType=cv2.BORDER_REFLECT)
constant = cv2.GaussianBlur(img, (5, 5), 1.0, borderType=cv2.BORDER_CONSTANT)

如果你在边缘附近看到了异常亮边或暗边,第一反应往往不该是“算法失效了”,而应该先检查是否是 padding 策略改变了边界响应。

7.3 数据类型与梯度显示

这是最容易踩坑的地方之一。许多图像默认是 uint8,而梯度计算往往会产生负数和超过 255 的值。如果直接在 uint8 上做差分,可能会发生截断或溢出,导致结果完全失真。

因此,在实际实现 Sobel 或 Laplacian 时,通常会先输出到 CV_16SCV_32F,再取绝对值或做归一化显示。否则你看到的并不是“真实梯度弱”,而可能只是数值表达已经把信息提前丢掉了。

下面是一个典型写法:

1
2
3
4
5
6
7
8
9
10
11
import cv2

img = cv2.imread("sample.png", cv2.IMREAD_GRAYSCALE)

# 先保留符号信息和更大的数值范围
grad_x_32f = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=3)
grad_y_32f = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=3)

# 再转成适合显示的 8-bit 图像
grad_x_vis = cv2.convertScaleAbs(grad_x_32f)
grad_y_vis = cv2.convertScaleAbs(grad_y_32f)

如果一开始就把输出限制在 uint8,负梯度会被直接截断,很多原本应该保留下来的方向信息也会随之丢失。

8. 从手工卷积核到卷积神经网络

到这里,我们已经可以看出传统图像处理和深度学习之间一条非常清晰的脉络。前面讨论的内容看似都属于经典图像处理,但如果把它们抽象到“局部模式分析”这个层面,就会发现它们与卷积神经网络之间并没有真正的断层。

在传统方法中,卷积核通常由人来设计。例如:

  • 均值核用于平滑;
  • Sobel 核用于梯度提取;
  • Laplacian 核用于高频增强;
  • 高斯核用于去噪。

这些卷积核的共同特点是:它们都在提取局部模式,只不过这些模式由人手工指定。

如下图所示,从手工卷积核到卷积神经网络之间并不存在断裂,真正延续下来的主线始终是“局部模式提取”这件事:传统方法由人定义卷积核,CNN 则把卷积核交给数据去学习。

而卷积神经网络的核心变化在于:卷积核不再由人工预先写死,而是通过数据驱动的方式自动学习出来。换句话说,CNN 并没有抛弃卷积这个思想,恰恰相反,它是把“局部感受野 + 卷积核滑动 + 特征提取”这套框架推广到了更大规模、更高维度、更可学习的场景中。

如果用一句话概括两者的差别,那么传统视觉方法回答的是“什么样的局部模式值得被强调”,而 CNN 回答的是“这些模式不必由人手工指定,而可以由数据自己学出来”。前者依赖经验设计核,后者依赖样本学习核,但它们共享的底层假设是一致的:图像中的关键信息存在于局部结构之中,而且这种局部结构可以通过滑动窗口式的卷积运算被提取出来。

这也是为什么理解传统卷积核,会让后续理解 CNN 的第一层、特征图和可学习滤波器变得自然得多。从这个角度回看,传统图像处理中的平滑、锐化与边缘检测,并不是一套过时的历史知识,而是理解卷积神经网络最自然、最扎实的起点。

9. 总结

本文从邻域操作的角度出发,系统讨论了图像卷积与滤波的核心思想。真正需要建立的,不是“我记住了多少个卷积核”,而是一个更统一的认识:平滑、锐化和边缘检测,本质上都是在同一个局部算子框架下,对局部变化做不同方式的保留、压制和筛选。

平滑通过局部平均抑制高频波动,因此能够去噪,但也必然牺牲细节;锐化通过重新强调高频残差恢复结构,因此能够增强纹理,却也容易放大噪声;边缘检测则比锐化更进一步,它不是泛泛地强调“变化”,而是在变化中寻找真正稳定、可解释的结构边界。Canny 的价值,则在于把这件事从单个算子推进成一条可执行、可控制的处理流程。

如果把全文再压缩成一句话,那么卷积最重要的意义就在于:它让“局部结构”第一次获得了统一的数学表达。也正因为有了这套表达,我们才既能设计均值核、高斯核、Sobel 核这样的传统算子,也能进一步走向由数据自动学习卷积核的深度学习模型。

因此,这篇文章真正搭起来的桥梁,并不是“传统视觉”和“深度学习”两个标签之间的桥,而是“手工定义局部模式”和“数据学习局部模式”之间的桥。下一篇文章中,我们就可以顺着这条线继续往前走,进一步讨论:为什么卷积神经网络天然适合处理图像,以及“可学习卷积核”相较于手工特征设计到底强在哪里。