FLANN结合SURF/SIFT进行关键点的描述和匹配
视频讲解如下:
在本章节中给大家演示FLANN结合SURF/SIFT进行关键点的描述和匹配,主要函数:SURF和SIFT。
毛星云的代码为opencv-2.4.9版本的SURF功能演示,可是博主在opencv-2.4.9版本中并未发现SURF功能,目前只能演示SIFT了。
C#、C++版本中提供了Detect和DetectAndCompute两种算法,效果其实也都差不多,博主代码中两种操作方法都提供了,Python版本中好像好像无法使用Detect,执行后返回结果是空的。。。
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
首先呢,我们需要准备一张测试图片,由于采用摄像头实时识别的,进行测试的童鞋最好是自己拍摄一张自己测试环境的图片,当然你也可以直接用我的图片,并找个优酸乳进行测试。
C#版本运行效果如下:
C#版本代码如下:
using OpenCvSharp;
using System;
using System.Collections.Generic;
namespace demo
{
internal class Program
{
/// <summary>
/// 视频操作
/// </summary>
public static VideoCapture Cap = new VideoCapture();
static void Main(string[] args)
{
//demo1();
demo2();
Cv2.WaitKey();
}
/// <summary>
/// SURF
/// </summary>
static void demo1()
{
// 打开ID为0的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
Console.WriteLine("摄像头打开失败.");
return;
}
//Cap.Set(VideoCaptureProperties.FrameWidth, 640); // 设置采集的图像宽度:640
//Cap.Set(VideoCaptureProperties.FrameHeight, 480); // 设置采集的图像高度:480
//Cap.Set(VideoCaptureProperties.Exposure, 0); // 曝光
// 载入源图片
Mat trainImage = new Mat();
for (int i = 0; i < 10; i++)
Cap.Read(trainImage);
Mat trainImage_gray = new Mat();
Cv2.CvtColor(trainImage, trainImage_gray, ColorConversionCodes.BGR2GRAY);
// 定义一个特征检测类对象
var MySurf = OpenCvSharp.XFeatures2D.SURF.Create(80);
Mat trainDescriptor = new Mat();
KeyPoint[] train_keyPoint;
// 模板类是能够存放任意类型的动态数组,能够增加和压缩数据
// 方法1:计算描述符(特征向量),将Detect和Compute操作分开
train_keyPoint = MySurf.Detect(trainImage_gray);
MySurf.Compute(trainImage_gray, ref train_keyPoint, trainDescriptor);
// 方法2:计算描述符(特征向量),将Detect和Compute操作合并
//MySurf.DetectAndCompute(trainImage_gray, null, out train_keyPoint, trainDescriptor);
// 创建基于FLANN的描述符匹配对象
List<Mat> descriptors = new List<Mat>() { trainDescriptor };
//BFMatcher matcher = new BFMatcher();
FlannBasedMatcher matcher = new FlannBasedMatcher();
matcher.Add(descriptors);
matcher.Train();
Mat testImage = new Mat();
Mat testImage_gray = new Mat();
while (true)
{
if (Cap.Read(testImage))
{
// 转化图像到灰度
Cv2.CvtColor(testImage, testImage_gray, ColorConversionCodes.BGR2GRAY);
// 检测S关键点、提取测试图像描述符
Mat testDescriptor = new Mat();
KeyPoint[] test_keyPoint;
// 方法1:计算描述符(特征向量)
test_keyPoint = MySurf.Detect(testImage_gray);
MySurf.Compute(testImage_gray, ref test_keyPoint, testDescriptor);
// 方法2:计算描述符(特征向量)
//MySurf.DetectAndCompute(testImage_gray, null, out test_keyPoint, testDescriptor);
// 匹配训练和测试描述符
DMatch[][] matches = matcher.KnnMatch(testDescriptor, 2);
// 根据劳氏算法(Lowe's algorithm),得到优秀的匹配点
List<DMatch> goodMatches = new List<DMatch>();
for (int i = 0; i < matches.Length; i++)
{
if (matches[i][0].Distance < 0.6 * matches[i][1].Distance)
goodMatches.Add(matches[i][0]);
}
//绘制匹配点并显示窗口
Mat dstImage = new Mat();
Cv2.DrawMatches(testImage, test_keyPoint, trainImage, train_keyPoint, goodMatches, dstImage);
// 显示效果图
Cv2.ImShow("匹配窗口", dstImage);
// 按ESC退出
if (Cv2.WaitKey(10) == 27)
break;
}
}
}
/// <summary>
/// SIFT
/// </summary>
static void demo2()
{
// 打开ID为0的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
Console.WriteLine("摄像头打开失败.");
return;
}
//Cap.Set(VideoCaptureProperties.FrameWidth, 640); // 设置采集的图像宽度:640
//Cap.Set(VideoCaptureProperties.FrameHeight, 480); // 设置采集的图像高度:480
//Cap.Set(VideoCaptureProperties.Exposure, 0); // 曝光
// 载入源图片
Mat trainImage = new Mat();
for(int i = 0;i<10;i++)
Cap.Read(trainImage);
Mat trainImage_gray = new Mat();
Cv2.CvtColor(trainImage, trainImage_gray, ColorConversionCodes.BGR2GRAY);
// 定义一个特征检测类对象
var MySift = OpenCvSharp.Features2D.SIFT.Create(80);
Mat trainDescriptor = new Mat();
KeyPoint[] train_keyPoint;
// 模板类是能够存放任意类型的动态数组,能够增加和压缩数据
// 方法1:计算描述符(特征向量),将Detect和Compute操作分开
train_keyPoint = MySift.Detect(trainImage_gray);
MySift.Compute(trainImage_gray, ref train_keyPoint, trainDescriptor);
// 方法2:计算描述符(特征向量),将Detect和Compute操作合并
// MySift.DetectAndCompute(trainImage_gray, null, out train_keyPoint, trainDescriptor);
// 创建基于FLANN的描述符匹配对象
List<Mat> descriptors = new List<Mat>() { trainDescriptor };
FlannBasedMatcher matcher = new FlannBasedMatcher();
matcher.Add(descriptors);
matcher.Train();
// 打开ID为0的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
Console.WriteLine("摄像头打开失败.");
return;
}
Mat testImage = new Mat();
Mat testImage_gray = new Mat();
while (true)
{
if (Cap.Read(testImage))
{
// 转化图像到灰度
Cv2.CvtColor(testImage, testImage_gray, ColorConversionCodes.BGR2GRAY);
// 检测S关键点、提取测试图像描述符
Mat testDescriptor = new Mat();
KeyPoint[] test_keyPoint;
// 方法1:计算描述符(特征向量)
test_keyPoint = MySift.Detect(testImage_gray);
MySift.Compute(testImage_gray, ref test_keyPoint, testDescriptor);
// 方法2:计算描述符(特征向量)
// MySift.DetectAndCompute(testImage_gray, null, out test_keyPoint, testDescriptor);
// 匹配训练和测试描述符
DMatch[][] matches = matcher.KnnMatch(testDescriptor, 2);
// 根据劳氏算法(Lowe's algorithm),得到优秀的匹配点
List<DMatch> goodMatches = new List<DMatch>();
for (int i = 0; i < matches.Length; i++)
{
if (matches[i][0].Distance < 0.6 * matches[i][1].Distance)
goodMatches.Add(matches[i][0]);
}
//绘制匹配点并显示窗口
Mat dstImage = new Mat();
Cv2.DrawMatches(testImage, test_keyPoint, trainImage, train_keyPoint, goodMatches, dstImage);
// 显示效果图
Cv2.ImShow("匹配窗口", dstImage);
// 按ESC退出
if (Cv2.WaitKey(10) == 27)
break;
}
}
}
}
}
C++版本代码如下:
// 程序描述:FLANN结合SURF进行关键点的描述和匹配
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/features2d/features2d.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat trainImage, trainImage_gray;
// 创建视频对象、定义帧率
VideoCapture cap(0);
// 载入图像、显示并转化为灰度图
for (int i = 0; i < 10; i++)
cap >> trainImage;
imshow("原始图", trainImage);
cvtColor(trainImage, trainImage_gray, COLOR_BGR2GRAY);
// 检测SIFT关键点、提取训练图像描述符
Ptr<SIFT> siftDetector = SIFT::create(80);
vector<KeyPoint> train_keyPoint;
Mat trainDescriptor;
// 方法1:计算描述符(特征向量),将Detect和Compute操作分开
//siftDetector->detect(trainImage_gray, train_keyPoint);
//siftDetector->compute(trainImage_gray, train_keyPoint, trainDescriptor);
// 方法2:计算描述符(特征向量),将Detect和Compute操作合并
siftDetector->detectAndCompute(trainImage_gray, cv::Mat(), train_keyPoint, trainDescriptor);
// 创建基于FLANN的描述符匹配对象
FlannBasedMatcher matcher;
vector<Mat> train_desc_collection(1, trainDescriptor);
matcher.add(train_desc_collection);
matcher.train();
// 不断循环,直到q键被按下
while (true)
{
//<1>参数设置
int64 time0 = getTickCount();
Mat testImage, testImage_gray;
cap >> testImage;//采集视频到testImage中
if (testImage.empty())
continue;
//imwrite("1.jpg", testImage);
//imshow("testImage", testImage);
//waitKey(0);
//<2>转化图像到灰度
cvtColor(testImage, testImage_gray, COLOR_BGR2GRAY);
//<3>检测S关键点、提取测试图像描述符
vector<KeyPoint> test_keyPoint;
Mat testDescriptor;
// 方法1:计算描述符(特征向量),将Detect和Compute操作分开
//siftDetector->detect(testImage_gray, test_keyPoint);
//siftDetector->compute(testImage_gray, test_keyPoint, testDescriptor);
// 方法2:计算描述符(特征向量),将Detect和Compute操作合并
siftDetector->detectAndCompute(testImage_gray, cv::Mat(), test_keyPoint, testDescriptor);
//<4>匹配训练和测试描述符
vector<vector<DMatch> > matches;
matcher.knnMatch(testDescriptor, matches, 2);
// <5>根据劳氏算法(Lowe's algorithm),得到优秀的匹配点
vector<DMatch> goodMatches;
for (unsigned int i = 0; i < matches.size(); i++)
{
if (matches[i][0].distance < 0.6 * matches[i][1].distance)
goodMatches.push_back(matches[i][0]);
}
//<6>绘制匹配点并显示窗口
Mat dstImage;
drawMatches(testImage, test_keyPoint, trainImage, train_keyPoint, goodMatches, dstImage);
imshow("匹配窗口", dstImage);
//<7>输出帧率信息
cout << "当前帧率为:" << getTickFrequency() / (getTickCount() - time0) << endl;
// 按ESC退出
if (waitKey(10) == 27)
break;
}
return 0;
}
Python版本代码如下:
import cv2
Cap = cv2.VideoCapture(0)
# 判断视频是否打开
if not Cap.isOpened():
print('Open Camera Error.')
exit()
Cap.read()
Cap.read()
Cap.read()
Cap.read()
# 载入图像、显示并转化为灰度图
grabbed, trainImage = Cap.read()
# trainImage = cv2.imread("1.jpg")
trainImage_gray = cv2.cvtColor(trainImage, cv2.COLOR_BGR2GRAY)
# 检测SIFT关键点、提取训练图像描述符
sift = cv2.SIFT_create(80)
# 方法1:计算描述符(特征向量),将Detect和Compute操作分开
# train_keyPoint = sift.detect(trainImage_gray)
# (train_keyPoint, trainDescriptor) = sift.compute(trainImage_gray, train_keyPoint)
# 方法2:计算描述符(特征向量),将Detect和Compute操作合并
(train_keyPoint, trainDescriptor) = sift.detectAndCompute(trainImage_gray, None)
# 创建基于FLANN的描述符匹配对象
matcher = cv2.FlannBasedMatcher() # cv2.BFMatcher() #建立匹配关系
matcher.add(trainDescriptor)
matcher.train()
# 读取图像
while True:
grabbed, testImage = Cap.read()
if testImage is None:
continue
testImage_gray = cv2.cvtColor(testImage, cv2.COLOR_BGR2GRAY)
# 方法1
# test_keyPoint = sift.detect(testImage_gray)
# (test_keyPoint, testDescriptor) = sift.compute(testImage_gray, test_keyPoint)
# 方法2
(test_keyPoint, testDescriptor) = sift.detectAndCompute(testImage_gray, None)
# 匹配训练和测试描述符
matches = matcher.knnMatch(testDescriptor, trainDescriptor, 2)
goodMatches = []
# 舍弃大于0.7的匹配
for m, n in matches:
if m.distance < 0.7 * n.distance:
goodMatches.append(m)
# 画出匹配关系
dstImage = cv2.drawMatches(testImage, test_keyPoint, trainImage, train_keyPoint, goodMatches, None)
cv2.imshow("dstImage", dstImage)
# 按ESC退出
if cv2.waitKey(10) == 27:
break
cv2.destroyAllWindows()