下一个教程 : 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,y∑w(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+vIy−I(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,y∑u2Ix2+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,y∑w(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,y∑w(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()
结果
原始图像:
检测到的角周围有一个黑色小圆圈