实现原理
详细参照https://en.wikipedia.org/wiki/Harris_Corner_Detector。
Harris Corner Detector是一种角点检测算子,它是由克里斯·哈里斯和迈克·斯蒂芬斯于1988年首次引入,其是莫拉维克角落探测器的改进。
角是一个点,其邻域内有两个不同方向的边缘。换句话说,角可以被解释为两个边缘的连接,其中边缘是图像亮度的突然变化产生的。角是图像中的重要特征,它们通常被称为对平移、旋转和照明不变的兴趣点。虽然角落只占图像的一小部分,但它们包含恢复图像信息的最重要特征,它们可用于最小化运动跟踪,图像拼接,建立2D马赛克,立体视觉,图像表示(image representation),和其他相关的计算机视觉领域。
为了从图像中提取角点,研究人员提出了许多不同的角点检测器,包括Kanade-Lucas-Tomasi(KLT)算子和Harris算子,它们在角点检测中使用最简单,高效和可靠。这两种流行的方法都与局部结构矩阵紧密相关并且基于局部结构矩阵。与Kanade-Lucas-Tomasi角点检测器相比,Harris角点检测器在变化的照明和旋转下提供了良好的重现,因此,它更常用于立体匹配和图像检索。虽然仍然存在缺点和局限,但Harris角点检测器仍然是许多计算机视觉应用的重要和基础技术。
在不失一般性的情况下,我们将假设使用灰度二维图像,用 I I I 表示。考虑一个 ( x , y ) (x,y) (x,y)的一个邻域,也即是一个图像块(patch),则此领域块图像灰度值与平移一个 ( Δ x , Δ y ) . {\displaystyle (\Delta x,\Delta y)}. (Δx,Δy).的块的SSD(sum of squared differences) f f f表示如下: f ( x , y ) = ∑ ( x k , y k ) ∈ W ( I ( x k , y k ) − I ( x k + Δ x , y k + Δ y ) ) 2 {\displaystyle f(x,y)={\underset {(x_{k},y_{k})\in W}{\sum }}\left(I(x_{k},y_{k})-I(x_{k}+\Delta x,y_{k}+\Delta y)\right)^{2}} f(x,y)=(xk,yk)∈W∑(I(xk,yk)−I(xk+Δx,yk+Δy))2
其中 I ( x + Δ x , y + Δ y ) {\displaystyle I(x+\Delta x,y+\Delta y)} I(x+Δx,y+Δy)可以泰勒展开式来逼近,令 I x I_{x} Ix 和 I y I_{y} Iy表示 I I I的偏导数,于是:
I ( x + Δ x , y + Δ y ) ≈ I ( x , y ) + I x ( x , y ) Δ x + I y ( x , y ) Δ y {\displaystyle I(x+\Delta x,y+\Delta y)\approx I(x,y)+I_{x}(x,y)\Delta x+I_{y}(x,y)\Delta y} I(x+Δx,y+Δy)≈I(x,y)+Ix(x,y)Δx+Iy(x,y)Δy
则:
f ( x , y ) ≈ ∑ ( x , y ) ∈ W ( I x ( x , y ) Δ x + I y ( x , y ) Δ y ) 2 , {\displaystyle f(x,y)\approx {\underset {(x,y)\in W}{\sum }}\left(I_{x}(x,y)\Delta x+I_{y}(x,y)\Delta y\right)^{2},} f(x,y)≈(x,y)∈W∑(Ix(x,y)Δx+Iy(x,y)Δy)2,
上式可以写成矩阵形式:
f ( x , y ) ≈ ( Δ x Δ y ) M ( Δ x Δ y ) , {\displaystyle f(x,y)\approx {\begin{pmatrix}\Delta x&\Delta y\end{pmatrix}}M{\begin{pmatrix}\Delta x\\\Delta y\end{pmatrix}},} f(x,y)≈(ΔxΔy)M(ΔxΔy),
上式中 M M M为结构张量;
M = ∑ ( x , y ) ∈ W [ I x 2 I x I y I x I y I y 2 ] = [ ∑ ( x , y ) ∈ W I x 2 ∑ ( x , y ) ∈ W I x I y ∑ ( x , y ) ∈ W I x I y ∑ ( x , y ) ∈ W I y 2 ] {\displaystyle M={\underset {(x,y)\in W}{\sum }}{\begin{bmatrix}I_{x}^{2}&I_{x}I_{y}\\I_{x}I_{y}&I_{y}^{2}\end{bmatrix}}={\begin{bmatrix}{\underset {(x,y)\in W}{\sum }}I_{x}^{2}&{\underset {(x,y)\in W}{\sum }}I_{x}I_{y}\\{\underset {(x,y)\in W}{\sum }}I_{x}I_{y}&{\underset {(x,y)\in W}{\sum }}I_{y}^{2}\end{bmatrix}}} M=(x,y)∈W∑[Ix2IxIyIxIyIy2]=⎣⎢⎡(x,y)∈W∑Ix2(x,y)∈W∑IxIy(x,y)∈W∑IxIy(x,y)∈W∑Iy2⎦⎥⎤
算法实现过程
通常,算法可分为五个步骤:
1.颜色到灰度、
如果有必要,将彩色图转成灰度图;
2.空间导数计算
计算 I x ( x , y ) I_{x}(x,y) Ix(x,y)、 I y ( x , y ) I_{y}(x,y) Iy(x,y),采用梯度算子计算;
3.结构张量计算
同 I x ( x , y ) I_{x}(x,y) Ix(x,y)和 I y ( x , y ) I_{y}(x,y) Iy(x,y),我们可以构造结构张量 M M M中号。
4.harris响应计算
可以通过下式计算结构张量的最小特征值:
λ m i n ≈ λ 1 λ 2 ( λ 1 + λ 2 ) = d e t ( M ) t r a c e ( M ) {\displaystyle \lambda _{min}\approx {\frac {\lambda _{1}\lambda _{2}}{(\lambda _{1}+\lambda _{2})}}={\frac {det(M)}{trace(M)}}} λmin≈(λ1+λ2)λ1λ2=trace(M)det(M),式中:
t r a c e ( M ) = m 11 + m 22 {\displaystyle trace(M)=m_{11}+m_{22}} trace(M)=m11+m22表示结构张量的迹;
另外一种方法是:
R = d e t ( M ) − k ( t r a c e ( M ) ) 2 = λ 1 λ 2 − k ( λ 1 + λ 2 ) 2 {\displaystyle R=det(M)-k(trace(M))^{2}=\lambda _{1}\lambda _{2}-k(\lambda _{1}+\lambda _{2})^{2}} R=det(M)−k(trace(M))2=λ1λ2−k(λ1+λ2)2
实际计算时采用式子的前半部分简单些; k k k是常数, k ∈ [ 0.04 , 0.06 ] {\displaystyle k\in [0.04,0.06]} k∈[0.04,0.06]
5.harris最大值点精确定位
采用基于二次型的亚像素插值精确定位,
二次型的一般形式如下:
f ( x , y ) = A x 2 + B y 2 + C x + D y + E x y + F  ​ f(x,y)=Ax^{2}+By^{2}+Cx+Dy+Exy+F\,\! f(x,y)=Ax2+By2+Cx+Dy+Exy+F
二次型的最值位置估计:
如果 4 A B − E 2 < 0 4AB-E^{2}<0 4AB−E2<0,此二次型没有最大值或最小值;
如果 4 A B − E 2 > 0 4AB-E^{2}>0 4AB−E2>0, A > 0 A>0 A>0, 有最小值; A < 0 A<0 A<0,有最大值,最值位于:
x m = − 2 B C − D E 4 A B − E 2 , x_{m}=-{\frac {2BC-DE}{4AB-E^{2}}}, xm=−4AB−E22BC−DE,
y m = − 2 A D − C E 4 A B − E 2 y_{m}=-{\frac {2AD-CE}{4AB-E^{2}}} ym=−4AB−E22AD−CE
A 、 B 、 C 、 D 、 E A、B、C、D、E A、B、C、D、E可以求 ( 0 , 0 ) (0,0) (0,0) 处的一阶偏导数或二阶偏导数而得。
算法实现
本算法实现参考matlab harris角点检测器实现
计算偏导数:
void grad(real_2d* img, real_2d** _hg, real_2d** _vg)
{
if (!img)return;
int cols, rows;
int i, j;
real_2d* hg = NULL;
real_2d* vg = NULL;
cols = img->cols;
rows = img->rows;
hg = create_real_2d(rows, cols);
vg = create_real_2d(rows, cols);
if (!hg || !vg)
{
destroy_real_2d(&hg);
destroy_real_2d(&vg);
return;
}
rows--, cols--;
float** img_arr;
float** hg_arr;
float** vg_arr;
img_arr = img->arr;
hg_arr = hg->arr;
vg_arr = vg->arr;
for (i = 1; i < rows; i++)
{
for (j = 1; j < cols; j++)
{
hg_arr[i][j] = img_arr[i][j - 1] - img_arr[i][j + 1];
vg_arr[i][j] = img_arr[i-1][j] - img_arr[i+1][j];
}
}
*_hg = hg;
*_vg = vg;
}
计算harris 响应:
void corner_metric(real_2d* img, real_2d** _m)
{
if (!img)return;
real_2d* kernerl = NULL;
int kernerl_size = 5;
kernerl = create_gaussian_kernel(kernerl_size / 3.0);
if (!kernerl)return;
real_2d* vg = NULL;
real_2d* hg = NULL;
real_2d* m = create_real_2d(img->rows, img->cols);
grad(img, &hg, &vg);
if (!hg || !vg || !m)
{
destroy_real_2d(&hg);
destroy_real_2d(&vg);
destroy_real_2d(&m);
return;
}
int rows, cols;
int i, j;
float** hg_arr;
float** vg_arr;
float** c_arr;
float** m_arr;
float hg_val;
float vg_val;
float c_val;
rows = img->rows;
cols = img->cols;
real_2d* c = create_real_2d(rows, cols);
if (!c)return;
hg_arr = hg->arr;
vg_arr = vg->arr;
c_arr = c->arr;
m_arr = m->arr;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
hg_val = hg_arr[i][j];
vg_val = vg_arr[i][j];
c_arr[i][j] = hg_val*vg_val;
hg_arr[i][j] = hg_val*hg_val;
vg_arr[i][j] = vg_val*vg_val;
}
}
//响应计算采用高斯加权求和
gaussian_blur(hg, kernerl, 0);
gaussian_blur(vg, kernerl, 0);
gaussian_blur(c, kernerl, 0);
rows--;
cols--;
for (i = 1; i < rows; i++)
{
for (j = 1; j < cols; j++)
{
hg_val = hg_arr[i][j];
vg_val = vg_arr[i][j];
c_val = c_arr[i][j];
m_arr[i][j] = hg_val*vg_val - c_val*c_val - K*(hg_val + vg_val)*(hg_val + vg_val);
}
}
*_m = m;
}
根据harris响应找到峰值点,然后shrink形态学处理, shrink可以参考上一篇博文:https://mp.csdn.net/mdeditor/89198610#
void find_peaks(real_2d* m, point_vec** _pv)
{
int_vec* r = NULL;
int_vec* c = NULL;
uint8_2d* bw = NULL;
if (!m)return;
int rows, cols;
float** m_arr;
uint8_t** bw_arr;
int i, j;
float m_val;
float threshold;
float m_max = FLT_MIN;
rows = m->rows;
cols = m->cols;
bw = create_uint8_2d(rows, cols);
if (!bw)return;
m_arr = m->arr;
bw_arr = bw->arr;
m_max = get_real_2d_max(m);
threshold = METRIC_QUALIT * m_max;
if (m_max < 2.2204e-16)threshold = FLT_MAX;
for (i = 1; i < rows - 1; i++)
{
for (j = 1; j < cols - 1; j++)
{
m_val = m_arr[i][j];
if (m_val < threshold)
bw_arr[i][j] = 0;
else
bw_arr[i][j] = m_val >= m_arr[i][j - 1] && \
m_val >= m_arr[i][j + 1] && \
m_val >= m_arr[i - 1][j] && \
m_val >= m_arr[i + 1][j] && \
m_val >= m_arr[i - 1][j - 1] && \
m_val >= m_arr[i + 1][j + 1] && \
m_val >= m_arr[i - 1][j + 1] && \
m_val >= m_arr[i + 1][j - 1];
}
}
shrink(bw, INT_MAX);
get_peak_locations(bw, _pv);
sub_pixel_peak_locations(m, *_pv);
destroy_uint8_2d(&bw);
}
获取最后峰值点:
static void get_peak_locations(uint8_2d* bw, point_vec** _pv)
{
int pc = get_peak_count(bw);
if (pc <= 0)return;
point_vec* pv = NULL;
int i, j;
uint8_t** arr;
int rows, cols;
point pt;
rows = bw->rows;
cols = bw->cols;
arr = bw->arr;
pv = create_point_vec(pc);
if (!pv)
{
return;
}
int count = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
if (arr[i][j])
{
pt.x = j;
pt.y = i;
pv->pts[count++] = pt;
}
}
}
*_pv = pv;
}
峰值点亚像素插值:
static void sub_pixel_peak_locations(real_2d* m, point_vec* pv)
{
if (!m || !pv)return;
int i;
uint8_t** arr;
int row, col;
point pt;
float x, y;
int count;
float m1, m2, m3;
float m4, m5, m6;
float m7, m8, m9;
float dx2, dy2, dxy, dx, dy, detinv;
arr = m->arr;
count = pv->size;
for (i = 0; i < count; i++)
{
pt = pv->pts[i];
row = pt.y;
col = pt.x;
m1 = arr[row - 1][col - 1], m2 = arr[row - 1][col], m3 = arr[row - 1][col + 1];
m4 = arr[row][col - 1], m5 = arr[row][col], m6 = arr[row][col + 1];
m7 = arr[row + 1][col - 1], m8 = arr[row + 1][col], m9 = arr[row + 1][col + 1];
dx2 = (m1 - 2 * m2 + m3 + \
2 * m4 - 4 * m5 + 2 * m6 + \
m7 - 2 * m8 + m9) / 8.0;
dy2 = (m1 + 2 * m2 + m3 + \
2 * m4 + 2 * m5 + m6 + \
m7 + 2 * m8 + m9) / 8.0;
dxy = (m1 - m3 - m7 + m9) / 4.0;
dx = (-m1 - 2 * m3 - m7 + \
m3 + 2 * m6 + m9)/8.0;
dy = (-m1 - 2 * m2 - m3 + \
m7 + 2 * m8 + m9) / 8.0;
detinv = 1 / (dx2*dy2 - 0.25 * dxy*dxy);
x = -0.5 *(dy2 * dx - 0.5*dxy*dy)*detinv;
y = -0.5 *(dx2*dy - 0.5*dxy*dx)*detinv;
if (fabs(x) < 1 && fabs(y) < 1)
{
pt.x += x;
pt.y += y;
}
//printf("%d,x=%f,y=%f\n" ,i+1,pt.x, pt.y);
}
//printf("\n");
}
整合代码:
point_vec* harris_corner(uint8_t* data, int w, int h)
{
real_2d* img = uint8_to_real(data, w, h);
if (!img)return;
normalize_real_2d(img, 255.0);
real_2d* metric = NULL;
point_vec* pv = NULL;
//printf_real_2d(img);
corner_metric(img, &metric);
find_peaks(metric, &pv);
return pv;
}