{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第6章 角点检测"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.1 简介"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在第4章模板匹配中，我们曾提到模板匹配是最简单最基础的图像匹配，即模板与待匹配子图必须具有极高的相似度。那么一般情况下，图像匹配如何实现呢？比如图6-1(a)中所示的在不同视角下拍摄的山峰。如何找到两张图像间的匹配关系呢？这需要我们去检测图像中的特征进行匹配。大家可能最先想到的特征就是如图6-1(b)所示的那些特殊位置，比如山峰的顶部、山峰上雪块和山体交错形成的交叉点。这些局部特征通常被称为“关键点特征”（keypoint feature）或者“兴趣点”（interesting point）。作为关键点特征，通常需要满足这样几个性质：1）重复性（repeatability）：同样的特征点应在不同几何变换和光度变换下的图像中都能被检测到；2）显著性（saliency）：每个特征点都具有与其他特征点不同的区分性描述，通常为其所在位置周围的图像块的表征；3）紧凑性（compactness）：特征点要比图像像素点个数少得多；4）鲁棒性（robustness）：特征点对图像杂乱背景和遮挡具有一定鲁棒性。图6-1(b)所示的特征点就具备上述性质，而且它们都处在两条线（边缘）的交叉点处，所以它们有个形象的名字——角点（corner）。通过匹配两张图像中对应的角点（图6-1(c)），基于匹配关系计算出两张图像的变换矩阵，就可以将两张图像拼接成一张大图了（图6-1(d)）。在这一章中我们将介绍一个经典的图像中角点检测算法——Harris角点检测算法\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic2.zhimg.com/80/v2-fe84fe7356a32e971b5e69468a547a11_1440w.jpg\" width=800>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-1 基于角点的图像匹配。（a）待匹配图像，（b）角点检测，（c）角点匹配，（d）图像拼接。</div>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.2 Harris角点检测算法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如何检测角点呢？首先我们得分析下角点的特点。图6-2显示了一个最基本的角点示例。从这个示例中，我们能发现什么规律呢？如果在图像中某一位置$\\mathbf{p}$放置一个小窗口，然后对其做局部范围内微小的移动，并考察移动前和移动后窗口内像素值的变化量。我们发现：当$\\mathbf{p}$处于平坦区域时，不论将窗口往哪个方向移动，移动前后窗口内像素值变化量都很小；当$\\mathbf{p}$处于边缘附近时，如果将其沿着边缘方向移动，移动前后窗口内像素值变化量也很小，但如果将其朝其他方向移动，则会出现较大像素值变化量；当$\\mathbf{p}$处于角点附近时，不论将窗口往哪个方向移动，都会出现较大像素值变化量。\n",
    "\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic4.zhimg.com/80/v2-64cb1fcab333ee4396e35fc4f30f5caf_1440w.jpg\" width=500>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-2 角点与平坦区域以及边缘的对比</div>\n",
    "</center>\n",
    "\n",
    "因此，根据上述讨论，我们可以按照以下思路来检测角点：我们使用一个固定大小的窗口在图像中某一位置进行任意方向上微小的移动，并比较移动前后该窗口内像素值变化量。如果任意方向上的微小位移都能引起窗口内较大的像素值变化，那么我们就认为该窗口中存在角点。Harris角点检测算法便是基于上述原理的一个代表性的算法。\n",
    "\n",
    "Harris算法主要分为三步：\n",
    "\n",
    "1. 对图像每个位置，计算窗口移动前后其内部的像素值变化量；\n",
    "2. 计算像素值变化量对应的角点响应函数；\n",
    "3. 对该函数进行阈值处理，提取角点。\n",
    "\n",
    "我们将按照步骤分别详细介绍。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2.1 像素值变化量计算"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在图像$\\mathbf{I}$中某一位置放置一个窗口$\\mathbf{W}$，将其移动一个微小位移$[u,v]$，如图 6-3 所示，我们定义窗口移动前后的像素值变化量$E[u,v]$如下：\n",
    "\n",
    "$$E[u,v] = \\sum_{[i, j]\\in\\mathbf{W}}(\\mathbf{I}[i+u, j+v] - \\mathbf{I}[i,j])^2$$\n",
    "\n",
    "其中，$[i,j]$是窗口内的一个位置，$\\mathbf{I}[i,j]$是当前像素值，$\\mathbf{I}[i+u, j+v]$是位移后的像素值。\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic1.zhimg.com/80/v2-beee75acec3b366f86ae5ea7d98733fc_1440w.jpg\" width=300>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-3 在某一位置窗口的移动</div>\n",
    "</center>\n",
    "\n",
    "我们知道，当窗口位于平坦区域时，无论位置$[u,v]$指向什么方向，$E[u,v]$的值都会比较小，因为在平坦区域上$\\mathbf{I}[i+u, j+v]$和$\\mathbf{I}[i,j]$基本相等；当窗口位于边缘时，如果位移$[u,v]$的方向是边缘法线的方向，此时计算得到$E[u,v]$会比较大，而如果位移$[u,v]$的方向是沿着边缘方向，则$E[u,v]$会比较小；只有当窗口处于角点时，无论位移$[u,v]$指向什么方向，其对应的$E[u,v]$都会非常大。因此，我们的目的是寻找这样的窗口，能够满足对于任意的$[u,v]$，$E[u,v]$的值都很大。不过，我们很难遍历所有的方向$[u,v]$以求得对应的$E[u,v]$。因此，我们需要数学工具对上式进行处理。\n",
    "\n",
    "我们使用上述公式的一阶泰勒展开以获取其近似形式。对一张二维图像，其一阶泰勒展开可写作：\n",
    "\n",
    "$$\\mathbf{I}[i+u,j+v] \\approx \\mathbf{I}[i,j] + u\\mathbf{I}_x + v\\mathbf{I}_y$$\n",
    "\n",
    "其中，$\\mathbf{I}_x$是图像沿$X$轴的梯度，$\\mathbf{I}_y$是图像沿$Y$轴的梯度，即：\n",
    "\n",
    "$$\\mathbf{I}_x = \\frac{\\partial \\mathbf{I}[i,j]}{\\partial x}, \\mathbf{I}_y = \\frac{\\partial \\mathbf{I}[i,j]}{\\partial y}$$\n",
    "\n",
    "因此，我们可以将$E[u,v]$写成：\n",
    "\\begin{equation}\n",
    "\\begin{aligned}\n",
    "E[u,v]  &= \\sum_{[i, j]\\in\\mathbf{W}}(\\mathbf{I}[i, j] +u\\mathbf{I}_x + v\\mathbf{I}_y- \\mathbf{I}[i,j])^2 \\\\\n",
    "& = \\sum_{[i, j]\\in\\mathbf{W}}(u\\mathbf{I}_x + v\\mathbf{I})^2 \\\\\n",
    "& = \\sum_{[i, j]\\in\\mathbf{W}}(u^2\\mathbf{I}_x^2+v^2\\mathbf{I}_y^2+2uv\\mathbf{I}_x\\mathbf{I}_y)\n",
    "\\end{aligned}\n",
    "\\end{equation}\n",
    "\n",
    "将$[u,v]$提取出，我们便得到最终的形式：\n",
    "\n",
    "$$E[u, v] = \\begin{bmatrix}u,v \\end{bmatrix}\\mathbf{M}\\begin{bmatrix}u\\\\v \\end{bmatrix}$$\n",
    "\n",
    "其中，矩阵$\\mathbf{M}$为：\n",
    "\n",
    "$$\\mathbf{M} = \\sum_{[i, j]\\in\\mathbf{W}}\\begin{bmatrix}\\mathbf{I}_x^2 & \\mathbf{I}_x\\mathbf{I}_y \\\\ \\mathbf{I}_x\\mathbf{I}_y & \\mathbf{I}_y^2\\end{bmatrix}$$\n",
    "\n",
    "不难发现$\\mathbf{M} = \\sum_{[i, j]\\in\\mathbf{W}}\\mathbf{H}$，其中$\\mathbf{H}$是图像$\\mathbf{I}$的Hessian矩阵。因为$\\mathbf{M}$是一个实对称矩阵，一定能实现对角化，所以我们将其进一步处理为：\n",
    "\n",
    "$$\\mathbf{M} = \\mathbf{R} \\begin{bmatrix} \\lambda_1 & 0 \\\\ 0 & \\lambda_2 \\end{bmatrix} \\mathbf{R}^\\mathbf{T} $$\n",
    "\n",
    "其中$\\lambda_1, \\lambda_2$是矩阵$\\mathbf{M}$的特征值，$\\mathbf{R}$是二维旋转矩阵。我们将上式代入${E}[u,v]$的计算，得到\n",
    "$$\n",
    "{E}[u,v] =  [u,v]\\mathbf{R} \\begin{bmatrix} \\lambda_1 & 0 \\\\ 0 & \\lambda_2 \\end{bmatrix} \\mathbf{R}^{\\mathsf{T}}\\begin{bmatrix}u\\\\v \\end{bmatrix}\n",
    "$$\n",
    "令$[m,n]=[u,v]\\mathbf{R}$，可得\n",
    "$$\n",
    "{E}[u,v] =[m,n] \\begin{bmatrix} \\lambda_1 & 0 \\\\ 0 & \\lambda_2 \\end{bmatrix} [m,n]^{\\mathsf{T}}=m^2\\lambda_1 + n^2\\lambda_2\n",
    "$$\n",
    "由此可见，像素值变化量${E}[u,v]$的大小与两个特征值$\\lambda_1,\\lambda_2$直接相关。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2.2 计算角点响应函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如前所述，角点附近的像素值变化量$E[u,v]$对于任意的微小位移$[u,v]$都有很大的值。也就是说，如果一个点是角点，那么它对应的像素值变化量$E[u,v]$的极小值也很大。那么$E[u,v]$的极小值如何求解呢？上一章节中我们已经将$E[u,v]$表示为两个特征值$\\lambda_1,\\lambda_2$的函数。我们又知道$[m,n]=[u,v]\\mathbf{R}$，那么\n",
    "\n",
    "$$m^2+n^2=[m,n]\\begin{bmatrix}m\\\\n \\end{bmatrix}=[u,v]\\mathbf{R}\\mathbf{R}^{\\mathsf{T}}\\begin{bmatrix}u\\\\v\\end{bmatrix}=u^2+v^2 \\approx 1$$\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic3.zhimg.com/80/v2-a22ac492fbec8087e030c50ebeff0e3e_1440w.jpg\" width=300>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-4 矩阵与特征值的关系</div>\n",
    "</center>\n",
    "\n",
    "这里我们假设微小位移$[u,v]$是一个近似单位向量。既然有$m^2+n^2 \\approx 1$，易得$E[u,v]$的极大值和极小值分别为\n",
    "\n",
    "$$\\max_{u,v}E[u,v]=\\max(\\lambda_1,\\lambda_2)$$\n",
    "\n",
    "$$\\min_{u,v}E[u,v]=\\min(\\lambda_1,\\lambda_2)$$ \n",
    "\n",
    "如图 6-4 所示，那么对于一个角点，它对应的两个特征值$\\lambda_1,\\lambda_2$都应该比较大，因为角点对应的像素值变化量$E[u,v]$的极小值也很大。对于边缘和平坦区域，同样也可以依据两个特征值$\\lambda_1,\\lambda_2$的大小来确定。如图 6-5 所示，总结如下：\n",
    "\n",
    "1. 两个特征值都趋于0，对应图像中的平坦区域；\n",
    "2. 一个特征值大，另一个特征值趋于0，对应图像中的边缘；\n",
    "3. 两个特征值都大，对应图像中的角点\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic2.zhimg.com/80/v2-a24c803d25f82621b887cd29097dd29d_1440w.jpg\" width=300>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-5 两个特征值与图像区域的关系</div>\n",
    "</center>\n",
    "\n",
    "虽然通过两个特征值$\\lambda_1,\\lambda_2$的大小可以定性得到角点的性质，但是我们总是希望能够通过一个单一的度量指标，即角点响应函数，来确定哪些点是角点。根据经验，我们定义角点响应函数为\n",
    "\n",
    "$$\\theta = \\lambda_1\\lambda_2 - \\alpha(\\lambda_1+\\lambda_2)^2$$\n",
    "\n",
    "其中$\\alpha$是一个常数，通常取0.04-0.06。大家可自行验证该角点响应函数是否可以反映两个特征值大小与平坦区域、边缘和角点的关系。如图 6-6 所示，我们将其总结如下：\n",
    "1. 如果$|\\theta|$很小，趋于零，那么对应图像中的平坦区域；\n",
    "2. 如果$\\theta$为负数，那么对应图像中的边缘；\n",
    "3. 如果$\\theta$为很大的正数，那么对应图像中角点。\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic2.zhimg.com/80/v2-bcbc935eea4af46ef95a0cb1605d298d_1440w.jpg\" width=200>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-6 角点响应函数与图像区域的关系</div>\n",
    "</center>\n",
    "\n",
    "也就是说，窗口所在处的角点响应函数$\\theta$的值为正，且越大，则窗口所在处越可能是角点。这样我们就得到了可量化的角点计算方法。根据线性代数的知识，我们知道\n",
    "\n",
    "$${\\rm det}(\\mathbf{M}) = \\lambda_1\\lambda_2$$\n",
    "\n",
    "$${\\rm trace}(\\mathbf{M}) = \\lambda_1+\\lambda_2$$\n",
    "\n",
    "所以我们可以直接通过矩阵$\\mathbf{M}$的行列式和迹来计算角点响应函数，而避免做特征值分解：\n",
    "\n",
    "$$\\theta = \\lambda_1\\lambda_2 - \\alpha(\\lambda_1+\\lambda_2)^2 = {\\rm det}(\\mathbf{M}) - \\alpha {\\rm trace}(\\mathbf{M})^2$$\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们来总结下对一张图像提取角点的步骤：\n",
    "1. 计算图像中每一个像素位置处的图像梯度$\\mathbf{I}_x,\\mathbf{I}_y$；\n",
    "2. 将一个局部窗口$\\mathbf{W}$滑动到图像上每个像素点的位置，计算窗口内图像Hessian矩阵的求和矩阵$\\mathbf{M} = \\sum_{[i, j]\\in\\mathbf{W}}\\mathbf{H}$；\n",
    "3. 计算角点响应函数$\\theta = {\\rm det}(\\mathbf{M}) - \\alpha {\\rm trace}(\\mathbf{M})^2$;\n",
    "4. 设定阈值$\\gamma$，对$\\theta$进行判断：若$\\theta>\\gamma$，则为角点;\n",
    "5. 对整张图的角点响应函数做非极大值抑制。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.3 算法实现"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们已经学习了Harris角点的理论知识，接下来，我们将编程实现Harris角点检测。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-04T08:46:10.663241Z",
     "iopub.status.busy": "2022-07-04T08:46:10.662713Z",
     "iopub.status.idle": "2022-07-04T08:46:11.722391Z",
     "shell.execute_reply": "2022-07-04T08:46:11.721158Z",
     "shell.execute_reply.started": "2022-07-04T08:46:10.663147Z"
    }
   },
   "outputs": [],
   "source": [
    "from utils import *\n",
    "\n",
    "\n",
    "# 计算角点响应函数\n",
    "def responseFunc(M):\n",
    "    # 设置超参数\n",
    "    k = 0.04\n",
    "    \n",
    "    # 计算M的行列式\n",
    "    det = np.linalg.det(M)\n",
    "    # 计算M的迹\n",
    "    trace = np.trace(M)\n",
    "    \n",
    "    # 计算角点响应函数\n",
    "    R = det - k * trace ** 2\n",
    "    \n",
    "    return R\n",
    "\n",
    "\n",
    "# Harris角点检测算法\n",
    "def harris_corners(src, NMS=False):\n",
    "    \n",
    "    # 获得输入图像的长宽\n",
    "    h, w = src.shape[:2]\n",
    "    \n",
    "    # 将图像转为灰度图像\n",
    "    gray_image = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)\n",
    "    \n",
    "    # 初始化角点矩阵\n",
    "    cornerPoint = np.zeros_like(gray_image,dtype=np.float32)\n",
    "    \n",
    "    # 计算图像沿横轴和纵轴的梯度\n",
    "    grad = np.zeros((h, w, 2), dtype=np.float32)\n",
    "    # x轴梯度\n",
    "    grad[:,:,0] = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0)\n",
    "    # y轴梯度\n",
    "    grad[:,:,1] = cv2.Sobel(gray_image, cv2.CV_16S, 0, 1)\n",
    "    \n",
    "    # 计算Hessian矩阵内元素的值，此时的值是求和的结果，即M矩阵的元素\n",
    "    Ixx = grad[:,:,0] ** 2\n",
    "    Iyy = grad[:,:,1] ** 2\n",
    "    Ixy = grad[:,:,0] * grad[:,:,1]\n",
    "     \n",
    "    # 计算窗口内Hessian矩阵元素的值，窗函数使用高斯函数\n",
    "    Ixx = cv2.GaussianBlur(Ixx, (3, 3), sigmaX=2)\n",
    "    Iyy = cv2.GaussianBlur(Iyy, (3, 3), sigmaX=2)\n",
    "    Ixy = cv2.GaussianBlur(Ixy, (3, 3), sigmaX=2)\n",
    "    \n",
    "    for i in range(gray_image.shape[0]):\n",
    "        for j in range(gray_image.shape[1]):\n",
    "            \n",
    "            # 构建M矩阵\n",
    "            struture_matrix = [[Ixx[i][j], Ixy[i][j]], [Ixy[i][j], Iyy[i][j]]]\n",
    "\n",
    "            # 计算角点响应函数\n",
    "            R = responseFunc(struture_matrix)\n",
    "            cornerPoint[i][j] = R\n",
    "\n",
    "    # 非最大抑制\n",
    "    corners = np.zeros_like(gray_image, dtype=np.float32)\n",
    "    threshold = 0.01\n",
    "    \n",
    "    # 返回所有角点响应的最大值\n",
    "    maxValue = np.max(cornerPoint)\n",
    "    \n",
    "    # 我们将角点响应函数的阈值设定为 threshold * maxValue\n",
    "    \n",
    "    for i in range(cornerPoint.shape[0]):\n",
    "        for j in range(cornerPoint.shape[1]):\n",
    "            \n",
    "            # 如果进行nms\n",
    "            if NMS:\n",
    "                # 当前角点响应值大于阈值，同时也是邻居周围的最大值\n",
    "                if cornerPoint[i][j] > threshold * maxValue and \\\n",
    "                cornerPoint[i][j] == np.max(cornerPoint[max(0, i - 1):min(i + 1, h - 1), \n",
    "                                                        max(0, j - 1):min(j + 1, w - 1)]):\n",
    "                    corners[i][j] = 255\n",
    "            else:\n",
    "                # 当前角点响应值大于阈值\n",
    "                if cornerPoint[i][j] > threshold * maxValue:\n",
    "                    corners[i][j] = 255\n",
    "                    \n",
    "    # 返回检测到的角点\n",
    "    return corners\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-04T08:46:11.724725Z",
     "iopub.status.busy": "2022-07-04T08:46:11.724326Z",
     "iopub.status.idle": "2022-07-04T08:46:28.125897Z",
     "shell.execute_reply": "2022-07-04T08:46:28.124575Z",
     "shell.execute_reply.started": "2022-07-04T08:46:11.724692Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 194048 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 227036 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "img = cv2.imread('sudoku.png')\n",
    "\n",
    "plt.imshow(img[:, :, ::-1])\n",
    "plt.axis('off')\n",
    "plt.title('Original Input')\n",
    "plt.show()\n",
    "\n",
    "response = harris_corners(img)\n",
    "img[response == 255] = [0, 0, 255]\n",
    "plt.imshow(img[:, :, ::-1])\n",
    "plt.axis('off')\n",
    "plt.title('Harris Corner Response')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.4 图像变换对角点检测的影响"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在章节6.1中我们提到我们希望特征点对于图像变换具有一定的不变性（重复性）。下面我们来探索一下，当图像发生变换，如旋转、平移、放缩等时，检测出的角点是否具有不变性？\n",
    "\n",
    "对于旋转而言，检测出的角点前后不变。如图 6-7 所示，我们可以很直观地发现，对图像的旋转改变的仅仅是矩阵$\\mathbf{M}$的旋转因子$\\mathbf{R}$，并不改变其对应的特征值，因此对角点检测没有影响。\n",
    "\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic1.zhimg.com/80/v2-073eb6ce5ba9d658bbb75d2271dfa110_1440w.jpg\" width=400>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-7 旋转图像对角点检测的影响</div>\n",
    "</center>\n",
    "\n",
    "同理，对图像平移并不会改变矩阵的特征值大小，因此Harris角点检测依旧具有不变性。但是对于一张缩放的图像而言，原来图像中的角点有可能被检测为边缘。如图 6-8 所示原来的角点经过放大之后，被检测为多个边缘点。所以，角点对于图像的尺度变化不具有不变性！\n",
    "\n",
    "\n",
    "\n",
    "<center>\n",
    "    <img style=\"border-radius: 0.3125em;\" \n",
    "    src=\"https://pic3.zhimg.com/80/v2-894b8a131d83380c3497a25b86c434ae_1440w.jpg\" width=400>\n",
    "    <br>\n",
    "    <div style=\"color:orange; \n",
    "    display: inline-block;\n",
    "    color: #999;\n",
    "    padding: 2px;\">图6-8 放缩图像对角点检测的影响</div>\n",
    "</center>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.5 小结"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在本章中，我们学习了一种图像中的特征点——角点，以及Harris角点检测算法。尽管角点对于很多图像变换具有不变性，是一种常用的图像匹配特征点，但我们在最后提到，角点对于图像尺度变化不具有不变性。这也是角点的局限性。那么是否存在对图像尺度变化也不变的特征点呢？请大家进入下一章的学习。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.6 习题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 习题 6.1：请动手验证图像变换对角点检测有无影响。\n",
    "\n",
    "#### 习题 6.2：本章中我们学习了Harris角点检测算法。随着人们研究的深入，Harris算法也在不断地被改进，如Shi-Tomasi算法等。请自行查阅相关资料，并动手实现Shi-Tomasi算法（参考文献：Good Features to Track，1994）。"
   ]
  }
 ],
 "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.7"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
