基于OpenCvSharp的截图对比误报问题技术求助
企业截图对比方案的误报问题排查求助
我正在开发一套企业截图对比优化方案,目前因像素差异等问题导致大量对比失败。基于OpenCvSharp实现的方案90%可用,但部分图像会出现随机误报——在未变更区域绘制红色轮廓框,且该问题与图像类型无关。详情可查看以下图像(忽略边角遮挡框):
- 基准图像:

- 差异图像(对比执行后):图表数据周围存在红色误报框

经过近两周调试仍未解决,现提供核心代码,寻求误报问题的解决思路。
核心对比方法
参数说明:
baselinePath:基准图像路径currentImageScreenshot:待对比的当前截图testName:用于调试和日志的测试名称imageConfig:包含阈值的配置对象
public static void CompareImagesForDifferences(string baselineImagePath, Screenshot currentImageScreenshot, string testName, ImageComparisonConfig imageConfig) { string currentImagePath = SaveCurrentImage(currentImageScreenshot, testName, imageConfig); Mat baselineImage = LoadImage(baselineImagePath); Mat currentImage = LoadImage(currentImagePath); ResizeImage(baselineImage, currentImage); Mat baselineGray = ConvertToGrayscale(baselineImage); Mat currentGray = ConvertToGrayscale(currentImage); double ssimScore = ComputeSSIM(baselineGray, currentGray, out Mat ssimMap); if (ssimScore >= double.Parse(imageConfig.GetSSIMThresholdSetting())) { // 图像一致 Logger.Info("Images are similar. No significant differences detected."); return; } if (isSignificantChangesBetweenImages(baselineImage, currentImage, ssimMap, imageConfig, out Mat filledImage)) { string diffImagePath = $@"{imageConfig.GetFailuresPath()}\{testName}_Diff.png"; SaveDiffImage(filledImage, testName, imageConfig, diffImagePath, baselineImagePath); } }
差异计算核心代码
private static bool isSignificantChangesBetweenImages(Mat baselineImage, Mat currentImage, Mat ssimMap, ImageComparisonConfig imageConfig, out Mat filledImage) { filledImage = currentImage.Clone(); Mat diff = new Mat(); ssimMap.ConvertTo(diff, MatType.CV_8UC1, 255); Mat thresh = new Mat(); Cv2.Threshold(diff, thresh, 0, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu); Point[][] contourDifferencePoints; HierarchyIndex[] hierarchyIndex; Cv2.FindContours(thresh, out contourDifferencePoints, out hierarchyIndex, RetrievalModes.List, ContourApproximationModes.ApproxSimple); return DrawSignificantChanges(baselineImage, contourDifferencePoints, imageConfig, filledImage); }
轮廓绘制代码
当前imageConfig.GetPixelToleranceSetting()设为10:
private static bool DrawSignificantChanges(Mat baselineImage, Point[][] contours, ImageComparisonConfig imageConfig, Mat filledImage, double minAreaRatio = 0.0001, double maxAreaRatio = 0.1) { bool hasSignificantChanges = false; double totalImageArea = baselineImage.Width * baselineImage.Height; double minArea = totalImageArea * minAreaRatio; double maxArea = totalImageArea * maxAreaRatio; foreach (var contour in contours) { double area = Cv2.ContourArea(contour); if (area < minArea || area > maxArea) continue; Rect boundingRect = Cv2.BoundingRect(contour); // 忽略图像边框附近的变更 int borderThreshold = 5; if (boundingRect.X <= borderThreshold || boundingRect.Y <= borderThreshold || boundingRect.X + boundingRect.Width >= baselineImage.Width - borderThreshold || boundingRect.Y + boundingRect.Height >= baselineImage.Height - borderThreshold) { continue; } // 检查差异是否足够显著 using (Mat roi = new Mat(baselineImage, boundingRect)) { Scalar mean = Cv2.Mean(roi); if (mean.Val0 < int.Parse(imageConfig.GetPixelToleranceSetting())) // 根据需要调整阈值 { continue; } } // 在差异区域绘制红色矩形框 Cv2.Rectangle(filledImage, boundingRect, new Scalar(0, 0, 255), 2); hasSignificantChanges = true; } return hasSignificantChanges; }
SSIM计算方法(从Python转换)
public static double StructuralSimilarityIndex(Mat img1, Mat img2, out Mat diff) { const double K1 = 0.01; const double K2 = 0.03; const double L = 255; // SSIM计算常量 double c1 = Math.Pow(K1 * L, 2); double c2 = Math.Pow(K2 * L, 2); // 将图像转换为浮点型 using var f1 = new Mat(); using var f2 = new Mat(); img1.ConvertTo(f1, MatType.CV_32F); img2.ConvertTo(f2, MatType.CV_32F); // 计算均值 using var mu1 = new Mat(); using var mu2 = new Mat(); Cv2.GaussianBlur(f1, mu1, new Size(11, 11), 1.5); Cv2.GaussianBlur(f2, mu2, new Size(11, 11), 1.5); // 计算平方 using var mu1Sq = mu1.Mul(mu1); using var mu2Sq = mu2.Mul(mu2); using var mu1Mu2 = mu1.Mul(mu2); // 计算方差和协方差 using var temp1 = new Mat(); using var temp2 = new Mat(); using var sigma1Sq = new Mat(); using var sigma2Sq = new Mat(); using var sigma12 = new Mat(); Cv2.GaussianBlur(f1.Mul(f1), temp1, new Size(11, 11), 1.5); Cv2.GaussianBlur(f2.Mul(f2), temp2, new Size(11, 11), 1.5); Cv2.Subtract(temp1, mu1Sq, sigma1Sq); Cv2.Subtract(temp2, mu2Sq, sigma2Sq); Cv2.GaussianBlur(f1.Mul(f2), sigma12, new Size(11, 11), 1.5); Cv2.Subtract(sigma12, mu1Mu2, sigma12); // 计算SSIM using var numerator1 = new Mat(); using var numerator2 = new Mat(); using var denominator1 = new Mat(); using var denominator2 = new Mat(); Cv2.Multiply(mu1Mu2, 2, numerator1); Cv2.Add(numerator1, Scalar.All(c1), numerator1); Cv2.Multiply(sigma12, 2, numerator2); Cv2.Add(numerator2, Scalar.All(c2), numerator2); Cv2.Add(mu1Sq, mu2Sq, denominator1); Cv2.Add(denominator1, Scalar.All(c1), denominator1); Cv2.Add(sigma1Sq, sigma2Sq, denominator2); Cv2.Add(denominator2, Scalar.All(c2), denominator2); using var ssimMap = new Mat(); using var temp = new Mat(); Cv2.Multiply(numerator1, numerator2, temp); Cv2.Multiply(denominator1, denominator2, ssimMap); Cv2.Divide(temp, ssimMap, ssimMap); // 计算平均SSIM var mssim = Cv2.Mean(ssimMap); // 计算用于可视化的差异图 diff = new Mat(); Cv2.Absdiff(img1, img2, diff); Cv2.Normalize(diff, diff, 0, 255, NormTypes.MinMax); diff.ConvertTo(diff, MatType.CV_8UC1); return mssim.Val0; }
内容的提问来源于stack exchange,提问作者DeltaRage123
相关产品推荐
相关产品推荐

