快捷工具处添加比例尺开关
This commit is contained in:
@@ -22,18 +22,44 @@ namespace XP.ImageProcessing.Processors;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模板匹配算子(定位识别)
|
/// 模板匹配算子(定位识别)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 算法原理:
|
||||||
|
/// 模板匹配是一种基于图像块的匹配方法,通过在待搜索图像上滑动模板,
|
||||||
|
/// 计算每个位置与模板的相似度,找到最佳匹配位置。
|
||||||
|
///
|
||||||
|
/// 匹配方法说明:
|
||||||
|
/// 1. CcoeffNormed (归一化相关系数) - 推荐使用,对光照变化有一定的鲁棒性
|
||||||
|
/// 公式: corr = Σ(I(x,y) * T(x,y)) / sqrt(ΣI² * ΣT²)
|
||||||
|
/// 分数范围: -1 ~ 1,值越大越相似
|
||||||
|
///
|
||||||
|
/// 2. SqdiffNormed (归一化平方差) - 值越小越相似
|
||||||
|
/// 公式: diff = Σ(I(x,y) - T(x,y))² / (ΣI² + ΣT²)
|
||||||
|
/// 分数范围: 0 ~ 1,值越小越相似
|
||||||
|
///
|
||||||
|
/// 3. CcorrNormed (归一化相关) - 对模板和图像的亮度变化敏感
|
||||||
|
/// 4. Ccoeff (相关系数) - 未归一化版本
|
||||||
|
/// 5. Ccorr (相关) - 未归一化版本
|
||||||
|
/// 6. Sqdiff (平方差) - 未归一化版本
|
||||||
|
///
|
||||||
|
/// 性能说明:
|
||||||
|
/// - 时间复杂度: O(W * H * w * h),其中W/H为图像尺寸,w/h为模板尺寸
|
||||||
|
/// - 可通过设置SearchRegion限制搜索范围来提升性能
|
||||||
|
/// </remarks>
|
||||||
public class TemplateMatchingProcessor : ImageProcessorBase
|
public class TemplateMatchingProcessor : ImageProcessorBase
|
||||||
{
|
{
|
||||||
private static readonly ILogger _logger = Log.ForContext<TemplateMatchingProcessor>();
|
private static readonly ILogger _logger = Log.ForContext<TemplateMatchingProcessor>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 匹配方法选项列表,供UI下拉选择
|
||||||
|
/// </summary>
|
||||||
private static readonly string[] MatchMethodOptions =
|
private static readonly string[] MatchMethodOptions =
|
||||||
{
|
{
|
||||||
"CcoeffNormed",
|
"CcoeffNormed", // 归一化相关系数(推荐)
|
||||||
"SqdiffNormed",
|
"SqdiffNormed", // 归一化平方差
|
||||||
"CcorrNormed",
|
"CcorrNormed", // 归一化相关
|
||||||
"Ccoeff",
|
"Ccoeff", // 相关系数
|
||||||
"Ccorr",
|
"Ccorr", // 相关
|
||||||
"Sqdiff"
|
"Sqdiff" // 平方差
|
||||||
};
|
};
|
||||||
|
|
||||||
public TemplateMatchingProcessor()
|
public TemplateMatchingProcessor()
|
||||||
@@ -42,8 +68,16 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
Description = LocalizationHelper.GetString("TemplateMatchingProcessor_Description");
|
Description = LocalizationHelper.GetString("TemplateMatchingProcessor_Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化参数定义
|
||||||
|
/// </summary>
|
||||||
protected override void InitializeParameters()
|
protected override void InitializeParameters()
|
||||||
{
|
{
|
||||||
|
// ===== 模板相关参数 =====
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模板图片路径,支持灰度或彩色图片(自动转灰度)
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("TemplatePath", new ProcessorParameter(
|
Parameters.Add("TemplatePath", new ProcessorParameter(
|
||||||
"TemplatePath",
|
"TemplatePath",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_TemplatePath"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_TemplatePath"),
|
||||||
@@ -53,25 +87,36 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
null,
|
null,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_TemplatePath_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_TemplatePath_Desc")));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 匹配算法选择,不同算法对光照和旋转的敏感度不同
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("MatchMethod", new ProcessorParameter(
|
Parameters.Add("MatchMethod", new ProcessorParameter(
|
||||||
"MatchMethod",
|
"MatchMethod",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchMethod"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchMethod"),
|
||||||
typeof(string),
|
typeof(string),
|
||||||
"CcoeffNormed",
|
"CcoeffNormed", // 默认使用归一化相关系数
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchMethod_Desc"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchMethod_Desc"),
|
||||||
MatchMethodOptions));
|
MatchMethodOptions));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 匹配阈值,判断匹配是否成功的分数门限
|
||||||
|
/// - CcoeffNormed/CcorrNormed: 建议 0.75-0.95
|
||||||
|
/// - SqdiffNormed: 建议 0.1-0.3
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("MatchThreshold", new ProcessorParameter(
|
Parameters.Add("MatchThreshold", new ProcessorParameter(
|
||||||
"MatchThreshold",
|
"MatchThreshold",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchThreshold"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchThreshold"),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
0.75,
|
0.75, // 默认阈值
|
||||||
0.0,
|
0.0,
|
||||||
1.0,
|
1.0,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchThreshold_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_MatchThreshold_Desc")));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否在输出图像上绘制匹配矩形框
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("DrawRectangle", new ProcessorParameter(
|
Parameters.Add("DrawRectangle", new ProcessorParameter(
|
||||||
"DrawRectangle",
|
"DrawRectangle",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_DrawMatch"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_DrawMatch"),
|
||||||
@@ -81,6 +126,9 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
null,
|
null,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_DrawMatch_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_DrawMatch_Desc")));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 匹配矩形框的线条粗细
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("RectangleThickness", new ProcessorParameter(
|
Parameters.Add("RectangleThickness", new ProcessorParameter(
|
||||||
"RectangleThickness",
|
"RectangleThickness",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_RectThickness"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_RectThickness"),
|
||||||
@@ -90,6 +138,11 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
8,
|
8,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_RectThickness_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_RectThickness_Desc")));
|
||||||
|
|
||||||
|
// ===== 搜索区域参数(可选,用于限制搜索范围提升性能)=====
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 搜索区域左上角X坐标
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("SearchRegionX", new ProcessorParameter(
|
Parameters.Add("SearchRegionX", new ProcessorParameter(
|
||||||
"SearchRegionX",
|
"SearchRegionX",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionX"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionX"),
|
||||||
@@ -99,6 +152,9 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
null,
|
null,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegion_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegion_Desc")));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 搜索区域左上角Y坐标
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("SearchRegionY", new ProcessorParameter(
|
Parameters.Add("SearchRegionY", new ProcessorParameter(
|
||||||
"SearchRegionY",
|
"SearchRegionY",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionY"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionY"),
|
||||||
@@ -108,6 +164,9 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
null,
|
null,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegion_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegion_Desc")));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 搜索区域宽度,0表示使用整幅图像
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("SearchRegionWidth", new ProcessorParameter(
|
Parameters.Add("SearchRegionWidth", new ProcessorParameter(
|
||||||
"SearchRegionWidth",
|
"SearchRegionWidth",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionWidth"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionWidth"),
|
||||||
@@ -117,6 +176,9 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
null,
|
null,
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegion_Desc")));
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegion_Desc")));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 搜索区域高度,0表示使用整幅图像
|
||||||
|
/// </summary>
|
||||||
Parameters.Add("SearchRegionHeight", new ProcessorParameter(
|
Parameters.Add("SearchRegionHeight", new ProcessorParameter(
|
||||||
"SearchRegionHeight",
|
"SearchRegionHeight",
|
||||||
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionHeight"),
|
LocalizationHelper.GetString("TemplateMatchingProcessor_SearchRegionHeight"),
|
||||||
@@ -129,8 +191,14 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
_logger.Debug("InitializeParameters");
|
_logger.Debug("InitializeParameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行模板匹配处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputImage">输入的灰度图像</param>
|
||||||
|
/// <returns>处理后的图像(可选带匹配矩形框)</returns>
|
||||||
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
||||||
{
|
{
|
||||||
|
// ===== 1. 获取参数 =====
|
||||||
var path = (GetParameter<string>("TemplatePath") ?? string.Empty).Trim();
|
var path = (GetParameter<string>("TemplatePath") ?? string.Empty).Trim();
|
||||||
var methodName = GetParameter<string>("MatchMethod") ?? "CcoeffNormed";
|
var methodName = GetParameter<string>("MatchMethod") ?? "CcoeffNormed";
|
||||||
var threshold = GetParameter<double>("MatchThreshold");
|
var threshold = GetParameter<double>("MatchThreshold");
|
||||||
@@ -141,10 +209,13 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
var searchRw = GetParameter<int>("SearchRegionWidth");
|
var searchRw = GetParameter<int>("SearchRegionWidth");
|
||||||
var searchRh = GetParameter<int>("SearchRegionHeight");
|
var searchRh = GetParameter<int>("SearchRegionHeight");
|
||||||
|
|
||||||
|
// 清除上一次的输出数据
|
||||||
OutputData.Clear();
|
OutputData.Clear();
|
||||||
|
|
||||||
|
// 克隆输入图像用于输出(避免修改原图)
|
||||||
var output = inputImage.Clone();
|
var output = inputImage.Clone();
|
||||||
|
|
||||||
|
// ===== 2. 参数校验:模板文件 =====
|
||||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||||
{
|
{
|
||||||
_logger.Warning("TemplateMatching: invalid or missing template file: {Path}", path);
|
_logger.Warning("TemplateMatching: invalid or missing template file: {Path}", path);
|
||||||
@@ -153,6 +224,7 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 3. 加载模板图像 =====
|
||||||
using var template = LoadTemplate(path);
|
using var template = LoadTemplate(path);
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
@@ -161,17 +233,24 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 4. 确定搜索区域(ROI)=====
|
||||||
|
// 如果未指定搜索区域,则使用整幅图像
|
||||||
var searchRoi = ResolveSearchRoi(inputImage.Width, inputImage.Height, searchRx, searchRy, searchRw, searchRh);
|
var searchRoi = ResolveSearchRoi(inputImage.Width, inputImage.Height, searchRx, searchRy, searchRw, searchRh);
|
||||||
var offsetX = searchRoi.X;
|
var offsetX = searchRoi.X; // 记录ROI偏移,用于还原到全局坐标
|
||||||
var offsetY = searchRoi.Y;
|
var offsetY = searchRoi.Y;
|
||||||
|
|
||||||
|
// ===== 5. 在ROI区域内执行模板匹配 =====
|
||||||
Image<Gray, byte>? roiImage = null;
|
Image<Gray, byte>? roiImage = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 设置输入图像的ROI(感兴趣区域)
|
||||||
inputImage.ROI = searchRoi;
|
inputImage.ROI = searchRoi;
|
||||||
|
// 复制ROI区域到新图像
|
||||||
roiImage = inputImage.Copy();
|
roiImage = inputImage.Copy();
|
||||||
|
// 清除ROI设置
|
||||||
inputImage.ROI = Rectangle.Empty;
|
inputImage.ROI = Rectangle.Empty;
|
||||||
|
|
||||||
|
// ===== 5.1 校验模板尺寸 =====
|
||||||
if (template.Width > roiImage.Width || template.Height > roiImage.Height)
|
if (template.Width > roiImage.Width || template.Height > roiImage.Height)
|
||||||
{
|
{
|
||||||
_logger.Warning("TemplateMatching: template larger than search region ({Tw}x{Th} vs {Iw}x{Ih})",
|
_logger.Warning("TemplateMatching: template larger than search region ({Tw}x{Th} vs {Iw}x{Ih})",
|
||||||
@@ -181,20 +260,34 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 5.2 执行模板匹配 =====
|
||||||
|
// OpenCV MatchTemplate 在ROI上滑动模板,计算每个位置的相似度
|
||||||
|
// 结果是一个 (W-w+1) x (H-h+1) 的分数矩阵
|
||||||
var method = ParseMethod(methodName);
|
var method = ParseMethod(methodName);
|
||||||
using var resultMat = new Mat();
|
using var resultMat = new Mat();
|
||||||
CvInvoke.MatchTemplate(roiImage, template, resultMat, method);
|
CvInvoke.MatchTemplate(roiImage, template, resultMat, method);
|
||||||
|
|
||||||
|
// ===== 5.3 找到最佳匹配位置 =====
|
||||||
|
// MinMaxLoc 在分数矩阵中找到最小值和最大值的位置
|
||||||
double minVal = 0, maxVal = 0;
|
double minVal = 0, maxVal = 0;
|
||||||
Point minLoc = default, maxLoc = default;
|
Point minLoc = default, maxLoc = default;
|
||||||
CvInvoke.MinMaxLoc(resultMat, ref minVal, ref maxVal, ref minLoc, ref maxLoc);
|
CvInvoke.MinMaxLoc(resultMat, ref minVal, ref maxVal, ref minLoc, ref maxLoc);
|
||||||
|
|
||||||
|
// 根据匹配方法选择使用最小值还是最大值
|
||||||
|
// 平方差类方法:值越小越好(使用minLoc)
|
||||||
|
// 相关类方法:值越大越好(使用maxLoc)
|
||||||
var useMin = method == TemplateMatchingType.Sqdiff || method == TemplateMatchingType.SqdiffNormed;
|
var useMin = method == TemplateMatchingType.Sqdiff || method == TemplateMatchingType.SqdiffNormed;
|
||||||
var loc = useMin ? minLoc : maxLoc;
|
var loc = useMin ? minLoc : maxLoc;
|
||||||
var score = useMin ? minVal : maxVal;
|
var score = useMin ? minVal : maxVal;
|
||||||
|
|
||||||
|
// ===== 5.4 判定匹配是否成功 =====
|
||||||
var matched = IsMatchAcceptable(method, minVal, maxVal, threshold);
|
var matched = IsMatchAcceptable(method, minVal, maxVal, threshold);
|
||||||
|
|
||||||
|
// ===== 5.5 转换到全局坐标 =====
|
||||||
|
// 由于是在ROI内匹配的,需要加上ROI的偏移量
|
||||||
var globalLoc = new Point(loc.X + offsetX, loc.Y + offsetY);
|
var globalLoc = new Point(loc.X + offsetX, loc.Y + offsetY);
|
||||||
|
|
||||||
|
// ===== 5.6 输出结果数据 =====
|
||||||
OutputData["Matched"] = matched;
|
OutputData["Matched"] = matched;
|
||||||
OutputData["MatchScore"] = score;
|
OutputData["MatchScore"] = score;
|
||||||
OutputData["MatchX"] = globalLoc.X;
|
OutputData["MatchX"] = globalLoc.X;
|
||||||
@@ -203,6 +296,7 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
OutputData["TemplateHeight"] = template.Height;
|
OutputData["TemplateHeight"] = template.Height;
|
||||||
OutputData["MatchMethod"] = methodName;
|
OutputData["MatchMethod"] = methodName;
|
||||||
|
|
||||||
|
// ===== 5.7 可选:绘制匹配矩形 =====
|
||||||
if (matched && draw)
|
if (matched && draw)
|
||||||
{
|
{
|
||||||
var rect = new Rectangle(globalLoc.X, globalLoc.Y, template.Width, template.Height);
|
var rect = new Rectangle(globalLoc.X, globalLoc.Y, template.Width, template.Height);
|
||||||
@@ -216,34 +310,71 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
// 释放ROI图像资源
|
||||||
roiImage?.Dispose();
|
roiImage?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算实际的搜索ROI区域
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imgW">图像宽度</param>
|
||||||
|
/// <param name="imgH">图像高度</param>
|
||||||
|
/// <param name="rx">用户指定的ROI X坐标</param>
|
||||||
|
/// <param name="ry">用户指定的ROI Y坐标</param>
|
||||||
|
/// <param name="rw">用户指定的ROI宽度,0表示整幅图像</param>
|
||||||
|
/// <param name="rh">用户指定的ROI高度,0表示整幅图像</param>
|
||||||
|
/// <returns>计算后的有效ROI区域</returns>
|
||||||
private static Rectangle ResolveSearchRoi(int imgW, int imgH, int rx, int ry, int rw, int rh)
|
private static Rectangle ResolveSearchRoi(int imgW, int imgH, int rx, int ry, int rw, int rh)
|
||||||
{
|
{
|
||||||
|
// 宽度或高度为0时,使用整幅图像作为搜索区域
|
||||||
if (rw <= 0 || rh <= 0)
|
if (rw <= 0 || rh <= 0)
|
||||||
return new Rectangle(0, 0, imgW, imgH);
|
return new Rectangle(0, 0, imgW, imgH);
|
||||||
|
|
||||||
|
// 限制坐标在图像范围内
|
||||||
rx = Math.Clamp(rx, 0, Math.Max(0, imgW - 1));
|
rx = Math.Clamp(rx, 0, Math.Max(0, imgW - 1));
|
||||||
ry = Math.Clamp(ry, 0, Math.Max(0, imgH - 1));
|
ry = Math.Clamp(ry, 0, Math.Max(0, imgH - 1));
|
||||||
|
|
||||||
|
// 限制宽度和高度不超出图像边界
|
||||||
rw = Math.Clamp(rw, 1, Math.Max(1, imgW - rx));
|
rw = Math.Clamp(rw, 1, Math.Max(1, imgW - rx));
|
||||||
rh = Math.Clamp(rh, 1, Math.Max(1, imgH - ry));
|
rh = Math.Clamp(rh, 1, Math.Max(1, imgH - ry));
|
||||||
|
|
||||||
return new Rectangle(rx, ry, rw, rh);
|
return new Rectangle(rx, ry, rw, rh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断匹配结果是否满足阈值条件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">匹配方法类型</param>
|
||||||
|
/// <param name="minVal">最小相似度分数</param>
|
||||||
|
/// <param name="maxVal">最大相似度分数</param>
|
||||||
|
/// <param name="threshold">用户设定的阈值</param>
|
||||||
|
/// <returns>是否满足匹配条件</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 不同匹配方法的分数含义不同:
|
||||||
|
/// - 平方差类(Sqdiff/SqdiffNormed): 分数越小越相似,需要 minVal <= threshold
|
||||||
|
/// - 相关类(Ccoeff/Ccorr/CcoeffNormed/CcorrNormed): 分数越大越相似,需要 maxVal >= threshold
|
||||||
|
/// </remarks>
|
||||||
private static bool IsMatchAcceptable(TemplateMatchingType method, double minVal, double maxVal, double threshold)
|
private static bool IsMatchAcceptable(TemplateMatchingType method, double minVal, double maxVal, double threshold)
|
||||||
{
|
{
|
||||||
return method switch
|
return method switch
|
||||||
{
|
{
|
||||||
|
// 平方差类:值越小越好
|
||||||
TemplateMatchingType.SqdiffNormed => minVal <= threshold,
|
TemplateMatchingType.SqdiffNormed => minVal <= threshold,
|
||||||
TemplateMatchingType.Sqdiff => minVal <= threshold,
|
TemplateMatchingType.Sqdiff => minVal <= threshold,
|
||||||
|
// 相关类:值越大越好
|
||||||
TemplateMatchingType.CcorrNormed or TemplateMatchingType.CcoeffNormed => maxVal >= threshold,
|
TemplateMatchingType.CcorrNormed or TemplateMatchingType.CcoeffNormed => maxVal >= threshold,
|
||||||
TemplateMatchingType.Ccorr or TemplateMatchingType.Ccoeff => maxVal >= threshold,
|
TemplateMatchingType.Ccorr or TemplateMatchingType.Ccoeff => maxVal >= threshold,
|
||||||
|
// 默认按相关类处理
|
||||||
_ => maxVal >= threshold
|
_ => maxVal >= threshold
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将字符串方法名转换为OpenCV枚举类型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">方法名称字符串</param>
|
||||||
|
/// <returns>对应的TemplateMatchingType枚举值</returns>
|
||||||
private static TemplateMatchingType ParseMethod(string? name)
|
private static TemplateMatchingType ParseMethod(string? name)
|
||||||
{
|
{
|
||||||
return name?.Trim() switch
|
return name?.Trim() switch
|
||||||
@@ -253,23 +384,45 @@ public class TemplateMatchingProcessor : ImageProcessorBase
|
|||||||
"Ccorr" => TemplateMatchingType.Ccorr,
|
"Ccorr" => TemplateMatchingType.Ccorr,
|
||||||
"CcorrNormed" => TemplateMatchingType.CcorrNormed,
|
"CcorrNormed" => TemplateMatchingType.CcorrNormed,
|
||||||
"Ccoeff" => TemplateMatchingType.Ccoeff,
|
"Ccoeff" => TemplateMatchingType.Ccoeff,
|
||||||
|
// 默认返回归一化相关系数(推荐)
|
||||||
_ => TemplateMatchingType.CcoeffNormed
|
_ => TemplateMatchingType.CcoeffNormed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从磁盘加载模板图像并转换为灰度图
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">模板图片文件路径</param>
|
||||||
|
/// <returns>灰度模板图像,加载失败返回null</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 支持的输入格式:
|
||||||
|
/// - 灰度图像:直接使用
|
||||||
|
/// - 彩色图像:自动转换为灰度
|
||||||
|
/// - 支持任意位深度的图像
|
||||||
|
/// </remarks>
|
||||||
private static Image<Gray, byte>? LoadTemplate(string path)
|
private static Image<Gray, byte>? LoadTemplate(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 使用任意格式读取(支持灰度、彩色、16位等)
|
||||||
using var raw = CvInvoke.Imread(path, ImreadModes.AnyDepth | ImreadModes.AnyColor);
|
using var raw = CvInvoke.Imread(path, ImreadModes.AnyDepth | ImreadModes.AnyColor);
|
||||||
if (raw.IsEmpty)
|
if (raw.IsEmpty)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
// 创建灰度图像
|
||||||
var templ = new Image<Gray, byte>(raw.Width, raw.Height);
|
var templ = new Image<Gray, byte>(raw.Width, raw.Height);
|
||||||
|
|
||||||
|
// 根据通道数决定是否需要灰度转换
|
||||||
if (raw.NumberOfChannels == 1)
|
if (raw.NumberOfChannels == 1)
|
||||||
|
{
|
||||||
|
// 已经是灰度图,直接复制
|
||||||
raw.CopyTo(templ.Mat);
|
raw.CopyTo(templ.Mat);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
// 彩色图转灰度 (BGR -> Gray)
|
||||||
CvInvoke.CvtColor(raw, templ, ColorConversion.Bgr2Gray);
|
CvInvoke.CvtColor(raw, templ, ColorConversion.Bgr2Gray);
|
||||||
|
}
|
||||||
|
|
||||||
return templ;
|
return templ;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<behaviors:PointListToPointCollectionConverter x:Key="PointListToPointCollectionConverter" />
|
<behaviors:PointListToPointCollectionConverter x:Key="PointListToPointCollectionConverter" />
|
||||||
<converters:ROITypeToVisibilityConverter x:Key="ROITypeToVisibilityConverter" />
|
<converters:ROITypeToVisibilityConverter x:Key="ROITypeToVisibilityConverter" />
|
||||||
|
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Border BorderBrush="Transparent" BorderThickness="1" ClipToBounds="True">
|
<Border BorderBrush="Transparent" BorderThickness="1" ClipToBounds="True">
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -71,6 +72,95 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
|
<!-- 比例尺:位于图像坐标系内,随 LayoutTransform(缩放)与 RenderTransform(平移)与底图一致 -->
|
||||||
|
<Grid
|
||||||
|
x:Name="ScaleBarOverlay"
|
||||||
|
Canvas.Left="0"
|
||||||
|
Canvas.Top="0"
|
||||||
|
Width="{Binding CanvasWidth, ElementName=root}"
|
||||||
|
Height="{Binding CanvasHeight, ElementName=root}"
|
||||||
|
Panel.ZIndex="10000"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Visibility="{Binding ShowScaleBar, ElementName=root, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<Border
|
||||||
|
Margin="0,0,0,12"
|
||||||
|
Padding="0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Background="Transparent">
|
||||||
|
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
|
||||||
|
<!-- 尺身:底线 + 端部刻度 + 内侧无刻度竖线 -->
|
||||||
|
<Grid
|
||||||
|
x:Name="ScaleBarTrack"
|
||||||
|
Height="18"
|
||||||
|
MinWidth="8"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Width="{Binding ScaleBarLengthPixels, ElementName=root}">
|
||||||
|
<Line
|
||||||
|
X1="0"
|
||||||
|
Y1="16"
|
||||||
|
X2="{Binding ScaleBarLengthPixels, ElementName=root}"
|
||||||
|
Y2="16"
|
||||||
|
Stroke="Red"
|
||||||
|
StrokeThickness="2"
|
||||||
|
SnapsToDevicePixels="True" />
|
||||||
|
<Line
|
||||||
|
X1="0"
|
||||||
|
X2="0"
|
||||||
|
Y1="9"
|
||||||
|
Y2="16"
|
||||||
|
Stroke="Red"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
SnapsToDevicePixels="True" />
|
||||||
|
<Line
|
||||||
|
X1="{Binding ScaleBarLengthPixels, ElementName=root}"
|
||||||
|
X2="{Binding ScaleBarLengthPixels, ElementName=root}"
|
||||||
|
Y1="9"
|
||||||
|
Y2="16"
|
||||||
|
Stroke="Red"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
SnapsToDevicePixels="True" />
|
||||||
|
<ItemsControl ItemsSource="{Binding ScaleBarMinorTickXs, ElementName=root}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas
|
||||||
|
IsItemsHost="True"
|
||||||
|
Width="{Binding ScaleBarLengthPixels, ElementName=root}"
|
||||||
|
Height="18"
|
||||||
|
ClipToBounds="False" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style TargetType="ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding}" />
|
||||||
|
<Setter Property="Canvas.Top" Value="0" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Line
|
||||||
|
X1="0"
|
||||||
|
X2="0"
|
||||||
|
Y1="11"
|
||||||
|
Y2="16"
|
||||||
|
Stroke="Red"
|
||||||
|
StrokeThickness="1"
|
||||||
|
SnapsToDevicePixels="True" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock
|
||||||
|
Margin="6,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="11"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="Red"
|
||||||
|
Text="{Binding ScaleBarCaption, ElementName=root}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
|||||||
private const double ZoomStep = 1.2;
|
private const double ZoomStep = 1.2;
|
||||||
private Adorner? currentAdorner;
|
private Adorner? currentAdorner;
|
||||||
|
|
||||||
|
/// <summary>比例尺内侧竖向刻度线的 X 坐标(像素,相对尺身左端),仅画线不标数字。</summary>
|
||||||
|
public ObservableCollection<double> ScaleBarMinorTickXs { get; } = new();
|
||||||
|
|
||||||
public PolygonRoiCanvas()
|
public PolygonRoiCanvas()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -42,6 +45,8 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefreshScaleBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ROIItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
private void ROIItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
@@ -142,6 +147,8 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
|||||||
// 图像尺寸变化后刷新十字线
|
// 图像尺寸变化后刷新十字线
|
||||||
if (control.ShowCrosshair)
|
if (control.ShowCrosshair)
|
||||||
control.AddCrosshair();
|
control.AddCrosshair();
|
||||||
|
|
||||||
|
control.RefreshScaleBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty ROIItemsProperty =
|
public static readonly DependencyProperty ROIItemsProperty =
|
||||||
@@ -204,7 +211,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
|||||||
|
|
||||||
public static readonly DependencyProperty CanvasWidthProperty =
|
public static readonly DependencyProperty CanvasWidthProperty =
|
||||||
DependencyProperty.Register(nameof(CanvasWidth), typeof(double), typeof(PolygonRoiCanvas),
|
DependencyProperty.Register(nameof(CanvasWidth), typeof(double), typeof(PolygonRoiCanvas),
|
||||||
new PropertyMetadata(800.0));
|
new PropertyMetadata(800.0, OnCanvasOrScaleBarParameterChanged));
|
||||||
|
|
||||||
public double CanvasWidth
|
public double CanvasWidth
|
||||||
{
|
{
|
||||||
@@ -214,7 +221,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
|||||||
|
|
||||||
public static readonly DependencyProperty CanvasHeightProperty =
|
public static readonly DependencyProperty CanvasHeightProperty =
|
||||||
DependencyProperty.Register(nameof(CanvasHeight), typeof(double), typeof(PolygonRoiCanvas),
|
DependencyProperty.Register(nameof(CanvasHeight), typeof(double), typeof(PolygonRoiCanvas),
|
||||||
new PropertyMetadata(600.0));
|
new PropertyMetadata(600.0, OnCanvasOrScaleBarParameterChanged));
|
||||||
|
|
||||||
public double CanvasHeight
|
public double CanvasHeight
|
||||||
{
|
{
|
||||||
@@ -222,6 +229,116 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
|||||||
set => SetValue(CanvasHeightProperty, value);
|
set => SetValue(CanvasHeightProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>是否在图像上显示比例尺(叠在图像坐标系内,随缩放与平移移动)。</summary>
|
||||||
|
public static readonly DependencyProperty ShowScaleBarProperty =
|
||||||
|
DependencyProperty.Register(nameof(ShowScaleBar), typeof(bool), typeof(PolygonRoiCanvas),
|
||||||
|
new PropertyMetadata(false, OnCanvasOrScaleBarParameterChanged));
|
||||||
|
|
||||||
|
public bool ShowScaleBar
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(ShowScaleBarProperty);
|
||||||
|
set => SetValue(ShowScaleBarProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>单像素对应的物理长度(mm),用于比例尺刻度换算。</summary>
|
||||||
|
public static readonly DependencyProperty ScaleBarMmPerPixelProperty =
|
||||||
|
DependencyProperty.Register(nameof(ScaleBarMmPerPixel), typeof(double), typeof(PolygonRoiCanvas),
|
||||||
|
new PropertyMetadata(0.139, OnCanvasOrScaleBarParameterChanged));
|
||||||
|
|
||||||
|
public double ScaleBarMmPerPixel
|
||||||
|
{
|
||||||
|
get => (double)GetValue(ScaleBarMmPerPixelProperty);
|
||||||
|
set => SetValue(ScaleBarMmPerPixelProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ScaleBarLengthPixelsProperty =
|
||||||
|
DependencyProperty.Register(nameof(ScaleBarLengthPixels), typeof(double), typeof(PolygonRoiCanvas),
|
||||||
|
new PropertyMetadata(100.0));
|
||||||
|
|
||||||
|
public double ScaleBarLengthPixels
|
||||||
|
{
|
||||||
|
get => (double)GetValue(ScaleBarLengthPixelsProperty);
|
||||||
|
private set => SetValue(ScaleBarLengthPixelsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ScaleBarCaptionProperty =
|
||||||
|
DependencyProperty.Register(nameof(ScaleBarCaption), typeof(string), typeof(PolygonRoiCanvas),
|
||||||
|
new PropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
public string ScaleBarCaption
|
||||||
|
{
|
||||||
|
get => (string)GetValue(ScaleBarCaptionProperty);
|
||||||
|
private set => SetValue(ScaleBarCaptionProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnCanvasOrScaleBarParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
((PolygonRoiCanvas)d).RefreshScaleBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>按图像宽度选取 1–2–5 标度,使比例尺约占画布宽度的约 22%。</summary>
|
||||||
|
private void RefreshScaleBar()
|
||||||
|
{
|
||||||
|
const double targetFrac = 0.22;
|
||||||
|
const double maxFrac = 0.88;
|
||||||
|
const double minPx = 24.0;
|
||||||
|
|
||||||
|
if (!ShowScaleBar || CanvasWidth < 16 || ScaleBarMmPerPixel <= 0 || double.IsNaN(ScaleBarMmPerPixel))
|
||||||
|
{
|
||||||
|
ScaleBarLengthPixels = minPx;
|
||||||
|
ScaleBarCaption = string.Empty;
|
||||||
|
ScaleBarMinorTickXs.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double maxPx = Math.Max(minPx, CanvasWidth * maxFrac);
|
||||||
|
double idealPx = Math.Min(CanvasWidth * targetFrac, maxPx);
|
||||||
|
double idealMm = idealPx * ScaleBarMmPerPixel;
|
||||||
|
double niceMm = RoundToNiceLengthMm(idealMm);
|
||||||
|
double barPx = niceMm / ScaleBarMmPerPixel;
|
||||||
|
|
||||||
|
while (barPx > maxPx && niceMm > ScaleBarMmPerPixel * minPx * 1.5)
|
||||||
|
{
|
||||||
|
niceMm /= 2.0;
|
||||||
|
barPx = niceMm / ScaleBarMmPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (barPx < minPx && niceMm < idealMm * 200)
|
||||||
|
{
|
||||||
|
niceMm *= 2.0;
|
||||||
|
barPx = niceMm / ScaleBarMmPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (barPx < minPx)
|
||||||
|
barPx = minPx;
|
||||||
|
|
||||||
|
ScaleBarLengthPixels = barPx;
|
||||||
|
ScaleBarCaption = FormatScaleBarCaptionMm(barPx * ScaleBarMmPerPixel);
|
||||||
|
|
||||||
|
ScaleBarMinorTickXs.Clear();
|
||||||
|
int divisions = barPx >= 120 ? 10 : barPx >= 60 ? 6 : 4;
|
||||||
|
for (int i = 1; i < divisions; i++)
|
||||||
|
ScaleBarMinorTickXs.Add(barPx * i / divisions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double RoundToNiceLengthMm(double mm)
|
||||||
|
{
|
||||||
|
if (mm <= 0 || double.IsNaN(mm) || double.IsInfinity(mm))
|
||||||
|
return 0.1;
|
||||||
|
var magnitude = Math.Pow(10.0, Math.Floor(Math.Log10(mm)));
|
||||||
|
var normalized = mm / magnitude;
|
||||||
|
var nice = normalized < 1.5 ? 1.0 : normalized < 3.5 ? 2.0 : normalized < 7.5 ? 5.0 : 10.0;
|
||||||
|
return nice * magnitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatScaleBarCaptionMm(double mm)
|
||||||
|
{
|
||||||
|
if (mm >= 100) return $"{mm:F0} mm";
|
||||||
|
if (mm >= 10) return $"{mm:F1} mm";
|
||||||
|
if (mm >= 1) return $"{mm:F2} mm";
|
||||||
|
return $"{mm:F3} mm";
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty SelectedROIProperty =
|
public static readonly DependencyProperty SelectedROIProperty =
|
||||||
DependencyProperty.Register(nameof(SelectedROI), typeof(ROIShape), typeof(PolygonRoiCanvas),
|
DependencyProperty.Register(nameof(SelectedROI), typeof(ROIShape), typeof(PolygonRoiCanvas),
|
||||||
new PropertyMetadata(null, OnSelectedROIChanged));
|
new PropertyMetadata(null, OnSelectedROIChanged));
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 717 B |
@@ -122,6 +122,13 @@ namespace XplorePlane.ViewModels
|
|||||||
public DelegateCommand VoidDetectionCommand { get; }
|
public DelegateCommand VoidDetectionCommand { get; }
|
||||||
public DelegateCommand BubbleMeasureCommand { get; }
|
public DelegateCommand BubbleMeasureCommand { get; }
|
||||||
|
|
||||||
|
private bool _isScaleBarVisible;
|
||||||
|
public bool IsScaleBarVisible
|
||||||
|
{
|
||||||
|
get => _isScaleBarVisible;
|
||||||
|
set => SetProperty(ref _isScaleBarVisible, value);
|
||||||
|
}
|
||||||
|
|
||||||
// 辅助线命令
|
// 辅助线命令
|
||||||
public DelegateCommand ToggleCrosshairCommand { get; }
|
public DelegateCommand ToggleCrosshairCommand { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="XplorePlane.Views.ImagePanelView"
|
x:Class="XplorePlane.Views.ImagePanelView"
|
||||||
|
x:Name="Root"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
<TextBlock Margin="4,2" HorizontalAlignment="Left" VerticalAlignment="Center"
|
<TextBlock Margin="4,2" HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||||
FontWeight="SemiBold" Foreground="#333333" Text="图像处理" />
|
FontWeight="SemiBold" Foreground="#333333" Text="图像处理" />
|
||||||
</Border>
|
</Border>
|
||||||
<ContentControl Grid.Row="1" Content="{Binding ImagePanelContent}" />
|
<Grid Grid.Row="1">
|
||||||
|
<ContentControl Content="{Binding ImagePanelContent}" />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -165,6 +165,13 @@
|
|||||||
Size="Medium"
|
Size="Medium"
|
||||||
SmallImage="/Assets/Icons/crosshair.png"
|
SmallImage="/Assets/Icons/crosshair.png"
|
||||||
Text="辅助线" />
|
Text="辅助线" />
|
||||||
|
<telerik:RadRibbonToggleButton
|
||||||
|
telerik:ScreenTip.Description="显示/隐藏图像比例尺"
|
||||||
|
telerik:ScreenTip.Title="比例尺"
|
||||||
|
IsChecked="{Binding IsScaleBarVisible, Mode=TwoWay}"
|
||||||
|
Size="Medium"
|
||||||
|
SmallImage="/Assets/Icons/Scale.png"
|
||||||
|
Text="比例尺" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="白底检测"
|
telerik:ScreenTip.Title="白底检测"
|
||||||
Command="{Binding WhiteBackgroundDetectionCommand}"
|
Command="{Binding WhiteBackgroundDetectionCommand}"
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
d:DesignHeight="400"
|
d:DesignHeight="400"
|
||||||
d:DesignWidth="600"
|
d:DesignWidth="600"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources />
|
|
||||||
<Grid Background="#FFFFFF">
|
<Grid Background="#FFFFFF">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -32,8 +31,9 @@
|
|||||||
<roi:PolygonRoiCanvas
|
<roi:PolygonRoiCanvas
|
||||||
x:Name="RoiCanvas"
|
x:Name="RoiCanvas"
|
||||||
Background="White"
|
Background="White"
|
||||||
ImageSource="{Binding ImageSource}">
|
ImageSource="{Binding ImageSource}"
|
||||||
</roi:PolygonRoiCanvas>
|
ScaleBarMmPerPixel="0.139"
|
||||||
|
ShowScaleBar="{Binding DataContext.IsScaleBarVisible, RelativeSource={RelativeSource AncestorType=Window}}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user