當前位置: 妍妍網 > 碼農

C# OpenCvSharp 透過特征點匹配圖片

2024-05-07碼農

SIFT特征簡介

SIFT(Scale-Invariant Feature Transform)特征,即尺度不變特征變換,是一種電腦視覺的特征提取演算法,用來偵測與描述影像中的局部性特征。

實質上,它是在不同的尺度空間上尋找關鍵點(特征點),並計算出關鍵點的方向。SIFT所尋找到的關鍵點是一些十分突出、不會因光照、仿射變換和噪音等因素而變化的點,如角點、邊緣點、暗區的亮點及亮區的暗點等。

SURF特征簡介

SURF(Speeded Up Robust Features, 加速穩健特征) 是一種穩健的影像辨識和描述演算法。它是SIFT的高效變種,也是提取尺度不變特征,演算法步驟與SIFT演算法大致相同,但采用的方法不一樣,要比SIFT演算法更高效(正如其名)。SURF使用海森(Hesseian)矩陣的行列式值作特征點檢測並用積分圖加速運算;SURF 的描述子基於 2D 離散小波變換響應並且有效地利用了積分圖。

SIFT匹配效果

圖片源自網路侵刪

SURF匹配效果

圖片源自網路侵刪

程式碼

using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;
namespace OpenCvSharp_Demo
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
Mat matSrc = new Mat("1.jpg");
Mat matTo = new Mat("2.jpg");
var outMat = MatchPicBySift(matSrc, matTo);
pictureBox2.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(outMat);
}
private void button1_Click(object sender, EventArgs e)
{
Mat matSrc = new Mat("1.jpg");
Mat matTo = new Mat("2.jpg");
var outMat = MatchPicBySurf(matSrc, matTo, 10);
pictureBox2.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(outMat);
}
public Point2d Point2fToPoint2d(Point2f point) => new Point2d((double)point.X, (double)point.Y);
public Mat MatchPicBySift(Mat matSrc, Mat matTo)
{
using (Mat matSrcRet = new Mat())
using (Mat matToRet = new Mat())
{
KeyPoint[] keyPointsSrc, keyPointsTo;
using (var sift = OpenCvSharp.Features2D.SIFT.Create())
{
sift.DetectAndCompute(matSrc, null, out keyPointsSrc, matSrcRet);
sift.DetectAndCompute(matTo, null, out keyPointsTo, matToRet);
}
using (var bfMatcher = new OpenCvSharp.BFMatcher())
{
var matches = bfMatcher.KnnMatch(matSrcRet, matToRet, k: 2);
var pointsSrc = new List<Point2f>();
var pointsDst = new List<Point2f>();
var goodMatches = new List<DMatch>();
foreach (DMatch[] items in matches.Where(x => x.Length > 1))
{
if (items[0].Distance < 0.5 * items[1].Distance)
{
pointsSrc.Add(keyPointsSrc[items[0].QueryIdx].Pt);
pointsDst.Add(keyPointsTo[items[0].TrainIdx].Pt);
goodMatches.Add(items[0]);
Console.WriteLine($"{keyPointsSrc[items[0].QueryIdx].Pt.X}, {keyPointsSrc[items[0].QueryIdx].Pt.Y}");
}
}
var outMat = new Mat();
// 演算法RANSAC對匹配的結果做過濾
var pSrc = pointsSrc.ConvertAll(Point2fToPoint2d);
var pDst = pointsDst.ConvertAll(Point2fToPoint2d);
var outMask = new Mat();
// 如果原始的匹配結果為空, 則跳過過濾步驟
if (pSrc.Count > 0 && pDst.Count > 0)
Cv2.FindHomography(pSrc, pDst, HomographyMethods.Ransac, mask: outMask);
// 如果透過RANSAC處理後的匹配點大於10個,才套用過濾. 否則使用原始的匹配點結果(匹配點過少的時候透過RANSAC處理後,可能會得到0個匹配點的結果).
if (outMask.Rows > 10)
{
byte[] maskBytes = new byte[outMask.Rows * outMask.Cols];
outMask.GetArray(out maskBytes);
Cv2.DrawMatches(matSrc, keyPointsSrc, matTo, keyPointsTo, goodMatches, outMat, matchesMask: maskBytes, flags: DrawMatchesFlags.NotDrawSinglePoints);
}
else
Cv2.DrawMatches(matSrc, keyPointsSrc, matTo, keyPointsTo, goodMatches, outMat, flags: DrawMatchesFlags.NotDrawSinglePoints);
return outMat;
}
}
}
public Mat MatchPicBySurf(Mat matSrc, Mat matTo, double threshold = 400)
{
using (Mat matSrcRet = new Mat())
using (Mat matToRet = new Mat())
{
KeyPoint[] keyPointsSrc, keyPointsTo;
using (var surf = OpenCvSharp.XFeatures2D.SURF.Create(threshold, 4, 3, truetrue))
{
surf.DetectAndCompute(matSrc, null, out keyPointsSrc, matSrcRet);
surf.DetectAndCompute(matTo, null, out keyPointsTo, matToRet);
}
using (var flnMatcher = new OpenCvSharp.FlannBasedMatcher())
{
var matches = flnMatcher.Match(matSrcRet, matToRet);
//求最小最大距離
double minDistance = 1000;//反向逼近
double maxDistance = 0;
for (int i = 0; i < matSrcRet.Rows; i++)
{
double distance = matches[i].Distance;
if (distance > maxDistance)
{
maxDistance = distance;
}
if (distance < minDistance)
{
minDistance = distance;
}
}
Console.WriteLine($"max distance : {maxDistance}");
Console.WriteLine($"min distance : {minDistance}");
var pointsSrc = new List<Point2f>();
var pointsDst = new List<Point2f>();
//篩選較好的匹配點
var goodMatches = new List<DMatch>();
for (int i = 0; i < matSrcRet.Rows; i++)
{
double distance = matches[i].Distance;
if (distance < Math.Max(minDistance * 2, 0.02))
{
pointsSrc.Add(keyPointsSrc[matches[i].QueryIdx].Pt);
pointsDst.Add(keyPointsTo[matches[i].TrainIdx].Pt);
//距離小於範圍的壓入新的DMatch
goodMatches.Add(matches[i]);
}
}
var outMat = new Mat();
// 演算法RANSAC對匹配的結果做過濾
var pSrc = pointsSrc.ConvertAll(Point2fToPoint2d);
var pDst = pointsDst.ConvertAll(Point2fToPoint2d);
var outMask = new Mat();
// 如果原始的匹配結果為空, 則跳過過濾步驟
if (pSrc.Count > 0 && pDst.Count > 0)
Cv2.FindHomography(pSrc, pDst, HomographyMethods.Ransac, mask: outMask);
// 如果透過RANSAC處理後的匹配點大於10個,才套用過濾. 否則使用原始的匹配點結果(匹配點過少的時候透過RANSAC處理後,可能會得到0個匹配點的結果).
if (outMask.Rows > 10)
{
byte[] maskBytes = new byte[outMask.Rows * outMask.Cols];
outMask.GetArray(out maskBytes);
Cv2.DrawMatches(matSrc, keyPointsSrc, matTo, keyPointsTo, goodMatches, outMat, matchesMask: maskBytes, flags: DrawMatchesFlags.NotDrawSinglePoints);
}
else
Cv2.DrawMatches(matSrc, keyPointsSrc, matTo, keyPointsTo, goodMatches, outMat, flags: DrawMatchesFlags.NotDrawSinglePoints);
return outMat;
}
}
}
}
}