"""
第 03 章配套代码：2D 卷积 + 边缘检测 + 最大池化（CNN 的两个核心算子）
Runnable with: numpy only.  python3 03_convolution.py

演示卷积的"权重共享 + 局部感受野 + 平移不变"，以及 Sobel 边缘核。
"""
import numpy as np


def conv2d(img, kernel):
    """有效卷积（valid），步长 1，无 padding。
    数学：out[i,j] = sum_{u,v} img[i+u, j+v] * kernel[u,v]
    （工程上常用互相关；卷积是核翻转后的互相关，对学习无本质区别）
    """
    kh, kw = kernel.shape
    H, W = img.shape
    out = np.zeros((H - kh + 1, W - kw + 1))
    for i in range(out.shape[0]):
        for j in range(out.shape[1]):
            out[i, j] = np.sum(img[i:i + kh, j:j + kw] * kernel)
    return out


def max_pool(x, size=2):
    """步长=size 的最大池化：下采样 + 局部平移不变。"""
    H, W = x.shape
    out = np.zeros((H // size, W // size))
    for i in range(out.shape[0]):
        for j in range(out.shape[1]):
            out[i, j] = x[i * size:(i + 1) * size, j * size:(j + 1) * size].max()
    return out


if __name__ == "__main__":
    # 一个 6x6 的"图像"：左半暗右半亮，中间有竖直边缘
    img = np.zeros((6, 6))
    img[:, 3:] = 1.0
    print("输入图像:\n", img)

    # Sobel 竖直边缘核（检测水平方向的亮度突变）
    sobel_x = np.array([[-1, 0, 1],
                        [-2, 0, 2],
                        [-1, 0, 1]], dtype=float)
    feat = conv2d(img, sobel_x)
    print("\n卷积后(竖直边缘响应, 边缘处出现大值):\n", feat)

    pooled = max_pool(np.abs(feat), 2)
    print("\n2x2 最大池化后(下采样, 保留最强响应):\n", pooled)

    # 平移不变演示：把边缘右移一列，特征图响应也整体右移，模式不变
    img2 = np.zeros((6, 6)); img2[:, 4:] = 1.0
    print("\n边缘右移一列后的卷积响应(同样的核, 检测到同样的边缘模式):\n", conv2d(img2, sobel_x))
