harris角点检测
参考文献
参考论文:A Combined Corner and Edge Detector
参考博客:
参考PPT:https://github.com/CV-xueba/A01_cvclass_basic/blob/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80_%E8%AF%BE%E4%BB%B6/06_corner.pdf
原理
1. 角点的定义是: 滑窗沿着任意方向移动,亮度变化都很明显.
2. 定义对应的外观变化窗口能量函数如下所示:描述的是当能量函数越大,表示检测到角点的概率就是越大的。
3. 函数的各个部分的描述如下图所示,其中窗口可以是0,1窗口,或者是高斯窗口函数。
4. 然后要知道对应的能量函数在微小变化的条件下,对应的值是如何变化的,可以对应的对上面能量函数进行二阶泰勒展开,如下所示:
5. 对应链式求导可得如下的结果:
6. 将对应的初始值0,0带入到上面的公式中可得:
7.因此对应第二次向可以简化表示为如下的形式:
8. 其中对应的M可以通过分解得到关键的对角矩阵值 lambda1 和 lambda2
9. 因此可以将对应的结果归纳如下:
10. 最后进一步总结对应的结果,通过一个阈值去判断是否是角点,公式如下所示:
代码实现
1. 要求从对应的链接下载得到 两个头文件 stb_image.h 和 stb_image_write.h 放到源代码的同一个目录下,对应的链接如下: GitHub - nothings/stb: stb single-file public domain libraries for C/C++
2. 然后直接编译即可:g++ main.cpp -o harris -std=c++11
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION#include "stb_image.h"
#include "stb_image_write.h"#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>// Sobel 卷积核
const int SOBEL_X[3][3] = {{-1, 0, 1},{-2, 0, 2},{-1, 0, 1}
};
const int SOBEL_Y[3][3] = {{-1, -2, -1},{ 0, 0, 0},{ 1, 2, 1}
};const float k = 0.04f;
const float threshold = 1e6f;// 卷积
float convolve(const std::vector<std::vector<float>>& img, int x, int y, const int kernel[3][3]) {float sum = 0.0f;for (int dy = -1; dy <= 1; ++dy) {for (int dx = -1; dx <= 1; ++dx) {int ix = x + dx;int iy = y + dy;if (iy >= 0 && iy < img.size() && ix >= 0 && ix < img[0].size()) {sum += img[iy][ix] * kernel[dy + 1][dx + 1];}}}return sum;
}std::vector<std::pair<int, int>> harrisCornerDetect(const std::vector<std::vector<float>>& img) {int h = img.size(), w = img[0].size();std::vector<std::vector<float>> Ix(h, std::vector<float>(w));std::vector<std::vector<float>> Iy(h, std::vector<float>(w));std::vector<std::vector<float>> R(h, std::vector<float>(w));for (int y = 1; y < h - 1; ++y)for (int x = 1; x < w - 1; ++x) {Ix[y][x] = convolve(img, x, y, SOBEL_X);Iy[y][x] = convolve(img, x, y, SOBEL_Y);}for (int y = 1; y < h - 1; ++y)for (int x = 1; x < w - 1; ++x) {float sumIx2 = 0, sumIy2 = 0, sumIxy = 0;for (int dy = -1; dy <= 1; ++dy)for (int dx = -1; dx <= 1; ++dx) {float ix = Ix[y + dy][x + dx];float iy = Iy[y + dy][x + dx];sumIx2 += ix * ix;sumIy2 += iy * iy;sumIxy += ix * iy;}float det = sumIx2 * sumIy2 - sumIxy * sumIxy;float trace = sumIx2 + sumIy2;R[y][x] = det - k * trace * trace;}std::vector<std::pair<int, int>> corners;for (int y = 1; y < h - 1; ++y)for (int x = 1; x < w - 1; ++x)if (R[y][x] > threshold) {bool isMax = true;for (int dy = -1; dy <= 1; ++dy)for (int dx = -1; dx <= 1; ++dx)if (R[y + dy][x + dx] > R[y][x])isMax = false;if (isMax)corners.emplace_back(x, y);}return corners;
}void drawCorners(unsigned char* image, int width, int height, const std::vector<std::pair<int, int>>& corners) {for (const auto& [x, y] : corners) {for (int dy = -1; dy <= 1; ++dy)for (int dx = -1; dx <= 1; ++dx) {int ix = x + dx;int iy = y + dy;if (ix >= 0 && ix < width && iy >= 0 && iy < height) {int idx = (iy * width + ix) * 3;image[idx + 0] = 255; // Redimage[idx + 1] = 0;image[idx + 2] = 0;}}}
}int main() {int w, h, channels;unsigned char* data = stbi_load("119_0003.JPG", &w, &h, &channels, 1); // 灰度图if (!data) {std::cerr << "无法读取图像!" << std::endl;return 1;}std::vector<std::vector<float>> gray(h, std::vector<float>(w));for (int y = 0; y < h; ++y)for (int x = 0; x < w; ++x)gray[y][x] = static_cast<float>(data[y * w + x]);auto corners = harrisCornerDetect(gray);std::cout << "角点数量: " << corners.size() << std::endl;// 重新加载彩色图用于标记角点stbi_image_free(data);data = stbi_load("119_0003.JPG", &w, &h, &channels, 3); // 彩色图if (!data) {std::cerr << "无法读取图像(彩色)!" << std::endl;return 1;}drawCorners(data, w, h, corners);stbi_write_png("harris_result.png", w, h, 3, data, w * 3);stbi_image_free(data);std::cout << "已保存结果图像:harris_result.png" << std::endl;return 0;
}