微信公众号:OpenCV学堂
投稿作者:小黄弟
来自:中国电科智慧城市建模仿真与智能技术重点实验室
文字编辑:gloomyfish
特征提取
基于特征的图像配准,具有非常广泛的应用,大致流程可以如下:
经典的特征匹配算法有SIFT、SURF、ORB等,这三种方法在OpenCV里面都已实现。SURF基本就是SIFT的全面升级版,有 SURF基本就不用考虑SIFT,而ORB的强点在于计算时间,以下具体比较:
计算速度:ORB>>SURF>>SIFT(各差一个量级)
旋转鲁棒性:SURF>ORB~SIFT(~表示差不多)
模糊鲁棒性:SURF>ORB~SIFT
尺度变换鲁棒性:SURF>SIFT>ORB(ORB并不具备尺度变换性)
所以结论就是,如果对计算实时性要求非常高,可选用ORB算法,但基本要保证正对拍摄;如果对稳定性要求稍高,可以选择SURF;基本不用SIFT。此外补充一点,自从OpenCV3.x开始,受到SIFT跟SURF专利授权的影响,OpenCV正式的发布版本中已经移除了SIFT跟SURF算法。ORB特征提取算法是基于FAST跟BRIEF算法改进的组合算法,其中FAST实现关键点/特征点的检测,在此基础上基于几何矩添加方向属性,BRIEF实现描述子生成,添加旋转不变性支持。
ORB特征匹配速度快的一个原因之一就是使用字符串向量的描述子,避免了浮点数计算。字符串描述子匹配上可以采用汉明距离或者LSH改进算法实现,相比浮点数计算L2距离进一步降低了计算量。所以在一般情况下建议使用ORB特征匹配,如果效果不好再尝试AKAZE/SURF/SIFT等其它特征匹配算法。
特征对齐/配准
两幅图像之间的基于特征匹配的透视变换矩阵求解通常被称为图像对齐或者配准。基于特征的匹配可以很好实现图像对齐或者配准,首先需要获取两张图像的特征关键点与特征描述子,然后通过暴力匹配或者FLANN匹配寻找匹配度高的相关特征点。最后基于这些相关特征点估算它们之间的单应性矩阵,通过单应性矩阵实现透视变换,完成图像对齐与配准。OpenCV中有两个函数可以获得单映射变换矩阵,分别为:
- findHomography
- getPerspectiveTransform
两者之间的区别在于getPerspectiveTransform只会拿4个点去计算,findHomography则会拿一堆点(>=4)去计算。
应用代码演示
下面是一个简单的代码演示,基于特征对齐,实现基于分差的缺陷检测。
用基于ORB特征的匹配结果,如下图所示,可以看到有一些错误的匹配点
基于ORB特征实现图像相关特征点匹配的代码实现如下:
constint MAX_FEATURES = 5000;
constfloat GOOD_MATCH_PERCENT = 0.45f;
//im1为待配准图片
//im2为模板图片
//im1Reg为配准后的图片
//h为单应性矩阵
void alignImages(Mat&im1, Mat&im2, Mat&im1Reg, Mat&h)
{
// 将图像转为灰度图
Mat im1Gray, im2Gray;
cvtColor(im1, im1Gray, COLOR_BGR2GRAY);
cvtColor(im2, im2Gray, COLOR_BGR2GRAY);
// 存储特征与特征描述子的变量
std::vector keypoints1, keypoints2;
Mat descriptors1, descriptors2;
// 检测ORB特征计算特征描述子.
Ptr orb = ORB::create(MAX_FEATURES);
orb->detectAndCompute(im1Gray, Mat(), keypoints1, descriptors1);
clock_t start, end;
start = clock();
orb->detectAndCompute(im2Gray, Mat(), keypoints2, descriptors2); //77ms
// 特征匹配.
std::vector matches;
Ptr matcher = DescriptorMatcher::create("BruteForce-Hamming");
matcher->match(descriptors1, descriptors2, matches, Mat());
// Sort matches by score
std::sort(matches.begin(), matches.end());
//基于GMS的特征匹配算法
//vector matchesAll, matchesGMS;
//BFMatcher matcher(NORM_HAMMING);
//std::vector matches;
//matcher.match(descriptors1, descriptors2, matchesAll);
//cout << "matchesAll: " << matchesAll.size() << endl;
//matchGMS(im1.size(), im2.size(), keypoints1, keypoints2, matchesAll, matches);
//std::sort(matches.begin(), matches.end());
end = clock();
cout << (float)(end - start) * 1000 / CLOCKS_PER_SEC<<"ms"<< endl;
// 移除不好的匹配点
constint numGoodMatches = matches.size() * GOOD_MATCH_PERCENT;
matches.erase(matches.begin() + numGoodMatches, matches.end());
// 画匹配点
Mat imMatches;
drawMatches(im1, keypoints1, im2, keypoints2, matches, imMatches);
imwrite("matches.jpg", imMatches);
// 存储好的匹配点
std::vector points1, points2;
for (size_t i = 0; i < matches.size(); i++)
{
points1.push_back(keypoints1[matches[i].queryIdx].pt);
points2.push_back(keypoints2[matches[i].trainIdx].pt);
}
// 找出最优单映射变换矩阵h
h= findHomography(points1, points2, RANSAC);
// 利用h矩阵进行透视变换
warpPerspective(im1, im1Reg, h, im2.size());
}
Grid-based Motion Statistics(GMS)通过网格划分、运动统计特性的方法可以迅速剔除错误匹配,以此来提高匹配的稳定性。ORB+GMS的匹配效果如下,可见错误的匹配点少了很多。
配准后的图如下图所示:
将配准后的图与基准模板图做差分,效果如下:
进行形态学操作,
找出缺陷,比较大的缺陷可以找出来,较小的缺陷还是不能找出来。
这部分的代码实现如下:
int main(intargc, char **argv)
{
// Read reference image
string refFilename("8.jpg");
cout <<"Reading reference image : "<< refFilename << endl;
Mat imReference = imread(refFilename);
// Read image to be aligned
string imFilename("7.jpg");
cout <<"Reading image to align : "<< imFilename << endl;
Mat im = imread(imFilename);
// Registered image will be resotred in imReg.
// The estimated homography will be stored in h.
Mat imReg, h;
// Align images
cout <<"Aligning images ..."<< endl;
alignImages(im, imReference, imReg, h);
// Write aligned image to disk.
string outFilename("aligned.jpg");
cout <<"Saving aligned image : "<< outFilename << endl;
imwrite(outFilename, imReg);
// Print estimated homography
cout <<"Estimated homography : \n"<< h << endl;
Mat currentframe, previousframe;
cvtColor(imReference, previousframe, COLOR_BGR2GRAY);
cvtColor(imReg, currentframe, COLOR_BGR2GRAY); //转化为单通道灰度图
absdiff(currentframe, previousframe, currentframe);//做差求绝对值
imshow("1", currentframe);
imwrite("re.jpg", currentframe);
threshold(currentframe, currentframe, 120, 255.0, THRESH_BINARY);
imwrite("re11.jpg", currentframe);
erode(currentframe, currentframe, Mat());//腐蚀
dilate(currentframe, currentframe, Mat());//膨胀
dilate(currentframe, currentframe, Mat());//膨胀
imshow("moving area", currentframe); //显示图像
vector<vector> v;
vector hierarchy;
Mat result;
Rect rect;
findContours(currentframe, v, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
for (int i = 0; i < hierarchy.size(); i++)
{
rect = boundingRect(v.at(i));
if (rect.area() > 1)
{
rectangle(imReg, rect, Scalar(0, 0, 255), 2);
}
}
imwrite("res1.jpg", imReg);
imshow("moving area1", imReg);
waitKey(0);
}
关于特征检测跟提取,基于特征的对齐、全景拼接、图像配准等相关知识还可以阅读下面的相关链接获取更多知识。
志合者不以山海为远
道乖者不以咫尺为近
推荐阅读
OpenCV4.8+YOLOv8对象检测C++推理演示
ZXING+OpenCV打造开源条码检测应用
攻略 | 学习深度学习只需要三个月的好方法
三行代码实现 TensorRT8.6 C++ 深度学习模型部署
实战 | YOLOv8+OpenCV 实现DM码定位检测与解析
对象检测边界框损失 – 从IOU到ProbIOU
初学者必看 | 学习深度学习的五个误区