本文最后更新于:14 天前

Harris角点检测

【原理】

人眼对角点的识别通常是在一个局部的小区域或小窗口完成的。如果在各个方向上移动这个特征的小窗口,窗口内区域的灰度发生了较大的变化,那么就认为在窗口内遇到了角点。如果这个特定的窗口在图像各个方向上移动时,窗口内图像的灰度没有发生变化,那么窗口内就不存在角点;如果窗口在某一个方向移动时,窗口内图像的灰度发生了较大的变化,而在另一些方向上没有发生变化,那么,窗口内的图像可能就是一条直线的线段。

  • 在灰度变化平缓区域,窗口内像素灰度积分近似保持不变
  • 在 边缘区域,边缘方向:灰度积分近似不变, 其余任意方向:剧烈变化。
  • 在角点出:任意方向剧烈变化。

原理

数学推导

  • 定义灰度积分变化

    灰度积分

  • 定义灰度积分变化

    2

  • 如果$u、v$很小, 则有:

    2

  • 其中

    2

  • 注意$E$是一个二次型,即:

    3

  • $\lambda_1^{-\frac12}、\lambda_2^{-\frac12}$是椭圆的长短轴

    • 当$\lambda_1、\lambda_2$都比较小时,点(x, y)处于灰度变化平缓区域.
    • 当$\lambda_1>\lambda_2$或者$\lambda_1<\lambda_2$时,点(x, y)为边界元素.
    • 当$\lambda_1、\lambda_2$都比较大时,且近似相等,点(x, y)为角点.

    3

  • 角点响应函数

    4

  • 当R接近于零时,处于灰度变化平缓区域

  • 当R<0时,点为边界像素

  • 当R>0时,点为角点

代码实例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2019/1/18 18:51
# @Author  : Seven
# @File    : HarrisDemo.py
# @Software: PyCharm
import cv2
import numpy as np

img = cv2.imread('chess.png')
img = cv2.pyrDown(img)
cv2.imshow('source', img)


def harrisDemo(img):
    """
    cornerHarris(src, blockSize, ksize, k, dst=None, borderType=None)
    src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。
    dst,函数调用后的运算结果存在这里,即这个参数用于存放Harris角点检测的输出结果,和源图片有一样的尺寸和类型。
    blockSize,表示邻域的大小,更多的详细信息在cornerEigenValsAndVecs中有讲到。
    ksize,表示Sobel()算子的孔径大小。
    k,Harris 角点检测方程中的自由参数,取值参数为[0,04,0.06]
    borderType,图像像素的边界模式,注意它有默认值BORDER_DEFAULT。更详细的解释,参考borderInterpolate函数。
    :param img:
    :return:
    """
    grayImage = np.float32(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY))
    # 输入图像必须是float32,最后一个参数在0.04 到0.05 之间
    # harris角点检测
    harrisImg = cv2.cornerHarris(grayImage, 2, 3, 0.04)
    # 膨胀
    dst = cv2.dilate(harrisImg, None)
    # 0.01是人为设定的阈值
    # 把角点设为红色
    img[dst > 0.01 * dst.max()] = [0, 0, 255]
    cv2.imshow('harris', img)


def harrisSubPix(img):
    """
    cornerSubPix(image, corners, winSize, zeroZone, criteria)
    image:输入图像

    corners:输入角点的初始坐标以及精准化后的坐标用于输出。

    winSize:搜索窗口边长的一半,例如如果winSize=Size(5,5),则一个大小为(5*2+1)*(5*2+1)=11*11的搜索窗口将被使用。

    zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
    criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。

    :param img:
    :return:
    """
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # harris角点检测
    gray = np.float32(gray)
    dst = cv2.cornerHarris(gray, 2, 3, 0.04)
    dst = cv2.dilate(dst, None)
    ret, dst = cv2.threshold(dst, 0.01 * dst.max(), 255, 0)
    dst = np.uint8(dst)

    # 获取质心坐标-centroids 是每个域的质心坐标
    ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)

    # 设置停止和转换条件
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
    corners = cv2.cornerSubPix(gray, np.float32(centroids), (5, 5), (-1, -1), criteria)

    # Now draw them
    res = np.hstack((centroids, corners))
    res = np.int0(res)
    img[res[:, 1], res[:, 0]] = [0, 0, 255]
    img[res[:, 3], res[:, 2]] = [0, 255, 0]

    cv2.imshow('SubPix', img)


def harrisImage(img):
    grayImage = np.float32(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY))
    # 输入图像必须是float32,最后一个参数在0.04 到0.05 之间
    # harris角点检测
    harrisImg = cv2.cornerHarris(grayImage, 2, 3, 0.04)
    # 膨胀
    dst = cv2.dilate(harrisImg, None)
    # 对角点图像进行阈值化
    ret, dst = cv2.threshold(dst, 0.01 * dst.max(), 255, 0)
    # 转换为8位图像
    dst = np.uint8(dst)
    # 获取质心坐标-centroids 是每个域的质心坐标
    ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)

    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
    #  计算得出角点坐标
    corners = cv2.cornerSubPix(grayImage, np.float32(centroids), (5, 5), (-1, -1), criteria)
    # 对所有角点用0圈出来
    for point in corners:
        cv2.circle(img, tuple(point), 5, (0, 0, 255), 2)
    cv2.imshow('harris-2', img)


harrisDemo(img)
harrisSubPix(img)
harrisImage(img)
cv2.waitKey(0)


本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

【十一】SIFT角点检测 上一篇
【九】直线检测 下一篇