Skip to content

필사 모드: 计算机视觉完全指南:从 CNN 到 ViT、YOLO、Stable Diffusion

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言

计算机视觉(Computer Vision)是让机器理解图像和视频的 AI 核心领域。智能手机人脸解锁、自动驾驶汽车的障碍物识别、医学影像诊断辅助——这些都在依靠计算机视觉技术运作。

本指南将从像素级基础出发,系统讲解到最新的 Vision Transformer、Stable Diffusion为止。


1. 图像基础:像素、通道、卷积

1.1 数字图像的结构

数字图像是像素(pixel)构成的 2D 网格。

  • 灰度图像:H x W 形状的 2D 数组,每个像素值 0~255
  • 彩色图像(RGB):H x W x 3 形状的 3D 张量,R/G/B 每个通道 0~255
  • 分辨率:图像尺寸(例如 1920x1080)、像素密度(DPI)
import torch
import torchvision.transforms as T
from PIL import Image

# 加载图像并转换为张量
img = Image.open("sample.jpg").convert("RGB")
transform = T.Compose([
    T.Resize((224, 224)),
    T.ToTensor(),          # [0,255] -> [0.0,1.0], HWC -> CHW
    T.Normalize(mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225])
])
tensor = transform(img)   # shape: [3, 224, 224]
print(f"Shape: {tensor.shape}, dtype: {tensor.dtype}")

1.2 卷积与主要滤波器

卷积(Convolution)是将一个小的核(滤波器)在整幅图像上滑动,以提取特征的操作。

import torch
import torch.nn.functional as F

# 3x3 Sobel 边缘检测核(水平方向)
kernel = torch.tensor([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]
], dtype=torch.float32).unsqueeze(0).unsqueeze(0)  # [1, 1, 3, 3]

# 对灰度图像应用卷积
gray_tensor = T.ToTensor()(T.Grayscale()(img)).unsqueeze(0)
edges = F.conv2d(gray_tensor, kernel, padding=1)
核类型目的应用场景
Sobel边缘检测(水平/垂直)自动驾驶车道识别
Gaussian模糊、去噪图像预处理
Laplacian边缘强化锐化
Average平均模糊下采样

1.3 Albumentations 数据增强流水线

import albumentations as A
from albumentations.pytorch import ToTensorV2
import cv2

train_transform = A.Compose([
    A.RandomResizedCrop(224, 224, scale=(0.7, 1.0)),
    A.HorizontalFlip(p=0.5),
    A.ColorJitter(brightness=0.4, contrast=0.4,
                  saturation=0.4, hue=0.1, p=0.8),
    A.GaussianBlur(blur_limit=(3, 7), p=0.2),
    A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.1,
                       rotate_limit=15, p=0.5),
    A.Normalize(mean=(0.485, 0.456, 0.406),
                std=(0.229, 0.224, 0.225)),
    ToTensorV2(),
])

image = cv2.cvtColor(cv2.imread("sample.jpg"), cv2.COLOR_BGR2RGB)
augmented = train_transform(image=image)["image"]

2. CNN 架构发展史

2.1 主要架构年表

年份架构核心贡献ImageNet Top-1
1998LeNet-5首个实用 CNN,卷积+池化结构-
2012AlexNetReLU、Dropout、GPU 训练63.3%
2014VGGNet深层堆叠 3x3 核74.4%
2015ResNet-50Skip Connection,残差学习76.0%
2017DenseNet所有层直接连接77.4%
2019EfficientNet-B7复合缩放84.4%
2022ConvNeXt-L将 Transformer 设计原则应用于 CNN86.6%

2.2 ResNet:残差学习的革命

ResNet 的核心是skip connection(残差连接)。通过将输入 x 直接加到输出上,解决了梯度消失问题。残差块的输出为 F(x) + x,求导后得到 dF/dx + 1,从而保证最小梯度为 1。

import torch
import torch.nn as nn
import torchvision.models as models

class ResNetClassifier(nn.Module):
    def __init__(self, num_classes: int, pretrained: bool = True):
        super().__init__()
        weights = models.ResNet50_Weights.IMAGENET1K_V2 if pretrained else None
        self.backbone = models.resnet50(weights=weights)
        in_features = self.backbone.fc.in_features
        self.backbone.fc = nn.Sequential(
            nn.Dropout(0.3),
            nn.Linear(in_features, num_classes)
        )

    def forward(self, x):
        return self.backbone(x)

def train_one_epoch(model, loader, optimizer, criterion, device):
    model.train()
    total_loss, correct = 0.0, 0
    for images, labels in loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item() * images.size(0)
        correct += (outputs.argmax(1) == labels).sum().item()
    return total_loss / len(loader.dataset), correct / len(loader.dataset)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ResNetClassifier(num_classes=10).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)
criterion = nn.CrossEntropyLoss()

2.3 EfficientNet:复合缩放

EfficientNet 引入了同时缩放宽度(width)、深度(depth)、分辨率(resolution)的复合系数(compound coefficient)。

import timm

# EfficientNet-B4 微调
model = timm.create_model(
    "efficientnet_b4",
    pretrained=True,
    num_classes=100,
    drop_rate=0.3
)

# 冻结主干网络(特征提取模式)
for name, param in model.named_parameters():
    if "classifier" not in name:
        param.requires_grad = False

2.4 ConvNeXt:CNN 的现代化

ConvNeXt 将 Transformer 的设计原则(大核、LayerNorm、GELU、更少的激活函数)应用于纯 CNN,达到了与 Swin Transformer 相当的性能。

model = timm.create_model(
    "convnext_large",
    pretrained=True,
    num_classes=1000
)
# 使用 7x7 depthwise conv、LayerNorm、GELU 激活函数

3. 目标检测:YOLO、DETR、Faster R-CNN

3.1 检测方式对比

方式模型速度准确率特点
Anchor-based 2-stageFaster R-CNNRPN + 分类器分离
Anchor-based 1-stageYOLOv8中-高单次前向推理
Anchor-free 1-stageYOLOv10, FCOS非常快无需 NMS,去除锚框
TransformerDETR中等端到端,关系建模

3.2 YOLOv8 实战使用

from ultralytics import YOLO

# 加载预训练模型
model = YOLO("yolov8n.pt")   # nano:速度优先
# model = YOLO("yolov8x.pt") # extra-large:准确率优先

# 单张图像推理
results = model("image.jpg", conf=0.25, iou=0.45)
for result in results:
    for box in result.boxes:
        cls = int(box.cls[0])
        conf = float(box.conf[0])
        x1, y1, x2, y2 = box.xyxy[0].tolist()
        print(f"Class: {result.names[cls]}, Conf: {conf:.2f}")

# 在自定义数据集上微调
model.train(
    data="custom.yaml",
    epochs=100,
    imgsz=640,
    batch=16,
    lr0=0.01,
    device=0
)

# 评估
metrics = model.val()
print(f"mAP50: {metrics.box.map50:.3f}")
print(f"mAP50-95: {metrics.box.map:.3f}")

3.3 YOLOv10:无需 NMS 的检测

YOLOv10 去除了非极大值抑制(Non-Maximum Suppression,NMS)后处理步骤,通过双重标签分配(dual label assignment)实现了端到端学习。它利用一致性匹配(consistency matching),同时运用 one-to-one 预测和 one-to-many 预测。

from ultralytics import YOLO

model = YOLO("yolov10n.pt")
# 无需 NMS,直接返回最终结果 — 降低延迟
results = model.predict("video.mp4", stream=True)
for frame_result in results:
    print(frame_result.boxes)

3.4 DETR:Detection Transformer

DETR 通过二分图匹配损失(bipartite matching loss),无需锚框和 NMS,直接预测最终的边界框集合。

import torch
from transformers import DetrImageProcessor, DetrForObjectDetection
from PIL import Image

processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")

img = Image.open("image.jpg")
inputs = processor(images=img, return_tensors="pt")
outputs = model(**inputs)

target_sizes = torch.tensor([img.size[::-1]])
results = processor.post_process_object_detection(
    outputs, target_sizes=target_sizes, threshold=0.9
)[0]

for score, label, box in zip(
    results["scores"], results["labels"], results["boxes"]
):
    name = model.config.id2label[label.item()]
    print(f"{name}: {score:.3f} at {[round(i,2) for i in box.tolist()]}")

4. 分割:DeepLab、Mask R-CNN、SAM

4.1 分割类型

  • 语义分割(Semantic):为每个像素分配类别标签(汽车、道路、天空……)
  • 实例分割(Instance):区分同一类别中的各个独立对象(汽车1、汽车2……)
  • 全景分割(Panoptic):语义分割 + 实例分割的整合

4.2 SAM:Segment Anything Model

Meta 的 SAM 接收提示(prompt)(点、框、掩码)来对任意对象进行分割。它由 Image Encoder(ViT-H)、Prompt Encoder、Mask Decoder 3 个模块构成,是在 SA-1B 数据集(1 亿个掩码)上训练出的通用分割模型。

import torch
import numpy as np
import cv2
from segment_anything import sam_model_registry, SamPredictor

sam = sam_model_registry["vit_h"](checkpoint="sam_vit_h_4b8939.pth")
sam.to("cuda")
predictor = SamPredictor(sam)

# 设置图像
image = cv2.cvtColor(cv2.imread("image.jpg"), cv2.COLOR_BGR2RGB)
predictor.set_image(image)

# 点提示分割
input_point = np.array([[500, 375]])  # 点击位置 (x, y)
input_label = np.array([1])           # 1=前景,0=背景

masks, scores, logits = predictor.predict(
    point_coords=input_point,
    point_labels=input_label,
    multimask_output=True   # 返回多个候选掩码
)

best_mask = masks[scores.argmax()]
print(f"Mask shape: {best_mask.shape}, Score: {scores.max():.3f}")

# 框提示
input_box = np.array([100, 100, 400, 400])
masks_box, _, _ = predictor.predict(
    box=input_box[None, :],
    multimask_output=False
)

4.3 DeepLabV3+ 语义分割

DeepLabV3+ 使用 ASPP(Atrous Spatial Pyramid Pooling)捕获多尺度的上下文信息。

import torch
import torchvision.models.segmentation as seg_models

model = seg_models.deeplabv3_resnet101(
    weights=seg_models.DeepLabV3_ResNet101_Weights.COCO_WITH_VOC_LABELS_V1
)
model.eval()

with torch.no_grad():
    output = model(tensor.unsqueeze(0))["out"]  # [1, num_classes, H, W]
    pred = output.argmax(dim=1).squeeze()        # [H, W]
    print(f"Segmentation map shape: {pred.shape}")

4.4 Mask R-CNN 实例分割

from torchvision.models.detection import maskrcnn_resnet50_fpn_v2
from torchvision.models.detection import MaskRCNN_ResNet50_FPN_V2_Weights
from torchvision.utils import draw_segmentation_masks
import torchvision.transforms.functional as TF
from PIL import Image

weights = MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT
model = maskrcnn_resnet50_fpn_v2(weights=weights, box_score_thresh=0.75)
model.eval()

img = Image.open("people.jpg").convert("RGB")
inp = weights.transforms()(img).unsqueeze(0)

with torch.no_grad():
    predictions = model(inp)

pred = predictions[0]
masks = (pred["masks"] > 0.5).squeeze(1)
img_uint8 = (TF.to_tensor(img) * 255).byte()
result = draw_segmentation_masks(img_uint8, masks, alpha=0.5)

5. Vision Transformer:ViT、Swin、DINOv2

5.1 ViT 核心原理

ViT(Vision Transformer)将图像划分为固定大小的图块(16x16),把每个图块当作 token 输入 Transformer。与 CNN 不同,它不依赖局部性归纳偏置(locality inductive bias),而是学习全局关系。

import timm
import torch

# ViT-Base/16 微调
model = timm.create_model(
    "vit_base_patch16_224",
    pretrained=True,
    num_classes=10,
    img_size=224
)

# ViT 搭配更强的数据增强 + AdamW + cosine 调度效果更好
data_config = timm.data.resolve_model_data_config(model)
transforms_train = timm.data.create_transform(**data_config, is_training=True)
transforms_val = timm.data.create_transform(**data_config, is_training=False)

optimizer = torch.optim.AdamW(
    model.parameters(), lr=1e-3, weight_decay=0.05
)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=100
)

5.2 Swin Transformer:层级化 ViT

Swin Transformer 通过层级化特征图Shifted Window Attention,将 CNN 的局部性引入了 ViT。每个阶段将分辨率减半、通道数翻倍,因此与 FPN 兼容。

模型分辨率参数量ImageNet Top-1
ViT-B/1622486M81.8%
Swin-T22428M81.3%
Swin-B22488M83.5%
Swin-L384197M87.3%
DINOv2-L518307M86.3%

5.3 DINOv2:自监督学习的巅峰

DINOv2 是在无标签的大规模图像数据上训练出的通用视觉编码器。通过自监督学习(self-supervised learning),其性能超越了基于 ImageNet 的监督学习模型。

import torch

# DINOv2 特征提取器(无需训练即可直接使用)
dinov2 = torch.hub.load("facebookresearch/dinov2", "dinov2_vitl14")
dinov2.eval().cuda()

# 处理任意尺寸的图像
import torchvision.transforms as T
preprocess = T.Compose([
    T.Resize(518),
    T.CenterCrop(518),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225]),
])

with torch.no_grad():
    # [B, 3, 518, 518] -> [B, 1024] 特征
    features = dinov2(preprocess(img).unsqueeze(0).cuda())
    print(f"Feature dim: {features.shape}")  # [1, 1024]

# DINOv2 用 k-NN 分类器也能展现出强大的性能

6. 生成模型:GAN、Diffusion、ControlNet

6.1 生成模型对比

生成模型代表模型训练方式优点缺点
GANStyleGAN3对抗训练生成速度快训练不稳定,模式崩溃
VAEVQ-VAE-2重构 + KL训练稳定图像模糊
DiffusionDDPM, DDIM去噪质量最高生成速度慢
LDMStable Diffusion潜在空间扩散质量与速度均衡GPU 显存占用高

6.2 Stable Diffusion:潜在扩散模型

Stable Diffusion 是一种Latent Diffusion Model(LDM,潜在扩散模型)。U-Net 在潜在空间中逐步去除噪声。

  • Forward process(正向过程):在 T 个步骤中向图像逐步添加高斯噪声
  • Reverse process(逆向过程):U-Net 接收 noisy latent z_t、timestep t、文本嵌入,预测噪声 epsilon
  • VAE decoder:将最终的潜在向量还原为像素空间
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
import torch

# SDXL 1.0 文本生成图像
pipe = StableDiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
)
pipe.scheduler = DPMSolverMultistepScheduler.from_config(
    pipe.scheduler.config
)
pipe.to("cuda")

image = pipe(
    prompt="a photorealistic cat on a desk, 8k, studio lighting",
    negative_prompt="blurry, low quality, cartoon",
    num_inference_steps=25,
    guidance_scale=7.5,
    width=1024,
    height=1024
).images[0]
image.save("generated.png")

6.3 ControlNet:结构条件生成

ControlNet 利用边缘图、深度图、姿态等结构条件,对图像生成进行精细控制。

from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
from diffusers.utils import load_image
import cv2
import numpy as np
from PIL import Image

controlnet = ControlNetModel.from_pretrained(
    "lllyasviel/sd-controlnet-canny",
    torch_dtype=torch.float16
)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    controlnet=controlnet,
    torch_dtype=torch.float16
).to("cuda")

# 生成 Canny 边缘图
ref_image = np.array(load_image("reference.jpg"))
edges = cv2.Canny(ref_image, 100, 200)
edges_rgb = np.stack([edges] * 3, axis=-1)

result = pipe(
    "a beautiful landscape painting",
    image=Image.fromarray(edges_rgb),
    num_inference_steps=20
).images[0]
result.save("controlnet_output.png")

7. 实战流水线:从 DataLoader 到 TensorRT

7.1 自定义 Dataset 与 DataLoader

from torch.utils.data import Dataset, DataLoader
from pathlib import Path
import albumentations as A
from albumentations.pytorch import ToTensorV2
import cv2

class CustomDataset(Dataset):
    def __init__(self, root: str, split: str = "train"):
        self.root = Path(root)
        self.image_paths = list(
            (self.root / split / "images").glob("*.jpg")
        )
        self.label_paths = [
            self.root / split / "labels" / p.with_suffix(".txt").name
            for p in self.image_paths
        ]
        self.transform = self._get_transforms(split)

    def _get_transforms(self, split: str):
        if split == "train":
            return A.Compose([
                A.RandomResizedCrop(224, 224, scale=(0.7, 1.0)),
                A.HorizontalFlip(p=0.5),
                A.ColorJitter(brightness=0.4, contrast=0.4,
                              saturation=0.4, hue=0.1, p=0.8),
                A.GaussianBlur(blur_limit=(3, 7), p=0.2),
                A.Normalize(mean=(0.485, 0.456, 0.406),
                            std=(0.229, 0.224, 0.225)),
                ToTensorV2(),
            ])
        return A.Compose([
            A.Resize(256, 256),
            A.CenterCrop(224, 224),
            A.Normalize(mean=(0.485, 0.456, 0.406),
                        std=(0.229, 0.224, 0.225)),
            ToTensorV2(),
        ])

    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, idx):
        image = cv2.cvtColor(
            cv2.imread(str(self.image_paths[idx])), cv2.COLOR_BGR2RGB
        )
        label = int(self.label_paths[idx].read_text().strip())
        augmented = self.transform(image=image)
        return augmented["image"], label

train_dataset = CustomDataset("data/", split="train")
train_loader = DataLoader(
    train_dataset, batch_size=32, shuffle=True,
    num_workers=4, pin_memory=True, persistent_workers=True
)

7.2 ONNX 转换

import torch
import torch.onnx

model.eval()
dummy_input = torch.randn(1, 3, 224, 224, device="cuda")

torch.onnx.export(
    model,
    dummy_input,
    "model.onnx",
    export_params=True,
    opset_version=17,
    do_constant_folding=True,
    input_names=["input"],
    output_names=["output"],
    dynamic_axes={
        "input":  {0: "batch_size"},
        "output": {0: "batch_size"}
    }
)
print("ONNX 转换完成:model.onnx")

# 用 ONNX Runtime 验证
import onnxruntime as ort
import numpy as np

sess = ort.InferenceSession(
    "model.onnx", providers=["CUDAExecutionProvider"]
)
input_name = sess.get_inputs()[0].name
result = sess.run(None, {input_name: dummy_input.cpu().numpy()})
print(f"ONNX 推理结果 shape: {result[0].shape}")

7.3 TensorRT 优化

# 使用 trtexec 进行 TensorRT 转换
trtexec \
  --onnx=model.onnx \
  --saveEngine=model_fp16.engine \
  --fp16 \
  --workspace=4096 \
  --optShapes=input:8x3x224x224
import tensorrt as trt
import numpy as np
import pycuda.driver as cuda
import pycuda.autoinit

def trt_inference(engine_path: str, input_data: np.ndarray) -> np.ndarray:
    logger = trt.Logger(trt.Logger.WARNING)
    with open(engine_path, "rb") as f:
        engine = trt.Runtime(logger).deserialize_cuda_engine(f.read())
    context = engine.create_execution_context()

    input_mem = cuda.mem_alloc(input_data.nbytes)
    output = np.empty((input_data.shape[0], 1000), dtype=np.float32)
    output_mem = cuda.mem_alloc(output.nbytes)

    cuda.memcpy_htod(input_mem, input_data)
    context.execute_v2([int(input_mem), int(output_mem)])
    cuda.memcpy_dtoh(output, output_mem)
    return output

测验:深入理解计算机视觉

Q1. ResNet 的 skip connection 是如何解决 vanishing gradient 问题的?

答案:反向传播时,梯度通过 skip connection 直接传递,因此即使在深层网络中也不会消失。

解析:在普通的深层神经网络中,反向传播的梯度会随着层数增加而以乘法方式逐渐变小。ResNet 的残差块 F(x) + x 求导后为 dF/dx + 1,从而始终保证最小梯度为 1。正因如此,即使超过 100 层也能稳定训练。

Q2. 为什么 YOLO 比 Faster R-CNN 更适合实时推理?

答案:YOLO 通过单次前向传播(single forward pass)就能完成检测,而 Faster R-CNN 需要 Region Proposal Network 和分类器两个阶段。

解析:Faster R-CNN 是(1)RPN 生成候选区域,(2)RoI Pooling,(3)分类与回归的多阶段结构。YOLO 将图像划分为网格,通过一次 CNN 前向传播同时预测所有边界框和类别。YOLOv8n 在 A100 GPU 上可以达到 80+ FPS。

Q3. 为什么 Vision Transformer 在大规模数据上的表现优于 CNN?

答案:ViT 的 Self-Attention 学习所有图块之间的全局关系,在没有 inductive bias 的情况下直接从数据中学习最优表示。

解析:CNN 内置了局部性(locality)和平移等变性(translation equivariance)这两种 inductive bias。在数据较少时,这种偏置是有帮助的,但在大规模数据(如 JFT-300M)上会限制表达能力。ViT 不受偏置约束,可以自由学习全局模式,因此在数据充足时会全面超越 CNN。

Q4. 在 Stable Diffusion 的 denoising diffusion process 中,U-Net 起什么作用?

答案:U-Net 在每个 timestep 预测并去除添加到潜在向量中的噪声,并通过 cross-attention 融合文本条件(CLIP 嵌入)。

解析:在正向过程(forward process)中,会在 T 个步骤中向图像添加高斯噪声。在逆向过程(reverse process)中,U-Net 接收 noisy latent z_t、timestep t 和文本嵌入作为输入,预测噪声成分 epsilon。最后由 VAE 解码器将最终的潜在向量还原为像素空间。

Q5. SAM 的 prompt-based segmentation 与传统方法有何不同?

答案:SAM 接收点、框、掩码等多种提示,无需额外训练即可以零样本方式分割任意对象。

解析:传统的分割模型(DeepLab、Mask R-CNN)是针对特定类别集合进行监督训练的。SAM 是在 SA-1B 数据集(1 亿个掩码)上训练出的通用模型,不受类别限制,可以分割用户指定的任意区域。它由 Image Encoder(ViT-H)、Prompt Encoder、Mask Decoder 3 个模块构成。


结语:学习路线图

计算机视觉是一个快速发展的领域。推荐的学习路径如下:

  1. 基础:OpenCV、NumPy 图像处理 → torchvision 实践
  2. 分类:ResNet/EfficientNet 微调 → 应用于自定义数据集
  3. 检测:YOLOv8 实验 → 自定义训练 → ONNX/TensorRT 部署
  4. 分割:SAM 实验 → Mask R-CNN/DeepLabV3+ 自定义训练
  5. 进阶:ViT/DINOv2 特征提取 → Stable Diffusion 微调

在每个阶段都通过Kaggle 竞赛实际项目加以应用,是最快的学习方法。

현재 단락 (1/390)

计算机视觉(Computer Vision)是让机器理解图像和视频的 AI 核心领域。智能手机人脸解锁、自动驾驶汽车的障碍物识别、医学影像诊断辅助——这些都在依靠计算机视觉技术运作。

작성 글자: 0원문 글자: 14,944작성 단락: 0/390