{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 代码实现"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们动手编写一维卷积函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-05-24T11:34:38.646143Z",
     "iopub.status.busy": "2022-05-24T11:34:38.645730Z",
     "iopub.status.idle": "2022-05-24T11:34:38.679790Z",
     "shell.execute_reply": "2022-05-24T11:34:38.679354Z",
     "shell.execute_reply.started": "2022-05-24T11:34:38.646049Z"
    }
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "# 一维卷积\n",
    "class conv_1d():\n",
    "    def __init__(self, a, b):\n",
    "        # 输入信号\n",
    "        self.a = a\n",
    "        # 卷积核\n",
    "        self.b = b\n",
    "        # 输入信号的坐标，默认从0开始。\n",
    "        self.ax = [i for i in range(len(a))]\n",
    "        # 卷积核的坐标，默认从0开始。\n",
    "        self.bx = [i for i in range(len(b))]\n",
    "\n",
    "    def conv(self):\n",
    "        lst1 = self.a\n",
    "        lst2 = self.b\n",
    "        l1 = len(lst1)\n",
    "        l2 = len(lst2)\n",
    "        lst1 = [0] * (l2 - 1) + lst1 + [0] * (l2 - 1)\n",
    "        lst2.reverse()\n",
    "        c = [0 for x in range(0, l1 + l2 - 1)]\n",
    "        for i in range(l1 + l2 - 1):\n",
    "            for j in range(l2):\n",
    "                c[i] += lst1[i + j] * lst2[j]\n",
    "        return c\n",
    "\n",
    "    def plot(self):\n",
    "        a = self.a\n",
    "        b = self.b\n",
    "        ax = self.ax\n",
    "        bx = self.bx\n",
    "\n",
    "        # 为了更直观的查看结果，我们分别绘制a、b与它们卷积得到的信号\n",
    "        plt.figure(1)\n",
    "        # 图一包含1行2列子图，当前画在第一行第一列图上\n",
    "        plt.subplot(1, 2, 1)\n",
    "        plt.title('Input')\n",
    "        plt.bar(ax, a, color='lightcoral', width=0.2)\n",
    "        plt.plot(ax, a, color='lightcoral')\n",
    "\n",
    "        plt.figure(1)\n",
    "        # 当前画在第一行第2列图上\n",
    "        plt.subplot(1, 2, 2) \n",
    "        plt.title('Kernel')\n",
    "        plt.bar(bx, b, color='lightgreen', width=0.2)\n",
    "        #plt.plot(bx, b, color='lightgreen')\n",
    "\n",
    "\n",
    "        # 计算输出信号以及其坐标，并画图 \n",
    "        c = self.conv()\n",
    "        length = len(c)\n",
    "        cx = [i for i in range(length)]\n",
    "        plt.figure()\n",
    "        plt.title('Output')\n",
    "        plt.bar(cx, c, color='lightseagreen', width=0.2)\n",
    "        plt.plot(cx, c, color='lightseagreen')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们举例来显示上述卷积实现的效果。用不同的卷积核去对一个三角波信号进行卷积，然后观察卷积的效果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-05-24T11:34:38.681075Z",
     "iopub.status.busy": "2022-05-24T11:34:38.680868Z",
     "iopub.status.idle": "2022-05-24T11:34:39.126887Z",
     "shell.execute_reply": "2022-05-24T11:34:39.126056Z",
     "shell.execute_reply.started": "2022-05-24T11:34:38.681048Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 24396 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 23452 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 定义输入信号与卷积核\n",
    "a = [0,1,2,3,2,1,0]\n",
    "\n",
    "# 冲激函数\n",
    "k = [0,0,1,0,0]\n",
    "\n",
    "conv = conv_1d(a, k)\n",
    "conv.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当所用的卷积核是一个单位冲激信号（即“面积”等于1只在一个位置出现的窄脉冲）时，输入信号在卷积之后并没有发生变化。这是卷积的一个重要性质，请大家记住。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面更换一下卷积核，看看输出会有什么不同的效果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-05-24T11:34:39.128268Z",
     "iopub.status.busy": "2022-05-24T11:34:39.128027Z",
     "iopub.status.idle": "2022-05-24T11:34:39.668668Z",
     "shell.execute_reply": "2022-05-24T11:34:39.667651Z",
     "shell.execute_reply.started": "2022-05-24T11:34:39.128242Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 24456 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 24644 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 定义输入信号与卷积核\n",
    "a = [0,1,2,3,2,1,0]\n",
    "# 方波信号\n",
    "k = [1,1,1,1,1]\n",
    "\n",
    "conv = conv_1d(a, k)\n",
    "conv.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "大家是否注意到，用一个方波信号对一个三角波信号进行卷积，其效果是将三角波变得平滑了。请大家记住这个现象，后续会再次提到。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "接下来，我们开始编写二维卷积。我们将对一张图像进行处理，并直观的展示卷积前后图像的变化。和一维卷积类似，也使用冲激信号和方波信号作为卷积核。大家注意代码中二维冲激信号和二维方波信号的实现。这里直接调用Python中的库函数cv2.filter2D()进行二维卷积操作。我们把二维卷积的代码实现作为留给大家的习题，请大家自己完成。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-05-24T11:34:39.670572Z",
     "iopub.status.busy": "2022-05-24T11:34:39.670331Z",
     "iopub.status.idle": "2022-05-24T11:34:40.876823Z",
     "shell.execute_reply": "2022-05-24T11:34:40.875494Z",
     "shell.execute_reply.started": "2022-05-24T11:34:39.670539Z"
    }
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import cv2\n",
    "import seaborn as sns\n",
    "\n",
    "class conv_2d():\n",
    "    def __init__(self, image, kernel):\n",
    "        self.img = image\n",
    "        self.k = kernel\n",
    "        \n",
    "    def plot(self):\n",
    "        # 展示输入图像\n",
    "        plt.imshow(self.img[:, :, ::-1])\n",
    "        plt.axis('off')\n",
    "        plt.title('Input')\n",
    "        plt.show()\n",
    "    \n",
    "        # 展示卷积核\n",
    "        fig = plt.figure(figsize=(2, 1.5))\n",
    "        sns.heatmap(self.k)\n",
    "        plt.axis('off')\n",
    "        plt.title('Kernel')\n",
    "        \n",
    "    # 定义二维卷积\n",
    "    def convolution(self, data, k):\n",
    "        # 直接调用库函数进行卷积操作\n",
    "        return cv2.filter2D(data, -1, k)\n",
    "\n",
    "    \n",
    "    # 展示二维卷积结果\n",
    "    def plot_conv(self):\n",
    "        # 卷积过程\n",
    "        img_new = self.convolution(self.img, self.k)\n",
    "        # 卷积结果可视化            \n",
    "        plt.figure()\n",
    "        plt.imshow(img_new[:, :, ::-1])\n",
    "        plt.title('Output')\n",
    "        plt.axis('off')\n",
    "        return\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了后续章节方便使用，我们编写utils.py文件并将上述代码导入该文件中。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们先来观察一下使用冲击信号对图像卷积的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-05-24T11:34:40.879227Z",
     "iopub.status.busy": "2022-05-24T11:34:40.878950Z",
     "iopub.status.idle": "2022-05-24T11:34:41.551923Z",
     "shell.execute_reply": "2022-05-24T11:34:41.551024Z",
     "shell.execute_reply.started": "2022-05-24T11:34:40.879195Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 296908 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 6352 characters]",
      "text/plain": [
       "<Figure size 200x150 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 297544 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "img =cv2.imread('lena.jpeg')\n",
    "\n",
    "# 二维冲激卷积核\n",
    "size = 15\n",
    "k1 = np.zeros((size, size))\n",
    "mid = (size-1) // 2\n",
    "k1[mid][mid] = 1\n",
    "\n",
    "# 展示输入图像与卷积核\n",
    "conv = conv_2d(img, k1)\n",
    "conv.plot()\n",
    "\\\n",
    "# 展示卷积结果\n",
    "conv.plot_conv()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由结果可见，用二维冲激函数对图像进行卷积，得到的结果和原图是一样的。这与进行一维卷积得到的结果一致。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着，我们再来观察使用方波信号对图像卷积的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-05-24T11:34:41.553330Z",
     "iopub.status.busy": "2022-05-24T11:34:41.553115Z",
     "iopub.status.idle": "2022-05-24T11:34:42.363693Z",
     "shell.execute_reply": "2022-05-24T11:34:42.363291Z",
     "shell.execute_reply.started": "2022-05-24T11:34:41.553304Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 296908 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 6452 characters]",
      "text/plain": [
       "<Figure size 200x150 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 174836 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 二维方波卷积核\n",
    "size = 15\n",
    "# 因为二维的核函数的大小是n*n的，因此在实现方波信号时我们需要除以(size*size)\n",
    "k2 = 1/size/size * np.ones((size, size))\n",
    "\n",
    "# 展示输入图像与卷积核\n",
    "conv = conv_2d(img, k2)\n",
    "conv.plot()\n",
    "\n",
    "# 展示卷积结果\n",
    "conv.plot_conv()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由结果可见，用方波信号作为卷积核，对图像做卷积，得到的结果是平滑模糊的图像，这也与我们在一维卷积中的实验结果一致。这是不是有一些图像处理的味道了？请大家记住这个结果，我们将在之后的章节中进一步阐述其原理。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
