AI计算机视觉应用开发完全教程

教程简介

本教程全面讲解AI计算机视觉应用开发的核心技术,涵盖CNN架构演进、YOLO目标检测、SAM语义分割、PaddleOCR文字识别、视频分析与行为识别、3D视觉、视觉Transformer(ViT/Swin)、边缘端部署等核心内容,通过智能安防视觉系统案例帮助开发者掌握计算机视觉技术。

AI计算机视觉应用开发完全教程

1. 计算机视觉概述与技术栈

计算机视觉(Computer Vision)旨在让计算机从图像和视频中获取高层次的理解能力。从技术栈角度来看,一个典型的视觉系统包含以下层次:

基础设施层:GPU/TPU计算资源、CUDA/cuDNN加速库、容器化部署 框架层:PyTorch、TensorFlow、ONNX Runtime、OpenCV 模型层:预训练模型(ImageNet、COCO)、自定义微调模型 应用层:图像分类、目标检测、分割、OCR、视频分析

现代计算机视觉的技术演进可以概括为:手工特征(SIFT/HOG)→ CNN(AlexNet/VGG)→ 深度CNN(ResNet/EfficientNet)→ Transformer(ViT/Swin)→ 多模态大模型(CLIP/GPT-4V)。每一轮演进都显著提升了视觉系统的感知能力。

2. 图像分类与CNN架构演进

2.1 CNN基础架构

卷积神经网络通过卷积层提取局部特征、池化层降低空间维度、全连接层进行分类决策。

import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicCNN(nn.Module):
    """基础CNN分类网络"""
    def __init__(self, num_classes=10):
        super().__init__()
        self.features = nn.Sequential(
            # 第一个卷积块
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),

            # 第二个卷积块
            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),

            # 第三个卷积块
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.AdaptiveAvgPool2d((1, 1))
        )
        self.classifier = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(128, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        return self.classifier(x)

# 模型实例化与推理
model = BasicCNN(num_classes=10)
x = torch.randn(1, 3, 32, 32)  # 批次1,3通道,32x32图像
output = model(x)
print(f"输出形状: {output.shape}")  # (1, 10)

2.2 经典架构演进

import torchvision.models as models

# 各阶段经典模型加载
models_dict = {
    "AlexNet": models.alexnet(pretrained=True),           # 2012, 开启深度学习时代
    "VGG16": models.vgg16(pretrained=True),               # 2014, 更深的网络
    "ResNet50": models.resnet50(pretrained=True),          # 2015, 残差连接
    "DenseNet121": models.densenet121(pretrained=True),    # 2017, 密集连接
    "EfficientNet-B0": models.efficientnet_b0(pretrained=True),  # 2019, 复合缩放
}

# 使用预训练ResNet进行推理
from torchvision import transforms
from PIL import Image

model = models.resnet50(pretrained=True)
model.eval()

preprocess = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225]),
])

# image = Image.open("cat.jpg")
# input_tensor = preprocess(image).unsqueeze(0)
# with torch.no_grad():
#     output = model(input_tensor)
#     probabilities = torch.softmax(output[0], dim=0)
#     top5 = torch.topk(probabilities, 5)

2.3 迁移学习实战

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

def create_classifier(num_classes, freeze_backbone=True):
    """基于预训练ResNet50的迁移学习分类器"""
    model = models.resnet50(pretrained=True)

    if freeze_backbone:
        # 冻结骨干网络参数
        for param in model.parameters():
            param.requires_grad = False

    # 替换最后的全连接层
    in_features = model.fc.in_features
    model.fc = nn.Sequential(
        nn.Dropout(0.3),
        nn.Linear(in_features, 512),
        nn.ReLU(),
        nn.Dropout(0.2),
        nn.Linear(512, num_classes)
    )

    return model

# 创建5分类模型
model = create_classifier(num_classes=5)

# 只训练最后的分类层
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f"可训练参数: {trainable_params:,} / 总参数: {total_params:,}")
print(f"训练比例: {trainable_params/total_params*100:.2f}%")

3. 目标检测:YOLO系列与DETR

3.1 YOLO系列

YOLO(You Only Look Once)将目标检测视为单次回归问题,实现了速度与精度的良好平衡。

# 使用Ultralytics YOLOv8
# pip install ultralytics
from ultralytics import YOLO

# 加载预训练模型
model = YOLO("yolov8n.pt")  # nano版本,速度最快

# 推理
results = model("street_scene.jpg", conf=0.5, iou=0.45)

# 解析检测结果
for result in results:
    boxes = result.boxes
    for box in boxes:
        cls_id = int(box.cls[0])
        conf = float(box.conf[0])
        xyxy = box.xyxy[0].tolist()  # [x1, y1, x2, y2]
        class_name = model.names[cls_id]
        print(f"检测到: {class_name}, 置信度: {conf:.3f}, 位置: {xyxy}")

# 自定义训练
def train_yolo_custom():
    """训练自定义YOLO检测模型"""
    model = YOLO("yolov8s.pt")  # 使用small版本作为基础

    # 训练(需要准备YOLO格式的数据集)
    results = model.train(
        data="custom_dataset.yaml",
        epochs=100,
        imgsz=640,
        batch=16,
        lr0=0.01,
        lrf=0.01,
        momentum=0.937,
        weight_decay=0.0005,
        augment=True,
        device="0"  # GPU编号
    )
    return results

3.2 DETR:基于Transformer的端到端检测

DETR(Detection Transformer)摒弃了锚框和NMS等手工组件,实现了真正的端到端检测。

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

# 加载DETR模型
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")

# 推理
image = Image.open("street_scene.jpg")
inputs = processor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)

# 解析结果
results = processor.post_process_object_detection(
    outputs, threshold=0.9, target_sizes=[(image.height, image.width)]
)[0]

for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
    box = [round(i, 2) for i in box.tolist()]
    print(f"检测到 {model.config.id2label[label.item()]}: "
          f"置信度={round(score.item(), 3)}, 位置={box}")

3.3 YOLO与DETR对比

特性 YOLOv8 DETR
速度 极快(实时) 较慢
小目标检测 良好 较弱
端到端 否(需NMS)
训练难度 简单 需要更多数据
适用场景 实时检测 精度优先场景

4. 实例分割与语义分割

4.1 Mask R-CNN实例分割

import torch
import torchvision
from torchvision.models.detection import maskrcnn_resnet50_fpn
from torchvision import transforms
from PIL import Image
import numpy as np

# 加载预训练Mask R-CNN
model = maskrcnn_resnet50_fpn(pretrained=True)
model.eval()

# 图像预处理
image = Image.open("scene.jpg").convert("RGB")
transform = transforms.Compose([transforms.ToTensor()])
input_tensor = transform(image).unsqueeze(0)

# 推理
with torch.no_grad():
    predictions = model(input_tensor)

# 解析分割结果
pred = predictions[0]
masks = pred["masks"]           # (N, 1, H, W) 分割掩码
boxes = pred["boxes"]           # (N, 4) 边界框
labels = pred["labels"]         # (N,) 类别标签
scores = pred["scores"]         # (N,) 置信度

# 筛选高置信度结果
threshold = 0.7
high_conf = scores > threshold
masks = masks[high_conf].squeeze(1)  # (N, H, W)
boxes = boxes[high_conf]
labels = labels[high_conf]

print(f"检测到 {len(masks)} 个实例")
for i in range(len(masks)):
    print(f"实例 {i+1}: 类别={labels[i].item()}, "
          f"面积={masks[i].sum().item():.0f} 像素")

4.2 SAM:Segment Anything Model

SAM是Meta推出的通用分割模型,能够根据点击、框选或文本提示对任意物体进行分割。

# pip install segment-anything
from segment_anything import sam_model_registry, SamPredictor
import cv2
import numpy as np

# 加载SAM模型
sam_checkpoint = "sam_vit_h_4b8939.pth"
model_type = "vit_h"
sam = sam_model_registry[model_type](checkpoint=sam_checkpoint)
sam.to("cuda")
predictor = SamPredictor(sam)

# 加载图像并设置
image = cv2.imread("scene.jpg")
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
predictor.set_image(image_rgb)

# 点击式分割:指定一个点进行分割
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[np.argmax(scores)]
print(f"掩码形状: {best_mask.shape}")
print(f"分割面积: {best_mask.sum()} 像素")

# 框选式分割
box = np.array([100, 100, 400, 400])  # [x1, y1, x2, y2]
masks, scores, logits = predictor.predict(
    box=box,
    multimask_output=False
)

4.3 语义分割

语义分割为图像中的每个像素分配类别标签,不区分同类实例。

from transformers import SegformerForSemanticSegmentation, SegformerFeatureExtractor
from PIL import Image
import torch
import numpy as np

# 加载SegFormer语义分割模型
model = SegformerForSemanticSegmentation.from_pretrained(
    "nvidia/segformer-b0-finetuned-ade-512-512"
)
feature_extractor = SegformerFeatureExtractor.from_pretrained(
    "nvidia/segformer-b0-finetuned-ade-512-512"
)

# 推理
image = Image.open("street_scene.jpg")
inputs = feature_extractor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)
    logits = outputs.logits

# 上采样到原图大小
upsampled = torch.nn.functional.interpolate(
    logits, size=image.size[::-1], mode="bilinear", align_corners=False
)
pred = upsampled.argmax(dim=1)[0].numpy()

# 可视化(使用ADE20K颜色映射)
unique_classes = np.unique(pred)
print(f"检测到的类别数: {len(unique_classes)}")
for cls_id in unique_classes:
    pixel_count = (pred == cls_id).sum()
    label = model.config.id2label[cls_id]
    print(f"  {label}: {pixel_count} 像素")

5. OCR文字识别

5.1 PaddleOCR

PaddleOCR是百度开源的OCR工具库,支持中英文识别,精度高且部署灵活。

# pip install paddlepaddle paddleocr
from paddleocr import PaddleOCR

# 初始化OCR引擎
ocr = PaddleOCR(
    use_angle_cls=True,  # 启用文字方向分类
    lang="ch",           # 中文识别
    use_gpu=True
)

# 识别
result = ocr.ocr("document.jpg", cls=True)

for line in result[0]:
    box, (text, confidence) = line
    print(f"文字: {text}")
    print(f"置信度: {confidence:.4f}")
    print(f"位置: {box}\n")

# 批量处理
def batch_ocr(image_paths):
    """批量OCR处理"""
    all_results = []
    for path in image_paths:
        result = ocr.ocr(path, cls=True)
        texts = [line[1][0] for line in result[0]]
        all_results.append({
            "file": path,
            "texts": texts,
            "full_text": "\n".join(texts)
        })
    return all_results

# 版面分析
from paddleocr import PPStructure

engine = PPStructure(show_log=False, lang="ch")
result = engine("complex_document.jpg")

for block in result:
    if block["type"] == "text":
        print(f"[文本区域] {block['res'][0]['text']}")
    elif block["type"] == "table":
        print(f"[表格区域] {block['res']['html']}")
    elif block["type"] == "figure":
        print("[图片区域]")

5.2 Tesseract OCR

import pytesseract
from PIL import Image

# 基础文字识别
image = Image.open("text_image.jpg")
text = pytesseract.image_to_string(image, lang="chi_sim+eng")
print(text)

# 获取详细信息(位置、置信度)
data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT)
for i, word in enumerate(data["text"]):
    if word.strip():
        conf = int(data["conf"][i])
        x, y, w, h = data["left"][i], data["top"][i], data["width"][i], data["height"][i]
        print(f"'{word}' 置信度:{conf} 位置:({x},{y},{w},{h})")

# 表格识别
import pandas as pd
tables = pytesseract.image_to_data(image, output_type="data.frame")

6. 视频分析与行为识别

6.1 视频目标跟踪

import cv2
from ultralytics import YOLO

# 使用YOLOv8进行视频跟踪
model = YOLO("yolov8n.pt")

# 处理视频流
cap = cv2.VideoCapture("video.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

# 输出视频
out = cv2.VideoWriter("output_tracked.mp4",
                       cv2.VideoWriter_fourcc(*"mp4v"),
                       fps, (width, height))

track_history = {}  # 跟踪轨迹记录

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break

    # 使用ByteTrack进行多目标跟踪
    results = model.track(frame, persist=True, tracker="bytetrack.yaml")

    if results[0].boxes.id is not None:
        boxes = results[0].boxes
        for box in boxes:
            track_id = int(box.id[0])
            cls = int(box.cls[0])
            xyxy = box.xyxy[0].tolist()

            # 记录轨迹
            center = ((xyxy[0]+xyxy[2])/2, (xyxy[1]+xyxy[3])/2)
            if track_id not in track_history:
                track_history[track_id] = []
            track_history[track_id].append(center)

    # 绘制结果
    annotated = results[0].plot()
    out.write(annotated)

cap.release()
out.release()

# 分析轨迹
for track_id, points in track_history.items():
    print(f"目标 {track_id}: 轨迹点数={len(points)}, "
          f"移动距离={sum(((points[i][0]-points[i-1][0])**2 + (points[i][1]-points[i-1][1])**2)**0.5 for i in range(1, len(points))):.1f}px")

6.2 行为识别

import torch
from transformers import VideoMAEForVideoClassification, VideoMAEFeatureExtractor
import cv2
import numpy as np

# 使用VideoMAE进行行为识别
model = VideoMAEForVideoClassification.from_pretrained(
    "MCG-NJU/videomae-base-finetuned-kinetics"
)
feature_extractor = VideoMAEFeatureExtractor.from_pretrained(
    "MCG-NJU/videomae-base-finetuned-kinetics"
)

def extract_frames(video_path, num_frames=16):
    """从视频中均匀采样帧"""
    cap = cv2.VideoCapture(video_path)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    indices = np.linspace(0, total_frames - 1, num_frames, dtype=int)

    frames = []
    for idx in indices:
        cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
        ret, frame = cap.read()
        if ret:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            frames.append(frame)
    cap.release()
    return frames

# 推理
frames = extract_frames("action_video.mp4", num_frames=16)
inputs = feature_extractor(frames, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)
    probs = torch.softmax(outputs.logits, dim=-1)

top5 = torch.topk(probs[0], 5)
for score, idx in zip(top5.values, top5.indices):
    label = model.config.id2label[idx.item()]
    print(f"{label}: {score.item():.4f}")

7. 3D视觉:点云与深度估计

7.1 单目深度估计

import torch
from transformers import DPTForDepthEstimation, DPTFeatureExtractor
from PIL import Image
import numpy as np

# 使用DPT进行单目深度估计
feature_extractor = DPTFeatureExtractor.from_pretrained("Intel/dpt-large")
model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large")

image = Image.open("indoor_scene.jpg")
inputs = feature_extractor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)
    predicted_depth = outputs.predicted_depth

# 插值到原图大小
depth = torch.nn.functional.interpolate(
    predicted_depth.unsqueeze(1),
    size=image.size[::-1],
    mode="bicubic",
    align_corners=False
).squeeze().numpy()

print(f"深度图尺寸: {depth.shape}")
print(f"深度范围: {depth.min():.2f} ~ {depth.max():.2f} 米")

# 归一化用于可视化
depth_normalized = ((depth - depth.min()) / (depth.max() - depth.min()) * 255).astype(np.uint8)
# Image.fromarray(depth_normalized).save("depth_map.png")

7.2 点云处理基础

import numpy as np

# 从深度图生成点云
def depth_to_pointcloud(depth_map, intrinsic, rgb_image=None):
    """将深度图转换为3D点云"""
    h, w = depth_map.shape
    fx, fy = intrinsic[0, 0], intrinsic[1, 1]
    cx, cy = intrinsic[0, 2], intrinsic[1, 2]

    # 生成像素网格
    u, v = np.meshgrid(np.arange(w), np.arange(h))

    # 反投影到3D空间
    z = depth_map
    x = (u - cx) * z / fx
    y = (v - cy) * z / fy

    # 组合点云
    points = np.stack([x, y, z], axis=-1).reshape(-1, 3)

    # 过滤无效点
    valid = (z.reshape(-1) > 0) & (z.reshape(-1) < 100)
    points = points[valid]

    if rgb_image is not None:
        colors = rgb_image.reshape(-1, 3)[valid] / 255.0
        return points, colors

    return points

# 使用Open3D可视化(可选)
# import open3d as o3d
# pcd = o3d.geometry.PointCloud()
# pcd.points = o3d.utility.Vector3dVector(points)
# pcd.colors = o3d.utility.Vector3dVector(colors)
# o3d.visualization.draw_geometries([pcd])

# 简单的相机内参示例
intrinsic = np.array([
    [525.0, 0, 319.5],
    [0, 525.0, 239.5],
    [0, 0, 1]
])

8. 视觉Transformer:ViT与Swin

8.1 Vision Transformer (ViT)

ViT将图像分割为固定大小的patch,将每个patch视为一个token,直接应用标准Transformer编码器。

import torch
from transformers import ViTForImageClassification, ViTFeatureExtractor
from PIL import Image

# 加载预训练ViT
model = ViTForImageClassification.from_pretrained(
    "google/vit-base-patch16-224"
)
feature_extractor = ViTFeatureExtractor.from_pretrained(
    "google/vit-base-patch16-224"
)

# 推理
image = Image.open("dog.jpg")
inputs = feature_extractor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)
    probs = torch.softmax(outputs.logits, dim=-1)

top5 = torch.topk(probs[0], 5)
for score, idx in zip(top5.values, top5.indices):
    print(f"{model.config.id2label[idx.item()]}: {score.item():.4f}")

# 查看ViT内部结构
print(f"隐藏维度: {model.config.hidden_size}")      # 768
print(f"注意力头数: {model.config.num_attention_heads}")  # 12
print(f"Transformer层数: {model.config.num_hidden_layers}")  # 12
print(f"Patch大小: {model.config.patch_size}")        # 16

8.2 Swin Transformer

Swin Transformer通过层级结构和滑动窗口注意力机制,在多种视觉任务上取得了优异表现。

from transformers import AutoImageProcessor, SwinForImageClassification
from PIL import Image
import torch

# 加载Swin Transformer
processor = AutoImageProcessor.from_pretrained("microsoft/swin-tiny-patch4-window7-224")
model = SwinForImageClassification.from_pretrained("microsoft/swin-tiny-patch4-window7-224")

# 推理
image = Image.open("cat.jpg")
inputs = processor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)
    probs = torch.softmax(outputs.logits, dim=-1)

pred_class = probs.argmax(-1).item()
print(f"预测类别: {model.config.id2label[pred_class]}")
print(f"置信度: {probs[0][pred_class].item():.4f}")

# Swin的层级特性:不同stage的特征图分辨率
# Stage 1: H/4 x W/4,   dim=96
# Stage 2: H/8 x W/8,   dim=192
# Stage 3: H/16 x W/16, dim=384
# Stage 4: H/32 x W/32, dim=768

8.3 CNN vs Transformer 对比

import time
import torch
from torchvision import models
from transformers import ViTForImageClassification

def benchmark_model(model, input_shape, device="cuda", num_runs=100):
    """模型性能基准测试"""
    model = model.to(device).eval()
    x = torch.randn(*input_shape).to(device)

    # 预热
    for _ in range(10):
        with torch.no_grad():
            model(x)

    # 计时
    start = time.time()
    for _ in range(num_runs):
        with torch.no_grad():
            model(x)
    elapsed = (time.time() - start) / num_runs

    # 参数量
    params = sum(p.numel() for p in model.parameters())

    return {
        "参数量": f"{params/1e6:.1f}M",
        "推理时间": f"{elapsed*1000:.2f}ms",
        "FPS": f"{1/elapsed:.1f}"
    }

# 对比测试
# resnet50 = models.resnet50(pretrained=True)
# vit = ViTForImageClassification.from_pretrained("google/vit-base-patch16-224")
# input_shape = (1, 3, 224, 224)
# print("ResNet50:", benchmark_model(resnet50, input_shape))
# print("ViT-Base:", benchmark_model(vit, input_shape))

9. 边缘端视觉部署

将视觉模型部署到边缘设备(手机、嵌入式、IoT设备)需要考虑模型大小、推理速度和功耗。

9.1 模型量化

import torch
from torchvision import models

model = models.resnet50(pretrained=True)
model.eval()

# 动态量化(推理时量化权重)
quantized_dynamic = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear}, dtype=torch.qint8
)

# 静态量化(需要校准数据)
model.qconfig = torch.quantization.get_default_qconfig("qnnpack")
torch.quantization.prepare(model, inplace=True)

# 校准(使用少量样本)
# with torch.no_grad():
#     for calibration_sample in calibration_data:
#         model(calibration_sample)

torch.quantization.convert(model, inplace=True)

# 模型大小对比
import os
torch.save(model.state_dict(), "model_original.pth")
torch.save(quantized_dynamic.state_dict(), "model_quantized.pth")
# 量化后模型通常缩小3-4倍

9.2 ONNX导出与部署

import torch
from torchvision import models

model = models.resnet50(pretrained=True)
model.eval()

# 导出ONNX
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
    model,
    dummy_input,
    "resnet50.onnx",
    export_params=True,
    opset_version=11,
    do_constant_folding=True,
    input_names=["input"],
    output_names=["output"],
    dynamic_axes={
        "input": {0: "batch_size"},
        "output": {0: "batch_size"}
    }
)

# 使用ONNX Runtime推理
import onnxruntime as ort
import numpy as np

session = ort.InferenceSession("resnet50.onnx",
    providers=["CUDAExecutionProvider", "CPUExecutionProvider"])

input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
outputs = session.run(None, {"input": input_data})
print(f"输出形状: {outputs[0].shape}")

9.3 TensorRT加速

# TensorRT部署示例(需要NVIDIA GPU)
import tensorrt as trt

def build_engine(onnx_path, engine_path, fp16=True):
    """将ONNX模型转换为TensorRT引擎"""
    logger = trt.Logger(trt.Logger.WARNING)
    builder = trt.Builder(logger)
    network = builder.create_network(
        1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    )
    parser = trt.OnnxParser(network, logger)

    # 解析ONNX
    with open(onnx_path, "rb") as f:
        if not parser.parse(f.read()):
            for error in range(parser.num_errors):
                print(parser.get_error(error))
            return None

    # 配置构建参数
    config = builder.create_builder_config()
    config.max_workspace_size = 1 << 30  # 1GB

    if fp16 and builder.platform_has_fast_fp16:
        config.set_flag(trt.BuilderFlag.FP16)

    # 构建引擎
    engine = builder.build_serialized_network(network, config)

    with open(engine_path, "wb") as f:
        f.write(engine)

    print(f"TensorRT引擎已保存: {engine_path}")
    return engine

10. 实战案例:智能安防视觉系统

下面展示一个完整的智能安防视觉系统,整合了目标检测、行为识别和告警功能:

import cv2
import torch
import time
from datetime import datetime
from collections import defaultdict
from ultralytics import YOLO

class SmartSurveillanceSystem:
    """智能安防视觉系统"""

    def __init__(self, config=None):
        self.config = config or {
            "detection_model": "yolov8m.pt",
            "confidence_threshold": 0.5,
            "alert_classes": ["person"],
            "intrusion_zones": [],  # 侵入区域坐标
            "loitering_threshold": 30,  # 停留告警阈值(秒)
            "output_path": "surveillance_output.mp4"
        }

        # 加载模型
        self.model = YOLO(self.config["detection_model"])
        self.track_history = defaultdict(list)
        self.alerts = []

    def define_intrusion_zone(self, points):
        """定义入侵检测区域(多边形)"""
        self.config["intrusion_zones"].append(points)

    def check_intrusion(self, point):
        """检查目标是否在入侵区域内"""
        for zone in self.config["intrusion_zones"]:
            if cv2.pointPolygonTest(zone, point, False) >= 0:
                return True
        return False

    def process_frame(self, frame, timestamp):
        """处理单帧图像"""
        results = self.model.track(
            frame, persist=True,
            conf=self.config["confidence_threshold"],
            tracker="bytetrack.yaml"
        )

        detections = []
        frame_alerts = []

        if results[0].boxes.id is not None:
            for box in results[0].boxes:
                track_id = int(box.id[0])
                cls = int(box.cls[0])
                conf = float(box.conf[0])
                class_name = self.model.names[cls]
                xyxy = box.xyxy[0].tolist()
                center = ((xyxy[0]+xyxy[2])/2, (xyxy[1]+xyxy[3])/2)

                detection = {
                    "track_id": track_id,
                    "class": class_name,
                    "confidence": conf,
                    "bbox": xyxy,
                    "center": center,
                    "timestamp": timestamp
                }
                detections.append(detection)

                # 记录轨迹
                self.track_history[track_id].append({
                    "center": center,
                    "time": timestamp
                })

                # 检查入侵
                if class_name in self.config["alert_classes"]:
                    if self.check_intrusion(center):
                        alert = {
                            "type": "intrusion",
                            "track_id": track_id,
                            "class": class_name,
                            "time": timestamp,
                            "position": center
                        }
                        frame_alerts.append(alert)

                    # 检查停留
                    history = self.track_history[track_id]
                    if len(history) > 1:
                        duration = (timestamp - history[0]["time"]).total_seconds()
                        if duration > self.config["loitering_threshold"]:
                            # 检查是否移动很小
                            movement = sum(
                                ((h["center"][0]-history[0]["center"][0])**2 +
                                 (h["center"][1]-history[0]["center"][1])**2)**0.5
                                for h in history[-10:]
                            ) / min(10, len(history))
                            if movement < 20:  # 移动距离小于20像素
                                alert = {
                                    "type": "loitering",
                                    "track_id": track_id,
                                    "duration": duration,
                                    "time": timestamp
                                }
                                frame_alerts.append(alert)

        self.alerts.extend(frame_alerts)
        return detections, frame_alerts, results[0].plot()

    def run(self, video_source=0):
        """运行安防系统"""
        cap = cv2.VideoCapture(video_source)
        fps = cap.get(cv2.CAP_PROP_FPS) or 30
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        out = cv2.VideoWriter(
            self.config["output_path"],
            cv2.VideoWriter_fourcc(*"mp4v"),
            fps, (width, height)
        )

        print(f"安防系统启动 - 源: {video_source}")
        print(f"监控告警类别: {self.config['alert_classes']}")
        print(f"入侵区域数量: {len(self.config['intrusion_zones'])}")

        frame_count = 0
        start_time = time.time()

        while cap.isOpened():
            ret, frame = cap.read()
            if not ret:
                break

            timestamp = datetime.now()
            detections, alerts, annotated = self.process_frame(frame, timestamp)

            # 显示告警信息
            for alert in alerts:
                print(f"[{alert['time']}] ⚠️ {alert['type'].upper()}: "
                      f"目标 {alert['track_id']}")

            # 在画面上显示统计
            info = f"Frame: {frame_count} | Detections: {len(detections)}"
            cv2.putText(annotated, info, (10, 30),
                       cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

            if alerts:
                cv2.putText(annotated, "ALERT!", (width-200, 30),
                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3)

            out.write(annotated)
            frame_count += 1

        elapsed = time.time() - start_time
        cap.release()
        out.release()

        print(f"\n处理完成:")
        print(f"  总帧数: {frame_count}")
        print(f"  处理时间: {elapsed:.1f}s")
        print(f"  平均FPS: {frame_count/elapsed:.1f}")
        print(f"  告警数量: {len(self.alerts)}")

        return self.alerts


# 使用示例
if __name__ == "__main__":
    system = SmartSurveillanceSystem()

    # 定义入侵检测区域(矩形)
    zone = [(100, 100), (500, 100), (500, 400), (100, 400)]
    import numpy as np
    system.define_intrusion_zone(np.array(zone, dtype=np.int32))

    # 运行
    # alerts = system.run("surveillance_video.mp4")

11. 评估指标与优化策略

11.1 常用评估指标

import numpy as np

def compute_iou(box1, box2):
    """计算两个边界框的IoU"""
    x1 = max(box1[0], box2[0])
    y1 = max(box1[1], box2[1])
    x2 = min(box1[2], box2[2])
    y2 = min(box1[3], box2[3])

    intersection = max(0, x2 - x1) * max(0, y2 - y1)
    area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
    area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
    union = area1 + area2 - intersection

    return intersection / union if union > 0 else 0

def compute_ap(predictions, ground_truths, iou_threshold=0.5):
    """计算平均精度(AP)"""
    # 按置信度排序
    predictions = sorted(predictions, key=lambda x: x["score"], reverse=True)

    tp = []
    fp = []
    matched_gt = set()

    for pred in predictions:
        best_iou = 0
        best_gt_idx = -1

        for i, gt in enumerate(ground_truths):
            if i in matched_gt:
                continue
            iou = compute_iou(pred["bbox"], gt["bbox"])
            if iou > best_iou:
                best_iou = iou
                best_gt_idx = i

        if best_iou >= iou_threshold and best_gt_idx >= 0:
            tp.append(1)
            fp.append(0)
            matched_gt.add(best_gt_idx)
        else:
            tp.append(0)
            fp.append(1)

    # 计算累积precision和recall
    tp_cumsum = np.cumsum(tp)
    fp_cumsum = np.cumsum(fp)
    recalls = tp_cumsum / len(ground_truths)
    precisions = tp_cumsum / (tp_cumsum + fp_cumsum)

    # 计算AP(11点插值法)
    ap = 0
    for t in np.arange(0, 1.1, 0.1):
        precisions_at_recall = precisions[recalls >= t]
        if len(precisions_at_recall) > 0:
            ap += np.max(precisions_at_recall) / 11

    return ap

def compute_map(all_predictions, all_ground_truths, num_classes, iou_threshold=0.5):
    """计算mAP(各类别AP的平均值)"""
    aps = []
    for cls_id in range(num_classes):
        cls_preds = [p for p in all_predictions if p["class"] == cls_id]
        cls_gts = [g for g in all_ground_truths if g["class"] == cls_id]
        if len(cls_gts) > 0:
            ap = compute_ap(cls_preds, cls_gts, iou_threshold)
            aps.append(ap)

    return np.mean(aps) if aps else 0

# 分割任务指标:mIoU
def compute_miou(pred_mask, gt_mask, num_classes):
    """计算平均交并比(mIoU)"""
    ious = []
    for cls in range(num_classes):
        pred_cls = (pred_mask == cls)
        gt_cls = (gt_mask == cls)
        intersection = (pred_cls & gt_cls).sum()
        union = (pred_cls | gt_cls).sum()
        if union > 0:
            ious.append(intersection / union)
    return np.mean(ious) if ious else 0

11.2 优化策略

# 数据增强策略
from torchvision import transforms

train_transform = transforms.Compose([
    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomVerticalFlip(p=0.1),
    transforms.RandomRotation(15),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
    transforms.RandomGrayscale(p=0.05),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225]),
    transforms.RandomErasing(p=0.2),
])

# 混合精度训练
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)

def train_epoch(model, dataloader, criterion, optimizer, scaler):
    model.train()
    total_loss = 0

    for images, labels in dataloader:
        images, labels = images.cuda(), labels.cuda()
        optimizer.zero_grad()

        with autocast():  # 混合精度前向传播
            outputs = model(images)
            loss = criterion(outputs, labels)

        scaler.scale(loss).backward()  # 缩放梯度反向传播
        scaler.step(optimizer)
        scaler.update()

        total_loss += loss.item()

    scheduler.step()
    return total_loss / len(dataloader)

# 知识蒸馏
class DistillationLoss(torch.nn.Module):
    def __init__(self, temperature=4.0, alpha=0.7):
        super().__init__()
        self.temperature = temperature
        self.alpha = alpha
        self.ce_loss = torch.nn.CrossEntropyLoss()
        self.kl_loss = torch.nn.KLDivLoss(reduction="batchmean")

    def forward(self, student_logits, teacher_logits, labels):
        # 硬标签损失
        hard_loss = self.ce_loss(student_logits, labels)

        # 软标签损失
        soft_student = torch.log_softmax(student_logits / self.temperature, dim=-1)
        soft_teacher = torch.softmax(teacher_logits / self.temperature, dim=-1)
        soft_loss = self.kl_loss(soft_student, soft_teacher) * (self.temperature ** 2)

        return self.alpha * soft_loss + (1 - self.alpha) * hard_loss

总结

计算机视觉技术已经从实验室走向了大规模工业应用。从基础的图像分类到复杂的视频理解,从云端推理到边缘部署,整个技术栈日趋成熟。关键要点包括:

  • 选择合适的架构:CNN适合特征明确的任务,Transformer在大规模数据上表现更优
  • 善用预训练模型:迁移学习能以极少的数据达到不错的性能
  • 注重工程实践:模型量化、ONNX导出、TensorRT加速是生产部署的必备技能
  • 端到端思维:从数据采集、标注、训练到部署监控,每个环节都需要关注
  • 评估指标驱动:mAP、mIoU等指标是优化方向的指南针

在实际项目中,建议从预训练模型+微调起步,逐步优化数据质量和模型架构。视觉系统的效果往往80%取决于数据,20%取决于模型。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录