机器视觉基础2026-7-20更新

计算机视觉理伦其实不难,主要是大部份文档都 是针对你是专业计算机的人,所以术语太多


图像处理第一课:不用任何公式推导,用 25 个格子讲透滤波、边缘检测与傅里叶变换,来快速理解机器视觉

摘要:均值滤波、高斯滤波、中值滤波、边缘检测、二阶导数、傅里叶变换……这些名词劝退了无数初学者。本文不推导任何公式,只用加法、除法、排队三件小学数学的事,配合一张张手算示例,把图像处理最核心的一组概念从头到尾讲明白。全文约 4000 字,阅读需要 15 分钟。

写在前面

打开任何一门计算机视觉或数字图像处理的课程,前几章大概率会出现这些内容:滤波、卷积、一阶导数、二阶导数、傅里叶变换。教科书习惯从定义和公式讲起,而初学者真正需要的往往是先建立直觉:这些东西到底在对图片做什么操作?
这篇文章换一个讲法。我们把图片还原成它最本质的样子——一张数字表格,然后拿一个 3×3 的小窗口在表格上滑来滑去,把每一种操作都亲手算一遍。算完之后你会发现:所谓滤波、边缘检测、频域处理,全都是对格子里的数字做加减乘除。
这篇文章适合谁:
  • 刚接触机器视觉 / 计算机视觉,被术语吓住的同学;
  • 会用 OpenCV 调函数,但没想过函数内部在干嘛的开发者;
  • 任何对"美颜、锐化、降噪是怎么实现的"感到好奇的人。
阅读前提:会加法、会除法、会把几个数从小到大排队。

0. 先建立一个画面:图片就是一张数字表格

一张黑白图片,在计算机眼里就是一张表格。每个格子叫一个像素,填一个 0~255 的整数:0 是纯黑,255 是纯白,中间是深浅不同的灰。
本文全程使用同一张 5×5 的"实验照片":一片灰色(10),正中央有一个很亮的点(100)。你可以把它想象成脸上的一颗痘,或者照片上的一个噪点:
Text
 
10  10  10  10  10
10  10  10  10  10
10  10 100  10  10
10  10  10  10  10
10  10  10  10  10
工具也只有一个:一个 3×3 的小窗口(教材里叫它"模板"或"卷积核")。把窗口扣在表格上,中心对准哪个格子,就处理哪个格子。下面所有操作,区别只在于"窗口里这 9 个数按什么规则合成一个新值"。

1. 均值滤波:最朴素的"磨皮"

规则:把窗口罩住的 9 个数加起来除以 9,得数写进一张空白的"结果图"。
窗口从左上角开始,中心对准第 2 行第 2 列:
Text
 
罩住的 9 个数:
10  10  10
10  10  10        ← 窗口右下角罩住了那个 100
10  10 [100]

10+10+10+10+10+10+10+10+100 = 180
180 ÷ 9 = 20    →  结果图第 2 行第 2 列写上 20
窗口向右挪一格,中心对准第 2 行第 3 列:
Text
 
10  10  10
10  10  10        ← 100 仍在窗口里(正下方)
10 [100] 10

还是 8 个 10 + 1 个 100 = 180  →  180 ÷ 9 = 20
继续挪动会发现:9 个可处理的位置,每个窗口都恰好罩住那个 100,所以每个位置算出来全是 20。全部滑完,结果图如下:
Text
 
·   ·   ·   ·   ·        (最外一圈窗口伸不出去,暂不处理)
·  20  20  20   ·
·  20  20  20   ·
·  20  20  20   ·
·   ·   ·   ·   ·
结果说明了什么: 原本只有 1 格的亮点 100,变成了一片 3×3 的亮斑 20。噪点没有被消灭,而是被"摊开、稀释"了。这就是磨皮的最基本原理——把脸上突兀的点和周围皮肤平均掉;同时也是均值滤波让整张图变模糊的原因。
为什么除以 9?因为 9 个数取平均。如果不除,整张图会整体变亮 9 倍,画面就失真了。

2. 中值滤波:排队取中间,噪点克星

规则:把窗口里的 9 个数从小到大排队,站正中间(第 5 个)的数当新值。
还是第一个窗口,9 个数排队:
Text
 
10  10  10  10 [10] 10  10  10  100
                ↑ 第 5 个 = 10
那个刺眼的 100 去哪了?它永远排在队尾,永远轮不到站中间。 每个窗口都是"8 个 10 + 1 个 100",第 5 个永远是 10。滑完的结果图:
Text
 
·   ·   ·   ·   ·
·  10  10  10   ·
·  10  10  10   ·      ← 噪点彻底消失,一点痕迹都没留
·  10  10  10   ·
·   ·   ·   ·   ·
对比前两关: 均值是"稀释"(100 被摊成一片 20),中值是"扔掉"(100 直接消失)。所以面对"椒盐噪声"(照片上撒满纯黑纯白点,像撒了盐和胡椒)这类极端噪点,中值滤波是公认的克星——极端值在排队里永远站两头,影响不到中间。

3. 高斯滤波:偏心版平均,美颜相机的选择

均值滤波对 9 个数一视同仁,下手太狠,容易把整张图糊掉。高斯滤波只做了一点改进:还是求平均,但每个数先乘一个"话语权"——离窗口中心越近,权重越大。 权重表如下("高斯"只是这个权重分配方式的名字):
Text
 
1  2  1
2  4  2        全部加起来 = 16
1  2  1
把窗口中心对准噪点(第 3 行第 3 列),完整算一遍:
Text
 
10×1  10×2  10×1
10×2 100×4  10×2
10×1  10×2  10×1

= 10 + 20 + 10 + 20 + 400 + 20 + 10 + 20 + 10
= 520
520 ÷ 16 = 32.5
除以 16 是因为权重总和是 16,道理和上一节除以 9 完全相同:保持整体亮度不变。
三种平滑方式同台对比,同一个噪点 100:
表格
 
 
规则 100 变成了 一句话点评
均值滤波 20 一视同仁,稀释最狠,容易把整图糊掉
高斯滤波 32.5 偏心平均,中心保留得多,糊得更自然
中值滤波 10 排队扔掉,噪点克星
高斯滤波还有个"好脾气":对平坦区域完全不动手。 验证一下——若窗口里全是 10:加权和 = 10×16 = 160,÷16 = 10,原样奉还。这就是为什么美颜算法普遍选它:好皮肤一点不碰,只对突兀的地方温柔下手。

4. 最大值 / 最小值滤波:一句话就能讲完

同一张图,规则换成"取窗口里最大(或最小)的数":
  • 最大值滤波:每个窗口都罩住了 100,于是每格都变成 100 → 亮点从 1 格"长大"成 3×3;
  • 最小值滤波:每个窗口里都有 10,于是全变 10 → 亮点被吃掉。
实际用途:想去除黑色小点用最大值滤波,想去除白色小点用最小值滤波。就这么简单。

5. 边缘检测(一阶差分):用减法找"台阶"

先回答一个根本问题:边缘是什么? 边缘就是颜色突变的地方。黑桌子边上突然变成白墙,那条分界线就是边缘。
看一行像素(一条竖边的横截面):
Text
 
20   20   20 │ 90   90   90
              ↑ 这里从 20 突然跳到 90,就是边缘
规则(一阶差分):每个格子,用右边的数减左边的数。 逐格计算:
Text
 
20 − 20 = 0
20 − 20 = 0
90 − 20 = 70    ← 冒出个大数!
90 − 90 = 0
90 − 90 = 0

差分结果:  0    0    70    0    0
规律一目了然:平坦的地方减出来全是 0,只有突变处冒出大数。差分大的地方就是边缘。 教材里说的"一阶导数""梯度",本质就是这个减法,没有更多内容了。
打个比方:数字是地面高度,差分是你每一步抬脚的高度。平地上每步抬 0,走到台阶前一脚要抬 70——抬脚猛地变高,说明到台阶了。

一个巨大的陷阱:噪点会冒充边缘

如果这行像素里混进一个噪点:
Text
 
原数:  20   20  100   20   20   20
差分:       0    80   −80    0    0
                  ↑     ↑
              两个"边缘",全是假的!
一个噪点让差分报警了两次。怎么办?先做中值滤波:窗口滑一遍——[20,20,100] 排队取中间得 20;[20,100,20] 得 20;[100,20,20] 也得 20——整行变回 20,差分全部归零,假警报解除。
这就是几乎所有边缘检测流程的第一步都是"先平滑(高斯/中值),再检测"的原因:先磨皮,再找边,否则噪点会冒充边缘。 大名鼎鼎的 Canny 算子那一整套流程(灰度化 → 高斯模糊 → 计算差分 → 筛选边缘),拆开了看,就是本文第 3 节和本节内容的组合。

6. 二阶差分:把边缘精确到一个点

还是那条干净的台阶,把刚才的差分结果再做一遍同样的减法:
Text
 
原数:    20   20   20   90   90   90
一阶差分:      0    0   70    0    0
二阶差分:         0   70  −70    0
                   (70−0=70,0−70=−70,0−0=0)
盯住那个 +70 紧跟着 −70:数值从正跳到负,中间跨过了 0。这个"过零点"的位置,精确地钉在台阶边缘上——这就是教材里"二阶导数过零点对应边缘"的含义。
继续用走路的比方:
  • 数字 = 你的位置;
  • 一阶差分 = 你每步抬多高(速度);
  • 二阶差分 = 抬脚高度的变化(加速度)。
平路上走得好好的,突然要抬 70,下一刻又不用抬了——"抬脚动作猛变"这件事本身,就暴露了台阶的位置。
为什么要费劲做二阶?一阶只能告诉你"这一段很陡"(一片区域),二阶把边缘压缩成一个精确的点,定位更准。代价是它对噪点更敏感,所以工程上总是先平滑再使用。

7. 锐化:原图 +(原图 − 模糊图)

锐化的公式只有一行:锐化图 = 原图 +(原图 − 模糊图)。分三步完整推一遍。
第一步:模糊。 用"3 格滑动平均"处理那行台阶:
Text
 
第 2 格:(20+20+20) ÷ 3 = 20
第 3 格:(20+20+90) ÷ 3 = 43.3
第 4 格:(20+90+90) ÷ 3 = 66.7
第 5 格:90

模糊后:20   20   43.3   66.7   90   90
第二步:相减,得到"细节"。 原图 − 模糊图:
Text
 
0    0    20−43.3 = −23.3    90−66.7 = +23.3    0    0
注意看:平坦的地方减出来是 0,减出来的正好是边缘处的"细节"——暗侧欠了 23.3,亮侧多出 23.3。
第三步:把细节加回原图:
Text
 
原图 + 细节:20   20   −3.3   113.3   90   90
对比边缘两侧:暗的从 20 被压到 −3.3(更暗),亮的从 90 被抬到 113.3(更亮)。落差从 70 拉大到了 116.6,边缘两边反差更猛,人眼就觉得"变清晰了"。
所以锐化并没有创造任何新细节,它只是夸大边缘处的反差——先找出边缘,再给它加粗加亮。这也是"原图 − 模糊图 = 细节"这个小等式值得记住的原因。

8. 傅里叶变换:把图像拆成一堆"波浪条纹"

这是大多数课程里最唬人的一章,其实思想非常美。先看一串只有 4 个数的"信号":
Text
 
55   45   55   45
它可以拆成两种"成分"相加:
Text
 
成分 A(平底):50   50   50   50
成分 B(快抖):+5   −5   +5   −5
─────────────────────────────
加起来验算:  55   45   55   45   ✓
  • 成分 A 变化慢(几乎不变),叫低频——对应图片里大片平缓的区域:天空、墙面、皮肤;
  • 成分 B 每一格就翻转一次,变化极快,叫高频——对应图片里的边缘、毛发,以及噪点。
傅里叶变换就是一台"拆方子的机器":你扔给它 55 45 55 45,它告诉你"平底放了 50,快抖放了 5"。(这个例子的拆法就是真正的傅里叶分解,一点没有缩水,只是真实图片的成分更多而已。)

拆开之后能干什么?像调均衡器一样调图片

  • 磨皮:把快抖成分调成 0 → 50 50 50 50 → 画面变平滑了;
  • 锐化:把快抖成分乘 3 → 65 35 65 35 → 起伏从 ±5 变成 ±15,对比更强烈。
这和第 1、7 节在空间域做的事殊途同归,但在频率域里,一切变成了"拧旋钮"。

频谱图怎么读?fftshift 是干嘛的?

在真实图片上,"波浪"变成了有方向的条纹(横条纹、竖条纹、斜条纹),频谱图上的每个点记录"这种条纹放了多少":
  • 图中心 = 慢条纹(低频):大块平缓的画面;
  • 图四周 = 快条纹(高频):细节、边缘、噪点。
这就是为什么绝大多数照片的频谱图都是中心最亮——任何照片都是平缓区域占大头。而代码里常见的 fftshift,只是把低频成分从四个角搬到图的正中央,纯粹为了看着舒服,数据本身一个没变。
最妙的应用:如果照片上带有规律的网纹噪点,它在频谱图上就是一个孤零零的亮点——直接把那个亮点擦掉,再做逆变换拼回去,网纹就消失了。这种精准操作在原始图片上是几乎不可能完成的。

一张表总结全文

表格
 
 
课程名词 它真正做的事
滤波 / 模板 / 卷积 3×3 小窗口在表格上滑动,给中心格子换新值
均值滤波 9 个数相加 ÷ 9 → 磨皮,噪点被摊开
高斯滤波 乘权重再 ÷ 16 → 温柔版磨皮,平坦区域不受影响
中值滤波 排队取第 5 个 → 噪点直接扔掉
一阶导数 / 梯度 右边的数减左边的数 → 差值大的地方是边缘
二阶导数 / 过零点 对差分再减一次 → 从正跳到负的位置精确是边缘
锐化 原图 +(原图 − 模糊图)→ 夸大边缘反差
傅里叶变换 把图像拆成"慢条纹 + 快条纹"的配方
fftshift 把慢条纹挪到频谱图正中央,纯为好看

动手试试:20 行代码验证全文

如果你装了 Python 和 NumPy,下面这段代码会把本文第 1、2、3 节的手算结果原样打印出来:
Python
 
import numpy as np
from scipy.ndimage import median_filter, gaussian_filter

# 本文的 5×5 实验图
img = np.full((5, 5), 10.0)
img[2, 2] = 100

# 均值滤波(3×3)
mean_out = np.zeros_like(img)
for i in range(1, 4):
    for j in range(1, 4):
        mean_out[i, j] = img[i-1:i+2, j-1:j+2].mean()
print("均值滤波结果:\n", mean_out[1:4, 1:4])   # 全是 20

# 高斯滤波(近似本文的 1-2-1 权重)
w = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]])
gauss = (img[1:4, 1:4] * w).sum() / 16
print("高斯滤波(中心格):", gauss)            # 32.5

# 中值滤波
med = median_filter(img, size=3, mode='constant')
print("中值滤波结果:\n", med[1:4, 1:4])        # 全是 10
跑一遍,对照正文的手算结果,我们就过多成就完成了一次"从直觉到代码"的闭环。

结语

图像处理并没有想象中那么高深:滤波是给格子里的数换值,边缘检测是相邻两数相减,锐化是把减出来的细节加回去,傅里叶变换是把图像拆成条纹的配方。建立了这层直觉之后,再回头去看教科书上的公式,你会发现每个符号都对应着你亲手算过的某一步。
后续如果想继续深入,建议的顺序是:卷积的完整定义 → Sobel / Prewitt 算子 → 拉普拉斯算子 → Canny 边缘检测完整流程 → 二维离散傅里叶变换的数学表达。每一步都只是在本文内容上添一小块砖。
如果这篇文章帮到了你,欢迎在评论区留下你手算时卡住的地方,我们一起把它拆开。
posted @ 2026-08-22 20:38  鬼门元歌  阅读(22)  评论(0)    收藏  举报