引言
计算机视觉(CV)是深度学习落地最广的方向——图像分类、目标检测、分割、OCR。核心管线清晰:图像变成张量 → CNN 提特征 → 任务头输出。本文从图像数据的底层表示讲起,用 OpenCV 讲预处理与增强,再完整跑通 PyTorch 的 CIFAR-10 图像分类,最后概览目标检测/分割和迁移学习,让你对 CV 实战有全景认知。
前置:/ml-deep-learning-advanced/(CNN 结构)、/ml-python-environment-setup/(NumPy/环境)。
目录
- 1. 图像数据的表示
- 2. OpenCV 图像预处理
- 3. 数据增强:让模型见多识广
- 4. 经典 CNN 网络演进
- 5. PyTorch 实战:CIFAR-10 图像分类
- 6. 迁移学习与 ResNet 微调
- 7. 目标检测概览
- 8. 图像分割与 OCR
- 9. 部署与常见问题
- 10. 速查表
- 延伸阅读
1. 图像数据的表示
图像 = 三维张量:H × W × C(高、宽、通道)。
灰度图: 224 × 224 × 1(0-255 亮度)
彩色图: 224 × 224 × 3(RGB 三通道)
NumPy/PyTorch 表示:
import numpy as np
from PIL import Image
# PIL 打开 → numpy 数组 (H, W, C)
img = np.array(Image.open('cat.jpg')) # (224, 224, 3), 0-255
print(img.shape, img.dtype)
# 归一化到 [0,1](模型输入习惯)
img_norm = img / 255.0
# 通道顺序:PIL/OpenCV 是 HWC,PyTorch 要 CHW
img_chw = img.transpose(2, 0, 1) # (3, 224, 224)
| 维度 | 含义 | 注意 |
|---|---|---|
| H/W | 像素行列 | 需统一尺寸 |
| C | 通道数 | RGB=3/灰度=1 |
| dtype | uint8(0-255) | 转 float 归一化 |
关键认知:模型不吃「图像」,吃「数字张量」——加载、缩放、归一化、转 CHW 是固定四步。
2. OpenCV 图像预处理
OpenCV 常用预处理:
import cv2
# 读取与缩放
img = cv2.imread('cat.jpg') # BGR 顺序!(与 RGB 相反)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转 RGB
img = cv2.resize(img, (224, 224)) # 统一尺寸
# 灰度/二值化(传统 CV)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 模糊/边缘
blur = cv2.GaussianBlur(img, (5, 5), 0)
edges = cv2.Canny(gray, 100, 200)
# 仿射变换/裁剪
M = cv2.getRotationMatrix2D((112, 112), 30, 1.0) # 旋转 30°
rot = cv2.warpAffine(img, M, (224, 224))
| 预处理 | 用途 |
|---|---|
| resize | 统一模型输入尺寸 |
| cvtColor | BGR↔RGB、灰度 |
| normalize | 均值为 0、方差为 1 |
| 去噪 | GaussianBlur |
| 边缘检测 | Canny(传统特征) |
| 透视校正 | 文档扫描 |
BGR 陷阱:OpenCV 读图默认 BGR,送 PyTorch 前必须转 RGB,否则颜色全错。
3. 数据增强:让模型见多识广
增强 = 用变换制造「更多样本」,专治数据不足与过拟合:
from torchvision import transforms
train_tf = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), # 随机裁剪缩放
transforms.RandomHorizontalFlip(p=0.5), # 随机翻转
transforms.RandomRotation(10), # 随机旋转
transforms.ColorJitter(brightness=0.2, contrast=0.2), # 颜色抖动
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], # ImageNet 均值方差
[0.229, 0.224, 0.225]),
])
# 验证/测试:只做标准化,不做增强
val_tf = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225]),
])
增强原则:
| 原则 | 说明 |
|---|---|
| 训练集增强、测试集不增 | 测试要「公平」 |
| 用真实变换 | 翻转适合自然场景,不适合数字识别 |
| 保持标签不变 | 裁剪别裁掉主体 |
| 增强强度适度 | 过强反而学不到 |
记忆:增强 = 免费的更多数据——同预算下常能提 2-5 个点准确率。
4. 经典 CNN 网络演进
看几个里程碑网络(理解设计思路):
| 网络 | 年份 | 关键创新 |
|---|---|---|
| AlexNet | 2012 | 深度 CNN + ReLU + Dropout |
| VGG | 2014 | 小核堆叠(3×3) |
| ResNet | 2015 | 残差连接(解决梯度消失) |
| Inception | 2014 | 多尺度并行卷积 |
| EfficientNet | 2019 | 缩放法则(深/宽/分辨率) |
ResNet 残差连接(最重要的思想):
普通块: y = F(x) # 深层难训练
残差块: y = F(x) + x # 学"残差",退化网络保底 = 恒等映射
import torch.nn as nn
class BasicBlock(nn.Module):
def __init__(self, in_c, out_c):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_c, out_c, 3, padding=1),
nn.BatchNorm2d(out_c), nn.ReLU(),
nn.Conv2d(out_c, out_c, 3, padding=1),
nn.BatchNorm2d(out_c),
)
self.shortcut = nn.Identity() if in_c == out_c else \
nn.Conv2d(in_c, out_c, 1)
self.relu = nn.ReLU()
def forward(self, x):
return self.relu(self.conv(x) + self.shortcut(x)) # 残差相加
残差让网络「加深不退化」——ResNet 至今仍是提取特征的主干标准。
5. PyTorch 实战:CIFAR-10 图像分类
完整流程(数据 → 模型 → 训练 → 评估):
import torch, torch.nn as nn, torchvision
from torchvision import datasets, transforms
import torchvision.models as models
# 1. 数据(增强 + 归一化)
tf = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomCrop(32, padding=4),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616)),
])
trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=tf)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
# 2. 模型(用 torchvision 现成 ResNet18)
model = models.resnet18(num_classes=10)
# 3. 训练
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss()
for epoch in range(20):
model.train()
for images, labels in trainloader:
optimizer.zero_grad()
loss = criterion(model(images), labels)
loss.backward()
optimizer.step()
print(f'epoch {epoch}: loss={loss.item():.3f}')
# 4. 评估
model.eval()
correct = total = 0
with torch.no_grad():
for images, labels in torch.utils.data.DataLoader(
datasets.CIFAR10(root='./data', train=False, download=True,
transform=transforms.ToTensor()), batch_size=64):
correct += (model(images).argmax(1) == labels).sum().item()
total += labels.size(0)
print(f'准确率: {correct/total:.4f}')
CIFAR-10:6 万张 32×32 彩色图、10 类(飞机/汽车/鸟/猫/狗/蛙/马/船/卡车)。ResNet18 从头训约 90%+。
6. 迁移学习与 ResNet 微调
数据少时直接用预训练 ResNet(ImageNet 1000 类权重):
import torchvision.models as models
# 加载 ImageNet 预训练
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
# 冻结主干
for param in model.parameters():
param.requires_grad = False
# 替换分类头 → 你的 N 类
model.fc = nn.Linear(model.fc.in_features, num_classes)
# 只训分类头(数据 <1k 时);数据多再解冻微调深层
optimizer = torch.optim.AdamW(model.fc.parameters(), lr=1e-3)
| 数据量 | 冻结策略 |
|---|---|
| <1k | 冻结全部主干,只训分类头 |
| 1k-10k | 解冻后几层微调 |
| >10k | 全量微调 + 小学习率 |
记忆:CV 项目 90% 是「预训练 + 微调」——别从随机权重从头训。
7. 目标检测概览
分类知道「是什么」,检测还要「在哪里」——输出边界框 + 类别:
| 家族 | 代表 | 思想 |
|---|---|---|
| 两阶段 | Faster R-CNN | 先生成候选框再分类 |
| 单阶段 | YOLO / SSD | 直接回归框+类别(快) |
| Transformer | DETR | 注意力做检测(端到端) |
YOLO 一句话:把图像分网格,每格直接预测「框 + 类别概率」,一次前向出结果——快,适合实时。
# 用 Ultralytics YOLO(极简)
from ultralytics import YOLO
model = YOLO('yolov8n.pt') # 预训练
results = model('test.jpg') # 检测
for r in results:
print(r.boxes.xyxy, r.boxes.cls, r.boxes.conf)
何时用:行人检测、缺陷检测、自动驾驶感知、票据要素定位。
8. 图像分割与 OCR
分割(Segmentation):像素级分类——区分「哪些像素是猫」:
| 类型 | 输出 | 代表 |
|---|---|---|
| 语义分割 | 每像素类别 | U-Net、DeepLab |
| 实例分割 | 每个实例独立 | Mask R-CNN |
| 全景分割 | 语义+实例 | Panoptic |
U-Net(医学图像分割标配):编码-解码 + 跳跃连接(保空间细节)。
OCR(文字识别):
# 经典 OCR 工具链
import pytesseract, cv2
from PIL import Image
img = Image.open('receipt.jpg')
text = pytesseract.image_to_string(img, lang='chi_sim+eng')
print(text)
# 工业级:PaddleOCR(中文强、版式好)
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
result = ocr.ocr('scan.jpg', cls=True)
OCR 流程:检测文本区域 → 识别文字 → 结构化(键值对/表格)。
9. 部署与常见问题
部署要点(完整见 /ml-model-deployment/):
# 导出 ONNX(跨框架部署)
import torch
dummy = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy, 'model.onnx',
input_names=['input'], output_names=['output'])
CV 常见问题:
| 问题 | 解法 |
|---|---|
| 过拟合(数据少) | 增强 + 迁移学习 |
| 类别不平衡 | 加权损失/采样 |
| 训练不稳定 | BN + 小学习率 |
| 颜色全错 | 检查 BGR→RGB |
| 尺寸不一致 | 统一 resize/crop |
| 小目标检测差 | 多尺度/更高分辨率 |
| 推理慢 | 模型量化/剪枝/ONNX |
记忆:CV 管线 = 读图转张量 → 预处理增强 → CNN 特征 → 任务头 → 部署;数据少迁移、训练稳靠 BN、部署用 ONNX。
10. 速查表
| 需求 | 做法 |
|---|---|
| 图像分类 | CNN / ResNet(迁移) |
| 数据少 | 增强 + 预训练微调 |
| 目标检测 | YOLO(快)或 Faster R-CNN(准) |
| 像素级分割 | U-Net / Mask R-CNN |
| 文字识别 | PaddleOCR / Tesseract |
| 预处理 | resize + normalize + RGB |
| 训练稳 | BatchNorm + AdamW |
| 部署 | ONNX / TensorRT |
| 实时检测 | YOLO + 量化 |
一句话记忆:图像是三维张量,OpenCV 管预处理、增强造数据;分类用 ResNet 迁移、检测用 YOLO、分割用 U-Net、OCR 用 PaddleOCR;部署转 ONNX——CV 一套管线走天下。
延伸阅读
- /ml-deep-learning-advanced/ — CNN 结构与训练技巧
- /ml-model-deployment/ — 模型部署与 MLOps
- /ml-neural-networks-basics/ — 张量与自动求导
- [[ai-ml]] — CV 算法深度专题
- [[hpc]] — 推理加速(GPU)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。