资讯动态

计算机视觉六次实验:从OpenCV环境配置到目标跟踪的工程化实践

发布时间:2026/10/13 15:09:18 来源:尧图企业网站定制
简介这份资源面向计算机视觉初学者与在校学生整理了浙江大学2020-2021秋冬学期计算机视觉课程的完整学习材料涵盖六次实验与复习资料并附带OpenCV学习内容适合需要系统入门图像处理与深度学习实践的学习者。包内共1319个文件以jpg图像、txt说明、pgm灰度图为主另有pdf讲义、py脚本、png示意图及少量模型checkpoint与json配置压缩包约72.75MB目录按实验与资料分类便于逐项查阅。实验内容覆盖无声短片制作、圆形与直线检测、Harris角点检测、Eigenface人脸识别以及CNN训练等典型任务从传统图像处理逐步过渡到深度学习能帮助读者理解算法原理并动手复现。目前已有330人学习下载可作为课程作业参考、期末复习与OpenCV练手的实用素材。1. 从六次实验到一套可复用的计算机视觉工程底子很多人第一次接触计算机视觉是从一门课的六次实验开始的图像基础操作、特征提取、相机标定、立体视觉、光流、目标跟踪一路做下来代码能跑报告能交但合上电脑就忘。问题不在课程本身而在于这些实验被当成了一次性作业而不是一套可以反复调用的工程底子。这份“6次实验复习资料OpenCV学习资料”的组合真正值钱的地方不是资料本身而是它把计算机视觉从数学定义到 OpenCV 落地串成了一条线。适合谁适合已经会写 Python、想系统补齐视觉工程能力的人也适合准备面试、需要把零散知识点串成体系的人。下面我按自己复现这套实验的顺序把每一步的选型理由、参数含义和踩坑点讲清楚。2. 六次实验的依赖环境与最小可跑通配置2.1 为什么优先用 conda 而不是 pip 裸装计算机视觉实验最怕的不是算法难而是环境崩。OpenCV 的 Python 包在不同平台上的 wheel 差异很大尤其是涉及 SIFT、SURF 这类专利算法时opencv-python和opencv-contrib-python的功能边界完全不同。我一般会用一个独立 conda 环境锁死版本避免和系统里其他项目的 numpy 打架。# 创建独立环境python 版本不要追新3.8~3.10 兼容性最好 conda create -n cvlab python3.9 -y conda activate cvlab # 核心依赖contrib 版本才带 SIFT/SURF/光流等实验常用模块 pip install opencv-contrib-python4.8.1.78 pip install numpy1.24.3 matplotlib3.7.2 scipy1.11.1 pip install scikit-image0.21.0 # 部分特征对比实验会用到逻辑说明opencv-contrib-python比普通opencv-python多出xfeatures2d等模块六次实验里至少有两三次会用到。参数上numpy 锁在 1.24 是因为 1.25 之后部分旧代码的np.float别名被彻底移除容易在复制来的示例里翻车。安装完用下面这段验证import cv2 import numpy as np print(OpenCV:, cv2.__version__) print(是否有 SIFT:, hasattr(cv2, SIFT_create)) print(是否有 xfeatures2d:, hasattr(cv2, xfeatures2d)) # 最小图像读写测试确认编解码链路正常 img np.zeros((100, 100, 3), dtypenp.uint8) img[20:80, 20:80] (0, 255, 0) cv2.imwrite(test_green.png, img) back cv2.imread(test_green.png) print(读回形状:, back.shape, 像素均值:, back.mean())如果hasattr(cv2, xfeatures2d)返回 False说明装成了普通版直接卸载重装 contrib 版即可。这一步看着简单但每年都有人在这里卡半天。2.2 实验数据的组织方式决定你后期调试效率六次实验会反复读写图像、视频、标定板照片。我习惯按实验编号建目录原始数据只读输出统一进output/中间结果进cache/。这样做的直接好处是当某个实验参数调错时你能快速判断是输入问题还是处理问题而不是在一堆同名文件里找。cvlab/ ├── data/ │ ├── exp1_basic/ # 图像基础操作素材 │ ├── exp2_feature/ # 特征提取测试图 │ ├── exp3_calib/ # 棋盘格标定图 │ ├── exp4_stereo/ # 左右目图像对 │ ├── exp5_flow/ # 光流视频帧 │ └── exp6_track/ # 跟踪视频 ├── output/ # 每次实验的可视化结果 ├── cache/ # 中间矩阵、标定参数 └── src/ # 每个实验一个脚本提示标定和立体视觉实验的中间结果相机矩阵、畸变系数一定要落盘成.npz否则每次调参都要重新标定时间成本极高。3. 图像基础与特征提取从像素操作到关键点匹配3.1 图像基础实验里最容易被忽略的三个参数第一次实验通常是灰度化、滤波、边缘检测、形态学。看起来简单但cv2.Canny的双阈值、cv2.GaussianBlur的核大小、cv2.morphologyEx的结构元素形状这三个参数直接决定后续所有实验的输入质量。我一般会写一个参数对比脚本把不同组合并排输出而不是凭感觉调。import cv2 import numpy as np img cv2.imread(data/exp1_basic/scene.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯核必须是奇数越大越模糊这里对比 3/5/7 for k in [3, 5, 7]: blur cv2.GaussianBlur(gray, (k, k), 0) # Canny 双阈值比例经验值 1:2 或 1:3 edges cv2.Canny(blur, 50, 150) cv2.imwrite(foutput/exp1_canny_k{k}.png, edges) # 形态学开运算去噪闭运算补洞结构元素形状影响很大 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) opened cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel) closed cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) cv2.imwrite(output/exp1_open.png, opened) cv2.imwrite(output/exp1_close.png, closed)逻辑说明高斯核大小影响边缘的连续性核太大边缘会断核太小噪声去不掉。Canny 的低阈值控制弱边缘保留高阈值控制强边缘起点两者比例失衡会导致边缘要么碎要么糊。形态学结构元素用椭圆比矩形更贴近自然物体轮廓这是我在做细胞分割类任务时养成的习惯。3.2 SIFT 与 ORB 的选型精度和速度的取舍第二次实验通常做特征提取与匹配。SIFT 精度高但慢ORB 快但旋转不变性弱。我的经验是如果图像分辨率超过 1080p 且匹配对精度要求高用 SIFT如果是视频流实时处理用 ORB。下面这段代码把两者放在一起对比方便你直观感受差异。import cv2 import numpy as np import time img1 cv2.imread(data/exp2_feature/box.png, 0) img2 cv2.imread(data/exp2_feature/box_in_scene.png, 0) def match_with(detector, norm_type, ratio0.75): t0 time.time() kp1, des1 detector.detectAndCompute(img1, None) kp2, des2 detector.detectAndCompute(img2, None) bf cv2.BFMatcher(norm_type) raw bf.knnMatch(des1, des2, k2) # Lowes ratio test过滤误匹配 good [m for m, n in raw if m.distance ratio * n.distance] return kp1, kp2, good, time.time() - t0 sift cv2.SIFT_create(nfeatures500) kp1, kp2, good_sift, t_sift match_with(sift, cv2.NORM_L2) orb cv2.ORB_create(nfeatures500) kp1o, kp2o, good_orb, t_orb match_with(orb, cv2.NORM_HAMMING) print(fSIFT 匹配数 {len(good_sift)}耗时 {t_sift:.3f}s) print(fORB 匹配数 {len(good_orb)}耗时 {t_orb:.3f}s)参数说明nfeatures控制关键点上限设太大会拖慢匹配设太小会丢特征。ratio0.75是 Lowe 论文里的经验值低于 0.7 匹配更严但数量骤减高于 0.8 误匹配明显增多。ORB 必须用NORM_HAMMING用 L2 会得到完全错误的结果这是新手最常见的翻车点之一。4. 相机标定与立体视觉把三维重建跑通的关键步骤4.1 棋盘格标定的完整流程与参数落盘第三次实验是相机标定第四次是立体视觉这两次实验是整门课的分水岭。标定的核心是cv2.findChessboardCorners加cv2.calibrateCamera但真正决定精度的是棋盘格角点的亚像素优化和标定图像的数量与角度分布。import cv2 import numpy as np import glob # 棋盘格内角点数量注意是内角点不是格子数 pattern (9, 6) criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) objp np.zeros((pattern[0] * pattern[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern[0], 0:pattern[1]].T.reshape(-1, 2) objpoints, imgpoints [], [] images glob.glob(data/exp3_calib/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern, None) if ret: # 亚像素优化窗口大小 11x11 是常用值 corners2 cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) objpoints.append(objp) imgpoints.append(corners2) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None ) print(重投影误差:, ret) print(相机内参:\n, mtx) print(畸变系数:, dist.ravel()) # 落盘后续立体视觉实验直接加载 np.savez(cache/calib_params.npz, mtxmtx, distdist)逻辑说明cornerSubPix的窗口大小决定亚像素精度11x11 在多数分辨率下稳定。重投影误差ret是判断标定质量的直接指标一般要小于 0.5 像素超过 1 像素说明有图像角点检测错误或角度分布太单一。标定图像建议 15 张以上且棋盘格要覆盖画面四个角和中心否则畸变系数会不准。4.2 立体校正与视差图从左右目到深度第四次实验在标定基础上做立体校正和视差计算。核心是cv2.stereoCalibrate和cv2.StereoSGBM_create。SGBM 的参数多但真正影响效果的只有几个。import cv2 import numpy as np # 加载双目标定结果假设已通过 stereoCalibrate 得到 data np.load(cache/stereo_params.npz) K1, D1, K2, D2 data[K1], data[D1], data[K2], data[D2] R, T data[R], data[T] img_size tuple(data[img_size]) R1, R2, P1, P2, Q, _, _ cv2.stereoRectify( K1, D1, K2, D2, img_size, R, T, alpha0 ) left cv2.imread(data/exp4_stereo/left.png) right cv2.imread(data/exp4_stereo/right.png) # 去畸变 校正 map1x, map1y cv2.initUndistortRectifyMap(K1, D1, R1, P1, img_size, cv2.CV_32FC1) map2x, map2y cv2.initUndistortRectifyMap(K2, D2, R2, P2, img_size, cv2.CV_32FC1) left_rect cv2.remap(left, map1x, map1y, cv2.INTER_LINEAR) right_rect cv2.remap(right, map2x, map2y, cv2.INTER_LINEAR) # SGBM 视差计算 window_size 5 min_disp 0 num_disp 16 * 5 # 必须是 16 的倍数 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizewindow_size, P18 * 3 * window_size ** 2, P232 * 3 * window_size ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32 ) disp stereo.compute(left_rect, right_rect).astype(np.float32) / 16.0 disp_norm cv2.normalize(disp, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) cv2.imwrite(output/exp4_disparity.png, disp_norm)参数说明numDisparities必须是 16 的倍数它决定能测到的最近距离设太小近处物体视差会溢出。blockSize越大视差图越平滑但细节丢失5 或 7 是常用值。uniquenessRatio控制匹配唯一性10 到 15 之间比较稳。P1、P2是平滑惩罚项按blockSize的平方缩放是 OpenCV 官方示例的推荐做法。注意立体校正后一定要检查左右图是否行对齐如果极线不水平说明stereoRectify的输入标定参数有问题视差图会整体错乱。5. 光流与目标跟踪视频序列里的运动估计5.1 稀疏光流与稠密光流的适用场景第五次实验是光流第六次是目标跟踪。光流分稀疏Lucas-Kanade和稠密Farneback两种。稀疏光流适合跟踪少量特征点速度快稠密光流给出每个像素的运动适合运动分割但计算量大。import cv2 import numpy as np cap cv2.VideoCapture(data/exp5_flow/video.mp4) ret, old_frame cap.read() old_gray cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY) # Shi-Tomasi 角点作为稀疏光流跟踪点 feature_params dict(maxCorners100, qualityLevel0.3, minDistance7, blockSize7) p0 cv2.goodFeaturesToTrack(old_gray, maskNone, **feature_params) lk_params dict(winSize(15, 15), maxLevel2, criteria(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) while True: ret, frame cap.read() if not ret: break frame_gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) p1, st, err cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params) if p1 is not None: good_new p1[st 1] good_old p0[st 1] for (new, old) in zip(good_new, good_old): a, b new.ravel() c, d old.ravel() cv2.line(frame, (int(a), int(b)), (int(c), int(d)), (0, 255, 0), 2) cv2.imwrite(output/exp5_flow_frame.png, frame) old_gray frame_gray.copy() p0 good_new.reshape(-1, 1, 2) break # 演示单帧实际处理去掉参数说明winSize是搜索窗口15x15 适合中等运动速度运动快就加大但太大容易跟丢。maxLevel是金字塔层数2 层能处理一定尺度变化。qualityLevel0.3表示角点质量阈值越低点越多但越不稳定。minDistance控制角点最小间距避免扎堆。5.2 跟踪算法选型KCF、CSRT 与 MOSSE 的取舍第六次实验的目标跟踪OpenCV 提供了多种跟踪器。我的经验是精度优先用 CSRT速度优先用 MOSSE平衡用 KCF。但要注意OpenCV 4.5 之后部分跟踪器 API 有变化创建方式从cv2.TrackerKCF_create()变成了cv2.legacy.TrackerKCF_create()这是升级版本后最常见的报错来源。import cv2 # OpenCV 4.5 需要 legacy 命名空间 tracker cv2.legacy.TrackerCSRT_create() cap cv2.VideoCapture(data/exp6_track/video.mp4) ret, frame cap.read() # 手动框选初始目标实际脚本里可以用固定坐标 bbox cv2.selectROI(select, frame, fromCenterFalse, showCrosshairTrue) tracker.init(frame, bbox) while True: ret, frame cap.read() if not ret: break ok, box tracker.update(frame) if ok: x, y, w, h [int(v) for v in box] cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imwrite(output/exp6_track_frame.png, frame) break逻辑说明CSRT 精度高但帧率低适合离线分析KCF 速度快但在遮挡时容易漂移MOSSE 最快但精度最差。如果视频里目标有遮挡建议加一个简单的重检测机制比如每 30 帧用模板匹配重新定位一次否则跟丢后很难恢复。6. 避坑与排查六次实验里最容易翻车的五个点6.1 现象SIFT 创建报错module cv2 has no attribute SIFT_create原因装的是opencv-python而不是opencv-contrib-python或者版本低于 4.4。解决卸载后重装 contrib 版并确认版本号。如果公司网络受限可以先用pip download在能联网的机器上下载 wheel 再离线安装。6.2 现象标定重投影误差大于 2 像素原因棋盘格角点检测错误或者标定图像角度太单一。解决先用cv2.drawChessboardCorners把检测结果画出来逐张检查剔除检测错误的图再补充倾斜角度大、棋盘格靠近画面边缘的图像。亚像素优化窗口从 11 调到 5 有时也能改善。6.3 现象视差图大面积黑色或噪点原因numDisparities设得太小或者左右图没有真正行对齐。解决先检查校正后的左右图在相同行上找同一个物体看是否水平对齐再把numDisparities调到 16 的倍数并逐步增大直到近处物体视差不再溢出。speckleWindowSize从 100 调到 200 可以去掉部分噪点。6.4 现象光流跟踪点大量丢失原因winSize太小或maxLevel不够目标运动超出搜索范围。解决把winSize从 15 加到 21 或 31maxLevel从 2 加到 3。同时检查goodFeaturesToTrack的qualityLevel太低会引入不稳定角点太高则点太少。6.5 现象跟踪器初始化后立即报错或跟丢原因OpenCV 版本与跟踪器 API 不匹配或者初始 bbox 超出图像边界。解决确认使用cv2.legacy.TrackerXXX_create()检查 bbox 的w、h是否大于 0 且不越界。如果视频第一帧目标太小先放大再框选或者换用 CSRT 这类对初始框更鲁棒的跟踪器。7. 把六次实验压成一套可复用的视觉工具函数六次实验做完如果每个脚本都是独立的下次遇到新任务还得重写。我的习惯是把重复逻辑抽成一个小工具模块比如cv_utils.py里面放图像读写、标定参数加载、视差计算、跟踪器工厂这几类函数。这样做的价值在于当你换一个数据集或换一个场景时只需要改参数不需要改结构。# cv_utils.py import cv2 import numpy as np def load_calib(pathcache/calib_params.npz): 加载标定参数返回 mtx, dist data np.load(path) return data[mtx], data[dist] def undistort(img, mtx, dist): 去畸变返回校正后图像 h, w img.shape[:2] new_mtx, roi cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h)) return cv2.undistort(img, mtx, dist, None, new_mtx) def make_tracker(namecsrt): 跟踪器工厂统一 API 差异 name name.lower() if name csrt: return cv2.legacy.TrackerCSRT_create() if name kcf: return cv2.legacy.TrackerKCF_create() if name mosse: return cv2.legacy.TrackerMOSSE_create() raise ValueError(f不支持的跟踪器: {name}) def compute_disparity(left, right, num_disp80, block5): SGBM 视差计算参数集中管理 stereo cv2.StereoSGBM_create( minDisparity0, numDisparitiesnum_disp, blockSizeblock, P18 * 3 * block ** 2, P232 * 3 * block ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32 ) disp stereo.compute(left, right).astype(np.float32) / 16.0 return disp逻辑说明load_calib和undistort把标定实验的成果固化下来后续任何实验都能直接调用。make_tracker用工厂模式屏蔽了 OpenCV 版本差异换版本时只改这一处。compute_disparity把 SGBM 的参数集中管理调参时不用在多个脚本里找。这套工具函数不复杂但能让你从“每次重写”变成“每次复用”。验证方法也很直接拿一张新拍的棋盘格图走一遍load_calib加undistort看直线是否变直拿一段新视频用make_tracker(csrt)跑一遍看框是否稳定。如果这两步都通过说明你的视觉底子已经搭起来了。我自己的教训是早期做实验总想一次调出完美结果结果在参数上耗掉大量时间。后来改成先跑通最小闭环再逐步加精度效率反而高得多。这套六次实验的资料最大的价值不是让你交六份报告而是让你在反复调试中形成自己的参数直觉和排错习惯。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑