{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c8fff47d",
   "metadata": {
    "papermill": {
     "duration": 0.011567,
     "end_time": "2023-08-15T12:30:21.828070",
     "exception": false,
     "start_time": "2023-08-15T12:30:21.816503",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## 代码实现"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8aa661dd",
   "metadata": {},
   "source": [
    "接下来让我们动手学习 Lucas-Kanade 算法。首先，我们制造一张噪音图像并将其顺时针旋转1度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "64ddeb9c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:21.854131Z",
     "iopub.status.busy": "2023-08-15T12:30:21.853436Z",
     "iopub.status.idle": "2023-08-15T12:30:22.515918Z",
     "shell.execute_reply": "2023-08-15T12:30:22.514586Z"
    },
    "papermill": {
     "duration": 0.681375,
     "end_time": "2023-08-15T12:30:22.521137",
     "exception": false,
     "start_time": "2023-08-15T12:30:21.839762",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 411436 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import random\n",
    "import math\n",
    "\n",
    "# 根据角度旋转图像\n",
    "def rotate_image(img, angle):\n",
    "    # 计算旋转中心\n",
    "    center = tuple(np.array(img.shape[1::-1]) / 2)\n",
    "    # 计算旋转矩阵\n",
    "    rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0)\n",
    "    # 旋转图像\n",
    "    rotated = cv2.warpAffine(img, rot_mat, \n",
    "                    img.shape[1::-1], flags=cv2.INTER_LINEAR)\n",
    "    return rotated\n",
    "\n",
    "# 创建一个200*200的随机数组，并将其转换为float32类型\n",
    "img_t0 = np.random.rand(200, 200).astype(np.float32)\n",
    "\n",
    "# 复制img_t0的内容到img_t1\n",
    "img_t1 = img_t0.copy()\n",
    "\n",
    "# 将img_t1旋转-1度\n",
    "img_t1 = rotate_image(img_t1, -1)\n",
    "\n",
    "# 可视化\n",
    "fig1, (ax_1, ax_2) = plt.subplots(1, 2)\n",
    "# 在ax_1中显示img_t0\n",
    "ax_1.imshow(img_t0)\n",
    "ax_1.set_title(\"Frame t0\")\n",
    "# 在ax_2中显示img_t1\n",
    "ax_2.imshow(img_t1)\n",
    "ax_2.set_title(\"Frame t1\")\n",
    "# 显示图像\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c496796",
   "metadata": {
    "papermill": {
     "duration": 0.014672,
     "end_time": "2023-08-15T12:30:22.551023",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.536351",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "接着，我们实现一个高斯滤波器对图像进行平滑处理以降低噪声影响，然后计算图像的梯度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "15f5f756",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:22.583967Z",
     "iopub.status.busy": "2023-08-15T12:30:22.583482Z",
     "iopub.status.idle": "2023-08-15T12:30:22.600785Z",
     "shell.execute_reply": "2023-08-15T12:30:22.599417Z"
    },
    "papermill": {
     "duration": 0.037377,
     "end_time": "2023-08-15T12:30:22.604171",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.566794",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def smooth(img, sigma):\n",
    "    \"\"\"\n",
    "    高斯平滑函数，返回平滑后的图像。\n",
    "\n",
    "    参数:\n",
    "    - img: 输入图像。\n",
    "    - sigma: 高斯核的标准差。\n",
    "    \"\"\"\n",
    "    # 创建x数组，范围为[-3sigma,3sigma]\n",
    "    x = np.array(list(range(math.floor(-3.0 * sigma + 0.5), \n",
    "                            math.floor(3.0 * sigma + 0.5) + 1)))\n",
    "    \n",
    "    # 计算高斯核G\n",
    "    G = np.exp(-x ** 2 / (2 * sigma ** 2))\n",
    "    G = G / np.sum(G)\n",
    "\n",
    "    # 对图像进行二维卷积\n",
    "    return cv2.sepFilter2D(img, -1, G, G)\n",
    "\n",
    "\n",
    "def gaussderiv(img, sigma):\n",
    "    \"\"\"\n",
    "    高斯求导函数。\n",
    "\n",
    "    参数:\n",
    "    - param img: 输入图像。\n",
    "    - param sigma: 高斯核的标准差。\n",
    "\n",
    "    返回:\n",
    "    - Dx: x轴方向的导数图像。\n",
    "    - Dy: y轴方向的导数图像。\n",
    "    \"\"\"\n",
    "    # 创建x数组，范围为[-3sigma,3sigma]\n",
    "    x = np.array(list(range(math.floor(-3.0 * sigma + 0.5), \n",
    "                            math.floor(3.0 * sigma + 0.5) + 1)))\n",
    "\n",
    "    # 计算高斯核G\n",
    "    G = np.exp(-x ** 2 / (2 * sigma ** 2))\n",
    "    G = G / np.sum(G)\n",
    "\n",
    "    # 计算高斯求导核D\n",
    "    D = -2 * (x * np.exp(-x ** 2 / (2 * sigma ** 2))) / ( \\\n",
    "                            np.sqrt(2 * math.pi) * sigma ** 3)\n",
    "    D = D / (np.sum(np.abs(D)) / 2)\n",
    "\n",
    "    # 分别对图像进行x方向和y方向二维卷积\n",
    "    Dx = cv2.sepFilter2D(img, -1, D, G)\n",
    "    Dy = cv2.sepFilter2D(img, -1, G, D)\n",
    "\n",
    "    return Dx, Dy\n",
    "\n",
    "def calculate_derivatives(img1, img2, smoothing, derivation):\n",
    "    \"\"\"\n",
    "    计算图像的导数。\n",
    "\n",
    "    参数:\n",
    "    - img1: 前一帧图像。\n",
    "    - img2: 后一帧图像。\n",
    "    - smoothing: 高斯平滑的标准差。\n",
    "    - derivation: 高斯求导的标准差。\n",
    "\n",
    "    返回:\n",
    "    - i_x: x轴方向的导数图像。\n",
    "    - i_y: y轴方向的导数图像。\n",
    "    - i_t: 时间方向的导数图像。\n",
    "    \"\"\"\n",
    "    # 计算t方向导数\n",
    "    i_t = smooth(img2 - img1, smoothing)\n",
    "    # 计算x、y方向导数\n",
    "    i_x, i_y = gaussderiv(smooth(np.divide(img1 + \\\n",
    "                                img2, 2), smoothing), derivation)\n",
    "    return i_x, i_y, i_t"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb56bd31",
   "metadata": {
    "papermill": {
     "duration": 0.014507,
     "end_time": "2023-08-15T12:30:22.634774",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.620267",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "再根据得到的梯度分块计算区块内的光流。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "705b986c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:22.667477Z",
     "iopub.status.busy": "2023-08-15T12:30:22.667046Z",
     "iopub.status.idle": "2023-08-15T12:30:22.680085Z",
     "shell.execute_reply": "2023-08-15T12:30:22.678649Z"
    },
    "papermill": {
     "duration": 0.033486,
     "end_time": "2023-08-15T12:30:22.683013",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.649527",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def sum_kernel(x, N):\n",
    "    '''\n",
    "    计算块内和。\n",
    "    '''\n",
    "    return cv2.filter2D(x, -1, np.ones((N, N)))\n",
    "\n",
    "def lucas_kanade(img1, img2, N, smoothing=1, derivation=0.4):\n",
    "    \"\"\"\n",
    "    Lucas-Kanade算法。\n",
    "\n",
    "    参数:\n",
    "    - img1: 前一帧图像。\n",
    "    - img2: 后一帧图像。\n",
    "    - N: 块大小。\n",
    "    - smoothing: 高斯平滑的标准差。\n",
    "    - derivation: 高斯求导的标准差。\n",
    "\n",
    "    返回:\n",
    "    - u: x轴方向的速度图像。\n",
    "    - v: y轴方向的速度图像。\n",
    "    \"\"\"\n",
    "    # 计算图像导数\n",
    "    i_x, i_y, i_t = calculate_derivatives(img1, img2, \n",
    "                              smoothing, derivation)\n",
    "    \n",
    "    # 计算i_x_t和i_y_t\n",
    "    i_x_t = sum_kernel(np.multiply(i_x, i_t), N)\n",
    "    i_y_t = sum_kernel(np.multiply(i_y, i_t), N)\n",
    "    \n",
    "    # 计算梯度的平方\n",
    "    i_x_2 = sum_kernel(np.square(i_x), N)\n",
    "    i_y_2 = sum_kernel(np.square(i_y), N)\n",
    "    \n",
    "    i_x_y = sum_kernel(np.multiply(i_x, i_y), N)\n",
    "    \n",
    "    # 计算平方和\n",
    "    D = np.subtract(\n",
    "        np.multiply(i_x_2, i_y_2),\n",
    "        np.square(i_x_y)\n",
    "    )\n",
    "\n",
    "    D += 1e-5\n",
    "    \n",
    "    # 计算各块内的u, v\n",
    "    u = np.divide(\n",
    "        np.add(\n",
    "            np.multiply(-i_y_2, i_x_t),\n",
    "            np.multiply(i_x_y, i_y_t)\n",
    "        ),\n",
    "        D\n",
    "    )\n",
    "\n",
    "    v = np.divide(\n",
    "        np.subtract(\n",
    "            np.multiply(i_x_y, i_x_t),\n",
    "            np.multiply(i_x_2, i_y_t)\n",
    "        ),\n",
    "        D\n",
    "    )\n",
    "\n",
    "    return u, v"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a73a251",
   "metadata": {
    "papermill": {
     "duration": 0.014372,
     "end_time": "2023-08-15T12:30:22.711967",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.697595",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "至此，我们已经得到两张图像间的运动向量，接下来我们将其可视化出来。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "61d9323b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:22.744079Z",
     "iopub.status.busy": "2023-08-15T12:30:22.743601Z",
     "iopub.status.idle": "2023-08-15T12:30:22.760395Z",
     "shell.execute_reply": "2023-08-15T12:30:22.759327Z"
    },
    "papermill": {
     "duration": 0.036762,
     "end_time": "2023-08-15T12:30:22.763255",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.726493",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def show_flow(U, V, ax):\n",
    "    \"\"\"\n",
    "    根据提供的U,V绘制光流。\n",
    "    \"\"\"\n",
    "    # 控制分块的大小\n",
    "    scaling = 0.1 \n",
    "    # 使用高斯平滑处理光流\n",
    "    u = cv2.resize(smooth(U, 1.5), (0, 0), \n",
    "                           fx=scaling, fy=scaling)\n",
    "    v = cv2.resize(smooth(V, 1.5), (0, 0), \n",
    "                           fx=scaling, fy=scaling)\n",
    "\n",
    "    # 生成x,y的坐标矩阵\n",
    "    x_ = (np.array(list(range(1, u.shape[1] + 1))) - 0.5) / scaling\n",
    "    y_ = -(np.array(list(range(1, u.shape[0] + 1))) - 0.5) / scaling\n",
    "    x, y = np.meshgrid(x_, y_)\n",
    "\n",
    "    # 画箭头\n",
    "    ax.quiver(x, y, -u * 5, v * 5)\n",
    "    ax.set_aspect(1.)\n",
    "\n",
    "\n",
    "def draw_optical_flow(image_1, image_2, \n",
    "                      normalizeImages=True, win_size=10):\n",
    "    \"\"\"\n",
    "    计算图像之间的光流并将其显示出来。\n",
    "    \"\"\"\n",
    "    # 归一化图像\n",
    "    if normalizeImages:\n",
    "        image_1 = image_1 / 255.0\n",
    "        image_2 = image_2 / 255.0\n",
    "    # 计算光流\n",
    "    U_lk, V_lk = lucas_kanade(image_1, image_2, win_size)\n",
    "\n",
    "    # 预先定义画布用于可视化\n",
    "    fig2, (ax_11, ax_12, ax_13) = plt.subplots(1, 3, dpi=200)\n",
    "    # 第一帧\n",
    "    ax_11.imshow(image_1)\n",
    "    ax_11.set_title(\"Frame t0\")\n",
    "    ax_11.axis('off')\n",
    "    # 第二帧\n",
    "    ax_12.imshow(image_2)\n",
    "    ax_12.set_title(\"Frame t1\")\n",
    "    ax_12.axis('off')\n",
    "    # 光流\n",
    "    show_flow(U_lk, V_lk, ax_13)\n",
    "    ax_13.set_title(\"Lucas-Kanade\")\n",
    "    ax_13.axis('off')\n",
    "    # 画布布局\n",
    "    fig2.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "387209c4",
   "metadata": {
    "papermill": {
     "duration": 0.015118,
     "end_time": "2023-08-15T12:30:22.793282",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.778164",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "我们将之前生成的图像和旋转后的图像进行光流计算。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "93459fa4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:22.829393Z",
     "iopub.status.busy": "2023-08-15T12:30:22.827671Z",
     "iopub.status.idle": "2023-08-15T12:30:23.507685Z",
     "shell.execute_reply": "2023-08-15T12:30:23.505994Z"
    },
    "papermill": {
     "duration": 0.715417,
     "end_time": "2023-08-15T12:30:23.524677",
     "exception": false,
     "start_time": "2023-08-15T12:30:22.809260",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1159296 characters]",
      "text/plain": [
       "<Figure size 1280x960 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "draw_optical_flow(img_t0, img_t1, normalizeImages=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b89c77a1",
   "metadata": {},
   "source": [
    "可以发现，Lucas-Kanade 算法在运动较小的情况下能够得到较好的效果。接下来让我们看看在运动较大的情况下，Lucas-Kanade 算法的效果如何。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3f54509f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:23.592669Z",
     "iopub.status.busy": "2023-08-15T12:30:23.592175Z",
     "iopub.status.idle": "2023-08-15T12:30:24.328467Z",
     "shell.execute_reply": "2023-08-15T12:30:24.327008Z"
    },
    "papermill": {
     "duration": 0.787708,
     "end_time": "2023-08-15T12:30:24.344985",
     "exception": false,
     "start_time": "2023-08-15T12:30:23.557277",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1132140 characters]",
      "text/plain": [
       "<Figure size 1280x960 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "img_t0 = np.random.rand(200, 200).astype(np.float32)\n",
    "img_t1 = img_t0.copy()\n",
    "\n",
    "# 测试运动较大的情况（旋转8度）\n",
    "img_t1 = rotate_image(img_t1, -8) \n",
    "draw_optical_flow(img_t0, img_t1, normalizeImages=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcb8ea0b",
   "metadata": {
    "papermill": {
     "duration": 0.046571,
     "end_time": "2023-08-15T12:30:24.439401",
     "exception": false,
     "start_time": "2023-08-15T12:30:24.392830",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "可以看到当运动幅度较大时，相较于小幅度运动，Lucas-Kanade 算法的效果就不那么理想了。\n",
    "接下来我们测试在真实的图像上的效果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a68dadb",
   "metadata": {
    "papermill": {
     "duration": 4.512267,
     "end_time": "2023-08-15T12:30:28.996715",
     "exception": false,
     "start_time": "2023-08-15T12:30:24.484448",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "!git clone https://github.com/boyu-ai/Hands-on-CV.git"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf882953",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:29.096712Z",
     "iopub.status.busy": "2023-08-15T12:30:29.095580Z",
     "iopub.status.idle": "2023-08-15T12:30:29.763140Z",
     "shell.execute_reply": "2023-08-15T12:30:29.761843Z"
    },
    "papermill": {
     "duration": 0.723058,
     "end_time": "2023-08-15T12:30:29.768650",
     "exception": false,
     "start_time": "2023-08-15T12:30:29.045592",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 280412 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "image_1 = cv2.imread(\"learncv_img/optical_flow/frame_0001.png\", \n",
    "                     cv2.IMREAD_GRAYSCALE).astype(np.float32)\n",
    "image_2 = cv2.imread(\"learncv_img/optical_flow/frame_0002.png\", \n",
    "                     cv2.IMREAD_GRAYSCALE).astype(np.float32)\n",
    "# 绘制出光流图\n",
    "draw_optical_flow(image_1, image_2, normalizeImages=True, win_size=15)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6aab45a1",
   "metadata": {
    "papermill": {
     "duration": 0.051674,
     "end_time": "2023-08-15T12:30:29.876669",
     "exception": false,
     "start_time": "2023-08-15T12:30:29.824995",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "以上我们实现的是在全部像素上计算光流，在实际的运用过程中，一般是先检测角点，再进行光流的计算，这里我们使用OpenCV的*calcOpticalFlowPyrLK*函数来计算光流。\n",
    "<!-- # 可视化视频\n",
    "from IPython.display import display, HTML\n",
    "# 读者可扫描如下二维码或点击链接\n",
    "# https://el.ptpress.cn/outLinkPreview?\n",
    "# id=98d6dc4d-93b6-4635-bde2-337e76cc35ec\n",
    "# 观看视频\n",
    "html_str = '''\n",
    "<video controls src=\\\"{}\\\">animation</video>\n",
    "'''.format(\"./learncv_img/optical_flow/slow_traffic_small.mp4\")\n",
    "display(HTML(html_str)) -->"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2e51e31",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-15T12:30:30.101499Z",
     "iopub.status.busy": "2023-08-15T12:30:30.100969Z",
     "iopub.status.idle": "2023-08-15T12:30:32.622558Z",
     "shell.execute_reply": "2023-08-15T12:30:32.620972Z"
    },
    "papermill": {
     "duration": 2.577616,
     "end_time": "2023-08-15T12:30:32.625758",
     "exception": false,
     "start_time": "2023-08-15T12:30:30.048142",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 122596 characters]",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import cv2 as cv\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "# 读取一段视频用于计算光流\n",
    "cap = cv.VideoCapture(\n",
    "    \"./learncv_img/optical_flow/slow_traffic_small.mp4\")\n",
    "# ShiTomasi 角点检测参数\n",
    "feature_params = dict(maxCorners = 100,\n",
    "                       qualityLevel = 0.3,\n",
    "                       minDistance = 7,\n",
    "                       blockSize = 7 )\n",
    "# Lucas-Kanade 光流法参数\n",
    "lk_params = dict( winSize  = (15, 15),\n",
    "    maxLevel = 2,\n",
    "    criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, \n",
    "                10, 0.03))\n",
    "# 生成随机颜色用于描述光流\n",
    "color = np.random.randint(0, 255, (100, 3))\n",
    "# 读取第一帧并找到角点\n",
    "ret, old_frame = cap.read()\n",
    "old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)\n",
    "p0 = cv.goodFeaturesToTrack(old_gray, \n",
    "                    mask = None, **feature_params)\n",
    "# 创建一个mask用于绘制轨迹\n",
    "mask = np.zeros_like(old_frame)\n",
    "while(1):\n",
    "    ret, frame = cap.read()\n",
    "    if not ret:\n",
    "        print('No frames grabbed!')\n",
    "        break\n",
    "    frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)\n",
    "    # 计算光流，并返回光流成功跟踪到的点p1，跟踪状态st以及跟踪差异err\n",
    "    p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, \n",
    "                                          p0, None, **lk_params)\n",
    "    # 选择好的跟踪点\n",
    "    if p1 is not None:\n",
    "        # 筛选出跟踪成功的点\n",
    "        good_new = p1[st==1]\n",
    "        good_old = p0[st==1]\n",
    "\n",
    "    # 绘制轨迹\n",
    "    for i, (new, old) in enumerate(zip(good_new, good_old)):\n",
    "        a, b = new.ravel()\n",
    "        c, d = old.ravel()\n",
    "        mask = cv.line(mask, (int(a), int(b)), (int(c), int(d)), \\\n",
    "                       color[i].tolist(), 2)\n",
    "        frame = cv.circle(frame, (int(a), int(b)), 5, \\\n",
    "                          color[i].tolist(), -1)\n",
    "        \n",
    "    img = cv.add(frame, mask)\n",
    "    # 更新上一帧的图像和跟踪点\n",
    "    old_gray = frame_gray.copy()\n",
    "    p0 = good_new.reshape(-1, 1, 2)\n",
    "\n",
    "# 显示结果\n",
    "plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))\n",
    "plt.axis('off')\n",
    "plt.show()"
   ]
  }
 ],
 "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.10.8"
  },
  "papermill": {
   "default_parameters": {},
   "duration": 26.154611,
   "end_time": "2023-08-15T12:30:34.170757",
   "environment_variables": {},
   "exception": null,
   "input_path": "__notebook__.ipynb",
   "output_path": "__notebook__.ipynb",
   "parameters": {},
   "start_time": "2023-08-15T12:30:08.016146",
   "version": "2.3.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
