计算机视觉课程实验与OpenCV实战:从特征提取到深度学习
2026/9/8 23:11:00 网站建设 项目流程

简介:涵盖浙江大学2020-2021秋冬计算机视觉课程的6次实验、复习资料及OpenCV学习材料,面向正在学习计算机视觉的高校学生和相关开发者。实验任务从Hw1无声小短片制作、Hw2圆形/直线检测、Hw3 Harris角点检测、Hw4 Eigenface人脸检测到Hw5 CNN模型训练,梯度设计合理,并附带CIFAR-10与MNIST模型检查点,方便对照验证中间结果。全部内容共1319个文件,以jpg图片、txt文本、pgm图像为主,结合PDF文档、Python脚本、PNG图片、HTML页面等,整体压缩包约72.75MB,目录结构清晰,适合按需检索。目前已有328人学习下载,既能支撑课程实验参考,也可作为期末复习时的完整练习库,帮助理解经典视觉算法与深度学习应用。 我翻了翻自己当年整理的一份课程资料合集,标题是“Computer-Vision:浙江大学2020-2021秋冬计算机视觉课程6次实验+复习资料+OpenCV学习资料”。本来只是个人备份,没想到陆陆续续有不少学弟学妹和转行做视觉的朋友来问我要。干脆写一篇长文,把这套资料背后的课程逻辑、实验设计、OpenCV学习路径以及复习备考经验一次说透。如果你正在学计算机视觉,或者准备入门OpenCV,这篇文章应该能帮你省下不少自己摸索的时间。

1. 这门课到底在教什么:课程定位与实验设计的递进逻辑

浙江大学2020-2021秋冬学期的计算机视觉课程,从名字看是“Computer-Vision”,但实际内容横跨了数字图像处理、传统视觉特征提取、几何视觉和基础深度学习几个大块。它不是一门纯理论的课,更偏向于“让你亲手把算法跑起来,再让你回答算法为什么这么设计”的工程+理论复合型课程。

整个学期安排了6次实验,这6次实验不是随意拼凑的,它们之间有非常清晰的递进关系:

实验序号主题方向核心知识点与后续实验的关联
实验1图像基础操作像素读写、灰度变换、直方图所有后续实验的图像预处理基础
实验2空间域滤波与边缘检测卷积、Sobel/Canny算子为特征点检测铺垫
实验3特征点检测与匹配SIFT/SURF/ORB、RANSAC为几何视觉和拼接做特征输入
实验4相机标定与立体视觉内参外参、畸变模型、对极几何实验5的三维重建基础
实验5图像拼接与三维重建单应矩阵、Bundle Adjustment基础综合运用实验3和实验4
实验6深度学习视觉入门CNN分类、常用框架使用衔接现代视觉方法

这个递进设计的精妙之处在于:它强迫你按视觉系统的“物理链路”走一遍——从像素到特征,从特征到几何,从几何到语义。很多自学OpenCV的同学容易东一榔头西一棒子,今天学个人脸识别,明天学个车牌检测,知识是碎片的。而这门课的实验体系能帮你把碎片串成一张网。

我当时最大的感触是:做完实验1到实验5之后,再去看高翔的《视觉SLAM十四讲》里关于特征点、对极约束、PnP的内容,简直顺畅得不像话。因为课程实验已经把数学公式背后的图像直觉建立起来了。

2. 6次实验逐个拆解:题目要求、实现思路与常见翻车点

这一部分我想把每个实验的细节都摊开来讲,包括我当时是怎么实现的、代码结构怎么组织、以及实验室里最常见的报错和坑。

2.1 实验1:图像基础操作与直方图均衡化

实验1的主要任务是读取一张图像,完成灰度化、反色、亮度调节以及直方图均衡化,最后对比均衡化前后的效果差异。看起来很简单,但它考察的是你能否理解“图像在计算机里就是三维数组”这个本质。

实现上要注意三点。第一,灰度化不要直接调OpenCV的cvtColor就完事,课程通常要求你自己写加权平均公式,这时候要注意OpenCV的默认通道顺序是BGR,而不是RGB,直接按RGB权重算会让图像色彩发蓝。第二,直方图均衡化要理解累计分布函数(CDF)的映射过程,OpenCV的equalizeHist虽然一行代码就能搞定,但实验报告里需要画出手动实现和库函数实现的对比曲线。第三,边界像素的处理策略(补零、复制、镜像)会影响滤波结果,养成好习惯,从实验1开始就统一用borderInterpolate处理。

这里翻车最多的是BGR和RGB通道顺序问题。很多同学用matplotlib显示图像发现颜色不对,第一反应是“OpenCV读坏了”,其实是显示库的通道约定不同。建议所有实验统一在显示前进行通道转换,不要在每个实验里反复踩同一个坑。

2.2 实验2:边缘检测与Hough变换

实验2开始上强度了。任务包括用Sobel算子计算梯度幅值和方向,实现非极大值抑制,完成双阈值滞后处理(也就是手写一个Canny边缘检测),然后用Hough变换检测直线和圆。

手写Canny的难点在于双阈值滞后连接的递归实现。很多同学阈值设得过大,边缘断裂成一段一段的;阈值过小,噪声全部被当成边缘。我当时总结了一个经验:高阈值设为梯度幅值直方图的第70到80百分位,低阈值设为高阈值的一半左右,效果通常比较稳定。这个经验在OpenCV的自动阈值函数里也能印证。

Hough变换那一块,直线检测要注意极坐标参数空间的离散化步长。步长太小时累计器数组巨大,运行极慢;步长太大时检测不到细小的直线。我用的是角度步长1度、距离步长1像素,在普通课程图片上性能和精度比较均衡。

实验2报告里还有个高频失分点:很多同学只贴出最终检测图像,但没画中间过程的梯度幅值图、非极大值抑制结果图和双阈值处理结果图。建议每一阶段的结果都单独输出保存,报告里放对比图,分数会高不少。

2.3 实验3:特征点检测、描述子与RANSAC匹配

实验3可能是6次实验中工作量最大的一个。要求分别用SIFT、SURF、ORB检测特征点并匹配两张有视角变化的图像,计算单应矩阵并用RANSAC剔除误匹配。

我记得我大一时第一次用SIFT,以为调用detectAndCompute就行,但到了写报告的时候就傻眼了——每个参数什么意思完全说不清楚。后来老老实实把尺度空间、DoG极值检测、主方向分配、描述子生成这几个模块在纸上各画了一遍流程图,才真正敢说“会用SIFT”。

RANSAC这块要特别注意迭代次数和阈值的选择。理论课上讲的公式是适应迭代次数随内点比例指数上升,但在实验里直接跑一个固定迭代次数(比如1000次)也能得到不错的结果。这里建议实现一个自适应迭代次数版本,也就是根据当前内点比例动态更新最大迭代次数,代码多不了几行,但报告里的技术含量看起来完全不一样。

ORB是必须掌握的,因为SIFT和SURF现在都受专利限制(虽然SIFT专利已过期),工业生产中用ORB最多。ORB的特点是没有尺度不变性,如果你匹配的图像对存在明显缩放,要先做resize再做特征匹配。

实验3还有一个隐藏考点,就是单应矩阵的归一化。直接用DLT算法求解H矩阵时,如果特征点坐标数值太大(比如1000像素量级),矩阵会严重病态。解决办法是先做坐标归一化,计算后再反归一化,这就是Hartley的经典八点法思路。实验中如果你发现匹配结果一团乱麻,多半是没做归一化。

2.4 实验4:相机标定与极线几何

实验4的任务是拍摄棋盘格标定图像,计算相机的内参矩阵和畸变系数,然后基于两视图计算基础矩阵F和本质矩阵E,画极线。

棋盘格标定这块,OpenCV提供了完整的cv::findChessboardCorners、cv::cornerSubPix和cv::calibrateCamera流程。有个细节很关键:标定图片最好在10到20张之间,并且棋盘格要覆盖视野的各个区域(四个角和中心都要出现),如果只在图像中心附近拍摄,标定出来的畸变系数会非常不准。

棋盘格尺寸务必要在标定前用小尺子量精确。很多同学偷懒直接用“棋盘格是10cm”这样的约数,最后标定出的焦距误差能到5%以上。角点数也要注意:findChessboardCorners传入的角点数是内角点数,不是棋盘格的行列数。比如8x6的棋盘格,内角点是7x5,这个数错了函数直接找不到角点。

极线几何部分,OpenCV的findFundamentalMat和findEssentialMat都可以用。要注意基础矩阵的秩为2约束,算出的F矩阵在可视化极线前最好做秩2强制(用SVD分解把最小奇异值置0再重构)。如果不做这一步,画出来的极线会歪得离谱,我当时在这个问题上卡了两个小时。

2.5 实验5:图像拼接与单应变换

实验5是把实验3和实验4的内容串起来做一件实操性很强的事:把两张有重叠区域的图像拼成全景图。核心流程是特征提取、特征匹配、RANSAC求单应、图像变换配准、融合拼接。

拼接中最容易出现鬼影的问题。简单的前向映射会造成黑色空洞和锯齿,需要做反向映射(即对目标图像每个像素计算它在源图像中的对应位置)。插值方法用双线性插值就够了,双三次插值效果更好但速度下降明显。

图像融合这里,如果你的两张图像曝光差异不大,直接用羽化融合(alpha渐晕)就行。如果曝光差异大,就要考虑多频段融合或者直方图匹配了。课程实验到不了多频段融合的深度,但你在报告里提一句“可进一步使用拉普拉斯金字塔融合消除接缝”,就是加分项。

实验5还考察一个细节:单应变换的自由度是8,理论上4对匹配点就能求解,但RANSAC每次随机采样4对点来计算模型时,要注意这4对点不能有三点共线的情况,否则求出的单应矩阵是退化的。

2.6 实验6:基于CNN的图像分类

实验6的风格和前面5个实验完全不同,从传统视觉一下跳到了深度学习。任务是在CIFAR-10或类似数据集上训练一个简单的CNN,达到一定准确率。

这个实验的核心诉求不是让你发明新网络,而是让你理解CNN的基本组件——卷积层、池化层、全连接层、BatchNorm、Dropout——以及它们对整个训练过程的影响。我在这个实验中做的几个尝试挺有价值:

  • 先用一个只有2个卷积层的极简网络跑通全流程,确保数据加载、模型定义、训练循环、评估指标这条链路是通的,再去加深结构。
  • 数据增强是性价比最高的精度提升手段,随机裁剪+水平翻转就能带来2到3个点的提升。
  • BatchNorm层一定要加,不加的话网络收敛速度慢一个量级。
  • 学习率用余弦退火(CosineAnnealingLR)替代固定学习率,最后一轮验证准确率通常能平滑提升。

做实验6的时候Anaconda环境配起来也算是个小坑。tensorflow和pytorch用conda装最稳妥,不要直接用pip装CUDA版,容易遇到cuDNN版本不匹配的问题。而且别忘了每次新建虚拟环境,不要所有依赖都堆在base环境里,否则版本冲突会让你怀疑人生。

3. OpenCV学习资料:从环境配置到核心API的实战筛选

这节专门讲我在这门课期间整理并沉淀下来的OpenCV学习路径,从环境搭建到核心API,分阶段来说。

3.1 环境配置:Windows下VS2013时代的老问题与现代化方案

标题里提到的“OpenCV安装教程”“VS2013导入OpenCV”这类热搜,其实是历史遗留问题了。VS2013搭配的是OpenCV 2.4.x或3.x时代的默认路径,配置时需要设置环境变量、在VS中配置附加包含目录和附加库目录,还得分清Debug和Release模式。

现在的做法是推荐用vcpkg安装OpenCV,或者直接用CMake构建项目。一个干净的CMakeLists.txt大致长这样:

cmake_minimum_required(VERSION 3.16) project(MyCVProject) set(CMAKE_CXX_STANDARD 17) find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) add_executable(main main.cpp) target_link_libraries(main ${OpenCV_LIBS})

CMake会自动处理Debug/Release对应的库文件,比手配VS强太多。如果你不用C++,Python端的安装就更简单了,直接用pip就行,但记得装opencv-python和opencv-contrib-python两个包,后者包含了SIFT、SURF等contrib模块的接口。

这里要强调一个搜索热度很高、同时也是很多新手必踩的坑:ModuleNotFoundError: No module named 'cv2'。绝大多数情况是当前命令行所在的环境和pip安装的环境不一致。解决办法是打开你实际在用的那个环境(比如Anaconda的myenv环境),执行conda install opencv,然后检查python和pip的路径是不是都在这个环境下。

3.2 核心API的掌握优先级:哪些常用,哪些必须手写

在课程实验过程中,我整理了一份OpenCV API优先级表格,按“必须会”和“了解即可”分级:

优先级类别代表API用途
必须会图像读写与显示imread, imwrite, imshow一切操作的基础
必须会颜色空间转换cvtColorBGR到灰度/HSV
必须会几何变换resize, warpAffine, warpPerspective图像缩放、仿射/透视变换
必须会滤波GaussianBlur, medianBlur, bilateralFilter去噪预处理
必须会边缘检测Canny边缘提取
必须会特征检测SIFT_create, ORB_create特征点检测与描述
必须会特征匹配BFMatcher, FLANN描述子匹配
必须会几何校验findHomography + RANSAC单应变换估计
必须会轮廓操作findContours, drawContours, fillPoly目标检测与区域填充
了解即可模板匹配matchTemplate简单场景下的匹配
了解即可直方图/反向投影calcHist, calcBackProject颜色直方图类应用
了解即可视频分析VideoCapture, BackgroundSubtractor入门级运动目标检测

findContours这个函数,搜索热度一直很高,用起来有个细节:OpenCV 3.2之后findContours的返回值从3个变成了2个(只返回contours和hierarchy,不再返回image),很多老教程会让人写contours, hierarchy, _ = cv2.findContours(...),新版本直接报错。另外findContours会修改输入的二值图像,所以传入前一定要用copy(),否则你后面还想用原图就傻眼了。

cv::fillPoly和drawContours的配合在掩膜生成中是常见操作。比如要生成一个不规则区域的掩膜,先用findContours找到轮廓,再用fillPoly填充,最后用bitwise_and做ROI提取,这套组合在几乎所有目标检测后处理里都会用到。

3.3 从OpenCV到理论:不要做只会调库的“调包侠”

课程实验和面试中都会有这样的处境:你调用了cv2.Canny一行搞定,但面试官问你“Canny的步骤是什么”“高低阈值怎么选”“非极大值抑制在做什么”,答不上来就尴尬了。

我自己的方法是:每个核心算法都找一个“周末下午”,用OpenCV手动复现一遍。比如Canny分解成Sobel、非极大值抑制、双阈值连接三步;直方图均衡化用查表法而不是直接调equalizeHist;SIFT的尺度空间用cv2.GaussianBlur自己模拟。这种“手工复刻”训练能在理解层面带来质的飞跃,写实验报告也更有底气。

我还建议多看OpenCV官方教程以及文档示例,尤其是OpenCV官方仓库里的samples目录,里面藏着大量“少走弯路”的完整示例代码,比零散博客靠谱得多。

4. 复习资料是怎么组织的:知识点图谱、代码题库与面试向提纲

期末复习阶段,我把自己整理复习资料的思路也分享一下。资料不是把PPT复制粘贴,而是按“图谱+题库+提纲”三层结构整理。

4.1 知识点图谱:把零散概念串联成体系

计算机视觉的知识点非常多,从底层像素到高层语义,如果没有一张图谱帮你串联,容易背了就忘。我的做法是画一张分层的脑图,一共分四层:

  • 底层:图像采样、颜色空间、直方图、滤波、频域变换(傅里叶变换)
  • 中层:边缘、角点、特征描述、尺度空间、图像分割
  • 上层几何层:相机模型、标定、对极几何、双目视觉、运动恢复结构(SfM)
  • 顶层语义层:CNN基础、分类、检测、分割

复习时先按层过一遍,再横向找层之间的联系。比如“高斯滤波”不仅出现在底层图像平滑,它还是Canny边缘检测的前置步骤,同时和SIFT尺度空间的高斯金字塔有内在关联。这种跨层的关联记忆,比死记硬背牢固得多。

4.2 代码题库:考试和面试的双重刚需

期末笔试里有一定比例的代码题,不是让你写完整程序,而是给你一段代码,问你输出什么、哪一步错了、某个参数的作用。所以复习时一定要积累“有坑”的代码片段。

我收集的高频代码题包括:

  • 彩色图像灰度化时如果用cv2.imread读图,直接取b = img[:,:,0]是什么通道,答案是蓝通道,不是红通道;
  • 用numpy翻转图像时img[::-1]得到的是垂直镜像还是水平镜像;
  • Canny里的两个阈值哪个是高阈值,如果低阈值设为0会发生什么;
  • findHomography的RANSAC阈值设太大会怎样(误匹配剔除不干净,单应矩阵精度下降);
  • 图像金字塔的pyrDown和resize到原图一半的区别。

这些题目往往是做实验踩过坑才能答好的,所以“做完6次实验再去考试”这句话在浙大计算机视觉课上绝对成立。

4.3 面试向提纲:从课程资源到提offer的无缝衔接

除了应付考试,这套资料里关于“面试常考计算机视觉问题”的提纲也很有价值。我对照自己后来面试的经历,整理了一份偏八股的提纲:

  • 图像处理基础:均值滤波和中值滤波各适合去什么噪声(高斯噪声vs椒盐噪声);为什么高斯滤波核的权重是那样分布的
  • 特征点:SIFT为什么具备尺度不变性;ORB为什么比SIFT快;RANSAC原理和迭代次数公式
  • 相机模型:针孔模型的焦距和内参;畸变的径向和切向;张正友标定法的核心思想(用平面棋盘格在多姿态下求解单应)
  • 深度学习:CNN的局部连接和权值共享为什么适合图像;1x1卷积的作用;感受野怎么算

这套提纲现在也在持续维护,每次有新感悟都会往里补充。

5. 真正折腾人的几个坑:完整排查链路复盘

最后必须好好讲讲我在整个学习和整理过程中踩过的那些坑,以及我用的排查思路。以下三个最具代表性。

5.1 findContours后画图一片黑的根因

有一次做实验,我想在二值掩膜上画出轮廓,结果用drawContours画的图一片黑,怎么调都调不出来。排查过程是这样的:

第一步,确认轮廓是否为空。打印len(contours),发现轮廓数组不为空,排除“没找到轮廓”的可能。

第二步,检查cv2.drawContours的层级逻辑。发现我的调用方式是把hierarchy也传进去,然后第二个参数直接传了contours,但第三个参数必须传-1才能画所有轮廓,而我传的是某个正整数索引,导致只画了一个不存在的轮廓。

第三步,检查画布类型。drawContours要求画布是8位彩色图像或8位灰度图,并且如果你传入的轮廓坐标是浮点数或超出图像边界,也会静默画不出来。最终发现我创建画布时用了np.zeros((h, w, 4), dtype=np.uint8),是4通道而非3通道,drawContours直接在4通道图上不工作。

这类问题的通用排查思路是“分模块验证”:先检查数据是否为空,再检查参数形态是否匹配,最后检查数据类型和通道数是否符合API要求。

5.2 棋盘格标定畸变系数为负几百的疯狂结果

还有一个印象很深的bug:用findChessboardCorners后标定出的k1、k2畸变系数是负几百,明显不合理。当时逐一排查:

  • 先怀疑角点检测精度不够,换用cornerSubPix做亚像素精化后结果没改善;
  • 再怀疑标定板图片数量太少,从8张增加到20张,问题依旧;
  • 检查是否不同图片使用了不同的棋盘格尺寸,发现没问题;
  • 最后检查图像读取,发现一张标定图片被程序按不同缩放比例读取了。因为用手机拍摄后中途有几天换了拍摄模式,照片分辨率不一致,导致calibrateCamera内部的世界坐标(单位是mm或cm的固定尺寸)和图像坐标的比例关系混乱,算出的畸变模型自然离谱。

解决办法是统一所有标定图片的分辨率,或者干脆重新拍摄一组照片。这个坑提醒我:标定流程里,图片尺寸的一致性比画面质量更基础。

5.3 OpenCV C++环境里cv::imshow直接崩溃

C++环境下用cv::imshow在部分Linux系统上可能会报Gtk-WARNING **: cannot open display。排查链路:首先确认是否在无图形界面的服务器上运行,如果是,改用cv::imwrite输出结果文件。其次,如果本地有图形界面但仍报错,检查是否缺少gtk相关库,最简单的方法是重新编译带Qt支持的OpenCV(cmake时加-D WITH_QT=ON)。如果用的是conda或vcpkg预编译包,要注意它们默认开启的图像后端可能不同,在故障机上优先用GUIToolkit选项对齐。

如果你没有图形界面,又想实时看效果,可以用matplotlib或直接把结果显示为Web页面。这个思路在后来的很多服务端项目中都在使用。

6. 关于这套资料,我想再多说几句

整理这套“Computer-Vision”资料的过程,远不止是下载几个文件那么简单。我在做实验和整理笔记中收获最大的一点是:视觉算法的核心不是代码,而是“图像直觉”。当你看到一张噪声图,能立刻想到是高斯噪声还是椒盐噪声;当你看到两幅有明显旋转视角的图片,能立刻反应该用SIFT而不是直接模板匹配——这才算真正入门了。

如果你现在刚开始学这门课,建议把6次实验当作一个完整项目来做,别拆成孤立的任务。每一份实验报告,都尽量把公式推导、代码实现、结果对比、问题讨论四个部分写全。等到学期末你会发现,这些报告比PPT复习资料有用得多,因为它们是你亲手走完的完整推理链。

复习阶段也别只盯着PPT,动手写点小代码,比如用numpy手动实现一遍直方图均衡化和高斯滤波,五分钟的小练习就能暴露你以为懂了但实际上理解有误的地方。

这套资料里我保留最久的其实是每份实验的“报告模板”和“代码注释习惯”。比如在每个函数的docstring里写清输入输出、边界条件和算法来源,这个习惯后来在工业界做项目一直沿用。现在来看,这样一门课能带给人的不光是成绩单上的分数,更是一整套可迁移的工程方法。这些方法在任何视觉相关的方向——物体检测、三维重建、图像生成——都同样适用。

希望这篇文章能帮你在学计算机视觉和OpenCV的路上少走一些弯路。如果你在复现实验或者配置环境的过程中遇到卡点,欢迎按照我说的排查思路回头检查数据、接口和数据类型这三板斧,大概率能自己解决掉大半问题。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询