C# 使用OpenCvSharp實(shí)現(xiàn)模板差異檢測(cè)
本文將詳細(xì)介紹如何使用OpenCvSharp來(lái)實(shí)現(xiàn)模板圖像與待檢測(cè)圖像之間的差異檢測(cè)。這種技術(shù)常用于產(chǎn)品質(zhì)量檢測(cè)、PCB板檢測(cè)等場(chǎng)景。上面的示意圖展示了一個(gè)簡(jiǎn)單的例子,左邊是完整的模板圖像,右邊是缺少部分元件的待檢測(cè)圖像。
環(huán)境準(zhǔn)備
// 需要安裝的NuGet包
// Install-Package OpenCvSharp4
// Install-Package OpenCvSharp4.runtime.win
using OpenCvSharp;
using System;
using System.Collections.Generic;
完整代碼實(shí)現(xiàn)
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenCv082902
{
public class TemplateDifferenceDetector
{
/// <summary>
/// 檢測(cè)結(jié)果類
/// </summary>
public class DetectionResult
{
public Point Location { get; set; }
public double Difference { get; set; }
public Rect Region { get; set; }
}
/// <summary>
/// PCB圖像差異檢測(cè)的配置參數(shù)
/// </summary>
public class DetectionConfig
{
public double Threshold { get; set; } = 0.5; // 差異閾值
public double MinArea { get; set; } = 10; // 最小檢測(cè)面積
public int BlurSize { get; set; } = 3; // 高斯模糊核大小
public int MorphSize { get; set; } = 3; // 形態(tài)學(xué)操作核大小
public bool EnableHistEqualization { get; set; } = true; // 是否啟用直方圖均衡化
public bool EnableNoiseReduction { get; set; } = true; // 是否啟用降噪
}
/// <summary>
/// 執(zhí)行差異檢測(cè)
/// </summary>
/// <param name="templatePath">模板圖像路徑</param>
/// <param name="targetPath">待檢測(cè)圖像路徑</param>
/// <param name="config">檢測(cè)配置參數(shù)</param>
/// <returns>檢測(cè)結(jié)果列表</returns>
public static List<DetectionResult> DetectDifferences(
string templatePath,
string targetPath,
DetectionConfig config = null)
{
config ??= new DetectionConfig();
// 讀取圖像
using var template = Cv2.ImRead(templatePath);
using var target = Cv2.ImRead(targetPath);
// 確保圖像成功加載
if (template.Empty() || target.Empty())
{
throw new Exception("無(wú)法加載圖像");
}
// 預(yù)處理圖像
using var processedTemplate = PreprocessImage(template, config);
using var processedTarget = PreprocessImage(target, config);
// 計(jì)算差異圖
using var diff = new Mat();
Cv2.Absdiff(processedTemplate, processedTarget, diff);
// 應(yīng)用閾值處理
using var thresholdMat = new Mat();
Cv2.Threshold(diff, thresholdMat, config.Threshold * 255, 255, ThresholdTypes.Binary);
// 應(yīng)用形態(tài)學(xué)操作來(lái)減少噪聲
if (config.EnableNoiseReduction)
{
var kernel = Cv2.GetStructuringElement(
MorphShapes.Rect,
new Size(config.MorphSize, config.MorphSize)
);
Cv2.MorphologyEx(
thresholdMat,
thresholdMat,
MorphTypes.Open,
kernel
);
}
// 尋找輪廓
Cv2.FindContours(
thresholdMat,
out Point[][] contours,
out HierarchyIndex[] hierarchy,
RetrievalModes.External,
ContourApproximationModes.ApproxSimple
);
// 分析差異區(qū)域
var results = new List<DetectionResult>();
foreach (var contour in contours)
{
// 計(jì)算輪廓面積,過(guò)濾掉太小的區(qū)域
double area = Cv2.ContourArea(contour);
if (area < config.MinArea)
continue;
// 獲取邊界矩形
var boundingRect = Cv2.BoundingRect(contour);
// 計(jì)算該區(qū)域的平均差異值
using var roi = new Mat(diff, boundingRect);
Scalar meanDiff = Cv2.Mean(roi);
results.Add(new DetectionResult
{
Location = new Point(boundingRect.X, boundingRect.Y),
Difference = meanDiff.Val0 / 255.0, // 歸一化差異值
Region = boundingRect
});
}
return results;
}
/// <summary>
/// 圖像預(yù)處理
/// </summary>
private static Mat PreprocessImage(Mat input, DetectionConfig config)
{
// 轉(zhuǎn)換為灰度圖
var processed = input.CvtColor(ColorConversionCodes.BGR2GRAY);
// 應(yīng)用高斯模糊減少噪聲
if (config.EnableNoiseReduction)
{
Cv2.GaussianBlur(
processed,
processed,
new Size(config.BlurSize, config.BlurSize),
0
);
}
// 直方圖均衡化增強(qiáng)對(duì)比度
if (config.EnableHistEqualization)
{
Cv2.EqualizeHist(processed, processed);
}
return processed;
}
/// <summary>
/// 可視化檢測(cè)結(jié)果
/// </summary>
public static void VisualizeResults(
string templatePath,
string targetPath,
string outputPath,
List<DetectionResult> results)
{
using var target = Cv2.ImRead(targetPath);
using var output = target.Clone();
// 在圖像上標(biāo)注差異區(qū)域
foreach (var result in results)
{
// 繪制矩形框
Cv2.Rectangle(
output,
result.Region,
new Scalar(0, 0, 255), // 紅色
2
);
// 添加差異值標(biāo)注
string text = $"Diff: {result.Difference:F2}";
Cv2.PutText(
output,
text,
new Point(result.Location.X, result.Location.Y - 5),
HersheyFonts.HersheySimplex,
0.5,
new Scalar(0, 0, 255),
1
);
}
// 創(chuàng)建對(duì)比圖
using var comparison = new Mat();
using var targetResized = target.Resize(new Size(target.Width / 2, target.Height));
using var outputResized = output.Resize(new Size(output.Width / 2, output.Height));
Cv2.HConcat(new[] { targetResized, outputResized }, comparison);
// 添加標(biāo)題
Cv2.PutText(
comparison,
"Original",
new Point(target.Width / 4 - 40, 30),
HersheyFonts.HersheySimplex,
1,
new Scalar(0, 255, 0),
2
);
Cv2.PutText(
comparison,
"Detected Differences",
new Point(target.Width * 3 / 4 - 100, 30),
HersheyFonts.HersheySimplex,
1,
new Scalar(0, 255, 0),
2
);
// 保存結(jié)果圖像
comparison.SaveImage(outputPath);
}
/// <summary>
/// 使用示例
/// </summary>
public static void Example()
{
var config = new DetectionConfig
{
Threshold = 0.3, // 降低閾值使檢測(cè)更敏感
MinArea = 5, // 降低最小面積閾值
BlurSize = 3, // 適當(dāng)?shù)哪:舜笮?
MorphSize = 2, // 較小的形態(tài)學(xué)操作核心
EnableHistEqualization = true,
EnableNoiseReduction = true
};
var results = DetectDifferences(
"template.jpg",
"target.jpg",
config
);
VisualizeResults(
"template.jpg",
"target.jpg",
"output.jpg",
results
);
Console.WriteLine($"Found {results.Count} differences");
foreach (var result in results)
{
Console.WriteLine($"Difference at ({result.Location.X}, {result.Location.Y}) " +
$"with difference value: {result.Difference:F2}");
}
}
}
}
使用示例
using OpenCv082902;
using OpenCvSharp;
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
// 使用自定義配置
var config = new TemplateDifferenceDetector.DetectionConfig
{
Threshold = 0.3,
MinArea = 5,
BlurSize = 3,
MorphSize = 2,
EnableHistEqualization = true,
EnableNoiseReduction = true
};
var results = TemplateDifferenceDetector.DetectDifferences("pcb_layout.png", "pcb_layout1.jpg", config);
// 可視化結(jié)果
TemplateDifferenceDetector.VisualizeResults("pcb_layout.png", "pcb_layout1.jpg", "output.jpg", results);
Console.ReadKey();
}
}
圖片
檢測(cè)失敗的可能原因:
- 圖像對(duì)齊問(wèn)題 - 兩張圖像可能有微小的偏移或旋轉(zhuǎn)
- 閾值設(shè)置過(guò)高 - 導(dǎo)致小的差異被忽略
- 最小面積設(shè)置過(guò)大 - 導(dǎo)致小的差異區(qū)域被過(guò)濾
- 圖像質(zhì)量問(wèn)題 - 如噪聲、模糊等影響檢測(cè)效果
總結(jié)
本文詳細(xì)介紹了使用OpenCvSharp實(shí)現(xiàn)模板差異檢測(cè)的完整方案。通過(guò)合適的圖像處理和計(jì)算機(jī)視覺(jué)技術(shù),可以有效地檢測(cè)出兩張圖像之間的差異。代碼實(shí)現(xiàn)了基本功能,并提供了良好的擴(kuò)展性。在實(shí)際應(yīng)用中,可以根據(jù)具體需求調(diào)整參數(shù)和添加其他功能。