【理解】经典角点检测算法--Harris角点

article/2025/10/8 4:26:17

目录

      • 什么是角点
      • 角点检测算法的原始思想:
      • Harris角点检测原理
      • Harris角点算法的基本步骤
      • 实践:
        • Harris角点检测可能会用到的OpenCV API:
        • 手写API:
          • 1.展示图片:
          • 2.手写Harris特征:
          • 3.手写非极大值抑制:
          • 4.在原图标注角点:
          • 5.响应值颜色渐变(为了美观,没什么用)
          • 6.滑动窗口:
          • 7.Harris角点检测回调函数:
          • main:
        • 实现效果:

什么是角点

角点还没有明确的数学定义,但普遍具有以下特征:

  1. 局部小窗口沿各方向移动,窗口内的像素均产生明显变化的点。
  2. 图像局部曲率(梯度)突变的点。
  3. 对于同一场景,即使视角发生变化,其角点通常具备某些稳定不变性质的特征
    在这里插入图片描述

角点检测算法的原始思想:

我们可以从角点具有的特征出发:
即选取一个局部窗口,将这个窗口沿着各个方向移动,计算移动前后窗口内像素的差异的多少进而判断窗口对应的区域是否是角点。

有了基本的对角点的描述思想,我们可以进一步把它转化为数学描述:
在这里插入图片描述
通过数学描述,我们可以总结出Harris角点的特征:

在这里插入图片描述
但事实上,如果我们此时用以上公式进行角点检测,会发现其中的参数u和v没有明确的规定,也就是窗口移动的方向。

●所以我们也可以人为规定u和v,但这样一来,指定方向窗口滑动又可能导致检测出来的角点其实是边缘点;
●所有我们可以指定若干组的u和v,即不同的窗口滑动方向,对所有的u和v求得E再进行加权平均,完美。

然而事实上Harris角点检测并没有这么做。
Harris可能在想,我应该如何优化这个原始的检测函数呢,提高精度,降低算法的复杂度呢?

Harris角点检测原理

这时候就要用到数学工具:

对E(u,v)表达式的进一步演化:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于不同区域的图像灰度梯度:
在这里插入图片描述
不同区域的图像灰度梯度分布:
当图像中灰度变化较为平坦时,Ix和Iy集中分布在原点附近
当图像中存在边缘点时,x和y其中一方具有较大的梯度
当图像中存在角点时,x和y都具有较大的梯度
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
平坦区域:两个特征值都小,且近似相等,能量函数在各个方向上都较小;
边缘区域:一个特征值大,另一个特征值小,能量函数在某一方向上增大,其他方向较小;
角点区域:两个特征值都大,且近似相等,能量函数在所有方向上都增大。
在这里插入图片描述
这样一来,我们就可以仅通过矩阵M的特征值,来评估图像是否存在角点
但Harris角点的计算方法甚至不需要用到特征值,只需要计算一个Harris响应值R
在这里插入图片描述
而对于n阶方阵又有以下性质:
行列式等于特征值之和;
迹等于特征值之积。

这样一来就避免单独求出特征值:

在这里插入图片描述
到此,通过求出R,我们便可以进行角点检测。(你会发现最后根本不需要代入u,v进行计算)

Harris角点算法的基本步骤

根据上述的讨论,我们总结出Harris角点算法的基本步骤:

  1. 计算窗口中各像素点在x和y方向的梯度;
  2. 计算两个方向梯度的乘积,即Ix ^ 2 , Iy ^ 2 , IxIy(可以用一些一阶梯度算子求得图像梯度)
  3. 使用滤波核对窗口中的每一像素进行加权,生成矩阵M和元素A,B,C
  4. 计算每个像素的Harris响应值R,并对小于某阈值T的R置0;
  5. 由于角点所在区域的一定邻域内都有可能被检测为角点,所以为了防止角点聚集,最后在3×33×3或5×55×5的邻域内进行非极大值抑制,局部最大值点即为图像中的角点。

实践:

Harris角点检测可能会用到的OpenCV API:

  1. normalize() ,归一化函数
  2. Sobel(),Sobel算子,求图像梯度
  3. cornerHarris(), OpenCV的Harris角点检测API
  4. createTrackbar(),添加滑动窗口
  5. convertScaleAbs(),图像的线性变换:
    在这里插入图片描述

手写API:

bool Pic_show(Mat src,const char *param);  //展示图片
void Harris_threshold_arrange();  //滑动窗口
void Harris_detaction(int, void*); //Harris角点检测回调函数
void draw_Point(Mat src,Mat SRC,int T);  //在原图标注角点
void Gradient_change(Mat src);  //响应值颜色渐变(channel:B0,G1,R2)
void My_cornerHarris(Mat src, Mat& dst, int ksize, double k);  //手写Harris特征
void NMF(Mat &src);  //手写非极大值抑制
1.展示图片:
bool Pic_show(Mat src,const char *param)  //展示图片
{   if(src.empty()) {        cout<<"图片打开失败!\n";        return false;    }    else imshow(param,src);    waitKey(0);    return true;} 
2.手写Harris特征:
void My_cornerHarris(Mat src, Mat& dst, int ksize, double k)  //手写Harris特征
{    Mat Ix, Iy;  //存储图像一阶梯度    Mat M(src.size(),CV_32FC3); //创建3通道矩阵M存储 Ix*Ix , Ix*Iy , Iy*Iy    Mat R(src.size(),CV_32FC1); //创建3通道矩阵R存储Harris响应值     //使用Sobel算子提取图像x方向梯度和y方向梯度    Sobel(src,Ix,CV_32FC1,1,0,ksize);    Sobel(src,Iy,CV_32FC1,0,1,ksize);    //存储Ix*Ix , Ix*Iy , Iy*Iy    for(int i = 0;i<src.rows;i++){        for(int j = 0;j<src.cols;j++){            M.at<Vec3f>(i,j)[0]= Ix.at<float>(i,j)*Ix.at<float>(i,j);  //Ix*Ix            M.at<Vec3f>(i,j)[1]= Ix.at<float>(i,j)*Iy.at<float>(i,j);  //Ix*Iy            M.at<Vec3f>(i,j)[2]= Iy.at<float>(i,j)*Iy.at<float>(i,j);  //Iy*Iy        }    }    //高斯滤波对M矩阵进行加权求和    GaussianBlur(M, M, Size(ksize, ksize),2,2);      //求得Harris响应值    for(int i = 0;i<src.rows;i++){        for(int j = 0;j<src.cols;j++){            float A = M.at<Vec3f>(i,j)[0];  //Ix*Ix            float C = M.at<Vec3f>(i,j)[1];  //Ix*Iy            float B = M.at<Vec3f>(i,j)[2];  //Iy*Iy            //响应值计算公式            R.at<float>(i,j) = (A*B - C*C) - (k*( A+B )*( A+B ));          }    }    dst = R.clone();
}
3.手写非极大值抑制:
void NMS(Mat &src)  //手写非极大值抑制
{    int i,j,k,l,cnt=0;    //遍历图像   for(i = 2;i<src.rows;i++)        for(j = 2;j<src.cols;j++)            //采用5×5窗口,小于中心像素置零            for(k=i-2;k<=i+2;k++)                for(l = j-2; l<=j+2;l++)                    if(src.at<float>(k,l)<=src.at<float>(i,j) && k!=i && l!=j && src.at<float>(k,l)>0)                        src.at<float>(i,j) = 0;                                                            
}
4.在原图标注角点:
void draw_Point(Mat src,Mat SRC,int T)  //在原图标注角点
{    cvtColor(SRC,DST,CV_GRAY2BGR);    int Num_of_corner = 0;    for(int row=0;row<src.rows;row++){        uchar* Currunt_row = src.ptr(row);  //行指针        for(int col = 0;col<src.cols;col++){            int R_value = Currunt_row[col]; //提取处理后的角点响应            if(R_value >= T){                 //颜色渐变                Num_of_corner++;  //计算角点数                int R,G,B;if(R_value<=63.75){                   B =  255;                      G = int(255*R_value/63.75);                     R = 0;                    circle(DST,Point(col,row),3,Scalar(B,G,R),1,LINE_MAX);  //圈出大于阈值的角点                   }                else if(R_value<=127.5){                    B = 255-int(255*(R_value-63.75)/63.75);                      G = 255;                      R = 0;                     circle(DST,Point(col,row),3,Scalar(B,G,R),1,LINE_MAX);  //圈出大于阈值的角点         }                else if(R_value<=191.25){                    B = 0;                      G = 255;                      R = int(255*(R_value-127.5)/63.75);                      circle(DST,Point(col,row),3,Scalar(B,G,R),1,LINE_MAX);  //圈出大于阈值的角点                }                else if(R_value<=255){                    B = 0;                      G = 255-saturate_cast<uchar>(255*(R_value-191.25)/63.75);                      R = 255;                      circle(DST,Point(col,row),3,Scalar(B,G,R),1,LINE_MAX);  //圈出大于阈值的角点                }            }        }        Currunt_row++;    }    cout<<"total nums of corner is:"<<Num_of_corner<<endl;
}
5.响应值颜色渐变(为了美观,没什么用)
void Gradient_change(Mat src)  //通道阈值渐变(channel:B0,G1,R2)
{    Mat dst = Mat::zeros(src.size(),CV_8UC3);    for(int row=0;row<src.rows;row++){        for(int col=0;col<src.cols;col++){             int BGR_value = src.at<uchar>(row,col); //三通道像素指针(RGB)            if(BGR_value<=63.75){                dst.at<Vec3b>(row,col)[0]= 255;                  dst.at<Vec3b>(row,col)[1]= int(255*BGR_value/63.75);                  dst.at<Vec3b>(row,col)[2]= 0;              }            else if(BGR_value<=127.5){                dst.at<Vec3b>(row,col)[0]= 255-int(255*(BGR_value-63.75)/63.75);                  dst.at<Vec3b>(row,col)[1]= 255;                  dst.at<Vec3b>(row,col)[2]= 0;              }            else if(BGR_value<=191.25){                dst.at<Vec3b>(row,col)[0]= 0;                  dst.at<Vec3b>(row,col)[1]= 255;                  dst.at<Vec3b>(row,col)[2]= int(255*(BGR_value-127.5)/63.75);              }            else if(BGR_value<=255){                dst.at<Vec3b>(row,col)[0]= 0;                  dst.at<Vec3b>(row,col)[1]= 255-saturate_cast<uchar>(255*(BGR_value-191.25)/63.75);                 dst.at<Vec3b>(row,col)[2]= 255;              }        }    }     imshow("My_Gradient_change",dst);
}
6.滑动窗口:
int Threshold = 172;
int K = 400;
void Harris_threshold_arrange()  //滑动窗口
{    namedWindow("Harris_detaction",CV_WINDOW_NORMAL);    Harris_detaction(0,0);    createTrackbar("T_value","Harris_detaction",&Threshold,255,Harris_detaction);  //阈值T    createTrackbar("k_value","Harris_detaction",&K,700,Harris_detaction);  //阈值k    waitKey(0);
}
7.Harris角点检测回调函数:
void Harris_detaction(int, void*) //Harris角点检测回调函数
{     Mat dst = Mat::zeros(SRC.size(), CV_32FC1);    int blockSize = 2;  //矩阵M的维数(二维以上的原理不太清楚)    int ksize = 3;  //窗口大小    double k = K*0.0001;  //阈值k  //求出每一个像素点的Harris响应值(使用OpenCV API)    //cornerHarris(SRC, dst,blockSize, ksize, k);    My_cornerHarris(SRC, dst, ksize, k);NMS(dst);  //手写非极大值抑制    normalize(dst,dst,0,255,NORM_MINMAX,CV_32FC1,Mat());//将Harris响应值归一化    convertScaleAbs(dst,dst,1,0); //将Harris响应值转为整型(uchar)    Gradient_change(dst);  //绘制角点响应分布图(渐变)    imshow("Harris_callback",dst);  //角点响应分布图    draw_Point(dst,SRC,Threshold);  //在原图标注角点    imshow("Harris_detaction",DST); //result
}
main:
int main()
{    Mat src = imread("test13.jpg",1);    Pic_show(src,"input");    cvtColor(src,SRC,CV_BGR2GRAY);    Harris_threshold_arrange();     //resize(src,DST,Size(1404,648));    //imwrite("test13.jpg",DST);
}

实现效果:

左 with NMS ,右 without NMS
在角点数量相似的情况下,通过非极大值抑制能使角点较为分散。
在这里插入图片描述
归一化后的Harris响应图:
在这里插入图片描述
在这里插入图片描述

参考:
Harris角点检测原理详解
Harris角点详细解释

Harris角点检测原论文:
http://www.bmva.org/bmvc/1988/avc-88-023.pdf


http://chatgpt.dhexx.cn/article/4Wajxdmc.shtml

相关文章

角点检测(Harris角点检测法)

博主联系方式&#xff1a; QQ:1540984562 QQ交流群&#xff1a;892023501 群里会有往届的smarters和电赛选手&#xff0c;群里也会不时分享一些有用的资料&#xff0c;有问题可以在群里多问问。 目录 原理讲解【1】为何选取角点作为特征&#xff1f;【2】角点的定义&#xff1a…

Harris角点检测原理详解

关于角点的应用在图像处理上比较广泛&#xff0c;如图像匹配(FPM特征点匹配)、相机标定等。网上也有很多博客对Harris角点检测原理进行描述&#xff0c;但基本上只是描述了算法流程&#xff0c;而其中相关细节并未作出解释&#xff0c;这里我想对有些地方做出补充说明&#xff…

OpenCV——Harris角点检测

目录 一、Harris角点检测二、C代码三、python代码四、结果展示1、原始图像2、Harris角点 一、Harris角点检测 角点原理来源于人对角点的感性判断&#xff0c;即图像在各个方向灰度有明显变化。算法的核心是利用局部窗口在图像上进行移动判断灰度发生较大的变化&#xff0c;所以…

Harris角点检测

目录 一.基本原理 1.基本思想 2.数学模型 二.实现代码 三.实验结果与分析 1.场景一&#xff1a;纹理平坦场景 2.场景二&#xff1a;多水平边缘场景 3.场景三&#xff1a;角点丰富场景 四.实验总结 一.基本原理 1.基本思想 判断图像的角点&#xff0c;可以利用卷积窗…

harris角点检测原理

目录 1、角点概述 2、数学知识 3、Harris角点检测基本原理 4、优化改进 1、角点概述 如果一个点在任意方向的一个微小变动都会引起灰度很大的变化&#xff0c;那么我们就把它称之为角点&#xff0c;也就是一阶导数(即灰度图的梯度)中的局部最大所对应的像素点就是角点。在现…

计算机视觉(角点检测)- 1 - Harris角点检测

计算机视觉&#xff08;角点检测&#xff09;- 1 - Harris角点检测 学习前言一、Harris角点检测  1、什么是角点&#xff1f;  2、Harris角点检测的基本原理&基本思想  3、Harris角点检测的数学表达  4、获取点数据后&#xff0c;计算 I x &#xff0c; I y I_x&#x…

Visual Studio的sln工程设置VTK、ITK项目

Visual Studio的sln工程设置VTK、ITK项目 最近在学习使用VTK和ITK&#xff0c;使用Visual Studio新建Qt项目时发现项目不是使用过去使用的CMake组织&#xff0c;而是使用的Visual Studio默认的.sln文件&#xff0c;便学习了一下使用Visual Studio的sln工程设置VTK、ITK项目&am…

windows sln的qt 工程

看起来应该vs开发 qt项目不需要qtcreator? 画界面应该还是需要的。装一个vs的插件 2019的如果打开的工程跟你本地的qt不一致 在工程名字上右键change qt version弹出一个框框,里面有俩这里应该选第一个,这个是我本地安装的,vs2015 x86 版本windows sln 工程【这个新建一个q…

Visual studio 2015修改项目文件名及.sln文件名

Visual studio 2015修改项目文件名及.sln文件名&#xff1a; 问题描述 提示&#xff1a;在使用原有的项目代建一个新项目时&#xff0c;为了方便区分与原项目&#xff0c;故要修改该项目的项目名。 如图&#xff1a; 需要将QtGuiApplication1.vcxproj修改成CARFILM.vcxproj&…

C# 解析 sln 文件

我的项目&#xff0c;编码工具 需要检测打开一个工程&#xff0c;获取所有项目。 但是发现原来的方法&#xff0c;如果存在文件夹&#xff0c;把项目放在文件夹中&#xff0c;那么是无法获得项目&#xff0c;于是我就找了一个方法去获得sln文件的所有项目。 原先使用的方法dte…

Visual studio无法打开C#\.sln文件,不兼容

23/7/19文章更新&#xff1a;后来我总结了我这个问题出现的原因&#xff0c;是因为我的VS是2015版本&#xff0c;本来安装在笔记本电脑上&#xff0c;为了转到台式机&#xff0c;直接U盘复制过来的&#xff0c;然后复制过来安装的时候提示某个东西安装失败&#xff0c;我就点击…

.sln图标异常修复

.sln图标显示异常 本人就喜欢新版本的东西&#xff0c;电脑上安装过vs2017,vs2019&#xff0c;想体验最新的vs2022是什么感觉&#xff0c;之后sln图标显示异常&#xff0c;不太顺眼 这是由于该文件VSFileHandler_64.dll异常带来的问题&#xff0c;下载我提供的文件&#xff0c…

Visual Studio打开无sln项目,修复无效sln文件

Visual Studio打开无sln项目&#xff0c;修复无效sln文件 打开项目文件地址&#xff08;可在项目名右键-在文件资源管理器中打开文件夹&#xff09; 打开-项目名.vcxproj文件 VC左上角点击全部保存&#xff0c;选择保存位置储存新的sln头文件 4.sln文件修复完成

VS2019 使用命令行编译工程sln

需要使用 devenv.com这个工具 具体的执行如下&#xff1a; test>"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\devenv.com" build\1.sln /Build执行结果如下&#xff1a; 在对应的路径下找到可执行文件运行

Visual studio 2019 创建.sln项目

文章目录 创建.sln项目在当前解决方案下&#xff0c;添加项目如何切换启动项目如何打开 .sln的解决方案的项目 创建.sln项目 文件–>新建 不要勾选最后一项。 点击 上面界面的右下角的 创建 &#xff0c;进入下面的界面 解决方案和项目在电脑目录及在IDE上的展示 在当…

Unity sln 和 csproj 基础

根目录下 sln 和 csproj 区别 sln&#xff1a; solusion 简写&#xff0c;即解决方案 csproj&#xff1a;c sharp project 简写&#xff0c;即 C# 项目 解决方案sln是项目csproj的集合&#xff0c;项目是文件的集合。 一个 sln 中可以包含多个 csproj。 一个 csproj 可以包含多…

c语言编程题没有sln,使用CMake生成sln项目和VS工程遇到的问题

用vs运行cmake后的工程 1、单个文件示例: 1) 首先建立文件夹CMakeTest/Src 2) 在文件夹Src中建立两个文件main.c和CMakeLists.txt 3) main.c: #include int main() {printf("hello world."); getchar(); return 0; } 4) CMakeLists.txt PROJECT (HELLO) SET (SRC_L…

理解 Visual Studio 解决方案文件格式(.sln)

一般情况下我们并不需要关心 Visual Studio 解决方案文件格式&#xff08;.sln&#xff09;&#xff0c;因为 Visual Studio 对解决方案文件的自动修复能力是非常强的。但是如果遇到自动解冲突错误或者编译不通过了&#xff0c;那么此文件还是需要手工修改的。 基本概念 Visua…

Win平台使用cmake工具生成sln工程示例

先安装一个版本的cmake&#xff0c;3.17.2; 这应该是比较新的版本&#xff1b;我看到有的示例是3.7以下版本&#xff1b; cmake加到系统path变量&#xff1b; 安装完成&#xff1b; 新建一个C#prj目录&#xff0c;下面放一个cs文件&#xff0c;新建一个myprj1目录&#xff1b; …

linux系统sln命令,dotnet sln

dotnet slndotnet sln 12/07/2020 本文内容 本文适用于&#xff1a; ✔️ .NET Core 2.x SDK 及更高版本This article applies to: ✔️ .NET Core 2.x SDK and later versions “属性”Name dotnet sln - 在 .NET 解决方案文件中列出或修改项目。dotnet sln - Lists or modifi…