计算机视觉第一周:卷积原理、代码实践与习题拆解 📅 发布时间:2026/9/9 13:39:39 👁 浏览次数: 作为过来人我先说句实在话“计算机视觉 第一周卷积基础知识”这个标题几乎是所有人入门CV的第一道坎也是第一座分水岭。很多同学在学到这里时感觉PPT上全是矩阵、箭头和公式代码一跑全是报错课后习题更是不知道从哪里下手。这篇文章我就结合第一周的核心知识点把卷积的数学原理、代码实践含可直接复现的Python代码以及课后习题的拆解思路一次性讲透帮你把“看得懂”变成“写得出来、讲得清楚”。如果你是刚打开计算机视觉大门的新手或者正在为第一次作业发愁这篇文章就是按你需要的顺序写的哪怕你已经对卷积有了点模糊的认识我也建议把代码部分亲手敲一遍因为“你以为你会了”和“你真有本事让卷积在你自己的电脑上跑起来”之间差的就是一个完整的上机实践。1. 整体学习思路与第一周核心目标拆解1.1 为什么第一周一定要死磕卷积很多初学者会问计算机视觉不是搞人脸识别、自动驾驶、目标检测的吗怎么第一周就在讲卷积我的回答很简单卷积是当前几乎所有主流视觉模型的基本组成单元。不管后面学ResNet、YOLO还是Transformer系列你都要和一个叫“卷积层”的东西打交道。第一周把卷积的“魂”抓住后面你看到任何网络结构图看到的都不是一堆方块而是一连串有明确物理意义的特征提取过程。1.2 第一周的三个核心目标按照我带人入门的经验第一周不需要你去看长篇论文也不要急着调库训练模型。你需要死磕三件事理解卷积的数学本质和图像意义从“加权求和”的角度理解卷积核如何扫描图像知道常见的平滑、锐化、边缘检测卷积核为什么长那样。掌握卷积层的关键基本概念卷积核尺寸Kernel Size、步长Stride、填充Padding、通道数Channels、特征图Feature Map尺寸计算公式。这五个概念是你后面调试所有CNN模型的基础。用Python从零手写卷积过程不要一开始就用torch.nn.Conv2d完事。我强烈建议先用NumPy手写一个针对单通道、单卷积核的二维卷积再扩展到多通道、多卷积核最后再用PyTorch实现并对比结果。这周基础打牢了后面学激活函数、池化、经典网络结构时你会觉得像爬楼梯一样顺。我用带新手最直观的一个类比来帮你建立感性认识卷积核就像你拿一个手电筒在图像上滑动照过但照到的每个区域都会和一个“模板”卷积核做加权匹配匹配后的结果就是新图像上的一个像素。后面你会发现这其实就是“模板匹配”思想的延伸。2. 卷积基础知识解析从连续到离散从公式到直觉2.1 卷积的数学定义与图像领域简化先看数学定义。一维连续卷积的公式是[ (f * g)(t) \int_{-\infty}^{\infty} f(\tau) g(t - \tau) d\tau ]刚接触这个公式时你可能会觉得头晕又是积分又是翻转的跟图像有什么关系其实在数字图像处理里我们用的是二维离散卷积公式长这样[ (f * g)(i, j) \sum_{m} \sum_{n} f(m, n) \cdot g(i - m, j - n) ]但在深度学习的实际应用中我们一般用的是“互相关”也就是卷积核不做翻转直接在图像上滑动点乘求和。严格来说它们在数学上有差异但在CNN里大家习惯上都叫“卷积”。这是很多教材没讲明白、但考试和面试都爱问的细节。你只要记住深度学习框架里的“卷积层”实际做的是互相关运算它没有真正翻转卷积核因为卷积核是学习出来的翻转与否都能通过训练学到等价的效果。2.2 单通道二维卷积手算示例为了把“加权求和”这件事彻底搞懂我们来看一个具体的例子。假设有一张5x5的输入图像卷积核是3x3输入图像[[ 1, 2, 3, 0, 1], [ 4, 5, 6, 1, 2], [ 7, 8, 9, 2, 3], [ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9]]卷积核比如一个垂直边缘检测器[[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]假设步长stride1padding0。那么这个卷积核首先覆盖输入图像左上角的3x3区域即[[1, 2, 3], [4, 5, 6], [7, 8, 9]]然后逐元素相乘再求和[ (1 \times -1) (2 \times 0) (3 \times 1) (4 \times -2) (5 \times 0) (6 \times 2) (7 \times -1) (8 \times 0) (9 \times 1) -1 0 3 - 8 0 12 - 7 0 9 8 ]所以输出特征图左上角的第一个像素值就是8。按这个逻辑卷积核从左到右、从上到下以步长1滑动每次计算一个输出像素。最终输出特征图尺寸是多少用公式算[ H_{out} \left\lfloor \frac{H_{in} 2 \times padding - kernel_size}{stride} \right\rfloor 1 ]代入数值( (5 0 - 3) / 1 1 3 )。所以输出是3x3的特征图。这个公式非常关键后面所有CNN的结构计算都用得到我建议你把它记在笔记的第一页。2.3 卷积在图像上到底做了什么从上面的手算例子可以看出每滑动一次卷积核就是在提取当前位置相邻像素间的某种特征关系。比如Sobel算子能检测边缘是因为它中心附近权重有正有负平坦区域的加权和接近0因为正负抵消而边缘区域因为有灰度的突变结果值就会比较大正或负。后续网络中我们会在图像特征图上不断叠加卷积层输入是原图输出是“特征响应图”网络越深提取到的特征越抽象。第一周你不必完全明白深层特征的含义但你必须建立这个直觉卷积核是对局部区域的模式做匹配。这一点想通了其他都好办。3. 课后习题核心考点与常见题型拆解3.1 必考1特征图尺寸计算不管是测验还是第一次大作业几乎必考一题给定输入尺寸、卷积核大小、padding、stride求输出尺寸。我直接给你一个极简的思考顺序先算“卷积核在输入上能放下多少个位置”。如果padding0stride1kernel3输入5x5那就是(5-313)个位置输出3x3。如果padding1输入仍然是5x5kernel3stride1那边缘各补一圈0输入“变成”7x7输出就是(7-315)尺寸不变。如果stride2输入5x5kernel3padding0那就是((5-3)/212)输出2x2。习题里常见的陷阱是“padding的加法是两边同时加”。很多人记padding1时直接在原尺寸上1其实等价于原尺寸2。我建议你在草稿纸上画一画“带padding后的输入长什么样”画一次就忘不了。还有一类题目问你在某一层之后特征图的通道数是多少。规则很简单通道数等于这一层卷积核的个数跟输入通道数无关——每个卷积核负责生成一张独立的特征图。举个例子如果输入是64x64x3RGB图像这一层有8个卷积核那么输出就是64x64x8假设尺寸不变。这题是送分题千万别错。3.2 必考2卷积层的参数量计算第二个高频考点是给定输入通道数、卷积核尺寸、输出通道数求该卷积层的参数量不算偏置和计算量。参数量公式很简单[ 参数量 输入通道数 \times 卷积核高度 \times 卷积核宽度 \times 输出通道数 ]例如输入3通道卷积核3x3输出16通道那么参数量就是(3 \times 3 \times 3 \times 16 432)。如果算偏置就再加上输出通道数因为每个输出通道共享一个偏置变成448。计算量稍微复杂一点但也很机械[ 计算量 输出特征图高度 \times 输出特征图宽度 \times 输入通道数 \times 卷积核高度 \times 卷积核宽度 \times 输出通道数 ]为什么这么算因为输出特征图上的每一个像素都需要做“输入通道数×卷积核高×卷积核宽”次乘加运算。这个也建议你动手推一次把“输出的每一个像素是怎么来的”在纸上画出来比背公式有效得多。考试时这一题通常是计算量最大的但只要逻辑对了就不会错。3.3 必考3感受野与边界效应第三类常见题是概念辨析题什么是感受野为什么需要padding卷积层的输出尺寸相比输入变小了信息是否会丢失这类题答的时候要有层次。感受野指的是输出特征图上一个像素点对应到输入图像上的区域大小。对于连续多个卷积层感受野的计算要逐层反推但第一周通常只会考单层感受野即卷积核本身的大小。padding的核心作用有两个一是控制输出特征图尺寸防止逐层缩小得太多二是更好地保留边缘信息——如果不padding边缘像素被卷积核覆盖到的次数远少于中心像素那边缘信息天然就被弱化了。这就是“边界效应”。你答到“边缘像素参与计算次数少”这个点老师就知道你真懂了。3.4 原理解答题卷积为什么能提取特征最后一类常见问答题是为什么通过卷积神经网络能够提取图像的局部特征怎么理解“局部感知”和“参数共享”这里我给你一个直接能用的回答框架局部感知卷积核每次只覆盖输入的一小片区域因此每个输出像素只与输入的一个局部邻域相关这符合图像中邻近像素关联紧密、远处像素关联弱的自然特性。权重共享同一个卷积核在整张图上滑动时它的参数是固定的。也就是说同一个特征检测器被应用到了所有位置——不管是一只猫出现在图片的左上角还是右下角同一组卷积核都能识别到相似的局部模式。这大幅降低了参数量相比全连接层也使模型具备一定的平移不变性。把多个不同的卷积核叠在一起网络就能在每一层提取多种不同的局部特征边缘、纹理、颜色变化等层数加深后高层特征就由低层特征组合而成最终实现从像素到语义的映射。以后再有“为什么CNN比全连接网络更适合图像”之类的题你直接按这三点答基本不会扣分。4. 代码实践从零实现并用PyTorch验证4.1 环境准备与建议第一周代码实践我用的是Python NumPy PyTorch。PyTorch是现在学术界和工业界用得最广的框架之一遇到问题好搜、好学、好问。关于环境你可以直接pip install torch torchvision numpy matplotlib。至于用不用GPU——第一周完全不需要CPU跑这个实践绰绰有余。4.2 用NumPy手写二维卷积Scipy的signal.convolve2d其实也行但建议自己写我建议你至少自己写一次循环实现的卷积这样对“滑动窗口”的理解会彻底到位。下面这段代码接收一个二维输入和一个二维卷积核返回特征图import numpy as np def conv2d_numpy(image, kernel, padding0, stride1): # image: 2D array # kernel: 2D array # 这里假设 kernel 是奇数尺寸方便计算中心 if padding 0: image_padded np.pad(image, pad_widthpadding, modeconstant, constant_values0) else: image_padded image H, W image_padded.shape kh, kw kernel.shape H_out (H - kh) // stride 1 W_out (W - kw) // stride 1 output np.zeros((H_out, W_out)) for i in range(H_out): for j in range(W_out): region image_padded[i*stride:i*stridekh, j*stride:j*stridekw] output[i, j] np.sum(region * kernel) return output # 测试用上一节手算的例子 image np.array([ [1, 2, 3, 0, 1], [4, 5, 6, 1, 2], [7, 8, 9, 2, 3], [0, 1, 2, 3, 4], [5, 6, 7, 8, 9] ], dtypenp.float64) kernel np.array([ [-1, 0, 1], [-2, 0, 2], [-1, 0, 1] ], dtypenp.float64) out conv2d_numpy(image, kernel, padding0, stride1) print(out) print(输出尺寸:, out.shape)你运行一下左上角第一个值就是8。这一步很重要把程序跑出来的结果和你手算的结果作对比如果一致说明你对卷积实现的理解完全正确。如果还没画过图我个人建议用matplotlib把原图和输出特征图画出来尤其是边缘检测核的输出你会发现特征图上亮的地方正好是原图里灰度突变的位置。这种直观的反馈比看十遍公式都管用。我在带人学的时候发现很多新手不画图只看数值学过就忘——所以这个环节务必亲自做一遍。4.3 矩阵乘法视角从嵌套循环到im2col手写循环版优点是直观缺点是太慢而且不利于你理解GPU为什么适合深度学习。这里我推荐你了解一下im2col思想把卷积操作转化为大矩阵乘法。简单说就是把每个卷积核要覆盖的局部区域拉平成向量拼成一个矩阵再把卷积核也拉平成向量作为另一个矩阵二者做矩阵乘法就一次性算出所有输出值。这个技巧在很多底层优化库里都在用。你第一周不一定非要用它写代码但知道这个思路后再看torch.nn.Unfold、F.conv2d的用法会有一种“原来如此”的感觉。4.4 用PyTorch实现Conv2d并验证结果手写循环验证之后我们再来看PyTorch怎么一行搞定。import torch import torch.nn as nn # 将numpy数组转为PyTorch张量形状为 (batch, channels, height, width) image_tensor torch.tensor(image, dtypetorch.float32).unsqueeze(0).unsqueeze(0) kernel_tensor torch.tensor(kernel, dtypetorch.float32).unsqueeze(0).unsqueeze(0) # 使用 nn.Conv2d手动设置权重并关闭偏置 conv_layer nn.Conv2d(in_channels1, out_channels1, kernel_size3, stride1, padding0, biasFalse) with torch.no_grad(): conv_layer.weight.copy_(kernel_tensor) output_tensor conv_layer(image_tensor) print(output_tensor)结果应该和NumPy版完全一致。这里你可能会好奇nn.Conv2d的weight形状为什么是(out_channels, in_channels, kh, kw)。因为它要一次性处理多输入通道、多输出通道的情况。我建议你课后自己再写一个多通道版本比如模拟一个RGB三通道输入、使用两个卷积核的情况输入形状为(1, 3, H, W)看看输出的形状是不是(1, 2, H_out, W_out)并手动验证一下“参数量 3×3×3×2”这个公式。4.5 特征图可视化为了把“卷积提取特征”这个抽象概念落到具体感受上我强烈建议你做一次可视化。用matplotlib画一个灰度图你可以自己用skimage.data.camera()加载一张相机图或者任何你喜欢的图片然后用三种不同的经典卷积核分别处理它水平Sobel检测水平边缘、垂直Sobel检测垂直边缘、高斯核做平滑。代码大概长这样import matplotlib.pyplot as plt from skimage import data from scipy.signal import convolve2d img data.camera() sobel_x np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) sobel_y np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) gaussian (1/16) * np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) fig, axes plt.subplots(2, 2, figsize(8, 8)) axes[0, 0].imshow(img, cmapgray); axes[0, 0].set_title(Original) axes[0, 1].imshow(convolve2d(img, sobel_x, modesame), cmapgray); axes[0, 1].set_title(Sobel X) axes[1, 0].imshow(convolve2d(img, sobel_y, modesame), cmapgray); axes[1, 0].set_title(Sobel Y) axes[1, 1].imshow(convolve2d(img, gaussian, modesame), cmapgray); axes[1, 1].set_title(Gaussian) plt.tight_layout() plt.show()你会在画面上清晰地看到水平边缘和垂直边缘的差异。这是第一周最直观的成就感来源也是你以后理解卷积层可视化的重要铺垫。很多同学做完这一步以后再看卷积网络里的特征图心里就不慌了。5. 常见报错、排错技巧与避坑经验5.1 维度不匹配是新手最大的坑代码实践中最常见的错误就是Expected stride to be a single integer value or a list of 1 values to match the convolution dimensions, but got stride...这类和张量形状有关的报错。我见过太多新手卡在这一步其实多数是因为PyTorch的输入要求是4D张量(N, C, H, W)而你把二维图像直接传进去了。解决办法很简单先image_tensor torch.tensor(image).unsqueeze(0).unsqueeze(0)一步步把形状从(H, W)变成(1, 1, H, W)。每写一步torch.Size打印一下确认没问题再做下一步这是所有深度学习调参的基本功。5.2 手写卷积结果和PyTorch结果不一致如果手写循环的数值和PyTorch算的不一致排查顺序如下检查是否写成了对卷积核翻转的版本。上面提过深度学习里的“卷积”实际是互相关如果你手动实现了翻转可以和PyTorch误差较大除非卷积核本身对称。检查数据精度。NumPy默认是float64PyTorch默认是float32精度差异可能导致最后几位小数不同用np.allclose判断而不是用。检查初始化。如果直接用torch.nn.Conv2d而不复制权重卷积核是随机初始化的那和手写核当然对不上。你需要在torch.no_grad()下用.copy_()把核覆盖掉。5.3 关于填充计算的几个常见误解做课后习题时我发现很多人容易把“padding1”理解为“只在一侧加一行0”。实际上padding1是在上下左右各加1行/列0相当于把输入的H和W各增加2。举一个最容易搞混的场景输入5x5kernel3padding1stride1输出是多少按公式((5 2 - 3) / 1 1 5)输出5x5。如果你的答案不是5说明填充理解有偏差建议画个带0边的图重新算。另外PyTorch中还有一个padding_modereplicate可以让你选择填充方式复制边缘像素而非填0如果以后做图像补全、分割任务时会很有用第一周可以先不深究。5.4 计算资源不充裕时的建议第一周的代码量很小不需要GPU。但如果你在本地安装PyTorch遇到网络慢或版本兼容等问题一个建议是使用Google Colab或国产的Kaggle镜像环境——浏览器里就能跑省去一堆环境折腾的时间。我自己第一次带学生做这个作业时发现至少三分之一的时间花在了环境安装上。我的建议是能用云环境跑通流程就先用云环境等代码完全跑通了再回本地解决环境问题。这样写代码的进度不会卡在安装上。6. 第一周进阶从单层卷积到卷积神经网络的桥接6.1 理解“卷积核是学出来的”手写固定核Sobel、高斯是帮你建立直觉但深度学习的核心在于卷积核不是人设计的而是通过反向传播从数据中学出来的。第一周的末尾我建议你做一个小实验用随机初始化卷积核来卷积一张图观察输出特征图的样子再训练一个极小的网络输入图像输出二分类后再看最终卷积核长什么样。你会发现训练后的卷积核会呈现某种有序的边缘/纹理模式这就是“学习”的意义。如果你时间充裕还可以看看网上分享的“第一层卷积核可视化”图片几乎都能看到类似边缘和颜色检测器的模式。这个认识的转变是你从“明白卷积”走到“明白神经网络”的重要一步。6.2 第一周习题的延伸思考题在课后习题的最后通常会有一些开放思考题比如“如果卷积核尺寸为1x1它到底在做什么”这里我先给个提示实际动手也是一种乐趣对单张RGB图像来说1x1卷积是在做“逐像素的跨通道线性组合”也就是混合每个位置的三原色通道类似一个全连接层作用在每个像素上。现在很多网络利用1x1卷积来调整通道数实现“瓶颈”结构。如果你能把这个问题想明白说明你对卷积的理解已经超过“扫描窗口”这个层次了。还有一道题也经常被拿出来讨论为什么很多现代网络喜欢用2个连续的3x3卷积而不是1个5x5卷积这不只是为了减少参数还因为两个3x3卷积叠加后感受野是5x5但中间多了一层非线性变换表达能力更强。这道题第一周可能做不出来但先留个印象以后学VGG时会豁然开朗。6.3 给代码实践做的最后加分项如果第一周作业搓完还有余力我建议你试一次把“手写卷积”改成“批量处理多张图、多通道、多卷积核”的版本。也就是让你的函数支持输入形状为(N, C, H, W)的NumPy数组并返回(N, F, H_out, W_out)的输出。完成这一步后你会发现PyTorch的nn.Conv2d不过是一个把你的实现高度优化并支持反向传播的封装而已。到了后面学习nn.Sequential、nn.Module时你对网络内部结构会非常有底气。第一周能写到这个程度已经比同批次很多人强了。7. 典型习题速查表与排错参考我整理了一张速查表你可以贴在笔记里。做题、调试代码时遇到不确定的回来对一眼就好。关键公式速查项目公式示例输入5x5核3x3stride1padding0输出尺寸((H_{in} 2p - k) / s 1)((50-3)/11 3)参数量不算偏置(C_{in} \times k_h \times k_w \times C_{out})3通道输入、3x3核、16输出 (3\times3\times3\times16432)偏置数(C_{out})16输出通道数卷积核个数16常见报错速查报错关键词原因解决方法Expected 4-dimensional input输入不是(N, C, H, W)形状用.unsqueeze()增加dimsize mismatch权重形状或输入输出配合不上打印.shape逐步排查device mismatch数据和模型在不同设备CPU/GPU统一.to(cuda)或.to(cpu)NaN出现在损失里学习率过大或数据未归一化检查输入范围降低学习率习题答题模板原理解答题先说结论比如“卷积通过局部加权求和提取局部特征”再分点解释局部感知、参数共享、多核提取最后配合公式或手算例子说明如果有必要写一句边界效应或padding的处理不要只答名词解释要联系卷积层的实际运算流程这张表我建议你保存下来。以后到了深度学习大作业阶段回来的频率会相当高。8. 个人实操总结与后续学习建议第一周的课和习题说到底就是帮你建立“卷积是一种局部特征提取操作”的核心直觉并且让你能够亲手写出来、跑起来。我在实际带人的过程中发现凡是肯静下心把NumPy手写卷积和PyTorch卷积对比验证一遍的人后面学BatchNorm、Dropout、ResNet时都顺很多而那些直接调库、从不看内部实现的人往往在第一次调参时束手无策甚至连报错都看不懂。这也是我为什么在这篇文章里反复强调“手推手算”的原因。最后再分享一个小技巧学卷积别只看公式一定要“动手加动笔”。你可以找一张老照片先用Sobel算子画出边缘图再用随机卷积核提取特征并对比差异。这个过程会让你真切感受到“卷积核里的参数决定了提取什么特征”也会让你理解为什么训练网络时反向传播能自动调出好用的核。后续你可以顺着这个方向去学习Stride与Padding的进阶用法、空洞卷积Dilated Convolution和深度可分离卷积Depthwise Separable Convolution这些新花样本质上都是在“如何控制卷积核感受野和计算开销”上做文章。第一周把基础打牢后面进入经典CNN结构LeNet、AlexNet、VGG时你就能像看积木一样轻松拆解它们了。