From 5b095b1591a0494a054cf1269222949e33d40357 Mon Sep 17 00:00:00 2001 From: Shengbi <39143915+Countonme@users.noreply.github.com> Date: Thu, 6 Nov 2025 08:13:20 +0800 Subject: [PATCH] #Add ImageAlignmentTests: unit tests for image registration and transformation accuracy --- .../Img_alignment/ImageAlignmentTests.cs | 141 ++++++++++++++++++ .../Img_alignment/Program.cs | 28 ++++ 2 files changed, 169 insertions(+) create mode 100644 test/OpenCvSharp.Tests/Img_alignment/ImageAlignmentTests.cs create mode 100644 test/OpenCvSharp.Tests/Img_alignment/Program.cs diff --git a/test/OpenCvSharp.Tests/Img_alignment/ImageAlignmentTests.cs b/test/OpenCvSharp.Tests/Img_alignment/ImageAlignmentTests.cs new file mode 100644 index 000000000..825fd700c --- /dev/null +++ b/test/OpenCvSharp.Tests/Img_alignment/ImageAlignmentTests.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace OpenCvSharp.Tests.Img_alignment; + +internal class ImageAlignmentTests +{ + private static Mat templateMatGray; + + /// + /// 初始化模板,只需调用一次 + /// + public static void InitTemplate(Bitmap templateBitmap) + { + templateMatGray = BitmapToMat(templateBitmap); + Cv2.CvtColor(templateMatGray, templateMatGray, ColorConversionCodes.BGR2GRAY); + } + + /// + /// 每帧对齐到模板 + /// + public static Bitmap AlignToTemplate(Bitmap srcBitmap) + { + if (srcBitmap == null || templateMatGray == null) + return srcBitmap; + + Mat srcMat = BitmapToMat(srcBitmap); + Mat srcGray = new Mat(); + Cv2.CvtColor(srcMat, srcGray, ColorConversionCodes.BGR2GRAY); + + // ORB 特征检测 + var orb = ORB.Create(500); + KeyPoint[] kpTemplate, kpSrc; + Mat desTemplate = new Mat(); + Mat desSrc = new Mat(); + orb.DetectAndCompute(templateMatGray, null, out kpTemplate, desTemplate); + orb.DetectAndCompute(srcGray, null, out kpSrc, desSrc); + + // BFMatcher 特征匹配 + var bf = new BFMatcher(NormTypes.Hamming, crossCheck: true); + var matches = bf.Match(desTemplate, desSrc); + if (matches.Length < 4) + return srcBitmap; // 匹配点太少,直接返回原图 + + Array.Sort(matches, (a, b) => a.Distance.CompareTo(b.Distance)); + + var ptsTemplate = new Point2f[matches.Length]; + var ptsSrc = new Point2f[matches.Length]; + for (int i = 0; i < matches.Length; i++) + { + ptsTemplate[i] = kpTemplate[matches[i].QueryIdx].Pt; + ptsSrc[i] = kpSrc[matches[i].TrainIdx].Pt; + } + + // 仿射矩阵 + Mat mask = new Mat(); + Mat M = Cv2.EstimateAffinePartial2D(InputArray.Create(ptsSrc), InputArray.Create(ptsTemplate), mask); + if (M.Empty()) + return srcBitmap; + + Mat aligned = new Mat(); + Cv2.WarpAffine(srcMat, aligned, M, new Size(templateMatGray.Width, templateMatGray.Height)); + + return MatToBitmap(aligned); + } + + #region Bitmap <-> Mat (无需 Extensions) + + public static Mat BitmapToMat(Bitmap bitmap) + { + using (var ms = new System.IO.MemoryStream()) + { + bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + byte[] bytes = ms.ToArray(); + return Cv2.ImDecode(bytes, ImreadModes.Color); // 彩色 + } + } + + public static Bitmap MatToBitmap(Mat mat) + { + if (mat == null || mat.Empty()) + return null; + + Bitmap bmp; + + if (mat.Channels() == 1) + { + // 灰度图 + bmp = new Bitmap(mat.Width, mat.Height, PixelFormat.Format8bppIndexed); + BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), + ImageLockMode.WriteOnly, bmp.PixelFormat); + + // 创建缓冲区 + int length = mat.Width * mat.Height; + byte[] buffer = new byte[length]; + Marshal.Copy(mat.Data, buffer, 0, length); + + // 拷贝到 Bitmap + Marshal.Copy(buffer, 0, data.Scan0, length); + bmp.UnlockBits(data); + + // 设置灰度调色板 + ColorPalette palette = bmp.Palette; + for (int i = 0; i < 256; i++) + palette.Entries[i] = Color.FromArgb(i, i, i); + bmp.Palette = palette; + } + else if (mat.Channels() == 3) + { + // 彩色图 BGR -> RGB + Mat rgbMat = new Mat(); + Cv2.CvtColor(mat, rgbMat, ColorConversionCodes.BGR2RGB); + + bmp = new Bitmap(rgbMat.Width, rgbMat.Height, PixelFormat.Format24bppRgb); + BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), + ImageLockMode.WriteOnly, bmp.PixelFormat); + + int length = rgbMat.Rows * int.Parse(rgbMat.Step().ToString()); + byte[] buffer = new byte[length]; + Marshal.Copy(rgbMat.Data, buffer, 0, length); + Marshal.Copy(buffer, 0, data.Scan0, length); + + bmp.UnlockBits(data); + } + else + { + throw new NotSupportedException("Unsupported Mat format: channels=" + mat.Channels()); + } + + return new Bitmap(bmp); // 返回安全副本 + } + + #endregion +} + diff --git a/test/OpenCvSharp.Tests/Img_alignment/Program.cs b/test/OpenCvSharp.Tests/Img_alignment/Program.cs new file mode 100644 index 000000000..09f662de7 --- /dev/null +++ b/test/OpenCvSharp.Tests/Img_alignment/Program.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenCvSharp.Tests.Img_alignment; + +internal class Program +{ + static void Main() + { + Console.WriteLine("=== Image Alignment Demo ==="); + + // 初始化模板 + Bitmap template = new Bitmap("template.jpg"); + ImageAlignmentTests.InitTemplate(template); + + // 模拟每帧输入(摄像头帧) + Bitmap frame = new Bitmap("input.jpg"); + + Bitmap aligned = ImageAlignmentTests.AlignToTemplate(frame); + + aligned.Save("aligned_output.jpg"); + Console.WriteLine("Aligned image saved!"); + } +}