理论
在图像中取一个滑动窗口 W ( x y ) W_{(xy)} W(xy),不断移动窗口,观察窗口内的像素变化情况:
- 任何一个方向上变化都很大: 角点
- 一个方向变化大,另一个方向没有变化:边缘
- 所有方向都没什么变化:平坦区域
数学表达:
-
对于灰度图像,滑动窗口,计算窗口的像素变化:
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 ) w(x,y) w(x,y):窗口的在(x,y)的位置
I ( x , y ) I(x,y) I(x,y):在(x,y) 的像素值
-
对 ∑ 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进行泰勒级数展开:
≈ ∑ x , y [ I ( x , y ) + u I x + v I y − I ( x , y ) ] 2 \approx \sum_{x,y}{[I(x,y)+uI_x+vI_y-I(x,y)]^2} ≈x,y∑[I(x,y)+uIx+vIy−I(x,y)]2
展开多项式:
≈ ∑ x , y u 2 I x 2 + 2 u v I x I y + v 2 I y 2 \approx \sum_{x,y}{u^2I_x^2+2uvI_xI_y+v^2I_y^2} ≈x,y∑u2Ix2+2uvIxIy+v2Iy2
-
对 E ( u , v ) E(u,v) E(u,v)写成矩阵的形式: 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\left[ \begin{matrix} u, \space v \end{matrix} \right] \left( \sum_{x,y}w(x,y) \left[ \begin{matrix} I_x^2 & I_xI_y\\ I_xI_y & I_y^2 \end{matrix} \right] \right)\left[ \begin{matrix} u \\ v \end{matrix} \right] E(u,v)≈[u, v](x,y∑w(x,y)[Ix2IxIyIxIyIy2])[uv]
- 我们让中间的部分记作
M
M
M(协方差矩阵),则有:
E ( u , v ) = [ u , v ] M [ u v ] E(u,v)= \left[\begin{matrix} u, \space v \end{matrix} \right]M\left[ \begin{matrix} u \\ v \end{matrix} \right] E(u,v)=[u, v]M[uv]
-
我们规定 R R R代表每个窗口的得分,根据 R R R来判断窗口内是否有角点: 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 R R > threashold :这是一个角点
R R R < 0 并且 数值较大:这是边缘
∣ R ∣ = |R|= ∣R∣=:小值:这是平坦区域
代码
#include <iostream>
#include <opencv2/opencv.hpp>
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(void)
{
src = imread("../res/image.png");
if(src.empty())
{
cout << "can't load the image, please confirm the path" << endl;
return -1;
}
cvtColor(src, src_gray, COLOR_BGR2GRAY);
namedWindow(source_window);
createTrackbar("Thresh", 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()); // normalize dst to dst_norm in the range of 0-255
convertScaleAbs(dst_norm, dst_norm_scaled); // dst(x,y) = saturate_cast<uchar>(src(x,y)*alpha+beta),defult: alpha:1 beta:0
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(i, j), 5, Scalar(0), 2);
circle(src, Point(i, j), 5, Scalar(255,0,0), 2);
}
}
}
namedWindow(corners_window);
imshow(corners_window, dst_norm_scaled);
imshow(source_window ,src);
}
结果:
OpenCV API
- 在图像上进行Harris角点检测,对于每个像素 ( x , y ) (x,y) (x,y) ,计算2x2的梯度的协方差矩阵 M M M over blockSize x blockSize neighborhood, 然后计算: d s t ( x , y ) = d e t ( M ) − k ( T r a c e ( M ) ) 2 dst(x,y)=det(M)-k(Trace(M))^2 dst(x,y)=det(M)−k(Trace(M))2
cornerHarris
(
InputArray src, // 单通道, 8bits 或者 float 图
OutputArray dst, // type CV_32FC1 and the same size as src,
int blockSize, //理论里面的窗口
w
w
w的大小
int ksize, // 计算梯度用的Sobel核的大小
double k, // 上面公式的k ,一般取0.04-0.06
int borderType = BORDER_DEFAULT
)