简介:这份PPT面向计算机、人工智能方向的初学者与进阶学习者,系统梳理图像处理与计算机视觉的基础概念、发展脉络与技术构成,帮助读者建立从二维图像分析到三维场景理解的完整知识框架。资源包内含1个PPT文件,约1.55MB,以图文并茂的幻灯片形式呈现,便于课堂讲解与自学翻阅。内容涵盖图像与数字图像的定义、像素与灰度概念、低中高级处理的划分,以及图像处理与计算机视觉、机器视觉之间的区别与联系;同时按时间线回顾从20世纪20年代图像编码重构、60年代航天图像处理,到70年代遥感与医学图像、80年代末多媒体技术兴起的演进过程。此外还介绍了图像采集传感器、高速采集系统及数码相机、扫描仪等输入输出设备的系统构成,并结合工业机器视觉的缺陷检测、尺寸测量等应用场景展开说明。目前已有89人学习,适合作为课程复习、技术入门与知识体系搭建的参考材料。
1. 图像处理与计算机视觉技术综述:从一份 PPT 大纲到能跑通的最小闭环
很多人第一次接触图像处理与计算机视觉技术综述,是在一份 PPT 里看到满屏的卷积核、特征金字塔和检测框,翻完觉得什么都讲了,合上又什么都做不出来。这份 PPT 真正的价值不在页面本身,而在于它能不能被拆成一条可执行的路径:先跑通读图、灰度、滤波、边缘,再进到特征提取、分类、检测,最后落到一个能复现指标的小项目。它适合两类人:刚转方向、需要一张完整地图的新手,以及做过单点任务、但说不清各模块怎么串起来的熟手。下面我按自己带人做项目的顺序,把这份综述拆成能照着敲、能验证、能踩坑的版本。
2. 图像处理与计算机视觉技术综述的骨架:从像素到语义的四层拆解
一份能落地的综述,不该按算法名字罗列,而该按数据形态的演进分层。像素层处理的是数值矩阵,特征层处理的是可描述的局部结构,语义层处理的是类别与位置,工程层处理的是速度、内存和部署。这四层对应不同的库、不同的评价指标,也对应不同的翻车方式。把层次分清,后面选型就不会拿检测模型去做去噪,也不会拿传统算子去硬扛语义分割。
2.1 像素层:读图、色彩空间与滤波的选型理由
像素层是所有后续步骤的地基。常见做法是先用 OpenCV 或 Pillow 读入图像,统一转成 numpy 数组,再决定是否转灰度。转灰度不是习惯动作,而是因为多数传统算子(Sobel、Canny、Harris)定义在单通道上,三通道直接算会得到三份互相干扰的梯度。色彩空间的选择也有讲究:HSV 适合做颜色阈值分割,因为色调和亮度解耦;Lab 适合做色差计算,因为欧氏距离更接近人眼感知。
滤波的选型取决于噪声类型。高斯噪声用高斯滤波,椒盐噪声用中值滤波,而保边去噪用双边滤波或导向滤波。很多人一上来就用均值滤波,结果边缘被糊掉,后续 Canny 检出的轮廓全是断的。我一般会先看噪声分布再选核,核大小从 3 起步,超过 7 就要警惕细节丢失。
import cv2 import numpy as np # 读入图像,统一转成 BGR 三通道,避免后续通道数不一致 img = cv2.imread("input.jpg", cv2.IMREAD_COLOR) if img is None: raise FileNotFoundError("图像路径错误或格式不支持") # 转灰度:传统算子需要单通道输入 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯滤波:ksize 必须是奇数,sigmaX 设为 0 表示由 ksize 自动推导 blur = cv2.GaussianBlur(gray, (5, 5), 0) # 中值滤波:对椒盐噪声更稳,ksize 同样取奇数 median = cv2.medianBlur(gray, 5) # 保存中间结果,方便肉眼比对滤波效果 cv2.imwrite("gray.jpg", gray) cv2.imwrite("blur.jpg", blur) cv2.imwrite("median.jpg", median)这段代码的关键参数是ksize和sigmaX。ksize越大,平滑越强,但边缘保留越差;sigmaX为 0 时 OpenCV 按0.3*((ksize-1)*0.5-1)+0.8推导,一般不用手改。中值滤波的ksize超过 7 后计算量明显上升,实时场景要谨慎。保存中间结果这一步别省,后面调参时能快速定位是哪一层出的问题。
2.2 特征层:边缘、角点与局部描述子的适用边界
特征层解决的是“图像里有什么结构”。边缘检测用 Canny,角点检测用 Harris 或 Shi-Tomasi,局部描述子用 SIFT、ORB 或 SURF。Canny 的双阈值是玄学重灾区:高阈值太高会丢弱边缘,太低会引入噪声。我一般按梯度幅值的 70% 和 30% 分位先设一版,再根据检出图微调。ORB 适合实时匹配,因为它快且免费;SIFT 精度高但计算重,专利已过期,现在可以放心用。
这里有个常见误用:拿 SIFT 去做实时视频拼接。SIFT 单帧特征提取在普通 CPU 上可能几十毫秒,加上匹配和 RANSAC,帧率直接掉到个位数。实时场景优先 ORB,精度不够再考虑 SIFT 加降采样。
# Canny 边缘检测:双阈值按梯度分位数自适应 grad_x = cv2.Sobel(blur, cv2.CV_32F, 1, 0, ksize=3) grad_y = cv2.Sobel(blur, cv2.CV_32F, 0, 1, ksize=3) mag = np.sqrt(grad_x**2 + grad_y**2) high = np.percentile(mag, 70) low = high * 0.4 edges = cv2.Canny(blur, low, high) # ORB 特征:nfeatures 控制上限,scaleFactor 控制金字塔层间缩放 orb = cv2.ORB_create(nfeatures=1000, scaleFactor=1.2, nlevels=8) keypoints, descriptors = orb.detectAndCompute(gray, None) # 画出特征点,检查分布是否集中在纹理丰富区域 vis = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0)) cv2.imwrite("orb_keypoints.jpg", vis)nfeatures设太大不会更准,只会更慢,因为弱特征点匹配时容易被误配。scaleFactor取 1.2 是精度和速度的折中,取 1.1 更细但金字塔层数变多。nlevels决定能覆盖多大的尺度变化,8 层大约覆盖 2.1 倍尺度。画特征点这步是排查“为什么匹配不上”的第一现场:如果点全挤在角落,说明纹理分布不均,得换区域或换描述子。
2.3 语义层:分类、检测与分割的任务边界
语义层开始出现“类别”和“位置”。分类只回答是什么,检测回答是什么和在哪,分割回答每个像素属于什么。选型时先问业务需不需要位置:只需要判断缺陷有无,分类就够;需要框出缺陷区域,上检测;需要精确到像素边界,才上分割。很多项目一上来就上分割,标注成本翻十倍,指标还未必比检测加后处理好。
检测里又分两阶段和单阶段。两阶段(如 Faster R-CNN 系列)精度高但慢,单阶段(如 YOLO 系列、SSD)快但小目标弱。分割里分语义分割和实例分割,前者不区分同类个体,后者区分。选型时把“同类个体要不要分开数”问清楚,能省掉大量返工。
2.4 工程层:延迟、内存与部署形态的取舍
工程层决定方案能不能上线。延迟分预处理、推理、后处理三段,很多人只盯推理,结果预处理里的 resize 和归一化占了大头。内存分模型权重和中间激活,移动端要关注峰值内存。部署形态分服务端 GPU、边缘 NPU、浏览器 WASM,不同形态对算子支持差异很大,训练时用的自定义算子部署时可能直接不支持。
我一般会在训练前就确认部署形态,再反推模型结构。比如边缘 NPU 对动态 shape 支持差,就固定输入分辨率;浏览器 WASM 对 int8 量化支持好,就提前规划量化感知训练。这一步提前做,比训练完再改结构省事得多。
3. 用 OpenCV 和 PyTorch 搭一条可复现的基线:从读图到指标输出
骨架清楚后,下一步是搭一条能跑通、能出指标的基线。基线不追求 SOTA,追求每一步可解释、可替换。我习惯用 OpenCV 做预处理和可视化,用 PyTorch 做模型和训练,中间用 numpy 衔接。这样调预处理时不用动模型,换模型时不用动预处理。
3.1 环境与依赖:版本锁定与最小依赖集
环境翻车多数来自版本漂移。OpenCV 4.x 和 3.x 的 API 有差异,PyTorch 1.x 和 2.x 的torchvision接口也有变化。我一般用 conda 建独立环境,把版本写进requirements.txt,避免“在我机器上能跑”。
conda create -n cv-baseline python=3.10 -y conda activate cv-baseline pip install opencv-python==4.9.0.80 pip install torch==2.2.0 torchvision==0.17.0 --index-url https://download.pytorch.org/whl/cpu pip install numpy==1.26.4 matplotlib==3.8.3CPU 版本够跑基线,有 GPU 再把 index-url 换成对应 CUDA 版本。opencv-python和opencv-contrib-python不要同时装,会冲突。numpy锁 1.26 是因为部分旧版 PyTorch 对 numpy 2.x 兼容不好,这是血泪经验。
3.2 数据管线:自定义 Dataset 与增强的四个必调参数
数据管线决定模型看到什么。自定义 Dataset 要保证__getitem__返回的 image 和 label 严格对齐,增强时同步变换。常见翻车是只对图像做随机裁剪,标签没跟着裁,训练时 loss 不降反升。
import torch from torch.utils.data import Dataset, DataLoader import cv2 import numpy as np import os class ImageDataset(Dataset): def __init__(self, root, split="train", img_size=224): self.root = root self.split = split self.img_size = img_size # 假设目录结构为 root/split/class_name/xxx.jpg self.samples = [] for cls_id, cls_name in enumerate(sorted(os.listdir(os.path.join(root, split)))): cls_dir = os.path.join(root, split, cls_name) for fname in os.listdir(cls_dir): self.samples.append((os.path.join(cls_dir, fname), cls_id)) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label = self.samples[idx] img = cv2.imread(path, cv2.IMREAD_COLOR) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # resize 到固定尺寸,保证 batch 内 shape 一致 img = cv2.resize(img, (self.img_size, self.img_size)) # 归一化到 [0,1],再按 ImageNet 均值方差标准化 img = img.astype(np.float32) / 255.0 mean = np.array([0.485, 0.456, 0.406], dtype=np.float32) std = np.array([0.229, 0.224, 0.225], dtype=np.float32) img = (img - mean) / std # 转成 CHW,PyTorch 卷积要求通道在前 img = np.transpose(img, (2, 0, 1)) return torch.from_numpy(img), label loader = DataLoader(ImageDataset("data", "train"), batch_size=32, shuffle=True, num_workers=4)四个必调参数:img_size决定输入分辨率和计算量,224 是分类常用值;batch_size受显存限制,32 是起点;num_workers影响数据加载速度,设成 CPU 核数的 70% 左右;shuffle训练集必须开,验证集必须关。归一化的均值和方差要和预训练模型一致,否则微调时收敛慢。
3.3 模型与训练:迁移学习的最小改动
基线用预训练 ResNet 做迁移学习,只改最后一层全连接。这样小数据集也能收敛,不用从零训。
import torch.nn as nn from torchvision import models def build_model(num_classes=10): model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) # 冻结主干,只训分类头,小数据集防过拟合 for param in model.parameters(): param.requires_grad = False in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) return model model = build_model(num_classes=10) criterion = nn.CrossEntropyLoss() # 只优化分类头参数,学习率可以设大一点 optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3) for epoch in range(10): model.train() for imgs, labels in loader: optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() print(f"epoch {epoch}, loss {loss.item():.4f}")冻结主干是防过拟合的关键,数据量上来后再解冻部分层做微调。学习率 1e-3 只适用于分类头,解冻主干后要降到 1e-4 量级。CrossEntropyLoss内部带 softmax,模型输出不要再加 softmax,否则梯度会出问题。
3.4 指标与可视化:混淆矩阵和 PR 曲线的读法
训练完不能只看 loss,要看混淆矩阵和 PR 曲线。混淆矩阵能看出哪两类互相误判,PR 曲线能看出阈值变化对精度和召回的影响。
from sklearn.metrics import confusion_matrix, precision_recall_curve import matplotlib.pyplot as plt import seaborn as sns model.eval() all_preds, all_labels, all_probs = [], [], [] with torch.no_grad(): for imgs, labels in loader: outputs = model(imgs) probs = torch.softmax(outputs, dim=1) preds = torch.argmax(probs, dim=1) all_preds.extend(preds.numpy()) all_labels.extend(labels.numpy()) all_probs.extend(probs.numpy()) cm = confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues") plt.savefig("confusion_matrix.png") # 二分类 PR 曲线,多分类需按类展开 prec, rec, _ = precision_recall_curve(all_labels, [p[1] for p in all_probs]) plt.plot(rec, prec) plt.xlabel("recall") plt.ylabel("precision") plt.savefig("pr_curve.png")混淆矩阵对角线越深越好,非对角线出现明显亮块说明这两类特征区分度不够,要么加数据,要么换更强的 backbone。PR 曲线看曲线下面积,面积越大越好;如果曲线在某个召回区间骤降,说明该区间阈值下误检激增,业务上要避开这个阈值。
4. 避坑与排查:图像处理与计算机视觉项目里最常见的五类翻车
这一章按“现象 → 原因 → 解决”写,都是我在实际项目里反复遇到的。
4.1 现象:训练 loss 震荡不降,验证集准确率随机跳
原因通常是学习率过大或 batch 内样本顺序有强相关。数据管线里shuffle没开,或者数据集本身按类别排序,模型会学到顺序偏差。解决:确认shuffle=True,学习率降一个量级,加 warmup。如果还震荡,检查归一化均值方差是否和预训练模型一致,不一致会让浅层梯度异常。
4.2 现象:Canny 边缘断裂严重,轮廓连不起来
原因是双阈值设得过高,弱边缘被抑制。解决:按梯度幅值分位数自适应设阈值,先取 70% 和 30% 分位,再根据检出图微调。如果噪声多,先做中值滤波再 Canny,不要用高斯滤波,高斯会把弱边缘一起抹掉。
4.3 现象:ORB 匹配点大量误配,RANSAC 后内点很少
原因是nfeatures设太大,弱特征点被纳入匹配。解决:降到 500 到 1000,加交叉验证过滤(crossCheck=True),再用比率测试(Lowe's ratio)筛一遍。如果场景纹理重复,ORB 本身就不适合,换 SIFT 或加语义约束。
4.4 现象:模型在本地跑得好,部署到边缘设备精度掉一截
原因是预处理不一致。训练时用 OpenCV 读图是 BGR,部署时用其他库读图是 RGB,通道顺序反了。解决:统一读图库和通道顺序,把预处理写成独立函数,训练和部署共用同一份代码。另一个常见原因是 resize 插值方式不同,训练用双线性,部署用最近邻,小目标上差异明显。
4.5 现象:显存溢出,batch 调小后训练变慢且指标下降
原因是 batch 变小后 BatchNorm 统计量不准,梯度噪声变大。解决:改用梯度累积,累积步数等于原 batch 除以现 batch,等效放大 batch。或者换 GroupNorm 替代 BatchNorm,小 batch 下更稳。显存溢出也可能是中间激活没释放,检查有没有在循环里保留计算图。
5. 从综述到落地:把 PPT 变成可复用模板的三个技巧
综述类 PPT 最大的问题是信息密度高但不可执行。我的做法是把它拆成三个可复用模板:预处理模板、模型模板、评估模板。预处理模板固定读图、色彩空间、归一化三步,换任务只改参数;模型模板固定 backbone、head、loss 三段,换任务只改 head 和 loss;评估模板固定混淆矩阵、PR 曲线、延迟测量三块,换任务只改类别数。
延迟测量有个容易忽略的点:要测端到端,不是只测推理。端到端包括读图、预处理、推理、后处理。我一般用time.perf_counter()在循环里打点,跑 100 次取中位数,避免首次加载的冷启动干扰。
import time def benchmark(model, img_path, runs=100): img = cv2.imread(img_path) img = cv2.resize(img, (224, 224)) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1)) tensor = torch.from_numpy(img).unsqueeze(0) model.eval() times = [] with torch.no_grad(): for _ in range(runs): start = time.perf_counter() _ = model(tensor) times.append(time.perf_counter() - start) times.sort() return times[len(times) // 2] # 中位数,抗离群值 latency = benchmark(model, "input.jpg") print(f"median latency: {latency*1000:.2f} ms")中位数比均值稳,因为首次推理和系统调度会产生离群值。如果中位数和均值差很多,说明有偶发卡顿,要查内存交换或线程竞争。
最后一个技巧是版本化。预处理参数、模型权重、评估脚本都要打版本号,写进配置文件和日志。我吃过亏:调了一版预处理参数,指标涨了两个点,结果忘了记,后面复现不出来,只能重跑。现在习惯把配置存成 yaml,和权重放同一目录,跑之前先打印一遍。
这套流程不复杂,难的是每步都留下可复现的记录。希望帮到你。
本文还有配套的精品资源,点击获取