11人工智能:一种现代方法(第4版)

计算机视觉

图像理解

图像分类目标检测图像分割

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第11章:计算机视觉(Computer Vision)

一、导读

1.1 本章学习目标

第11章介绍计算机视觉——让计算机理解和处理图像信息的技术。视觉是人类感知世界的主要方式,让机器"看懂"图像是AI长期以来的挑战。近年来,深度学习方法在计算机视觉领域取得了突破性进展。

本章的核心学习目标包括:

  • 理解图像的基本表示:像素、颜色空间、图像特征。
  • 掌握图像分类:CNN 的基本原理和经典架构(LeNet、AlexNet、VGG、ResNet)。
  • 理解目标检测:从传统方法到 YOLO、Faster R-CNN。
  • 掌握图像分割:语义分割、实例分割、全景分割。
  • 了解3D视觉:深度估计、点云处理、NeRF。
  • 了解视觉大模型:CLIP、DALL-E、Stable Diffusion 等生成模型。

1.2 为什么第十一章如此重要

计算机视觉是AI最成功的应用领域之一。从人脸识别到自动驾驶,从医学影像分析到工业检测,视觉技术已经深入到各行各业。特别是深度学习的突破,使得计算机在许多视觉任务上已经达到甚至超过人类水平。

理解计算机视觉的基本原理,不仅有助于开发视觉应用,也有助于理解现代AI系统如何处理视觉信息。本章将从基础的图像处理开始,逐步深入到深度学习方法。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

图像的表示:一张数字图像在计算机中是如何表示的?颜色空间有什么影响?

CNN 为什么适合图像? 卷积操作有什么特殊性质使得它特别适合处理图像?

目标检测的挑战:图像中可能有多个不同大小的物体,如何同时检测它们?

语义分割 vs 实例分割:两者的区别是什么?各自适用于什么场景?

视觉生成模型:DALL-E、Stable Diffusion 是如何从文本生成图像的?


二、核心概念详解

2.1 图像基础

2.1.1 图像的数字化表示

python
import numpy as np

class Image:
    def __init__(self, height, width, channels=3):
        self.height = height
        self.width = width
        self.channels = channels
        self.data = np.zeros((height, width, channels), dtype=np.uint8)

    def get_pixel(self, x, y):
        return self.data[y, x]

    def set_pixel(self, x, y, value):
        self.data[y, x] = value

    def to_grayscale(self):
        gray = np.dot(self.data[..., :3], [0.2989, 0.5870, 0.1140])
        return gray.astype(np.uint8)

    def resize(self, new_height, new_width):
        resized = np.zeros((new_height, new_width, self.channels), dtype=np.uint8)
        y_ratio = self.height / new_height
        x_ratio = self.width / new_width
        for i in range(new_height):
            for j in range(new_width):
                src_y = int(i * y_ratio)
                src_x = int(j * x_ratio)
                resized[i, j] = self.data[src_y, src_x]
        return resized

# 创建测试图像
img = Image(100, 100)
img.data[:50, :50] = 255
print(f"图像形状: {img.data.shape}")
gray = img.to_grayscale()
print(f"灰度图像形状: {gray.shape}")

2.1.2 颜色空间

python
class ColorSpace:
    @staticmethod
    def rgb_to_gray(rgb):
        return np.dot(rgb[..., :3], [0.2989, 0.5870, 0.1140])

    @staticmethod
    def rgb_to_hsv(rgb):
        rgb = rgb.astype(float) / 255.0
        r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]
        max_val = np.max(rgb, axis=-1)
        min_val = np.min(rgb, axis=-1)
        diff = max_val - min_val

        h = np.zeros_like(max_val)
        mask = diff > 0

        h[mask & (max_val == r)] = (60 * ((g[mask & (max_val == r)] - b[mask & (max_val == r)]) / diff[mask & (max_val == r)]) + 360) % 360
        h[mask & (max_val == g)] = (60 * ((b[mask & (max_val == g)] - r[mask & (max_val == g)]) / diff[mask & (max_val == g)]) + 120)
        h[mask & (max_val == b)] = (60 * ((r[mask & (max_val == b)] - g[mask & (max_val == b)]) / diff[mask & (max_val == b)]) + 240)

        s = np.where(max_val > 0, diff / max_val, 0)
        v = max_val

        return np.stack([h, s, v], axis=-1)

# 测试颜色转换
rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]]])
gray = ColorSpace.rgb_to_gray(rgb)
print(f"灰度值: {gray}")

2.1.3 图像滤波

python
class ImageFilter:
    @staticmethod
    def convolve(image, kernel):
        kh, kw = kernel.shape
        ph, pw = kh // 2, kw // 2
        padded = np.pad(image, ((ph, ph), (pw, pw)), mode='constant')
        output = np.zeros_like(image)

        for i in range(image.shape[0]):
            for j in range(image.shape[1]):
                region = padded[i:i+kh, j:j+kw]
                output[i, j] = np.sum(region * kernel)
        return output

    @staticmethod
    def gaussian_kernel(size=3, sigma=1.0):
        kernel = np.zeros((size, size))
        center = size // 2
        for i in range(size):
            for j in range(size):
                x, y = i - center, j - center
                kernel[i, j] = np.exp(-(x**2 + y**2) / (2 * sigma**2))
        return kernel / np.sum(kernel)

    @staticmethod
    def edge_detection(image):
        sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
        sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
        gx = ImageFilter.convolve(image, sobel_x)
        gy = ImageFilter.convolve(image, sobel_y)
        magnitude = np.sqrt(gx**2 + gy**2)
        return magnitude

# 测试滤波
image = np.random.randint(0, 256, (10, 10))
gaussian = ImageFilter.gaussian_kernel(3, 1.0)
smoothed = ImageFilter.convolve(image, gaussian)
edges = ImageFilter.edge_detection(image)
print(f"边缘检测完成")

2.2 卷积神经网络

2.2.1 卷积层

python
class ConvLayer:
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        self.stride = stride
        self.padding = padding
        self.kernel_size = kernel_size
        self.weights = np.random.randn(out_channels, in_channels, kernel_size, kernel_size) * 0.01
        self.bias = np.zeros(out_channels)

    def forward(self, X):
        batch, c, h, w = X.shape
        if self.padding > 0:
            X = np.pad(X, ((0,0), (0,0), (self.padding, self.padding), (self.padding, self.padding)))

        out_h = (h + 2 * self.padding - self.kernel_size) // self.stride + 1
        out_w = (w + 2 * self.padding - self.kernel_size) // self.stride + 1
        output = np.zeros((batch, len(self.weights), out_h, out_w))

        for i in range(out_h):
            for j in range(out_w):
                h_start = i * self.stride
                h_end = h_start + self.kernel_size
                w_start = j * self.stride
                w_end = w_start + self.kernel_size
                patch = X[:, :, h_start:h_end, w_start:w_end]
                for k in range(len(self.weights)):
                    output[:, k, i, j] = np.sum(patch * self.weights[k], axis=(1,2,3)) + self.bias[k]
        return output

# 测试卷积层
conv = ConvLayer(3, 16, 3, padding=1)
X = np.random.randn(1, 3, 32, 32)
output = conv.forward(X)
print(f"卷积输出形状: {output.shape}")

2.2.2 池化层

python
class PoolLayer:
    def __init__(self, pool_size=2, stride=2, mode='max'):
        self.pool_size = pool_size
        self.stride = stride
        self.mode = mode

    def forward(self, X):
        batch, c, h, w = X.shape
        out_h = (h - self.pool_size) // self.stride + 1
        out_w = (w - self.pool_size) // self.stride + 1
        output = np.zeros((batch, c, out_h, out_w))

        for i in range(out_h):
            for j in range(out_w):
                h_start = i * self.stride
                h_end = h_start + self.pool_size
                w_start = j * self.stride
                w_end = w_start + self.pool_size
                patch = X[:, :, h_start:h_end, w_start:w_end]
                if self.mode == 'max':
                    output[:, :, i, j] = np.max(patch, axis=(2, 3))
                elif self.mode == 'avg':
                    output[:, :, i, j] = np.mean(patch, axis=(2, 3))
        return output

# 测试池化层
pool = PoolLayer(pool_size=2, stride=2, mode='max')
X = np.random.randn(1, 16, 32, 32)
output = pool.forward(X)
print(f"池化输出形状: {output.shape}")

2.2.3 完整的 CNN

python
class SimpleCNN:
    def __init__(self, num_classes=10):
        self.conv1 = ConvLayer(1, 8, 3, padding=1)
        self.pool1 = PoolLayer(2, 2)
        self.conv2 = ConvLayer(8, 16, 3, padding=1)
        self.pool2 = PoolLayer(2, 2)
        self.num_classes = num_classes

    def forward(self, X):
        x = self.conv1.forward(X)
        x = np.maximum(0, x)
        x = self.pool1.forward(x)
        x = self.conv2.forward(x)
        x = np.maximum(0, x)
        x = self.pool2.forward(x)
        return x

# 测试 CNN
cnn = SimpleCNN(num_classes=10)
X = np.random.randn(4, 1, 28, 28)
features = cnn.forward(X)
print(f"CNN 特征形状: {features.shape}")

2.3 目标检测

2.3.1 滑动窗口检测

python
class SlidingWindowDetector:
    def __init__(self, window_sizes, scales):
        self.window_sizes = window_sizes
        self.scales = scales

    def generate_proposals(self, image_shape):
        h, w = image_shape[:2]
        proposals = []
        for size in self.window_sizes:
            for scale in self.scales:
                scaled_size = int(size * scale)
                for y in range(0, h - scaled_size, scaled_size // 2):
                    for x in range(0, w - scaled_size, scaled_size // 2):
                        proposals.append((x, y, scaled_size, scaled_size))
        return proposals

# 生成候选框
detector = SlidingWindowDetector([32, 64], [1.0, 1.5])
proposals = detector.generate_proposals((100, 100))
print(f"候选框数量: {len(proposals)}")

2.3.2 IoU 计算

python
def compute_iou(box1, box2):
    x1, y1, w1, h1 = box1
    x2, y2, w2, h2 = box2

    xi1 = max(x1, x2)
    yi1 = max(y1, y2)
    xi2 = min(x1 + w1, x2 + w2)
    yi2 = min(y1 + h1, y2 + h2)

    inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
    box1_area = w1 * h1
    box2_area = w2 * h2
    union_area = box1_area + box2_area - inter_area

    return inter_area / union_area if union_area > 0 else 0

# 测试 IoU
box1 = (10, 10, 50, 50)
box2 = (30, 30, 50, 50)
iou = compute_iou(box1, box2)
print(f"IoU: {iou:.4f}")

2.4 图像分割

2.4.1 语义分割

python
class SimpleSegmentation:
    def __init__(self, num_classes):
        self.num_classes = num_classes

    def predict(self, features):
        batch, c, h, w = features.shape
        segmentation = np.zeros((batch, h, w), dtype=np.int32)
        for b in range(batch):
            for i in range(h):
                for j in range(w):
                    pixel_features = features[b, :, i, j]
                    segmentation[b, i, j] = np.argmax(pixel_features[:self.num_classes])
        return segmentation

# 测试分割
seg = SimpleSegmentation(num_classes=3)
features = np.random.randn(1, 3, 32, 32)
mask = seg.predict(features)
print(f"分割掩码形状: {mask.shape}")

2.5 视觉生成模型

2.5.1 简单的图像生成

python
class SimpleGenerator:
    def __init__(self, latent_dim=100, image_shape=(1, 28, 28)):
        self.latent_dim = latent_dim
        self.image_shape = image_shape
        self.W1 = np.random.randn(latent_dim, 256) * 0.01
        self.W2 = np.random.randn(256, 512) * 0.01
        self.W3 = np.random.randn(512, np.prod(image_shape)) * 0.01

    def generate(self, z):
        h1 = np.maximum(0, z @ self.W1)
        h2 = np.maximum(0, h1 @ self.W2)
        output = h2 @ self.W3
        output = output.reshape(-1, *self.image_shape)
        output = 1 / (1 + np.exp(-output))
        return output

# 测试生成器
gen = SimpleGenerator(latent_dim=100, image_shape=(1, 28, 28))
z = np.random.randn(4, 100)
images = gen.generate(z)
print(f"生成图像形状: {images.shape}")

三、实战案例

3.1 案例一:简单的图像分类流程

python
class ImageClassificationPipeline:
    def __init__(self):
        self.cnn = SimpleCNN(num_classes=10)

    def preprocess(self, image):
        if len(image.shape) == 2:
            image = image[np.newaxis, np.newaxis, ...]
        elif len(image.shape) == 3:
            image = image[np.newaxis, ...]
        return image / 255.0

    def predict(self, image):
        processed = self.preprocess(image)
        features = self.cnn.forward(processed)
        return features

    def evaluate(self, images, labels):
        correct = 0
        for img, label in zip(images, labels):
            features = self.predict(img)
            pred = np.argmax(features)
            if pred == label:
                correct += 1
        return correct / len(labels)

# 测试流程
pipeline = ImageClassificationPipeline()
test_images = [np.random.randint(0, 256, (28, 28)) for _ in range(5)]
test_labels = [0, 1, 2, 3, 4]
accuracy = pipeline.evaluate(test_images, test_labels)
print(f"准确率: {accuracy:.2f}")

四、本章小结

本章介绍了计算机视觉的核心概念和方法。从基础的图像表示和处理,到卷积神经网络的原理,再到目标检测和图像分割,我们逐步构建了计算机视觉的知识体系。

核心知识点回顾:

  • 图像是像素的二维数组,颜色空间影响处理效果
  • CNN 通过卷积和池化提取图像特征
  • 目标检测需要定位和分类多个物体
  • 图像分割为每个像素分配类别标签
  • 视觉生成模型可以从噪声或文本生成图像

五、思考与练习

思考题

CNN 的平移不变性:为什么 CNN 对图像的平移具有不变性?这种性质有什么优势?

目标检测的挑战:小目标检测为什么困难?如何改进?

编程练习

实现简单的 CNN:用 NumPy 实现一个完整的 CNN 进行 MNIST 分类。

实现边缘检测:用 Sobel 算子实现边缘检测,比较不同阈值的效果。

实现图像增强:实现旋转、翻转、裁剪等数据增强方法。

11/13计算机视
0%