OpenCV提供了多种接口,可跨平台开发,本文主要介绍OpenCV的C++使用。
tips:因为Moravec算子较为简单,所以希望读者能够通过学习Moravec算子的实现打开使用OpenCV的大门。
(已经能使用OpenCV的读者可以跳过一、二,直接阅读三)
设置项目属性,在“C/C++”-常规-附加包含目录中添加
$(Opencv_Loc)\..\..\include
在“链接器”-常规-附加库目录中添加
$(Opencv_Loc)\lib
在“链接器”-输入-附加依赖项中添加
opencv_world411.lib //Release模式下添加这段
opencv_world411d.lib //Debug模式下添加这段
[参考文档:https://www.cnblogs.com/shlll/archive/2018/02/06/8424692.html]
#include"opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2\opencv.hpp"
using namespace cv;//或 使用cv::对应函数
tips:OpenCV中的Mat类型数据可以看成一个矩阵,OpenCV将图像读入存到Mat类型数据里
//读取图像使用imread函数
Mat m_srcimg = imread('图像路径');
//显示图像使用imshow函数
imshow("src image", m_srcimg);
waitKey(); //等待操作,将窗口关闭\回车\ESC等操作会运行以下的程序
Moravec算子对图像的每个像素点都会计算一个兴趣值,这里我们用OpenCV的Mat类型数据来存放。
Mat Interest=Mat::zeros(srcImg.rows, srcImg.cols, CV_64F);//用于存放兴趣值 double
这段代码表示创建一个名为“Interest”的Mat变量,初始化为0,矩阵大小与原始图像矩阵大小保存一致(即srcImg.rows, srcImg.cols)rows表示图像高,cols表示图像宽。而CV_64F表示存放的数据为double类型。
某一像素点的兴趣值定义为:在以该像素为中心的窗口中,四个方向相邻像素灰度差的平方和的最小值。
(四个方向分别为0°、45°、90°和135°,窗口大小需为大于等于3的奇数)
example:
若像素点(x,y)的灰度值为g(x,y),窗口大小为3
则四个方向的兴趣值分别为:
V1=[g(x-1, y )-g(x,y)]²+[g(x,y)-g(x+1, y )]²
V2=[g(x-1,y-1)-g(x,y)]²+[g(x,y)-g(x+1,y+1)]²
V3=[g( x ,y-1)-g(x,y)]²+[g(x,y)-g( x ,y+1)]²
V4=[g(x-1,y+1)-g(x,y)]²+[g(x,y)-g(x+1,y-1)]²
若窗口大小为5,则从g(x-2,y),g(x-2,y-2),g(x,y-2),g(x-2,y+2)开始算起。
该像素兴趣值取最小值为min{V1,V2,V3,V4}
将兴趣值大于某个经验阈值的像素点作为候选角点。这里同样用Mat类型数据来存放。(函数中的const double T为阈值)
Mat Candidate(srcImg.rows, srcImg.cols, srcImg.type());//候选点
srcImg.type()表示与原始灰度图一样的数据类型
将给定窗口大小内兴趣值最大的点作为特征点。这是Moravec算子的最后一步,也是其他点特征提取算子的最后一步。
注意:
1.这里的窗口大小可不同于计算兴趣值的窗口大小。
2.只对窗口的中心点进行判断。即若窗口中心点的兴趣值为窗口内最大,则作为特征点,而窗口其他点兴趣值为最大,则暂时不做判断。
下面给出代码全文:
【函数变量说明:srcImg为原始灰度图,iwindowsize为计算兴趣值窗口大小,ichecksize为抑制局部非最大窗口大小,T为经验阈值,IntrPtVec为输出的特征点】
//Morevec算子
void MorevecOperator(const Mat srcImg, const int iwindowsize,const int ichecksize, const double T, vector<Point2f> &IntrPtVec)
{
Mat Interest=Mat::zeros(srcImg.rows, srcImg.cols, CV_64F);//用于存放兴趣值 double
Mat Candidate(srcImg.rows, srcImg.cols, srcImg.type());//候选点
//计算各像素点兴趣值
for (int i = iwindowsize / 2; i < srcImg.rows /*- 1*/ - iwindowsize / 2; i++)//因rows cols从1起算
{
for (int j = iwindowsize / 2; j < srcImg.cols /*- 1*/ - iwindowsize / 2; j++)
{
double V1, V2, V3, V4;
V1 = V2 = V3 = V4 = 0;
for (int iw = -1 * iwindowsize / 2; iw <= iwindowsize / 2 - 1; iw++)
{
V1 += pow((double(srcImg.at<uchar>(i + iw, j))- double(srcImg.at<uchar>(i + iw + 1, j))),2);
V2 += pow((double(srcImg.at<uchar>(i + iw, j + iw)) - double(srcImg.at<uchar>(i + iw + 1, j + iw + 1))), 2);
V3 += pow(double(srcImg.at<uchar>(i, j + iw))-double(srcImg.at<uchar>(i, j + iw + 1)), 2);
V4 += pow((double(srcImg.at<uchar>(i + iw, j - iw)) - double(srcImg.at<uchar>(i + iw + 1, j - iw - 1))), 2);
}
double nMinV = V1 >= V2 ? V2 : V1;
nMinV = nMinV >= V3 ? V3 : nMinV;
nMinV = nMinV >= V4 ? V4 : nMinV;
Interest.at<double>(i, j) = nMinV;
//获取候选点
if (Interest.at<double>(i, j) >=T)
{
Candidate.at<uchar>(i, j) = 0;
}
else
{
Candidate.at<uchar>(i, j) = 255;
}
}
}
//抑制局部非最大
Point2f pt;
Mat Img = srcImg;
for (int i = ichecksize / 2; i < srcImg.rows - ichecksize / 2; i++)
{
for (int j = ichecksize / 2; j < srcImg.cols - ichecksize / 2; j++)
{
//只搜索候选点
if (Candidate.at<uchar>(i, j) == 0)
{
//抑制局部非最大窗口内
//ix iy为相对中心点的偏移量
double nMax = 0;//窗口内最大兴趣值
//查找窗口内兴趣值最大的点
for (int iy = -ichecksize / 2; iy < ichecksize / 2; iy++)
{
for (int ix = -ichecksize / 2; ix < ichecksize / 2; ix++)
{
if (Interest.at<double>(i + iy, j + ix) > nMax)
{
nMax = Interest.at<double>(i + iy, j + ix);
}
}
}
//若中心点为最大
if (Interest.at<double>(i, j) == nMax)
{
circle(Img, Point(j, i), 3, RGB(0, 0, 0));
pt.x = j;
pt.y = i;
IntrPtVec.push_back(pt);
}
}
}
}
}
<1>原始灰度图:
<2>候选点图(黑色为候选点):
<3>提取结果