引例
当我们在大街上突然遇到一位多年不见的好友,甚至不需要看到她的正脸,仅通过背影或者走路就可以认出是。在这个过程中,把好友看做一个集合体,这个集合中有许多特征点,比如大眼睛、长发、身材很好等,正是通过这些特征点来将这个人记在脑子里,最后在人群中突然出现这些特征点的某几个,就会和存在大脑里特征点集合匹配重合,确认就是她。
如上例在图像处理中,也要对图像进行特征提取,一个图像的特征有很多,比如颜色、纹理、形状等。这里我们介绍一种特征类型,角点。它是两个边的交点,它代表这两个边的方向改变的点。

看上图赫本的照片,我在其中截取了六块,现在如果将这个六块拼接回到原图中,E和F很容易就能认出是眼睛和嘴巴直接拼接会原位,D也可以看出是头发和背景的交界处也很容易找到原位置,但ABC特征不够明确,有很多可能的位置。现在将赫本看做为一个矩形,一遍更易理解。

如上图,有三个颜色的矩形窗口在蓝色大矩形上滑过,现在让这三个窗口都沿着横平竖直两个方向移动一定的距离,白色窗口移动时在两个方向上像素值都不会改变,黑色窗口在水平方向移动不会有像素变化竖直方向会有变化,红色窗口在两个方向像素值都会有变化。红色窗口中就是一个角,显然他比黑色窗口里的边缘更易定位。
Harris 角点检测器(Harris Corner Detection)
这里介绍一种角点检测算法, Harris 角点检测器(Harris Corner Detection),角点是两条边缘的交点,它表示两条边方向改变的地方,所以角点在任意一个方向上做微小移动,都会引起该区域的梯度图的方向和幅值发生很大变化。也就是一阶导数(即灰度图的梯度)中的局部最大所对应的像素点就是角点,使一个固定尺寸的窗口在图像上某个位置以任意方向做微小滑动,如果窗口内的灰度值(在梯度图上)都有较大的变化,那么这个窗口所在区域就存在角点。
1、让一个窗口的中心位置放置在灰度图像的
(
x
,
y
)
(x,y)
(x,y)位置,这个位置的灰度值为
I
(
x
,
y
)
I(x,y)
I(x,y),窗口沿着
x
x
x和
y
y
y方向分别移动一个很小的位移
u
u
u和
v
v
v到达一个新位置
(
x
+
u
,
y
+
v
)
(x+u,y+v)
(x+u,y+v),新位置灰度值为
I
(
x
+
u
,
y
+
v
)
I(x+u,y+v)
I(x+u,y+v),窗口移动过程中产生的灰度值变化值为
[
I
(
x
+
u
,
y
+
v
)
−
I
(
x
,
y
)
]
[I(x+u,y+v)-I(x,y)]
[I(x+u,y+v)−I(x,y)]。窗口函数为
w
(
x
,
y
)
w(x,y)
w(x,y)得到如下公式(1):
E
(
u
,
v
)
=
∑
(
x
,
y
)
w
(
x
,
y
)
×
[
I
(
x
+
u
,
y
+
v
)
−
I
(
x
,
y
)
]
2
E(u,v)=\displaystyle\sum_{(x,y)}w(x,y)\times[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 (1)
经过泰勒公式展开得到式(2).
E
(
u
,
v
)
≈
∑
(
x
,
y
)
w
(
x
,
y
)
×
[
I
(
x
,
y
)
+
u
I
x
+
v
I
y
−
I
(
x
,
y
)
]
2
E(u,v)\approx\displaystyle\sum_{(x,y)}w(x,y)\times[I(x,y)+uI_x+vI_y-I(x,y)]^2
E(u,v)≈(x,y)∑w(x,y)×[I(x,y)+uIx+vIy−I(x,y)]2 (2)
化简得到式(3).
E
(
u
,
v
)
=
∑
(
x
,
y
)
w
(
x
,
y
)
×
[
u
2
I
x
2
+
v
2
I
y
2
+
2
u
v
I
x
I
y
]
E(u,v)=\displaystyle\sum_{(x,y)}w(x,y)\times[u^2I_x^2+v^2I_y^2+2uvI_xI_y]
E(u,v)=(x,y)∑w(x,y)×[u2Ix2+v2Iy2+2uvIxIy] (3)
将
u
u
u和
v
v
v提出得到式(4).
E
(
x
,
y
)
=
[
u
,
v
]
M
[
u
v
]
E(x,y)=\left[\begin{array}{cccc}u,v\end{array}\right]M\left[\begin{array}{cccc}u\\v\end{array}\right]
E(x,y)=[u,v]M[uv] (4)
其中矩阵
M
M
M为:
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)\left[\begin{array}{cccc}I_x^2&I_xI_y\\I_xI_y&I_y^2\end{array}\right]
M=(x,y)∑w(x,y)[Ix2IxIyIxIyIy2]
→
\rightarrow
→
R
−
1
[
λ
1
0
0
λ
2
]
R
R^{-1}\left[\begin{array}{cccc}\lambda_1&0\\0&\lambda_2\end{array}\right]R
R−1[λ100λ2]R
可以把
R
R
R看成旋转因子,其不影响两个正交方向的变化分量。经对角化处理后,将两个正交方向的变化分量提取出来,就是
λ
1
\lambda_1
λ1 和
λ
2
\lambda_2
λ2(特征值)。
2、灰度值变化的大小则取决于矩阵
M
M
M,那么如何找到这些窗口,我们可以使用矩阵的特征值来实现。计算每个窗口对应的得分(角点响应函数
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是矩阵的轨迹。
λ
1
\lambda_1
λ1和
λ
2
\lambda_2
λ2是矩阵的特征值,
k
k
k是经验常数,范围在
[
0.04
,
0.06
]
[0.04,0.06]
[0.04,0.06]之间。
3、根据
R
R
R 的值,将这个窗口所在的区域划分为平面、边缘或角点。为了得到最优的角点,我们还可以使用非极大值抑制。因为特征值
λ
1
\lambda_1
λ1 和
λ
2
\lambda_2
λ2 决定了 R 的值,所以我们可以用特征值来决定一个窗口是平面、边缘还是角点:
*平面: 该窗口在平坦区域上滑动,窗口内的灰度值基本不会发生变化,所以
∣
R
∣
\left|R\right|
∣R∣值非常小,在水平和竖直方向的变化量均较小,即
I
x
I_x
Ix和
I
y
I_y
Iy 都较小,那么
λ
1
\lambda_1
λ1 和
λ
2
\lambda_2
λ2 都较小;
*边缘:[公式] 值为负数,仅在水平或竖直方向有较大的变化量,即
I
x
I_x
Ix和
I
y
I_y
Iy 只有一个较大,也就是
λ
1
≫
λ
2
\lambda_1\gg\lambda_2
λ1≫λ2或
λ
1
≪
λ
2
\lambda_1\ll\lambda_2
λ1≪λ2 ;
*角点:
R
R
R值很大,在水平、竖直两个方向上变化均较大的点,即
I
x
I_x
Ix和
I
y
I_y
Iy 都较大,也就是
λ
1
\lambda_1
λ1 和
λ
2
\lambda_2
λ2 都很大。
Harris 角点检测的结果是带有这些分数
R
R
R的灰度图像,设定一个阈值,分数大于这个阈值的像素就对应角点。
Opencv中Harris检测器的使用
opencv中提供实现Harris角点检测的函数cornerHarris(),使用起来非常方便,相关代码如下:
#include <iostream>
#include <string>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
using namespace std;
Mat src_image , src_image_gray;
int thresh = 100; //阈值变量
const int Max_thresh = 255; //最大阈值
const char* windows_title = "Harris检测器"; //展示窗口标题
const string PATH = "lena2.jpg";
RNG rng(12345);
void cornerHarris_demo(int , void*);
int main(int argc, char const *argv[])
{
src_image = imread(PATH);
cvtColor( src_image, src_image_gray, COLOR_BGR2GRAY );
namedWindow( windows_title, WINDOW_AUTOSIZE );
createTrackbar( "Threshold: ", windows_title, &thresh, Max_thresh, cornerHarris_demo );
cornerHarris_demo(0 , 0);
waitKey(0);
return 0;
}
void cornerHarris_demo(int , void*)
{
Mat dst, dst_norm, dst_norm_scaled;
dst = Mat::zeros( src_image.size() , CV_32FC1 );
int blockSize = 2;
int apertureSize = 3;
double k = 0.04;
cornerHarris( src_image_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT ); //Harris 角点检测
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( i , j ), 5, Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255)), 2, 8, 0 );
circle( src_image, Point( i , j ), 5, Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255)), -1, 8, 0 );
}
}
}
imshow( windows_title, src_image );
imwrite( "Harris.jpg", src_image );
}
将检测到的角点绘制在原图上并保存,最终运行结果如下:

142

被折叠的 条评论
为什么被折叠?



