在EmguCV中计算图像差异及基于物体的百分比差异(C#实现)
嘿,我太懂你找不到EmguCV C#版本资料的痛苦了!刚好我之前做过类似的需求,给你一步步讲清楚怎么实现,分两种场景来:
一、计算两张图像的基础像素差异
最直接的方法就是用EmguCV自带的AbsDiff方法,逐像素计算差值的绝对值,能快速得到两张图的差异区域。不过要先确保两张图像的尺寸、通道数完全一致,不然会报错。
给你贴一段可直接运行的C#代码:
using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using System; class ImageDiffCalculator { static void Main(string[] args) { // 加载两张待对比图像 Image<Bgr, byte> img1 = new Image<Bgr, byte>("image1.jpg"); Image<Bgr, byte> img2 = new Image<Bgr, byte>("image2.jpg"); // 先校验图像规格是否一致 if (img1.Size != img2.Size || img1.NumberOfChannels != img2.NumberOfChannels) { throw new ArgumentException("两张图像的尺寸、通道数必须完全匹配!"); } // 计算像素级绝对差异 Image<Bgr, byte> diffImage = img1.AbsDiff(img2); // 转灰度图+阈值化,让差异区域更明显(可选,根据需求调整阈值) Image<Gray, byte> grayDiff = diffImage.Convert<Gray, byte>(); CvInvoke.Threshold(grayDiff, grayDiff, 30, 255, ThresholdType.Binary); // 显示结果 CvInvoke.Imshow("原图1", img1); CvInvoke.Imshow("原图2", img2); CvInvoke.Imshow("差异图", diffImage); CvInvoke.Imshow("阈值化后差异图", grayDiff); CvInvoke.WaitKey(0); CvInvoke.DestroyAllWindows(); } }
运行后,差异图里亮色区域就是两张图不一样的地方,阈值化后能更清晰地定位差异区域。
二、基于物体的百分比差异计算(结合距离算法)
如果要针对图像里的特定物体计算差异,得先把物体从背景中提取出来,再用距离算法衡量特征差异,最后转化为百分比。这里给你两种常用的实现思路:
思路1:基于轮廓的豪斯多夫距离计算
适合简单、对比度高的物体,先提取物体轮廓,再用豪斯多夫距离衡量轮廓的相似性,最后归一化得到百分比差异。
代码示例:
using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using System; using System.Collections.Generic; class ObjectDiffCalculator { static void Main(string[] args) { Image<Bgr, byte> img1 = new Image<Bgr, byte>("object1.jpg"); Image<Bgr, byte> img2 = new Image<Bgr, byte>("object2.jpg"); // 预处理:灰度化+二值化(根据你的图像调整阈值) Image<Gray, byte> gray1 = img1.Convert<Gray, byte>(); Image<Gray, byte> gray2 = img2.Convert<Gray, byte>(); CvInvoke.Threshold(gray1, gray1, 127, 255, ThresholdType.BinaryInv); CvInvoke.Threshold(gray2, gray2, 127, 255, ThresholdType.BinaryInv); // 提取轮廓(只取最外层的物体轮廓) VectorOfVectorOfPoint contours1 = new VectorOfVectorOfPoint(); VectorOfVectorOfPoint contours2 = new VectorOfVectorOfPoint(); CvInvoke.FindContours(gray1, contours1, null, RetrType.External, ChainApproxMethod.ChainApproxSimple); CvInvoke.FindContours(gray2, contours2, null, RetrType.External, ChainApproxMethod.ChainApproxSimple); // 获取每张图里最大的轮廓(假设每张图只有一个主要物体) VectorOfPoint contour1 = GetLargestContour(contours1); VectorOfPoint contour2 = GetLargestContour(contours2); if (contour1 == null || contour2 == null) { Console.WriteLine("未检测到物体轮廓!"); return; } // 计算豪斯多夫距离(值越小,轮廓越相似) double hausdorffDist = CvInvoke.HausdorffDistance(contour1, contour2); // 归一化计算百分比差异:用轮廓外接矩形的对角线长度作为基准 Rectangle rect1 = CvInvoke.BoundingRectangle(contour1); Rectangle rect2 = CvInvoke.BoundingRectangle(contour2); double maxDiagonal = Math.Max( Math.Sqrt(Math.Pow(rect1.Width, 2) + Math.Pow(rect1.Height, 2)), Math.Sqrt(Math.Pow(rect2.Width, 2) + Math.Pow(rect2.Height, 2)) ); double percentageDiff = (hausdorffDist / maxDiagonal) * 100; Console.WriteLine($"物体轮廓的百分比差异:{percentageDiff:F2}%"); } // 辅助函数:找到面积最大的轮廓 static VectorOfPoint GetLargestContour(VectorOfVectorOfPoint contours) { double maxArea = 0; VectorOfPoint largestContour = null; for (int i = 0; i < contours.Size; i++) { double area = CvInvoke.ContourArea(contours[i]); if (area > maxArea) { maxArea = area; largestContour = contours[i]; } } return largestContour; } }
思路2:基于特征点匹配的差异计算
适合复杂、有纹理的物体,用SIFT/ORB等特征检测器提取物体特征,通过匹配率反推差异百分比。
代码示例(用SIFT):
using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Features2D; using Emgu.CV.Structure; using System; using System.Collections.Generic; class FeatureBasedDiffCalculator { static void Main(string[] args) { Image<Bgr, byte> img1 = new Image<Bgr, byte>("object1.jpg"); Image<Bgr, byte> img2 = new Image<Bgr, byte>("object2.jpg"); // 转灰度图 Image<Gray, byte> gray1 = img1.Convert<Gray, byte>(); Image<Gray, byte> gray2 = img2.Convert<Gray, byte>(); // 初始化SIFT检测器 using (var sift = new SIFTDetector()) { // 提取特征点和描述子 VectorOfKeyPoint keyPoints1 = new VectorOfKeyPoint(); VectorOfKeyPoint keyPoints2 = new VectorOfKeyPoint(); Mat descriptors1 = new Mat(); Mat descriptors2 = new Mat(); sift.DetectAndCompute(gray1, null, keyPoints1, descriptors1, false); sift.DetectAndCompute(gray2, null, keyPoints2, descriptors2, false); if (keyPoints1.Size == 0 || keyPoints2.Size == 0) { Console.WriteLine("未检测到足够的特征点!"); return; } // 暴力匹配描述子 BFMatcher matcher = new BFMatcher(DistanceType.L2); VectorOfVectorOfDMatch matches = new VectorOfVectorOfDMatch(); matcher.KnnMatch(descriptors1, descriptors2, matches, 2); // 用Lowe比例测试筛选优质匹配 List<DMatch> goodMatches = new List<DMatch>(); for (int i = 0; i < matches.Size; i++) { if (matches[i][0].Distance < 0.75 * matches[i][1].Distance) { goodMatches.Add(matches[i][0]); } } // 计算百分比差异:(1 - 优质匹配数/最多特征点数) * 100 double matchRatio = (double)goodMatches.Count / Math.Max(keyPoints1.Size, keyPoints2.Size); double percentageDiff = (1 - matchRatio) * 100; Console.WriteLine($"基于SIFT特征的百分比差异:{percentageDiff:F2}%"); } } }
注意事项
- 预处理很关键!如果图像有噪声,先做高斯模糊(
CvInvoke.GaussianBlur)再处理,结果会更准确。 - 如果物体有旋转、缩放,推荐用ORB特征(比SIFT快,且免费),或者归一化Hu矩来计算相似性。
- 百分比差异的计算基准可以根据你的场景调整,比如用物体的面积、周长代替对角线长度,只要逻辑自洽就行。
内容的提问来源于stack exchange,提问作者Vesper Lynd
相关产品推荐
相关产品推荐

