You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 06:45:54