LWLeon.
LEON / CV / EN + ZH

计算机视觉:从像素到感知

十六课从像素与简单 Python 列表出发,逐步进入识别、检测、跟踪、OCR 和可靠图像系统。每课都有故事、逐步计算、可运行代码、已解答问题及两个练习情境。

16 / 故事 · 概念 · 应用 · 公式

LEON / LEARNING MAP

先看学习路线

按箭头顺序学习。点击一个阶段进入第一节;完整知识框架列出了每一个主题。

  1. 01

    1 · 基础:看懂数字

    4 个主题 ↗
  2. 02

    2 · 核心:比较与定位

    4 个主题 ↗
  3. 03

    3 · 应用:测量、跟踪与读字

    4 个主题 ↗
  4. 04

    4 · 进阶:组合与验证

    4 个主题 ↗
LEON / FROM IDEAS TO IMPLEMENTATION

继续深入:完整案例

先读对应章节,再跟着故事预测输出、运行完整程序、核对解释。每个案例都在原有概念上增加一个实际问题。

  1. 01

    把检测框还原到原始图像

    在整个图像流程中传递坐标变换信息。

    故事 · 代码 · 推理
    先读对应章节: 推理流程:检查完整处理过程
  2. 02

    排除忽略像素,计算分割 IoU

    从计数推导 IoU,并说明平均规则。

    故事 · 代码 · 推理
    先读对应章节: 从一个矩形走向真正属于对象的像素
阅读字号
刚接触 Python 或这些数学知识?从这里开始 ↗下载全部 Python 示例 ↓
第一次运行 Python?按这四步操作
  1. 安装 Python 3.12 或更新版本。Python 官方下载页 ↗
  2. 点击示例旁的“下载 Python 代码”,得到一个 .py 文件。它是包含程序文字的文件。
  3. 打开终端,在下载目录中运行 python 文件名.py,并把“文件名”换成实际名称。在部分系统上,命令是 python3。终端是输入命令并查看文字结果的窗口。
  4. 把运行结果与页面的“预期输出”逐项比较。先改一个输入,再观察变化。

部分示例使用 NumPy,这是处理数字和数组的工具包。若代码中出现 import numpy as np,请先用同一 Python 安装一次:python -m pip install numpy。这行 import 加载 NumPy,并把它简称为 np。NumPy 官方安装说明 ↗

示例无需下载数据集或模型权重。安装 Python 和所需工具包后即可离线运行。页面上的代码文字可以复制;运行结果会显示在你的终端中。

把知识连接起来

这是一份结合 OpenCV、PyTorch 官方资料、Stanford 教学与基础视觉研究编写的原创概念指南。例子使用自拟数据;它是一条连贯的入门路径,并非某门课程的复制品,也不是生产部署手册。

开始前需要了解

  • 高中算术:加法、乘法、分数与坐标
  • 若不熟悉列表、循环与索引,先阅读共享 Python 入门
  • 不预设线性代数、微积分或深度学习框架知识

你将学到什么

  • 追踪图像的形状、尺度与坐标变化
  • 根据任务选择分类、检测或分割
  • 用明确维度理解卷积与图像块模型
  • 避免相关图像泄漏,可靠地评估效果

知识框架

点击一个概念,从故事走向原理与数学。

CV / 01Leon

像素是带有约定的测量值

入门

01 / 从一个完整故事开始

Maya 帮社区档案馆扫描手绘戏剧海报。一天早晨,新目录里的蓝戏服变成红色。文件能正常打开,团队先怀疑屏幕。Maya 改为追踪一块已知蓝色区域,逐步查看处理过程。

她发现读取程序先存蓝色,下一步却把第一个值当红色。她记录颜色顺序、图像尺寸与数值范围,修正颜色转换,戏服恢复蓝色。此后团队先检查这些简单约定,再让模型识别。若数值含义不一致,相同数字就可能描述不同颜色。

02 / 理解核心概念

数字图像是由像素组成的测量网格。灰度图在每个行列位置存一个数。彩色图通常存三个数:红、绿、蓝,简称 RGB。通道就是其中一种颜色的数据。数组是有组织的数值表;张量是具有一个或多个轴的数组。

形状 (4, 6, 3) 表示四行、六列、三个通道。Python 从零计数,所以 image[0, 1] 取第一行第二列。有些工具先存蓝色,即 BGR。移动通道轴并不会交换颜色的含义。

缩放把字节数值除以 255,使它落在 0 到 1。标准化再减去指定中心,除以正的离散程度。结果为负表示低于中心,并不是不可能的颜色。必须遵循模型训练时使用的输入规则。

先认识这些词

像素
图像网格中的一个位置。
通道
每个像素的一种测量类型,例如红色。
形状
数组沿每个轴的大小。

03 / 如何运用这个概念

搭建视觉流程时,可以先使用一张颜色与位置都已知的小型诊断图片。每次转换后,检查形状、类型、最小值、最大值,以及某个指定像素。采用预训练权重要求的归一化统计量;若自己设计流程,就只用训练数据估计。保留未修改的输入,以免可视化时误把标准化数值当成普通颜色。

04 / 官方教程与研究中的应用

OpenCV 的 Basic Operations on Images 官方教程直接展示像素访问、数组属性与通道操作,而不是把图像当作不透明文件。TorchVision 的变换文档介绍了图像与结构化目标的处理。两者共同体现档案馆故事中的实际问题:表示方式本身就是计算的一部分,在模型推理开始之前必须保持一致。

05 / 公式与逐项解析

z = (v / 255 − μ) / σ
HWC → CHW: (H, W, C) → (C, H, W)

v 是取值为 0 到 255 的八位通道数值。除以 255 后,它被映射到单位区间;μ 和正数 σ 是在相同尺度下使用的均值与标准差。z 是标准化后的数值,可以为负。H、W、C 分别表示高度、宽度与通道数。HWC 和 CHW 只说明轴的顺序,并不说明哪个通道代表红、绿或蓝。公式假设输入确实使用上述字节尺度;已经位于单位区间的浮点图像不应再次除以 255。

06 / 一步步算出结果

设自拟通道值 v = 153,μ = 0.5,σ = 0.25。先缩放:153 / 255 = 0.6。再中心化:0.6 − 0.5 = 0.1。最后除以标准差,得到 z = 0.4。

如果输入的 0.6 已经完成缩放,却再次除以 255,标准化结果将约为 −1.9906。形状需要单独检查:一张 4×6 的 RGB 图像形状为 (4, 6, 3),把通道轴移到前面后得到 (3, 4, 6),两者都包含 72 个数值。这两种操作本身都不会把 BGR 变成 RGB。只有同时检查数值和通道含义,才能判断输入是否正确。

一步步想清楚

  1. 为什么尺寸正确的图像仍可能显示错误?

    三个通道的含义可能弄错。把 BGR 当 RGB 会交换红蓝。

  2. v = 153 时的中间步骤是什么?

    153/255 = 0.6。减 0.5 得 0.1,然后才除以 0.25。

  3. 输入已经是 0.6 时还要再除吗?

    只有它仍属于字节尺度才可以。如果已经缩放到 0–1,再除会破坏约定范围。

运行一个完整示例 · 01

追踪一个通道

用列表表示一个已知蓝色像素,不需要图像文件或训练模型。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

bgr = [255, 0, 0]
rgb = list(reversed(bgr))
value = 153
scaled = value / 255
standardized = (scaled - 0.5) / 0.25
print("RGB:", rgb)
print(f"scaled={scaled:.1f}; standardized={standardized:.1f}")
print("stored numbers:", 4 * 6 * 3)
在本地运行
python pixels-contracts-pixel-scale.py

预期输出

RGB: [0, 0, 255]
scaled=0.6; standardized=0.4
stored numbers: 72

逐步读懂代码

第一行按蓝、绿、红存储。reversed 改变顺序,list 保存结果。接着三行把 153 缩放,减 0.5,再除以 0.25。f 字符串保留一位小数。最后的乘法得到 72 个通道数值,不是 72 个像素。这是在检查数据约定,并没有识别物体。

这些知识可以用在哪里

已知颜色检查

把 bgr 改成 [0, 0, 255]。RGB 应变成红色 [255, 0, 0]。解释为何元素数仍是三。

不同图像尺寸

2×5 的 RGB 图有 10 个像素、30 个通道数值。修改最后的乘法,分别检查两个计数。

类比的边界与注意事项. 包裹标签的类比解释的是接口一致性,并不能代替色彩科学。相机响应、伽马编码与光照也会影响像素。形状和范围正确,不能证明不同拍摄条件下的图像对应相同的物理测量;它只是排除了一类本可避免的错误。
记住这一点

在让模型学习像素之前,先明确每个接口的轴顺序、颜色含义、数值尺度与归一化约定。

↑ 返回知识框架
CV / 02Leon

颜色:先弄清通道

入门

01 / 从一个完整故事开始

学校园艺社拍摄彩色植物标牌。小梅先用高亮度找红牌,结果白标签也通过,阴影里的红牌却消失了。她分别查看三个通道。红牌通常红色分量比绿色、蓝色多,白纸则三个都高。

她在几个已知颜色上测试小规则,立刻看出差别。她还留下阴影样本供后续检查。现在社团能解释规则选中了什么。虽然仍要测试光线变化,但已经不再把亮度与颜色混为一谈。

02 / 理解核心概念

RGB 为每个像素保存红、绿、蓝分量。三个大数可以表示明亮白色;仅红分量很大可以表示红色。灰度图把颜色压成一个数,因此不同颜色可能变得无法区分。

简单颜色规则可以直接比较通道。另一种色彩空间 HSV 用色相(颜色家族)、饱和度(颜色强弱)、明度(归一化 RGB 中最大的通道)表示颜色。HSV 可以让颜色范围更好描述,但不能消除光照影响。红色色相范围可能跨越刻度首尾,不同库的刻度也不同。

先认识这些词

RGB
使用红、绿、蓝通道的颜色表示。
色相
红或蓝等颜色家族。
饱和度
相对于灰色,像素颜色的强烈程度。

03 / 如何运用这个概念

颜色筛选可选择标牌或为后续检查准备区域。信任掩码前要检查已知颜色、通道顺序与光照。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

red_margin = R − max(G, B)
select_red = red_margin > threshold

R、G、B 是同一像素的红、绿、蓝值,使用相同尺度。max 选择绿、蓝中较大者。red_margin 衡量红色超过较强竞争通道多少。threshold 是要求的最小差距,是规则设置,不是学习得到的确定性。

06 / 一步步算出结果

取阈值 50。红牌 (200,40,30) 的差为 200−40=160,通过。白纸 (220,220,220) 的差为 0,不通过。

暗红牌 (60,20,20) 的差为 40,也不通过。最后一项揭示局限:固定绝对差阈值可能漏掉阴影中的相同颜色。

一步步想清楚

  1. 为什么排除明亮白色?

    三个通道同样高,红色没有优势。

  2. 标牌差距怎样得到 160?

    max(40,30)=40,再算 200−40=160。

  3. 把阈值改成 30 能找回阴影红牌吗?

    可以,差距 40 会通过,但也可能放进更多非目标颜色。

运行一个完整示例 · 01

测试三种已知颜色

用普通 RGB 元组和透明规则。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

pixels = {"marker": (200, 40, 30), "paper": (220, 220, 220), "shade": (60, 20, 20)}
for name, (red, green, blue) in pixels.items():
    margin = red - max(green, blue)
    print(name, "margin:", margin, "selected:", margin > 50)
在本地运行
python colour-channels-red-channel-rule.py

预期输出

marker margin: 160 selected: True
paper margin: 0 selected: False
shade margin: 40 selected: False

逐步读懂代码

字典把名称对应到三个通道值。items 返回名称与数值对,解包给通道命名。max 选较强竞争通道,相减得到差距,>50 返回 True 或 False。这是手设颜色筛选器,不是训练好的检测器,也未实现完整 HSV 转换。

这些知识可以用在哪里

蓝标牌

交换红蓝角色,创建蓝色差距规则。测试 (30,40,200),蓝色差距是 160。

干扰背景

添加一个红墙像素。颜色规则无法把它与标牌区分,还需要形状或位置检查。

类比的边界与注意事项. 通道规则不等于物体识别。红墙和红牌一样可能通过,相机设置与阴影都会改变数值。
记住这一点

颜色筛选可选择标牌或为后续检查准备区域。信任掩码前要检查已知颜色、通道顺序与光照。

↑ 返回知识框架
CV / 03Leon

局部滤波提出局部问题

入门

01 / 从一个完整故事开始

Jon 在清理扫描星图。灰尘产生亮点,淡铅笔线标出星座。他先用邻近像素的均值替换每个像素。灰尘变弱,有用铅笔线却也变淡。

Jon 比较已知线条与空白区域,不再仅凭是否平滑判断。他试更小的平均窗口,并将原图放在结果旁。只有所选操作保留了需要的标记,星图才真正改善。他学会同时问局部计算删掉了什么、显示了什么。少见亮点既可能是噪声,也可能是重要证据。

02 / 理解核心概念

滤波器根据附近一小组像素替换当前像素。求均值时先把数值相加,再除以个数。这会减弱孤立亮点,也可能削弱真正的细线。

核是一张小型权重表。把每个邻近像素乘对应权重,再相加。3×3 均值核给九个像素各分配 1/9 的权重。检测变化的核可能使用负权重,因此要保留有符号数。

图像边缘会缺少部分邻居。填充用零或边缘像素副本等规则补上,必须明确规则。这里的公式移动权重时不翻转,称为互相关。严格的数学卷积先翻转核。对称均值核在两种方式下结果相同。

先认识这些词

核
小型权重表或邻域位置表。
填充
在原图边界外补充的数值。
加权和
每个数乘权重后再相加。

03 / 如何运用这个概念

选择滤波器前,先说明目标:抑制孤立噪声、估计梯度,还是为后续任务准备输入。既要查看平坦区域,也要查看代表性边界,记录填充规则;若负响应有意义,应保留浮点输出。要比较后续识别效果或标注质量,而不是把视觉上的平滑自动等同于改进。

04 / 官方教程与研究中的应用

OpenCV 的 Smoothing Images 教程展示多种邻域滤波方法,而 filter2D 接口参考明确了该操作使用的互相关约定。两类来源可以互补:教程说明流程,接口参考澄清实现细节。学习型卷积网络会从数据中拟合许多核,扩展了共享局部运算的思想;但训练过程与非线性层使它远不止固定平滑滤波器。

05 / 公式与逐项解析

filtered_value = pixel₁×weight₁ + pixel₂×weight₂ + …
3×3 mean = (sum of nine pixel values) / 9

每个像素数来自指定邻域中的一个位置,对应权重决定贡献。省略号表示继续处理所有位置。3×3 均值中,每个权重为 1/9,九个权重和为 1。公式移动权重时不翻转,称为互相关。边界需要说明怎样补缺失像素。不同操作可分别过滤通道,也可组合通道。

06 / 一步步算出结果

考虑一个 3×3 图像块:其中八个值为 10,中心值为 19。均值滤波后的中心值为 (8 × 10 + 19) / 9 = 11。亮点被减弱了,但并非没有代价:真正孤立的星点也会变暗。另看一维边缘探测,把互相关核 [−1, 0, 1] 作用于 [2, 5, 8],响应为 −2 + 0 + 8 = 6。

若输入顺序反转,结果就是 −6。粗心地使用无符号输出,可能掩盖负响应。这两项计算回答的是不同问题,应根据需要保留的信息选择和评估它们。

一步步想清楚

  1. 为什么亮中心变为 11 而不是 10?

    19 仍占总和的九分之一。求均值会削弱它,并未丢弃它。

  2. 边缘滤波器怎样得到 6?

    计算 2×−1、5×0、8×1,再相加 −2+0+8。

  3. 反转这一行会怎样?

    [8, 5, 2] 得到 −6。符号说明向哪个方向变亮。

运行一个完整示例 · 01

求小块均值

观察亮点与有正负号的边缘响应。这是手写滤波器,不是学习模型。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

patch = [10, 10, 10, 10, 19, 10, 10, 10, 10]
mean = sum(patch) / len(patch)
row = [2, 5, 8]
weights = [-1, 0, 1]
response = sum(value * weight for value, weight in zip(row, weights))
print(f"mean={mean:.1f}")
print("edge response:", response)
在本地运行
python filters-neighborhoods-local-filter.py

预期输出

mean=11.0
edge response: 6

逐步读懂代码

sum 累加九个数,len 计数,得到 99/9 = 11。zip 把每个像素和权重配对。生成式逐对相乘,sum 将 −2、0、8 加起来得到 6。例子只计算一个邻居齐全的位置,因此暂时不涉及填充。

这些知识可以用在哪里

保留细标记

把中央的 19 看作有用细线的一部分。均值仍为 11。在丢掉需要的证据前,先决定是否适合平滑。

恒定区域

把 row 改为 [5, 5, 5]。因为 −5+0+5=0,边缘响应为零。有亮度不等于亮度有变化。

类比的边界与注意事项. 邻近证人也可能犯同一种错误。平均无法恢复原本就没有被测到的细节,过强平滑还会损伤边界。边缘响应表示局部强度变化,并不等于语义对象边界;阴影、纹理和传感器噪声都可能产生响应。
记住这一点

滤波器是用权重表达的局部问题;既要检查它去掉了什么,也要检查它保留了什么。

↑ 返回知识框架
CV / 04Leon

边缘与掩码:先找变化,再清理

入门

01 / 从一个完整故事开始

学生为手工项目扫描模板。阈值化后,图里既有字母,也有细小白色灰尘点。小俊先检查亮度快速变化处,找出可能的边界,再查看是或否的掩码。他选用开运算:先缩小选中区域,再扩展剩余区域。

单个噪点消失,较宽笔画保留。他把结果与原扫描比较后才保存规则。极细笔画也可能消失,因此他不会把所有被删像素都叫噪声。只有同时看清对有用和无用标记的影响,操作才真正有用。

02 / 理解核心概念

边缘是明显的局部变化,但不一定是物体轮廓。相减附近亮度可测量变化。阈值化再把数值变成选中或未选中的二值掩码。

形态学使用小邻域改变掩码形状。腐蚀只在所需邻居全部被选中时保留当前位置;膨胀在任一所需邻居被选中时保留。开运算先腐蚀再膨胀,闭运算顺序相反。这些规则处理形状,并未学习物体含义。

先认识这些词

边缘
邻近图像数值明显变化的位置。
腐蚀
只保留所需邻居全部被选中的位置。
膨胀
只要所需邻居有一个被选中,就选中该位置。

03 / 如何运用这个概念

可用这些操作清理扫描文字掩码或检查简单形状。每次清理前后都应比较细小但有用的细节。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

change = right_value − left_value
erode[i] = min(mask[i−1], mask[i], mask[i+1])
dilate[i] = max(mask[i−1], mask[i], mask[i+1])

i 是一维行中的位置。掩码值为 0 或 1。三个值全为 1 时 min 才为 1,至少一个为 1 时 max 就为 1。行外用零填充。二维实现则在每个像素周围使用指定邻域。

06 / 一步步算出结果

对 [0,1,0,0,1,1,1,0] 腐蚀得到 [0,0,0,0,0,1,0,0]。只有长度为三的连续段中心及左右邻居全被选中。再膨胀得到 [0,0,0,0,1,1,1,0]。孤立的位置 1 被删除,较宽连续段恢复。

一步步想清楚

  1. 为什么孤立的 1 消失?

    它的邻域含零,因此腐蚀取 min=0。之后也没有幸存邻居将它恢复。

  2. 宽度为三的连续段腐蚀后剩下什么?

    仅中心位置,因为 [1,1,1] 的最小值为 1。

  3. 真实的一像素细线会保留吗?

    不会。该操作不知道这条细线有用。

运行一个完整示例 · 01

移除孤立噪点

使用零填充与三个位置的邻域。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

mask = [0, 1, 0, 0, 1, 1, 1, 0]
def local_rule(values, operation):
    padded = [0] + values + [0]
    return [operation(padded[i:i + 3]) for i in range(len(values))]
eroded = local_rule(mask, min)
opened = local_rule(eroded, max)
print("eroded:", eroded)
print("opened:", opened)
在本地运行
python edges-morphology-binary-opening.py

预期输出

eroded: [0, 0, 0, 0, 0, 1, 0, 0]
opened: [0, 0, 0, 0, 1, 1, 1, 0]

逐步读懂代码

def 定义可复用函数。列表相加在首尾各补一个零。range 遍历原位置;切片 i:i+3 取三个数,不包括终点索引。传入 min 做腐蚀,传入 max 做膨胀。开运算把腐蚀结果用作下一步输入。这是准确的小型形态学规则,不是学习图像系统。

这些知识可以用在哪里

更宽噪点

把孤立噪点改成三个连续 1。它可能通过开运算,说明清理依赖大小而非含义。

边界规则

对 [1,1,1] 使用同样零填充。膨胀前先观察腐蚀结果;边界假设会影响中间形状。

类比的边界与注意事项. 开运算可能移除真实小物体。边缘强度不能决定哪一侧属于物体。一维示例没有处理角点与曲线形状。
记住这一点

可用这些操作清理扫描文字掩码或检查简单形状。每次清理前后都应比较细小但有用的细节。

↑ 返回知识框架
CV / 05Leon

让图像与标注一起移动

核心

01 / 从一个完整故事开始

学校工坊拍摄桌面工具。Eva 为增加训练变化,放大一张图片并把工具移向角落。图片看着合理,但标记工具的旧矩形仍留在原位。

她将矩形两个角点按图片同样的倍数缩放、平移,新框终于覆盖工具。她还检查是否有部分超出画布。另一张图中,翻转会把方向箭头反过来,她因此拒绝该操作。这个练习说明,改图既要检查含义,也要让标注随图移动。

02 / 理解核心概念

几何描述图像内容的位置。缩放乘坐标,平移加偏移量。边界框用两个对角点包住物体。图片移动时,框也必须移动。

输出像素可能对应原图两个像素之间的位置。插值会估计该位置的数值,最简单的方法是选最近的原像素。对于类别标签掩码,普通平均可能创造不存在的标签,因此常使用最近邻采样。

数据增强通过改变训练样本来模拟预期变化。但标签必须仍然正确。翻转锤子可能保留类别,翻转左箭头却会变成右箭头。旋转和裁剪同样需要更新框或掩码坐标,并规定如何处理被截去一部分的物体。

先认识这些词

边界框
用角点记录的包围物体的矩形。
插值
估计已知采样位置之间的数值。
数据增强
改变训练样本,同时保持目标一致。

03 / 如何运用这个概念

先写下目标相机环境中哪些变化是合理的,以及哪些标注必须随之改变。预览增强样本时应叠加框与掩码,还要包含极端裁剪和边界情况。随机增强通常只用于训练流程,评估采用明确的固定处理。相关视频帧应放在同一数据划分中,避免增强掩盖训练与评估之间的泄漏。

04 / 官方教程与研究中的应用

OpenCV 的 Geometric Transformations 教程把坐标映射与图像重采样联系起来。TorchVision 的变换文档说明图像、框、掩码和视频为何需要兼容操作。在官方目标检测教程中,图像与目标的变换也是训练数据准备的一部分。这些资料支持工作间案例的流程,但并不意味着所有变换都适合每一种检测任务或标签体系。

05 / 公式与逐项解析

x′ = sₓ × x + tₓ
y′ = sᵧ × y + tᵧ

x、y 是变换前的水平与垂直坐标。撇号(′)表示变换后。sₓ、sᵧ 是缩放倍数;2 会把该轴距离加倍。tₓ、tᵧ 是以坐标单位表示的平移量。负平移表示沿轴的负方向移动。先乘再加,理解这两个式子不需要矩阵代数。

06 / 一步步算出结果

一个自拟工具框的两个对角为 (10, 20) 与 (30, 50)。设 sₓ = sᵧ = 2,tₓ = 5,tᵧ = −3。第一个顶点变为 (2 × 10 + 5, 2 × 20 − 3) = (25, 37),第二个变为 (65, 97)。宽从 20 变成 40,高从 30 变成 60,因此面积从 600 变成 2400 个平方坐标单位。

平移不会改变宽或面积。如果输出画布只到 y = 80,框就超出画布,需要一致地执行裁剪与对象保留规则。若只更新像素,原标注框就会留在不相关的位置。

一步步想清楚

  1. 为什么框也必须改变?

    标注说明物体在哪里。保留旧坐标会教出错误位置。

  2. 第一个 y 坐标怎样变成 37?

    先把 20 加倍为 40,再减 3 得 37。颠倒顺序会得到不同结果。

  3. 只平移会改变面积吗?

    不会。两个角点加同样偏移,因此坐标差保持不变。

运行一个完整示例 · 01

移动框的两个角

只用普通数字,先缩放再平移。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

corners = [(10, 20), (30, 50)]
moved = [(2 * x + 5, 2 * y - 3) for x, y in corners]
(x1, y1), (x2, y2) = moved
print("corners:", moved)
print("area:", (x2 - x1) * (y2 - y1))
print("clipped bottom:", min(y2, 80))
在本地运行
python geometry-augmentation-move-box.py

预期输出

corners: [(25, 37), (65, 97)]
area: 2400
clipped bottom: 80

逐步读懂代码

列表保存两个坐标对。列表推导式对每一对使用同样两个公式。解包为四个新坐标命名。相减得到宽 40、高 60,相乘得面积 2400。min 把底边限制在 80,但真实流程还要更新可见比例规则。本例变换标注,没有对图像像素重新采样。

这些知识可以用在哪里

标注检查

把缩放设为 1,保留平移。新角点是 (15, 17)、(35, 47),面积仍是 600。

含义检查

画左箭头后水平翻转。应把目标改为右箭头,或拒绝该增强。图像形状合法并不能保证标签保留。

类比的边界与注意事项. 移动舞台的类比不能保证对象含义在变换后保持不变。裁剪可能移除关键证据,插值也可能改变细小图案。增强只对有用且标签一致的变化有帮助,不能代替来自目标环境的代表性真实图像。
记住这一点

几何变化必须带着标注一起变化;每项增强都要说明,为什么标签含义仍然成立。

↑ 返回知识框架
CV / 06Leon

局部特征:比较有辨识度的图块

核心

01 / 从一个完整故事开始

历史社有同一扇装饰门的两张照片,一张更近且略有旋转。直接逐像素比较效果很差。Sara 改为寻找有辨识度的角点及周围小图案。她用短数值列表描述这些图块,再与另一照片里的候选描述比较。

一个候选明显更接近,而两块重复瓷砖同样合理。她接受清晰的建议,把瓷砖匹配留作未决。这说明有用的匹配器既需要相似度,也需要发现歧义的方法,不要求整张图完全相同。

02 / 理解核心概念

关键点是值得关注的图像位置。描述子是概括周围图块的一组数。比较描述子可以提出跨图片的对应关系。SIFT 是一种经典方法,它在多个尺度选择位置,并用含方向信息的局部亮度变化模式进行描述。

使用真实 SIFT 前,可用两个数的列表理解描述子匹配。对应元素相减,各自平方,相加后开方。所得欧几里得距离在列表相似时较小。再比较最近与次近距离:最优项若几乎打平,就应谨慎。还需用几何关系检查多个候选对应是否一致。

先认识这些词

关键点
周围区域值得比较的选中位置。
描述子
图像局部区域的数值摘要。
欧几里得距离
对应元素差平方之和的平方根。

03 / 如何运用这个概念

局部特征可支持全景对齐或将拍摄物体匹配到参考图。需要检查重复图案和几何一致性。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

d(a,b) = √((a₁−b₁)² + (a₂−b₂)²)
ratio = nearest_distance / second_distance

a、b 是描述子列表,这里不是像素坐标。下标 1、2 选择元素。平方是差值乘自身。√ 在最终距离处开方。ratio 比较前两名;越小表示区分越清楚。次近距离必须为正。

06 / 一步步算出结果

查询 (1,2) 与 A=(1,3) 的距离为 √(0²+1²)=1,与 B=(4,6) 的距离为 √(3²+4²)=5。比值 1/5=0.2。教学规则 ratio<0.8 保留 A。如果 B 变为 (1,3.1),距离为 1.1,比值约 0.9091,就拒绝这个有歧义的匹配。

一步步想清楚

  1. 为什么还要看第二名?

    即使候选都差或有歧义,也总会有最近项。与第二名的差距提供额外证据。

  2. B 的距离为什么是五?

    差为 3、4。平方和 9+16=25,√25=5。

  3. 比值 0.2 能证明物体匹配吗?

    不能。它只比较这些描述子。多个一致位置与直接检查才是更强的核查。

运行一个完整示例 · 01

比较两个图块描述

从手工描述子计算距离。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import math
query = (1, 2)
candidates = {"A": (1, 3), "B": (4, 6)}
def distance(a, b):
    return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)))
ranked = sorted((distance(query, value), name) for name, value in candidates.items())
ratio = ranked[0][0] / ranked[1][0]
print("ranked:", ranked)
print(f"ratio={ratio:.2f}; accept={ratio < 0.8}")
在本地运行
python classical-features-descriptor-distance.py

预期输出

ranked: [(1.0, 'A'), (5.0, 'B')]
ratio=0.20; accept=True

逐步读懂代码

zip 配对元素,**2 把差平方,sum 相加,math.sqrt 开方。sorted 按距离从小到大排列 (距离,名称) 对。ranked[0][0] 取第一对的距离,ranked[1][0] 取下一对。比值为 0.20。代码匹配手设描述子,没有寻找图像关键点,也没有实现 SIFT。

这些知识可以用在哪里

有歧义瓷砖

把 B 设为 (1,3.1)。比值约为 0.9091,规则将匹配保留为未决。

完全重复

添加另一个与 A 相同的候选。最近距离打平,比值为一。即使像素清晰,重复图案仍可能让局部匹配失败。

类比的边界与注意事项. 双数描述子只用于讲解匹配,并非包含更丰富局部描述的 SIFT 描述子。无纹理区域与重复图案可能几乎没有可靠匹配。
记住这一点

局部特征可支持全景对齐或将拍摄物体匹配到参考图。需要检查重复图案和几何一致性。

本节资料来源

↑ 返回知识框架
CV / 07Leon

借用视觉词汇,再学习新的判断

核心

01 / 从一个完整故事开始

社区花园请 Noor 将植物照片分成三组,但标注样本不多。一个没有已学视觉知识的大模型表现不稳。她改为复用在其他图片上学过模式的模型。

她先固定读图部分,只训练小型新决策部分,再用其他花园访问时拍摄的照片检查。傍晚图仍有错误,于是谨慎允许部分旧权重改变,然后重测。其他图片的经验有帮助,却不能代替本地样本。测试照片说明改动是否对训练之外的例子也有效。

02 / 理解核心概念

分类从杯子、书、球等标签列表中选择。首先,特征提取器把像素变成有用的数字。预测头把这些数字组合成每个标签的一个分数。原始分数也叫 logit。

训练利用带标签样本调整权重。损失是衡量当前输出与目标不符程度的数。迁移学习从另一批数据学到的权重开始。冻结使指定权重不变;微调允许它们随新任务改变。被复用的特征提取器通常称为主干。

Softmax 把分数变成总和为一的正数,但只比较列出的类别。未知物体的照片仍可能得到很高的杯子分数,所以高数值不等于识别正确的证据。

先认识这些词

特征
描述输入某种有用性质的数。
权重
可以从样本中学习的乘数。
损失
输出与目标不符时的数值惩罚。
Softmax
对分数取指数,再除以总和。

03 / 如何运用这个概念

先建立冻结特征的简单基线,使预处理符合预训练模型要求,再把预测头替换成自己的类别集合。相邻图像存在关联时,应按采集批次或来源划分数据。微调与基线要使用相同的留出组比较。按光照和视角查看错误,并保留足够评估数据,以便发现所谓更好的模型是否只是记住了局部模式。

04 / 官方教程与研究中的应用

PyTorch 的官方 Transfer Learning for Computer Vision 教程对比了调整预训练网络与把它作为固定特征提取器的做法。Stanford 的 CS231n 教学则提供了学习型视觉表示与识别的更广背景。Noor 的三类植物案例是原创例子;它与这些资料的联系,在于复用已学表示,并检验调整后是否能推广到新图像的工作流程。

05 / 公式与逐项解析

scoreₖ = wₖ₁ × h₁ + wₖ₂ × h₂ + bₖ
pₖ = exp(scoreₖ) / sum of exp(all scores)
loss = −ln(p_correct)

h₁、h₂ 是概括图像的两个特征数。k 选择类别。wₖ₁、wₖ₂ 指定各特征对该类别的贡献,bₖ 加一个固定偏移。exp(a) 表示 e 的 a 次方,e 约为 2.718。把正的指数值除以总和,可让 p 的总和为 1。ln 是自然对数,即 exp 的逆运算。p_correct=1 时损失为零,它降低时损失增加。

06 / 一步步算出结果

使用特征 [1,2]。第一类权重 [1,0],分数 1×1+0×2=1;第二类权重 [0,1],分数 0×1+1×2=2;第三类权重 [0,0],得 0。所有常数偏移均为零。

对 [1,2,0] 取指数得到约 [2.7183,7.3891,1],总和 11.1073。各除以总和得到 [0.2447,0.6652,0.0900]。若第二类正确,损失 −ln(0.6652)≈0.4076。模型偏好正确标签,却仍给其他类分配概率。特征是给定的,从像素学到这些特征仍是另一项工作。

一步步想清楚

  1. 为什么复用特征提取器?

    早先训练可能学到了边缘等有用模式。新样本可训练较小的预测头,但是否有效仍需检验。

  2. 第二个分数为何是 2?

    该行是 [0, 1],所以 0×1+1×2=2。

  3. 0.6652 能证明物体属于第二类吗?

    不能。这是模型在这些标签间的相对偏好,模型仍可能出错。

运行一个完整示例 · 01

把特征变成三个分数

手工特征让我们单独观察预测头。这不是训练好的图像分类器。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import math
features = [1, 2]
weights = [[1, 0], [0, 1], [0, 0]]
scores = [sum(w * h for w, h in zip(row, features)) for row in weights]
exp_scores = [math.exp(s - max(scores)) for s in scores]
probabilities = [v / sum(exp_scores) for v in exp_scores]
print("scores:", scores)
print("probabilities:", [round(p, 4) for p in probabilities])
print(f"loss={-math.log(probabilities[1]):.4f}")
在本地运行
python recognition-transfer-classification-head.py

预期输出

scores: [1, 2, 0]
probabilities: [0.2447, 0.6652, 0.09]
loss=0.4076

逐步读懂代码

每行权重通过乘法与加法得到一个类别分数。在 exp 前减最大分数可缩小数值,同时保持 softmax 不变。除以总和得到约 0.2447、0.6652、0.0900。索引 1 取第二类。math.log 求自然对数,负号使损失成为 0.4076。本代码没有训练主干。

这些知识可以用在哪里

改变证据

把特征改为 [2, 1]。前两个概率交换,说明相同预测头如何响应变化的证据。

未知类别

想象给杯子/书/球分类器展示自行车。证据不足时设置复核路线;softmax 本身无法添加自行车标签。

类比的边界与注意事项. 经验丰富的插画师类比,可能夸大能够迁移的能力。预训练可能包含无关捷径,较大的 softmax 值也可能对应错误类别。冻结特征是有用的基线,却不是通用答案;如何调整应依据数据量、相似性与评估证据决定。
记住这一点

迁移表示,仔细定义新的判断任务,再用真正独立的图像组衡量改进。

↑ 返回知识框架
CV / 08Leon

框定位候选对象,重叠度帮助消除重复

核心

01 / 从一个完整故事开始

观鸟社想统计照片里的海鸥。检测器在同一只海鸥周围画了多个矩形,直接数框就会多算。Lea 查看最高分矩形,再与附近框比较。

她计算共享面积占总覆盖面积的比例。低分框若重叠很强,就按指定规则作为可能重复项删除。但拥挤海鸥仍困难:重叠框可能代表两只真实鸟。她测试这种情形后才选择规则。找物体与判断哪些框重复同一物体,是相关但不同的任务。

02 / 理解核心概念

检测回答两个问题:有什么物体,它们在哪里?检测器可能为同一物体提出多个矩形。每个候选框都有类别、分数和角点坐标。

交集是两个矩形共享的面积。并集是任一个矩形覆盖的面积,共享部分只算一次。交并比 IoU 用前者除以后者。不重叠时为零,两个相同且非空的框为一。

非极大值抑制 NMS 用于减少重复框。先保留剩余框中分数最高者。如果某个同类低分框与它的 IoU 超过阈值,就删除该低分框,然后重复。TorchVision 的基础 NMS 不接收类别标签,因此调用者需自行按类处理。NMS 阈值用于去重复,评估阈值用于判断是否匹配标注,两者用途不同。

先认识这些词

检测
预测物体类别和位置。
交并比
共享面积除以总覆盖面积。
抑制
按规则删除较低分候选项。

03 / 如何运用这个概念

比较检测结果之前,先定义框坐标和类别处理方式。检查包含孤立对象、拥挤邻居与部分遮挡的叠加图。用验证数据选择分数阈值和抑制阈值,在最终评估时保持固定。

记录预测与标注的匹配方法,包括防止多个预测重复认领同一对象的规则。看起来合理的框是需要检查的证据,并不能证明计数正确。

04 / 官方教程与研究中的应用

TorchVision 官方检测教程在 Penn-Fudan 行人图像上微调 Mask R-CNN,并把框、类别与对象掩码作为结构化目标。NMS 参考文档则单独说明分数排序,以及重叠度严格大于给定阈值时的移除规则。结合阅读两份资料,可以区分完整检测示例与单个后处理操作,也能精确定义海鸟故事中的去重决策。

05 / 公式与逐项解析

intersection = shared_width × shared_height
union = area_A + area_B − intersection
IoU = intersection / union

A、B 是两个矩形。每个写作 (x₁, y₁, x₂, y₂),且 x₂>x₁、y₂>y₁。面积是 (x₂−x₁)×(y₂−y₁),这里使用连续边界坐标,不额外加 1。共享宽度为 max(0, min(x₂A,x₂B)−max(x₁A,x₁B)),高度同理。min 取较小数,max 取较大数。外层 max 防止出现负重叠。

06 / 一步步算出结果

考虑三个自拟海鸥框:A = (0, 0, 4, 4)、B = (1, 1, 5, 5)、C = (6, 0, 8, 2),分数分别为 0.9、0.8、0.7。A 与 B 的面积均为 16。它们的交集宽为 3、高为 3,因此面积为 9。并集面积为 16 + 16 − 9 = 23,所以 IoU = 9 / 23,约为 0.3913。

当 τ = 0.3 时,保留 A、抑制 B。C 与 A 没有重叠,因此保留 C,最终剩下两个框。当 τ = 0.5 时,B 也被保留,共有三个框。这些结果说明阈值的作用;没有标注,仍无法判断 A 与 B 指向同一只海鸥,还是两只相互遮挡的海鸥。

一步步想清楚

  1. 计算并集时为什么减交集?

    两面积相加时共享部分算了两次。减一次后就只算一次。

  2. 23 从哪里来?

    两个框面积都是 16,交集为 9,所以 16+16−9=23。

  3. 更高的 NMS 阈值会保留更多框吗?

    在这个双框例子中是的:0.3913 超过 0.3,但未超过 0.5。阈值不是正确概率。

运行一个完整示例 · 01

测量两个候选框

为两个同类框计算一次抑制判断。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

a = (0, 0, 4, 4)
b = (1, 1, 5, 5)
width = max(0, min(a[2], b[2]) - max(a[0], b[0]))
height = max(0, min(a[3], b[3]) - max(a[1], b[1]))
intersection = width * height
area_a = (a[2] - a[0]) * (a[3] - a[1])
area_b = (b[2] - b[0]) * (b[3] - b[1])
iou = intersection / (area_a + area_b - intersection)
print(f"intersection={intersection}; IoU={iou:.4f}")
for threshold in [0.3, 0.5]:
    print(threshold, "suppress B" if iou > threshold else "keep B")
在本地运行
python detection-overlap-box-overlap.py

预期输出

intersection=9; IoU=0.3913
0.3 suppress B
0.5 keep B

逐步读懂代码

索引 0、2 是左右边,1、3 是上下边。min、max 找到共享部分。接着算两个面积,将共享面积减一次,再相除。for 循环测试两个阈值,行内 if 选择消息。假设 A 分数较高。例子从已有框开始,并没有从像素检测物体。

这些知识可以用在哪里

没有重叠

把 B 移到 (6,0,8,2)。共享宽度为零,IoU 为零,两个阈值下都会保留 B。

拥挤物体

画两只重叠的鸟。若框重叠太多,NMS 可能删掉真实的第二只鸟;要单独检查拥挤情形。

类比的边界与注意事项. 铅笔圈的类比解释了重复候选,但高重叠度不能证明它们对应同一对象。贪心抑制可能移除真实邻居,高分框也可能定位不准。不同检测器可能采用其他去重设计;这里介绍的是常见操作,并非所有检测架构都使用的规则。
记住这一点

把框视为候选,精确定义重叠,并把去重规则与检测质量的判定规则分别验证。

↑ 返回知识框架
CV / 09Leon

检测分数:顺着排序列表评估

应用

01 / 从一个完整故事开始

观鸟社在同一组照片上比较两个检测器。两者都找到两只鸟,但其中一个最先以高分报告窗户反光。小瑞发现,发现数量忽略了警报顺序。她按分数排序,逐条检查。

每只真实鸟只允许匹配一次,重复框算额外误报,不能算第二次发现。她在每次找到新鸟时记录精确率。现在大家看清了,即使后来放宽阈值能找全鸟,排在前面的错误警报仍会降低排序结果的实用性。

02 / 理解核心概念

先选择类别与 IoU 匹配阈值,再按预测分数由高到低排列框。预测若匹配合格且尚未被匹配的参考物体,就是真正例;否则是假正例。随着列表变长,精确率与召回率都会变化。

平均精确率 AP 概括精确率—召回率曲线,但具体定义有多种。本教学计算用非插值方式,把每次召回率增加量乘该时精确率后相加。COCO 使用在指定召回率点采样的插值精确率,并按规定跨 IoU 阈值与类别平均。因此一个小例子的 AP 不是 COCO 基准分数。

先认识这些词

排序
按分数排列预测。
平均精确率
按指定约定概括召回率增加时的精确率。
匹配阈值
把预测与参考配对所需的最小重叠。

03 / 如何运用这个概念

在明确匹配规则下使用排序评估比较检测器。应同时保存分数、框、类别与参考数量。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

Pₖ = true_matches_in_first_k / k
Rₖ = true_matches_in_first_k / reference_count
AP_toy = sum of Pₖ × (Rₖ − Rₖ₋₁)

k 是从 1 开始的排名。Pₖ、Rₖ 是读完 k 条预测后的精确率、召回率。R₀ 为零。reference_count 包括该类所有参考物体,也包括漏检。总和遍历所有预测,只有新的正确匹配会增加召回率。

06 / 一步步算出结果

有两只参考鸟,排序结果为 [正确,错误,正确]。精确率为 1、1/2、2/3,召回率为 1/2、1/2、1。AP_toy=1×1/2+(1/2)×0+(2/3)×1/2=5/6≈0.8333。

如果只在首位找到一只鸟,AP_toy=1×1/2=0.5,并非 1。漏检必须计入。

一步步想清楚

  1. 为什么一个参考物体只匹配一次?

    否则重复框可以把同一个物体算成多次发现。

  2. 为什么第二名对 AP 的贡献为零?

    它是误报,召回率没有增加,但仍会降低后续精确率。

  3. 若参考有三只鸟呢?

    找到两只时召回率为 2/3,教学 AP 为 (1+2/3)/3≈0.5556。

运行一个完整示例 · 01

读取三条排序检测

输入是已检查的匹配结果,不是原始框。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

matches = [True, False, True]
reference_count = 2
found = 0
ap = 0.0
for rank, correct in enumerate(matches, start=1):
    found += int(correct)
    precision = found / rank
    recall = found / reference_count
    if correct:
        ap += precision / reference_count
    print(f"rank={rank}: precision={precision:.4f}, recall={recall:.4f}")
print(f"toy AP={ap:.4f}")
在本地运行
python detection-average-precision-ranked-ap.py

预期输出

rank=1: precision=1.0000, recall=0.5000
rank=2: precision=0.5000, recall=0.5000
rank=3: precision=0.6667, recall=1.0000
toy AP=0.8333

逐步读懂代码

enumerate 提供排名及对应结果。int 把 True 变成 1、False 变成 0。found 累计匹配数。每次正确匹配使召回率增加 1/reference_count,因此贡献是 precision/reference_count。+= 加入累计值。打印的 0.8333 仅是上述教学 AP,不是完整 COCO 指标。

这些知识可以用在哪里

错误排第一

改为 [False,True,True]。AP=((1/2)+(2/3))/2≈0.5833。靠前错误影响很大。

漏检物体

保留 [True] 和两个参考。确认即使精确率为一,AP 仍为 0.5。

类比的边界与注意事项. 本例假设真假匹配已核查,没有实现 COCO 的 crowd 规则、插值、多 IoU 阈值或尺寸分组。
记住这一点

在明确匹配规则下使用排序评估比较检测器。应同时保存分数、框、类别与参考数量。

↑ 返回知识框架
CV / 10Leon

从一个矩形走向真正属于对象的像素

应用

01 / 从一个完整故事开始

园艺课想测照片中的叶片面积。叶片边界框包含空背景,所以 Kim 改为逐像素标叶。第一个自动结果把所有像素都当背景。因为叶片小,它大部分像素都判断对,却漏掉了全班真正想测的东西。

Kim 将选中的叶片区域与仔细核实的参考比较,统计共享、多选和漏选像素。新报告清楚揭示失败。她还问,相接叶片应作为一个叶类区域,还是分别编号?这个答案会在选择模型之前改变所需标签。

02 / 理解核心概念

边界框说明物体大致在哪里。分割更进一步,为每个像素分配标签。掩码是保存这些标签的网格;二值掩码中,1 表示选中区域,0 表示背景。语义分割标记物体类型,实例分割还区分同类的不同物体。

比较预测前景与核实后的参考掩码时,要数各自选中多少像素,以及共享多少像素。IoU 和 Dice 关注被选区域。普通像素准确率也奖励背景,因此即使漏掉所有小物体,数值仍可能很高。

这些公式使用是或否的掩码,不使用不确定概率。完整训练可能采用相关但不同的损失公式。必须说明按图平均还是合并所有像素。若两个掩码都为空,分母会为零,需要明确约定,不能直接相除。

先认识这些词

掩码
保存选中状态或类别标签的像素网格。
前景
任务关注并选中的区域。
实例
一个独立物体,例如三片叶子中的一片。

03 / 如何运用这个概念

问题涉及某种材料的覆盖范围时,选择语义标签;需要区分不同对象时,选择实例标签。把掩码叠加在原图上,检查边界与小区域。报告前景重叠度时,同时保留类别频率和代表性错误,并说明是在图像、对象还是类别之间取平均。评估前先定义空掩码处理规则,避免没有前景的图片悄悄改变平均分数的含义。

04 / 官方教程与研究中的应用

TorchVision 文档提供了用于语义分割、采用 ResNet-50 主干的 FCN 模型。其检测教程则使用 Mask R-CNN 与 Penn-Fudan 标注,区分不同行人及其掩码。这是两类输出的具体实例。scikit-learn 指标指南给出了相关的 Jaccard 与 F 度量定义;在明确前景约定后,可以把这些定义用于展平的二值像素标签。

05 / 公式与逐项解析

IoU = |P ∩ G| / |P ∪ G|
Dice = 2|P ∩ G| / (|P| + |G|)
Dice = 2IoU / (1 + IoU)  (|P ∪ G| > 0)

P 是被预测为前景的像素集合,G 是参考前景集合。竖线表示像素数量;∩ 选取同时属于两个集合的像素,∪ 选取属于任一集合的像素。IoU 表示交并比,Dice 是上式定义的重叠系数。分母为正时,两者均介于 0 与 1 之间。如果两个掩码都为空,原始分式没有定义;必须选择并报告一种约定,例如把这种一致情况计为 1,或在指定的平均方式中排除这种情况。

06 / 一步步算出结果

设一张自拟图像包含 1000 个像素,参考标注中有 40 个叶片像素。全部预测为背景时,像素准确率达到 960 / 1000 = 96%,但由于没有找到任何叶片像素,前景 IoU 与 Dice 均为 0。第二个预测把 50 个像素标为叶片,其中 30 个与参考前景重合。并集包含 50 + 40 − 30 = 60 个像素,所以 IoU = 30 / 60 = 0.5。

Dice = 60 / 90,约为 0.6667,也等于 2 × 0.5 / 1.5。该预测有 20 个误报前景像素,漏掉 10 个叶片像素。两个重叠分数用不同尺度描述相同掩码;不能把它们与像素准确率直接作数值比较,仿佛三者衡量的是同一种量。

一步步想清楚

  1. 为什么全背景准确率会误导?

    大部分像素可能是背景。正确背景判断会掩盖前景物体的遗漏。

  2. 代码中的并集为何是四?

    位置 0、1、2、3 至少被一个掩码选中,4、5 均未被选中。

  3. 两个空掩码是否产生 IoU=0/0?

    是的,原始公式没有定义,评估必须明确处理方式。

  4. 忽略像素还算假阳性吗?

    本规则下不算。我们在计数前完整排除该位置。应写明规则,保证所有模型在相同像素上评估。

运行一个完整示例 · 01

统计前景重叠

用小型二值列表代替展平后的掩码。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

prediction = [1, 1, 1, 0, 0, 0]
reference = [0, 1, 1, 1, 0, 0]
shared = sum(p == 1 and g == 1 for p, g in zip(prediction, reference))
union = sum(p == 1 or g == 1 for p, g in zip(prediction, reference))
iou = shared / union
dice = 2 * shared / (sum(prediction) + sum(reference))
print("shared:", shared, "union:", union)
print(f"IoU={iou:.4f}; Dice={dice:.4f}")
在本地运行
python segmentation-regions-mask-counts.py

预期输出

shared: 2 union: 4
IoU=0.5000; Dice=0.6667

逐步读懂代码

zip 遍历对应位置。and 只在两个掩码都选中时计数;or 在任一掩码选中时计数。Python 累加时把 True 当 1、False 当 0。共享像素为二,并集为四。每个列表选中三个像素,因此 Dice 为 4/6。本例评估已有掩码,没有学习或预测掩码。

运行一个完整示例 · 02

排除忽略像素,计算分割 IoU

道路相机区分背景、道路和小物体。有些像素没有可靠标注。米娜起初把所有像素都计入,分数因此失真。她排除忽略像素,建立混淆矩阵,再从行列读取每个类别。这样,小物体有了自己的分数,不会淹没在大量背景像素中。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

from math import isclose

def segmentation_report(truth, prediction, classes, ignore=255):
    if len(truth) != len(prediction) or classes <= 0:
        raise ValueError("aligned arrays and positive class count required")
    matrix = [[0] * classes for _ in range(classes)]
    for actual, predicted in zip(truth, prediction):
        if actual == ignore:
            continue
        if not (0 <= actual < classes and 0 <= predicted < classes):
            raise ValueError("unknown class")
        matrix[actual][predicted] += 1
    ious = []
    for c in range(classes):
        tp = matrix[c][c]
        actual_total = sum(matrix[c])
        predicted_total = sum(row[c] for row in matrix)
        union = actual_total + predicted_total - tp
        ious.append(tp / union if union else None)
    defined = [value for value in ious if value is not None]
    mean = sum(defined) / len(defined) if defined else None
    total = sum(map(sum, matrix))
    accuracy = sum(matrix[c][c] for c in range(classes)) / total if total else None
    return matrix, ious, mean, accuracy

truth = [0, 0, 0, 1, 1, 2, 2, 255]
prediction = [0, 0, 1, 1, 2, 2, 0, 1]
matrix, ious, mean, accuracy = segmentation_report(truth, prediction, 3)
assert matrix == [[2, 1, 0], [0, 1, 1], [1, 0, 1]]
assert isclose(mean, 7 / 18) and isclose(accuracy, 4 / 7)
assert segmentation_report([255], [1], 3)[2] is None
assert segmentation_report([0], [0], 3)[1] == [1.0, None, None]
print("rows = truth; columns = prediction:")
for row in matrix:
    print(row)
print("class IoU:", [round(value, 6) for value in ious])
print(f"mean IoU: {mean:.6f}")
print(f"pixel accuracy: {accuracy:.6f}")
在本地运行
python segmentation-regions-confusion-matrix-miou.py

预期输出

rows = truth; columns = prediction:
[2, 1, 0]
[0, 1, 1]
[1, 0, 1]
class IoU: [0.5, 0.333333, 0.333333]
mean IoU: 0.388889
pixel accuracy: 0.571429

按执行顺序理解

  1. 看到真值为忽略标签时,直接跳过;该位置的预测也不进入任何行列。

  2. 对角线是 TP;行总数是真实像素数,列总数是预测像素数。

  3. 并集=真实总数+预测总数-TP,减法避免交集重复计数。

  4. 对有定义的类别 IoU 等权平均;若真值和预测都没有该类,则返回 None。

逐步读懂代码

IoU_c=TP_c/(TP_c+FP_c+FN_c)。背景类 TP=2、FP=1、FN=1,因此 IoU=2/4=0.5;道路和物体均为 1/3。平均 IoU=(1/2+1/3+1/3)/3=7/18,约 0.388889;像素准确率则是 4/7,它们回答的问题不同。

本混淆矩阵汇总所有参与评估的像素。先汇总全部图像再算 IoU,与先逐图算 IoU 再平均,结果可能不同,必须说明采用哪种方式。本实现只在并集为零时排除类别;真值没有某类,但模型预测过它,该类并集非零,IoU 应为零。不同基准可能采用不同缺失类别规则。

全是忽略像素的图像没有有效分数,并不表示完美预测。P 个像素、C 个类别时,本直观实现花费 O(P+C²) 时间、O(C²) 空间。比较模型时,应固定忽略掩码、类别映射、汇总方式与缩放方法。离散标签掩码不能用会产生小数类别编号的方式缩放。

这些知识可以用在哪里

完全匹配区域

让 prediction 与 reference 相同。交集、并集都为三,IoU 和 Dice 都为一。

区分叶片

画两片相接的叶子。语义叶片掩码可能连在一起;实例标签仍需区分叶 A 与叶 B。

类比的边界与注意事项. 一个着色区域的含义取决于标注规则。模糊边界、透明对象和相互重叠的实例可能需要细致约定。较高的平均重叠度仍可能掩盖小对象漏检或重要边界错误,而仅凭前景面积无法确定个体数量。
记住这一点

让掩码类型对应实际问题,检查占少数的前景,并明确重叠度与空掩码情况的评分方式。

↑ 返回知识框架
CV / 11Leon

跟踪:跨帧保留身份

应用

01 / 从一个完整故事开始

学校机器人小组拍摄两辆玩具车穿过桌面。逐帧检测车容易描述,但若每个检测都计数,同一辆车会被重复统计。Ana 给每辆车分配轨迹编号。她先预测已有车下一刻的位置,再把新检测与邻近轨迹配对。

两车交叉时,最近位置规则交换了名称。她保留这段视频作为测试,并考虑运动历史。大家发现,跟踪是序列问题:单帧中合理的框本身无法证明它属于此前看到的同一辆车。

02 / 理解核心概念

检测提供每帧观察,跟踪把观察随时间连接起来。轨迹保存身份和近期状态,例如中心位置、速度。在匹配新检测前,预测先把状态向前推进。

简单一维模型中,速度是每帧位置变化。将速度加到上次位置,预测下一位置。只有最近检测的距离在指定门限内才关联。真实多物体系统需要一对一分配,防止同一检测更新两条轨迹。SORT 将运动预测与分配结合;本例只隔离一条轨迹的预测与门限。

先认识这些词

轨迹
跨帧连接的身份及变化状态。
关联
将新检测与已有轨迹配对。
遮挡
物体被部分或完全遮住。

03 / 如何运用这个概念

跟踪可用于统计移动物体,或在视频中跟随玩具机器人和野生动物。应检查交叉、遮挡与漏检。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

v = x_now − x_previous
x_predicted = x_now + v
match if |x_detection − x_predicted| ≤ gate

x 是以像素计的水平中心位置。v 是每帧移动像素数,假设帧间隔相等。竖线表示绝对值,因此距离非负。gate 是本例关联允许的最大距离。

06 / 一步步算出结果

车从 x=2 移到 x=5,所以 v=3。下一预测为 5+3=8。检测 8、15 与预测的距离为 0、7。

门限为 2 时选 8。若只有 15,就让轨迹暂时无匹配,不强行连接远处检测。

一步步想清楚

  1. 为什么先预测再匹配?

    移动物体预计会离开旧位置。预测提供更合适的比较点。

  2. 为什么预测八?

    上次变化是 5−2=3;向 5 加相同变化得到 8。

  3. 没有检测通过门限怎么办?

    记录观察缺失。完整跟踪器可短暂保留预测,但不应把它当作已确认观察。

运行一个完整示例 · 01

预测一条轨迹并设门限

使用等时间间隔帧中的一辆车。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

previous, current = 2, 5
velocity = current - previous
predicted = current + velocity
detections = [8, 15]
nearest = min(detections, key=lambda x: abs(x - predicted))
accepted = abs(nearest - predicted) <= 2
print("predicted:", predicted)
print("nearest:", nearest, "accepted:", accepted)
在本地运行
python tracking-identity-track-gate.py

预期输出

predicted: 8
nearest: 8 accepted: True

逐步读懂代码

相减估计速度,相加预测下一个中心。min 搜索列表。key 是一个返回距预测绝对距离的 lambda 小函数,因此选的是最小距离而非最小坐标。<= 检查门限。结果接受 8。这是单轨迹关联示例,不是训练好的跟踪器。

这些知识可以用在哪里

突然转向

把检测改为 [4,15]。最近为 4,但距离 4 超过门限。即使车真的转向,规则也会拒绝。

两辆车

画两个预测中心与两个检测。确认身份前应要求每个检测只分给一条轨迹,独立最近选择可能冲突。

类比的边界与注意事项. 恒定速度在突然转弯时失效,仅靠位置无法解决相同物体交叉。例子只有一条轨迹,没有实现 SORT 的状态估计或全局分配。
记住这一点

跟踪可用于统计移动物体,或在视频中跟随玩具机器人和野生动物。应检查交叉、遮挡与漏检。

↑ 返回知识框架
CV / 12Leon

OCR:把可见笔画变成文字

应用

01 / 从一个完整故事开始

图书社扫描书架标签,希望输入编号就能查书。一张照片倾斜,字母 O 被读成数字零。Noah 先校正裁剪图,再检查每个字符是否有足够像素。他把识别文字与可见标签比较,并把小图保留在结果旁。

拼写规则有助于普通词,却可能破坏特殊书目编号,所以编号留待复核。改进后的流程把读字当作多个相关判断:找文字、准备图像、识别符号、核对证据。一个通顺句子本身并不能证明转写忠实。

02 / 理解核心概念

光学字符识别 OCR 把图像文字转为字符。常见流程先检测文字区域,再校正旋转、识别字符或序列,最后检查输出顺序。页面布局很重要:把两栏当成一行会改变内容。

小型模板匹配可讲解识别步骤。为每个候选符号存一个二值图案,再比较观察图案与每个模板,统计不同位置。差异最少者获选。完整 OCR 系统学习丰富得多的形状和语言模式,还要处理字体、间距与图像质量变化。

先认识这些词

光学字符识别
把图像中的可见文字变为字符数据。
模板
用于比较的已存参考样本。
阅读顺序
页面各区域应被阅读的次序。

03 / 如何运用这个概念

OCR 可用于扫描件搜索、标签识别与文档阅读。应保留原图区域及阅读顺序,便于核对不确定文字。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

mismatch(pattern, template) = number of unequal positions
chosen_label = label with the smallest mismatch

pattern 是从小图逐行读取的固定长度零一列表。template 是相同大小的参考列表。每个不同位置贡献一,相同位置贡献零。这是二值图案的汉明距离。

06 / 一步步算出结果

使用七个位置。H 教学模板为 [1,0,1,1,1,0,1],I 为 [0,1,0,1,0,1,0]。观察值 [1,0,1,1,1,0,0] 只在末位与 H 不同,距离为 1;与 I 有五处不同。因此在该教学编码下 H 更接近。

一步步想清楚

  1. 为什么保留裁剪小图?

    它提供核对符号是否读对的直接证据。

  2. 为什么 H 的距离是一?

    只有第七位不同,其他六位相同。

  3. 词典总能纠正 O 与 0 吗?

    不能。书架编号可能合法地包含其中任一种,语言合理性并不足够。

运行一个完整示例 · 01

统计符号差异

比较虚构二值符号图案;不是 Tesseract,也不是训练 OCR 模型。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

templates = {"H": [1, 0, 1, 1, 1, 0, 1], "I": [0, 1, 0, 1, 0, 1, 0]}
observed = [1, 0, 1, 1, 1, 0, 0]
distances = {}
for label, template in templates.items():
    distances[label] = sum(a != b for a, b in zip(observed, template))
print("distances:", distances)
print("closest:", min(distances, key=distances.get))
在本地运行
python ocr-reading-ocr-template.py

预期输出

distances: {'H': 1, 'I': 5}
closest: H

逐步读懂代码

字典为每个标签保存列表。循环用 zip 配对观察值与模板位置;不等时 != 返回 True,sum 统计 True。distances.get 将每个标签的距离提供给 min,最终选 H。这里假定长度相等,真实输入应检查。未实现文字区域检测。

这些知识可以用在哪里

不可读标记

试全零观察值并检查两个距离。接受标签前添加最大允许差异数。

双栏页面

在纸上写两栏短文字。比较按行读与按栏读。单个字符正确不保证句子顺序正确。

类比的边界与注意事项. 七位图案是虚构教学编码,不是真实字形图像。即使输入不可读,也总会有最近模板。未知符号与平局需要明确复核选项。
记住这一点

OCR 可用于扫描件搜索、标签识别与文档阅读。应保留原图区域及阅读顺序,便于核对不确定文字。

↑ 返回知识框架
CV / 13Leon

让图像块交换信息

进阶

01 / 从一个完整故事开始

Mateo 制作一个按样式分类手工风筝照片的教学展示。角落条纹只有与尾部一起看才有意义。他在照片上画网格,用短数字列表表示每格,让这些列表在模型选标签前交换信息。

他起初以为每格宽度减半只会让工作加倍。重新计数才发现格数增至四倍,可比较的配对更多。他把计数放在例子旁。课程因此同时说明远处区域为何能交互,以及保留更细细节为何可能需要更多计算。

02 / 理解核心概念

视觉词元是一组表示图像局部的数字。先把图像切成等大图像块。展平按顺序把块内通道值读成一个长列表,不会添加信息。学习得到的投影通过加权和,把长列表转成固定数量的特征。

原始 Vision Transformer 加入位置信息和一个类别词元。自注意力让每个词元与其他词元比较并混合信息。类别词元汇集分类所需的信息。图像块只是网格区域,不一定包含一个完整物体。

有 L 个词元时,全注意力考虑 L 个来源和 L 个目标,因此有 L×L 对。更小的块提供更多细节,也增加许多比较。这个计数只描述模型的一部分,不能直接保证所有实现的总运行时间或内存。

先认识这些词

图像块
从图像切出的一个小网格区域。
词元
序列中被处理的一个数值单元。
投影
用加权和把特征列表转换到另一种大小。
自注意力
在同一序列的词元之间混合信息。

03 / 如何运用这个概念

选择图像分辨率与图像块大小之前,先计算图像块数量,以及包括特殊词元在内的序列长度。检查图像尺寸能否整除,还是需要明确的缩放或填充规则。比较细节保留程度与实际资源消耗,并遵循预训练模型的位置处理方式。对于新任务,先检查缩放是否丢失有用证据,再分析注意力是否造成了错误。

04 / 官方教程与研究中的应用

原始 ViT 论文通过投影后的图像块与 Transformer 编码器完成图像分类,并研究大规模预训练后的迁移。TorchVision 的 VisionTransformer 实现使图像块投影和额外类别词元可以直接检查。原始 Transformer 论文解释了这种信息交换背后的注意力运算。这些资料支持这里介绍的设计;手工风筝展示及其小型数值例子是原创教学情景。

05 / 公式与逐项解析

patch_count = (H / P) × (W / P)
values_per_patch = P × P × C
L = patch_count + 1
pair_count = L × L

H、W 是像素高度与宽度。P 是正方形图像块的边长,这里假设能整除图像尺寸。C 是颜色通道数。L 是加上一个类别词元后的词元数;类别词元是原始 Vision Transformer 使用的可学习汇总位置。pair_count 为一个全注意力头统计所有有顺序的词元对。一个头是一组比较与混合操作。

06 / 一步步算出结果

32×32 的 RGB 图切成 8×8 块,每边四块,共 4×4=16 块。每块有 8×8×3=192 个通道值。学习投影可计算 64 种加权和,将每个 192 数列表转为例如 64 个特征数。

加入一个类别词元后 L=17,全注意力每头有 17×17=289 对。改用 4×4 块时,有 8×8=64 块、65 词元、65²=4225 对。倍率为 4225/289≈14.62。这是注意力部分的比较计数,不表示完整运行时间有相同倍率。

一步步想清楚

  1. 为什么保留块的位置?

    相同图像块以不同方式排列可能形成另一场景。位置顺序携带空间证据。

  2. 8×8 的 RGB 块里有多少数?

    8×8×3=192;64 个像素各有三个通道值。

  3. 块数四倍是否意味着比较对数四倍?

    不是。比较对数对词元数平方。这里因额外类别词元,倍率为 4225/289≈14.62。

运行一个完整示例 · 01

统计图像块与比较次数

先比较两种块大小,再考虑完整模型。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

height, width, channels = 32, 32, 3
for patch_size in [8, 4]:
    patches = (height // patch_size) * (width // patch_size)
    values = patch_size * patch_size * channels
    tokens = patches + 1
    print(f"P={patch_size}: patches={patches}, values={values}, pairs={tokens ** 2}")
在本地运行
python visual-tokens-patch-budget.py

预期输出

P=8: patches=16, values=192, pairs=289
P=4: patches=64, values=48, pairs=4225

逐步读懂代码

第一行给三个尺寸变量赋值。循环对边长 8 与 4 重复计算。// 是整数除法,这里尺寸都能整除。加一是类别汇总词元。**2 表示词元数乘自身。得到 289、4225 对;并未实现注意力、学习投影或训练好的 Transformer。

这些知识可以用在哪里

细小文字

考虑一块落在大图像块内部的小标牌。小块可能保留更多细节,也增加比较量。需要同时评估读字质量与成本。

没有汇总词元

去掉 +1,模拟对图像块输出汇总的变体。P=8 时有 16 个词元、256 对。这属于不同设计。

类比的边界与注意事项. 交换描述的类比,并不意味着词元像人一样推理,也不意味着注意力权重可以解释每次判断。简化计算针对嵌入宽度固定的普通全注意力。其他架构可以使用窗口、池化或不同词元设计,而更细的图像块本身也不保证识别更好。
记住这一点

明确计算图像块与特殊词元:更细的视觉单元会增加可能的交互,而有用细节与实际运行时间仍需测量。

↑ 返回知识框架
CV / 14Leon

缺陷检查:发现异常区域

进阶

01 / 从一个完整故事开始

陶艺社拍摄课堂制作的瓷片。大多数平整,少数有暗色缺口。Elena 先将每张照片与典型完好瓷片比较,一个区域差异很大,值得仔细看。但两次拍摄间台灯移动,也让完好瓷片显得异常。

她固定相机与灯光,查看剩余差异,再将可疑区域交给人检查。团队不再把每个异常像素都叫缺陷。比较有了明确任务:建议看哪里;真实缺口与无害变化样本则用来判断建议是否有用。

02 / 理解核心概念

缺陷是任务定义的问题,例如瓷片缺口。异常是与参考数据不同的情况。两者不相同:陌生但无害的花纹也可能异常。异常检测系统可从正常样本学习典型外观,再对偏离程度评分。

最简单基线对对齐后的观察图与参考图逐点求绝对差,再用阈值把差值图变成候选区域。真实系统常比较学习特征而非原始像素。应使用独立采集的正常及缺陷样本,同时评估整图警报与可疑区域位置。

先认识这些词

异常
与参考样本不同的观察。
缺陷
由实际检查任务定义的问题。
基线
作为比较起点的简单方法。

03 / 如何运用这个概念

可用异常图辅助检查纹理或物体。必须区分异常外观与已确认缺陷。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

dᵢ = |observedᵢ − referenceᵢ|
flagᵢ = dᵢ > threshold
image_score = max(d₁, …, dₙ)

i 选择一个对应像素或特征。dᵢ 是其非负绝对差。n 为比较位置数。max 取最大差。flag 是是或否判断。所有值必须使用同一尺度,并在空间上对齐。

06 / 一步步算出结果

参考 [10,10,10,10] 与观察 [11,9,25,10] 得到差 [1,1,15,0]。阈值 5 只标记从零计数的位置 2,整图分数为 15。若光照给每个值都加 8,即使没有局部缺口,也可能四处都被标记。

一步步想清楚

  1. 为什么区分异常与缺陷?

    新的无害图案可以很异常,细微缺陷也可能看着熟悉。

  2. 为什么标记位置二?

    该处 |25−10|=15,是唯一超过 5 的差。

  3. 更亮的灯会触发警报吗?

    会。即使没有损伤,全局亮度变化也影响原始像素差。

运行一个完整示例 · 01

找到最大变化

用四位置窄条单独说明差异图。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

reference = [10, 10, 10, 10]
observed = [11, 9, 25, 10]
differences = [abs(x - r) for x, r in zip(observed, reference)]
flagged = [i for i, value in enumerate(differences) if value > 5]
print("differences:", differences)
print("flagged positions:", flagged)
print("image score:", max(differences))
在本地运行
python defect-inspection-difference-inspection.py

预期输出

differences: [1, 1, 15, 0]
flagged positions: [2]
image score: 15

逐步读懂代码

zip 配对对齐位置。abs 去掉差值符号。第二个列表推导式用 enumerate 保留位置编号,用 if 仅留下大于 5 的值。max 得分 15。高差异只表明复核候选,不证明存在缺陷。这是使用手设参考的未训练基线。

这些知识可以用在哪里

无害变化

使用 observed=[12,11,9,10]。差为 [2,1,1,0],该阈值不发警报。

光照改变

使用 observed=[18,18,18,18]。每个位置都被标记。说明为何调模型前需要控制拍摄条件。

类比的边界与注意事项. 像素差对对齐和光照敏感。MVTec AD 是工业异常检测研究数据集,不能证明这个小规则满足实际检测要求。
记住这一点

可用异常图辅助检查纹理或物体。必须区分异常外观与已确认缺陷。

↑ 返回知识框架
CV / 15Leon

用下一次采集检验,而非只看熟悉的邻帧

进阶

01 / 从一个完整故事开始

野生动物社训练相机图像筛选器,并庆祝很高测试分。Sam 发现同一次连拍的相邻帧同时出现在训练与测试文件夹。测试虽是新文件,却几乎是已见图片的副本。

他将每次连拍放在同组,并另外留出整处相机位置检查。分数下降,尤其是夜间。他统计正确警报、错误警报与漏掉动物,不再只给百分比。新测试更难,却能回答新相机上的实用问题,也指出哪些条件需要更多例子或人工复核。

02 / 理解核心概念

评估考察系统面对没有学过的样本时的表现。训练集用于调整权重,验证集用于选择设置,最终测试集用于报告结果。近似重复图像和相关拍摄场次应放在同一部分。否则即使文件名是新的,测试内容也可能早已熟悉。

分组应符合未来使用情境。测试新相机时留出整台相机的数据,测试新物体时按物体身份留出。训练与使用之间的光照、视角或相机变化称为领域偏移。一种条件下表现好,不代表另一条件也好。

对于是或否任务,真正例是正确警报,假正例是错误警报,假负例是漏检。精确率问警报中多少正确,召回率问真实目标中找到多少。要同时报告计数与比例,并单独检查重要条件。

先认识这些词

精确率
正确警报数除以全部警报数。
召回率
找到的真实目标数除以全部真实目标数。
信息泄漏
测试信息进入模型开发,使评估产生误导。
领域偏移
开发与使用时的数据条件不同。

03 / 如何运用这个概念

划分数据前,先写清未来使用问题:熟悉相机的新帧、新采集时段,或全新地点,需要不同证据。据此对相关采集记录分组,并保留最终测试集。用验证数据选择阈值,按相关条件检查错误,同时报告总体与各组的计数。应明确测量新环境中的表现,而不是假设一个汇总分数能够原样迁移。

04 / 官方教程与研究中的应用

WILDS 研究基准包含野生动物监测中跨野外自动相机的分布偏移,提供了图像来源为何重要的具体记录。scikit-learn 的 GroupKFold 文档展示如何在训练折与测试折之间保持组身份分离,其评估指南则定义了精确率与召回率。这些资料支持考虑采集来源的评估方式;单凭分组划分,仍不能证明模型能适应未来每一台相机或每一个季节。

05 / 公式与逐项解析

Precision = TP / (TP + FP)
Recall = TP / (TP + FN)
F₁ = 2TP / (2TP + FP + FN)

TP 是真正例:有野生动物且正确警报的图。FP 是假正例:空图却错误警报。FN 是假负例:有动物却漏掉的图。这里数图像,不数像素或框。F₁ 组合精确率和召回率;给出的计数公式无需先学另一平均规则。比较时应固定决策阈值。若分母为零,要说明指标怎样处理并报告计数。

06 / 一步步算出结果

假设一个自拟留出组包含 100 张图片,其中 20 张有野生动物,80 张为空场景。在选定阈值下,模型对 25 张图片发出提示,其中 15 张确有动物,因此 TP = 15、FP = 10。它漏掉了 5 张有动物的图片,所以 FN = 5。精确率为 15 / 25 = 0.60,召回率为 15 / 20 = 0.75,F₁ 为 30 / 45,约为 0.6667。

其余 70 张空场景被正确排除。因此总体准确率为 85 / 100 = 85%,但单看这个数字,会掩盖十次误报与五次漏报。这些计数描述的是该抽样组在该阈值下的表现,不能保证模型在未见过的相机地点上也有同样表现。

一步步想清楚

  1. 为什么把相邻视频帧放在同一组?

    它们共享大量场景。拆开后可能测到的是识别近似副本,而非泛化。

  2. 召回率为什么是 15/20?

    找到 15 张动物图、漏掉 5 张,因此真实正例共 20 张。

  3. 错误警报翻倍会改变召回率吗?

    若真正例和漏检数不变,召回率仍是 0.75。精确率降为 15/35≈0.4286。

运行一个完整示例 · 01

统计警报与漏检

从一个留出组的明确计数计算指标。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

true_positive, false_positive, false_negative = 15, 10, 5
precision = true_positive / (true_positive + false_positive)
recall = true_positive / (true_positive + false_negative)
f1 = 2 * true_positive / (2 * true_positive + false_positive + false_negative)
print(f"precision={precision:.2f}; recall={recall:.2f}; F1={f1:.4f}")
sessions = {"train": {"morning-A", "evening-A"}, "test": {"morning-B"}}
print("shared sessions:", len(sessions["train"] & sessions["test"]))
在本地运行
python evaluation-shift-evaluate-alerts.py

预期输出

precision=0.60; recall=0.75; F1=0.6667
shared sessions: 0

逐步读懂代码

第一行设置三种计数。每个除法使用该指标对应的总体。字典将划分名称映射到拍摄场次集合。& 求集合交集,len 报告共享场次为零。这只检查一种分组规则,不能排除所有可能的泄漏。这里没有模型训练。

这些知识可以用在哪里

隐藏的重复

把 morning-A 也放入测试集。共享场次计数变为一。解释测试分数前先消除重叠。

夜间检查

分别报告白天与夜晚计数。若夜间测试图很少,优秀白天均值不能证明夜间可靠。

类比的边界与注意事项. 陌生照片的类比有助于发现泄漏,但陌生程度有多个维度。不同采集时段仍可能共享同一相机背景,新地点也可能改变类别频率。小评估组的估计具有不确定性;应始终保留样本数量,避免把一次划分当成通用性能证明。
记住这一点

可信的视觉分数应明确目标总体,隔离相关采集记录,并揭示平均值背后的误报与漏报。

↑ 返回知识框架
CV / 16Leon

推理流程:检查完整处理过程

进阶

01 / 从一个完整故事开始

机器人社做完一个小图像模型,在较慢笔电上试用。模型内部计算很快,结果却总是很迟。Imani 测量完整过程:调整图像大小、运行模型、把框转回原图。她还发现只有一台电脑改了分数阈值。

社团写下统一输入输出规范,再比较使用较小数值表示前后的准确性。只有通过核实样本才保留快版本。速度变成与正确性一起测量的目标,而不是跳过正确性检查的理由。

02 / 理解核心概念

推理是使用固定模型权重作预测。流程包括预处理、模型计算和后处理。只有这些步骤在通道、尺寸、坐标、阈值上保持一致,输出才可能正确。

延迟是一个请求等待的时间,吞吐量是每秒完成多少请求。两者不同:批处理可能提高吞吐量,同时让某个请求等待更久。量化使用更少种可能数值存储或计算。合适硬件上可降低成本,但舍入会改数,也可能改变决策。优化后必须重新评估完整流程。

先认识这些词

推理
使用当前固定权重作预测。
延迟
一个请求等待结果的时间。
量化
用更少种可能数值表示数据。
吞吐量
单位时间完成的请求数量。

03 / 如何运用这个概念

围绕模型建立可复现的预处理与后处理。用实际预期输入比较输出质量、延迟和内存。

04 / 官方教程与研究中的应用

用下面的原创计算与 Python 代码观察机制。所链接的一手资料解释完整系统怎样扩展这些机制。故事是虚构的教学情境。

05 / 公式与逐项解析

quantized = round(value / step)
restored = quantized × step
error = |restored − value|
serial_latency = preprocess_time + model_time + postprocess_time

step 是简单零偏移量化器可表示数值的间隔。round 取最近整数;本例忽略裁剪与数据类型限制。error 是绝对变化。延迟求和假设三步骤依次运行、不重叠,并使用相同时间单位。

06 / 一步步算出结果

value=0.26、step=0.1 时,0.26/0.1=2.6,舍入为 3,恢复为 0.3,误差 0.04。4、7、2 毫秒的阶段时间相加为 13 毫秒。若只有模型时间降为 4,总延迟为 10,而非 13 的一半。

一步步想清楚

  1. 为什么不能只测模型计算?

    图像准备和输出转换也消耗时间。用户要等全部必要步骤完成。

  2. 0.26 如何重建为 0.3?

    除以 0.1 得 2.6,舍入为 3,再乘 0.1。

  3. 数值误差很小就保证标签相同吗?

    不能。靠近决策阈值的分数可能因小变化而跨过阈值。

  4. 为什么先减补边,再除缩放比例?

    正向先缩放,再加补边。逆运算需要倒转顺序;顺序错误会造成框的偏移。

运行一个完整示例 · 01

检查舍入与总延迟

使用明确的假设时间,并非实测。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

values = [0.12, 0.26, 0.91]
step = 0.1
encoded = [round(value / step) for value in values]
restored = [number * step for number in encoded]
errors = [abs(a - b) for a, b in zip(values, restored)]
print("encoded:", encoded)
print("restored:", [round(x, 2) for x in restored])
print(f"largest error={max(errors):.2f}")
print("serial latency ms:", sum([4, 7, 2]))
在本地运行
python inference-pipelines-pipeline-budget.py

预期输出

encoded: [1, 3, 9]
restored: [0.1, 0.3, 0.9]
largest error=0.04
serial latency ms: 13

逐步读懂代码

第一个推导式把每个数除以间隔再舍入。第二个通过乘法重建近似数。zip 将原值和重建值配对,abs 测误差。显示时保留两位小数只影响展示。sum 累加三个假设阶段时间。这个教学量化器没有转换或运行训练模型。

运行一个完整示例 · 02

把检测框还原到原始图像

米娜把宽幅相机图像放入正方形模型输入:先缩放,再补边。模型找到了物体,可直接把模型坐标画回原图时,框却错位了。她记录实际缩放尺寸和补边量,按相反顺序还原坐标,并先做往返测试,再相信显示结果。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

from math import isclose

def metadata(width, height, side):
    if min(width, height, side) <= 0:
        raise ValueError("positive dimensions required")
    nominal = min(side / width, side / height)
    rw, rh = max(1, round(width * nominal)), max(1, round(height * nominal))
    return rw / width, rh / height, (side - rw) // 2, (side - rh) // 2

def forward(box, meta):
    sx, sy, px, py = meta
    x1, y1, x2, y2 = box
    return [sx * x1 + px, sy * y1 + py, sx * x2 + px, sy * y2 + py]

def restore(box, meta, width, height):
    sx, sy, px, py = meta
    x1, y1, x2, y2 = box
    restored = [(x1 - px) / sx, (y1 - py) / sy,
                (x2 - px) / sx, (y2 - py) / sy]
    clipped = [min(max(v, 0.0), limit)
               for v, limit in zip(restored, [width, height, width, height])]
    return clipped if clipped[0] < clipped[2] and clipped[1] < clipped[3] else None

original = [100.0, 50.0, 500.0, 300.0]
meta = metadata(800, 400, 640)
model_box = forward(original, meta)
restored = restore(model_box, meta, 800, 400)
assert all(isclose(a, b) for a, b in zip(original, restored))
odd_meta = metadata(853, 480, 640)
odd_back = restore(forward(original, odd_meta), odd_meta, 853, 480)
assert all(isclose(a, b) for a, b in zip(original, odd_back))
assert odd_meta[0] != odd_meta[1]  # Rounded height changes the actual y scale.
assert restore([0, 0, 20, 20], meta, 800, 400) is None
print("scale and padding:", meta)
print("model box:", model_box)
print("original box:", restored)
print("rounded-size round trip:", all(isclose(a, b) for a, b in zip(original, odd_back)))
在本地运行
python inference-pipelines-letterbox-coordinate-roundtrip.py

预期输出

scale and padding: (0.8, 0.8, 0, 160)
model box: [80.0, 200.0, 400.0, 400.0]
original box: [100.0, 50.0, 500.0, 300.0]
rounded-size round trip: True

按执行顺序理解

  1. 800×400 的图像变成 640×320,上下各补 160 像素。

  2. 映射两个角点:[100,50,500,300] 变为 [80,200,400,400]。

  3. 先减补边,再除缩放比例,最后裁剪到原图的连续边界。

  4. 用 853×480 再测一次。取整后的实际尺寸可能导致横纵缩放比例略有不同。完全落在补边中、裁剪后面积为零的框应丢弃。

逐步读懂代码

连续框边界满足 x_prime=s_x×x+p_x、y_prime=s_y×y+p_y。s_x 和 s_y 分别是实际缩放后宽度/原宽度、实际缩放后高度/原高度。p_x、p_y 是左侧和顶部补边量。逆变换为 x=(x_prime-p_x)/s_x、y=(y_prime-p_y)/s_y。

第一张图的两个比例都是 0.8。模型中的 y=200 对应原图 (200-160)/0.8=50。853×480 取整后变成 640×360,此时 s_x=640/853,s_y=360/480。两个方向都用名义比例,会引入小误差。应保存预处理实际使用的变换。

本例使用 [0,width]、[0,height] 内的连续框边界,不是最大索引为 width-1、height-1 的整数像素坐标。实际库的像素中心和插值约定可能不同;裁剪、旋转或其他补边规则也需要记录对应变换。往返测试检验坐标记录是否自洽,不能证明检测模型准确。

这些知识可以用在哪里

更细间隔

将 step 改成 0.01。这些数能更精确重建,但相同范围内需要更多可表示级别。

主要耗时阶段

把假设时间改成 [20,4,2]。只加速模型无法消除 20 毫秒预处理成本。

类比的边界与注意事项. 代码演示舍入和假设阶段时间,不是硬件基准。ONNX Runtime 文档介绍真实量化选项;不同模型或设备的收益不同。
记住这一点

围绕模型建立可复现的预处理与后处理。用实际预期输入比较输出质量、延迟和内存。

↑ 返回知识框架

知识速查

01 / 像素是带有约定的测量值

在让模型学习像素之前,先明确每个接口的轴顺序、颜色含义、数值尺度与归一化约定。

02 / 颜色:先弄清通道

颜色筛选可选择标牌或为后续检查准备区域。信任掩码前要检查已知颜色、通道顺序与光照。

03 / 局部滤波提出局部问题

滤波器是用权重表达的局部问题;既要检查它去掉了什么,也要检查它保留了什么。

04 / 边缘与掩码:先找变化,再清理

可用这些操作清理扫描文字掩码或检查简单形状。每次清理前后都应比较细小但有用的细节。

05 / 让图像与标注一起移动

几何变化必须带着标注一起变化;每项增强都要说明,为什么标签含义仍然成立。

06 / 局部特征:比较有辨识度的图块

局部特征可支持全景对齐或将拍摄物体匹配到参考图。需要检查重复图案和几何一致性。

07 / 借用视觉词汇,再学习新的判断

迁移表示,仔细定义新的判断任务,再用真正独立的图像组衡量改进。

08 / 框定位候选对象,重叠度帮助消除重复

把框视为候选,精确定义重叠,并把去重规则与检测质量的判定规则分别验证。

09 / 检测分数:顺着排序列表评估

在明确匹配规则下使用排序评估比较检测器。应同时保存分数、框、类别与参考数量。

10 / 从一个矩形走向真正属于对象的像素

让掩码类型对应实际问题,检查占少数的前景,并明确重叠度与空掩码情况的评分方式。

11 / 跟踪:跨帧保留身份

跟踪可用于统计移动物体,或在视频中跟随玩具机器人和野生动物。应检查交叉、遮挡与漏检。

12 / OCR:把可见笔画变成文字

OCR 可用于扫描件搜索、标签识别与文档阅读。应保留原图区域及阅读顺序,便于核对不确定文字。

13 / 让图像块交换信息

明确计算图像块与特殊词元:更细的视觉单元会增加可能的交互,而有用细节与实际运行时间仍需测量。

14 / 缺陷检查:发现异常区域

可用异常图辅助检查纹理或物体。必须区分异常外观与已确认缺陷。

15 / 用下一次采集检验,而非只看熟悉的邻帧

可信的视觉分数应明确目标总体,隔离相关采集记录,并揭示平均值背后的误报与漏报。

16 / 推理流程:检查完整处理过程

围绕模型建立可复现的预处理与后处理。用实际预期输入比较输出质量、延迟和内存。

官方教程与原始研究

故事、讲解与计算例子由 Leon 原创编写。链接中的教程和研究属于各自作者;本资料为独立学习笔记,并非官方译本或官方认可课程。

  1. 图像基本操作 ↗OpenCV · Official tutorial
  2. 图像平滑 ↗OpenCV · Official tutorial
  3. 图像滤波:filter2D ↗OpenCV · Official reference
  4. 图像几何变换 ↗OpenCV · Official tutorial
  5. 计算机视觉迁移学习 ↗PyTorch · Official tutorial
  6. TorchVision 目标检测微调教程 ↗PyTorch · Official tutorial
  7. 非极大值抑制 ↗TorchVision · Official reference
  8. 使用 ResNet-50 主干的 FCN ↗TorchVision · Official reference
  9. 变换图像、视频、框等数据 ↗TorchVision · Official tutorial
  10. CS231n:计算机视觉深度学习 ↗Stanford University · University course
  11. 一幅图像相当于 16×16 个词 ↗Dosovitskiy et al. · Research paper
  12. 指标与评分:量化预测质量 ↗scikit-learn · Official guide
  13. VisionTransformer 实现 ↗TorchVision · Official reference
  14. 注意力就是你所需要的一切 ↗Vaswani et al. · Research paper
  15. GroupKFold:组间不重叠的交叉验证 ↗scikit-learn · Official guide
  16. WILDS:真实环境中的分布偏移基准 ↗Koh et al. · Research paper
  17. 转换色彩空间 ↗OpenCV · Official guide
  18. 形态学变换 ↗OpenCV · Official guide
  19. SIFT 入门 ↗OpenCV · Official guide
  20. COCO 官方评估实现 ↗COCO authors · Official implementation
  21. 简单在线实时跟踪 ↗Bewley et al. · Research paper
  22. 改善输出质量 ↗Tesseract · Official guide
  23. MVTec AD 异常检测数据集 ↗MVTec · Official dataset documentation
  24. 量化 ONNX 模型 ↗ONNX Runtime · Official guide
  25. 基准评估:像素级 IoU 与忽略标签 ↗Cityscapes · Official reference