二维特征框架——Harris角检测器 OpenCV v4.8.0

下一个教程 : Shi-Tomasi 角检测器

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习

  • 什么是特征以及特征为何重要
  • 使用函数 cv::cornerHarris 使用 Harris-Stephens 方法检测角。

理论

什么是特征?

  • 在计算机视觉中,我们通常需要在环境的不同帧之间找到匹配点。为什么要这样做?如果我们知道两幅图像之间的关系,我们就可以利用这两幅图像来提取其中的信息。
  • 当我们说匹配点时,从一般意义上讲,我们指的是场景中我们可以轻松识别的特征。我们称这些特征为特征
  • 那么,特征应该具备哪些特点呢?
    • 它必须是唯一可识别

图像特征的类型

仅举几例:

  • 边缘
  • (也称为兴趣点)
  • 团块(也称为感兴趣区域)

在本教程中,我们将具体研究的特征。

为什么角如此特别?

  • 因为它是两条边缘的交点,代表了这两条边缘的方向发生变化的点。因此,图像(两个方向)的梯度变化很大,可以用来检测它。

它是如何工作的?

  • 让我们来寻找角落。由于边角代表了图像中梯度的变化,因此我们要寻找这种 “变化”。
  • 我们将扫描一个窗口 w(x,y)(在 x 方向上有位移 u,在 y 方向上有位移 v)I,并计算强度的变化。

E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u,v) = \sum _{x,y} w(x,y)[ I(x+u,y+v) - I(x,y)]^{2} E(u,v)=x,yw(x,y)[I(x+u,y+v)I(x,y)]2
其中

w(x,y) 是位置 (x,y) 的窗口
I(x,y) 为 (x,y) 处的强度
I(x+u,y+v)是移动窗口 (x+u,y+v) 处的强度

  • 由于我们要寻找的是有边角的窗口,因此要寻找强度变化较大的窗口。因此,我们必须最大化上式,特别是其中的项:

∑ x , y [ I ( x + u , y + v ) − I ( x , y ) ] 2 \sum _{x,y}[ I(x+u,y+v) - I(x,y)]^{2} x,y[I(x+u,y+v)I(x,y)]2

  • 使用泰勒展开

E ( u , v ) ≈ ∑ x , y [ I ( x , y ) + u I x + v I y − I ( x , y ) ] 2 E(u,v) \approx \sum _{x,y}[ I(x,y) + u I_{x} + vI_{y} - I(x,y)]^{2} E(u,v)x,y[I(x,y)+uIx+vIyI(x,y)]2

  • 将方程展开并适当抵消:

E ( u , v ) ≈ ∑ x , y u 2 I x 2 + 2 u v I x I y + v 2 I y 2 E(u,v) \approx \sum _{x,y} u^{2}I_{x}^{2} + 2uvI_{x}I_{y} + v^{2}I_{y}^{2} E(u,v)x,yu2Ix2+2uvIxIy+v2Iy2

  • 可以矩阵形式表示为

E ( u , v ) ≈ [ u v ] ( ∑ x , y w ( x , y ) [ I x 2 I x I y I x I y I y 2 ] ) [ u v ] E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} \left ( \displaystyle \sum_{x,y} w(x,y) \begin{bmatrix} I_x^{2} & I_{x}I_{y} \\ I_xI_{y} & I_{y}^{2} \end{bmatrix} \right ) \begin{bmatrix} u \\ v \end{bmatrix} E(u,v)[uv](x,yw(x,y)[Ix2IxIyIxIyIy2])[uv]

  • 记为

M = ∑ x , y w ( x , y ) [ I x 2 I x I y I x I y I y 2 ] M = \displaystyle \sum_{x,y} w(x,y) \begin{bmatrix} I_x^{2} & I_{x}I_{y} \\ I_xI_{y} & I_{y}^{2} \end{bmatrix} M=x,yw(x,y)[Ix2IxIyIxIyIy2]

  • 因此,我们现在的方程是

E ( u , v ) ≈ [ u v ] M [ u v ] E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} M \begin{bmatrix} u \\ v \end{bmatrix} E(u,v)[uv]M[uv]

  • 计算每个窗口的得分,以确定它是否可能包含一个角:

R = d e t ( M ) − k ( t r a c e ( M ) ) 2 R = det(M) - k(trace(M))^{2} R=det(M)k(trace(M))2
其中

d e t ( M ) = λ 1 λ 2 det(M)=\lambda_{1}\lambda_{2} det(M)=λ1λ2
t r a c e ( M ) = λ 1 + λ 2 trace(M) = \lambda_{1}+\lambda_{2} trace(M)=λ1+λ2
得到的数 R 大于一定值的窗口被视为 "角 "窗口

代码

C++
本教程代码如下所示。您也可以从此处下载


#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
int thresh = 200;
int max_thresh = 255;
const char* source_window = "Source image";
const char* corners_window = "Corners detected";
void cornerHarris_demo( int, void* );
int main( int argc, char** argv )
{
 CommandLineParser parser( argc, argv, "{@input | building.jpg | input image}" );
 src = imread( samples::findFile( parser.get<String>( "@input" ) ) );
 if ( src.empty() )
 {
 cout << "Could not open or find the image!\n" << endl;
 cout << "Usage: " << argv[0] << " <Input image>" << endl;
 return -1;
 }
 cvtColor( src, src_gray, COLOR_BGR2GRAY );
 namedWindow( source_window );
 createTrackbar( "Threshold: ", source_window, &thresh, max_thresh, cornerHarris_demo );
 imshow( source_window, src );
 cornerHarris_demo( 0, 0 );
 waitKey();
 return 0;
}
void cornerHarris_demo( int, void* )
{
 int blockSize = 2;
 int apertureSize = 3;
 double k = 0.04;
 Mat dst = Mat::zeros( src.size(), CV_32FC1 );
 cornerHarris( src_gray, dst, blockSize, apertureSize, k );
 Mat dst_norm, dst_norm_scaled;
 normalize( dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
 convertScaleAbs( dst_norm, dst_norm_scaled );
 for( int i = 0; i < dst_norm.rows ; i++ )
 {
 for( int j = 0; j < dst_norm.cols; j++ )
 {
 if( (int) dst_norm.at<float>(i,j) > thresh )
 {
 circle( dst_norm_scaled, Point(j,i), 5, Scalar(0), 2, 8, 0 );
 }
 }
 }
 namedWindow( corners_window );
 imshow( corners_window, dst_norm_scaled );
}

Java
本教程代码如下所示。您也可以从此处下载

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Image;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class CornerHarris {
 private Mat srcGray = new Mat();
 private Mat dst = new Mat();
 private Mat dstNorm = new Mat();
 private Mat dstNormScaled = new Mat();
 private JFrame frame;
 private JLabel imgLabel;
 private JLabel cornerLabel;
 private static final int MAX_THRESHOLD = 255;
 private int threshold = 200;
 public CornerHarris(String[] args) {
 String filename = args.length > 0 ? args[0] : "../data/building.jpg";
 Mat src = Imgcodecs.imread(filename);
 if (src.empty()) {
 System.err.println("Cannot read image: " + filename);
 System.exit(0);
 }
 Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
 // 创建并设置窗口。
 frame = new JFrame("Harris corner detector demo");
 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)// 设置内容窗格。
 Image img = HighGui.toBufferedImage(src)addComponentsToPane(frame.getContentPane(), img)// 使用内容窗格的默认边框布局。无需
 // setLayout(new BorderLayout());
 // 显示窗口。
 frame.pack();
 frame.setVisible(true);
 update();
 }
 private void addComponentsToPane(Container pane, Image img) {
 if (!(pane.getLayout() instanceof BorderLayout)) {
 pane.add(new JLabel("Container doesn't use BorderLayout!"));
 return;
 }
 JPanel sliderPanel = new JPanel();
 sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.PAGE_AXIS));
 sliderPanel.add(new JLabel("Threshold: "));
 JSlider slider = new JSlider(0, MAX_THRESHOLD, threshold);
 slider.setMajorTickSpacing(20);
 slider.setMinorTickSpacing(10);
 slider.setPaintTicks(true);
 slider.setPaintLabels(true);
 slider.addChangeListener(new ChangeListener() {
 @Override
 public void stateChanged(ChangeEvent e) {
 JSlider source = (JSlider) e.getSource();
 threshold = source.getValue();
 update();
 }
 });
 sliderPanel.add(slider);
 pane.add(sliderPanel, BorderLayout.PAGE_START);
 JPanel imgPanel = new JPanel();
 imgLabel = new JLabel(new ImageIcon(img));
 imgPanel.add(imgLabel);
 Mat blackImg = Mat.zeros(srcGray.size(), CvType.CV_8U);
 cornerLabel = new JLabel(new ImageIcon(HighGui.toBufferedImage(blackImg)));
 imgPanel.add(cornerLabel);
 pane.add(imgPanel, BorderLayout.CENTER);
 }
 private void update() {
 dst = Mat.zeros(srcGray.size(), CvType.CV_32F);
 int blockSize = 2;
 int apertureSize = 3;
 double k = 0.04;
 Imgproc.cornerHarris(srcGray, dst, blockSize, apertureSize, k);
 Core.normalize(dst, dstNorm, 0, 255, Core.NORM_MINMAX);
 Core.convertScaleAbs(dstNorm, dstNormScaled);
 float[] dstNormData = new float[(int) (dstNorm.total() * dstNorm.channels())];
 dstNorm.get(0, 0, dstNormData);
 for (int i = 0; i < dstNorm.rows(); i++) {
 for (int j = 0; j < dstNorm.cols(); j++) {
 if ((int) dstNormData[i * dstNorm.cols() + j] > threshold) {
 Imgproc.circle(dstNormScaled, new Point(j, i), 5, new Scalar(0), 2, 8, 0);
 }
 }
 }
 cornerLabel.setIcon(new ImageIcon(HighGui.toBufferedImage(dstNormScaled)));
 frame.repaint();
 }
}
public class CornerHarrisDemo {
 public static void main(String[] args) {
 // 加载本地 OpenCV 库
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME)// 为事件派发线程安排任务:
 // 创建并显示此应用程序的图形用户界面。
 javax.swing.SwingUtilities.invokeLater(new Runnable() {
 @Override
 public void run() {
 new CornerHarris(args);
 }
 });
 }
}

Python
本教程代码如下所示。您也可以从此处下载

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
source_window = 'Source image'
corners_window = 'Corners detected'
max_thresh = 255
def cornerHarris_demo(val):
 thresh = val
 # 检测器参数
 blockSize = 2
 apertureSize = 3
 k = 0.04
 # 检测角落
 dst = cv.cornerHarris(src_gray, blockSize, apertureSize, k)
 # 归一化
 dst_norm = np.empty(dst.shape, dtype=np.float32)
 cv.normalize(dst, dst_norm, alpha=0, beta=255, norm_type=cv.NORM_MINMAX)
 dst_norm_scaled = cv.convertScaleAbs(dst_norm)
 # 在四角画圆
 for i in range(dst_norm.shape[0]):
 for j in range(dst_norm.shape[1]):
 if int(dst_norm[i,j]) > thresh:
 cv.circle(dst_norm_scaled, (j,i), 5, (0), 2)
 # 显示结果
 cv.namedWindow(corners_window)
 cv.imshow(corners_window, dst_norm_scaled)
# 加载源图像并将其转换为灰度图像
parser = argparse.ArgumentParser(description='Code for Harris corner detector tutorial.')
parser.add_argument('--input', help='Path to input image.', default='building.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
 print('Could not open or find the image:', args.input)
 exit(0)
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 创建窗口和轨迹条
cv.namedWindow(source_window)
thresh = 200 # 初始阈值
cv.createTrackbar('Threshold: ', source_window, thresh, max_thresh, cornerHarris_demo)
cv.imshow(source_window, src)
cornerHarris_demo(thresh)
cv.waitKey()

结果

原始图像:

在这里插入图片描述

检测到的角周围有一个黑色小圆圈

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值