添加 MX-PD-盘古 项目文件

将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
Shi.Ji
2026-05-18 11:43:09 +08:00
parent 03632a379d
commit e31d3560bb
739 changed files with 99783 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
using Maxwell.SemiFramework.DefaultConfig.Vision;
using MwFramework.Device.Model;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Vision.Common
{
public static class CameraImageTypeConverter
{
public static SemiconductorVisionAlgorithm.SemiParams.Camera ToSemiCamera(this CameraData image)
{
Camera camera = new Camera();
if (image != null)
{
camera.Ptr = ImageHelper.GetImageHandle(image);
camera.Height = (int)image.Height;
camera.Width = (int)image.Width;
}
return camera;
}
}
}

View File

@@ -0,0 +1,23 @@
namespace MainShell.Vision
{
/// <summary>
/// 模板类视觉算法参数基类
/// </summary>
public abstract class TemplateVisionAlgorithmParameters : VisionAlgorithmParameters
{
protected TemplateVisionAlgorithmParameters()
{
MinScore = 0.8d;
}
/// <summary>
/// 模板路径
/// </summary>
public string TemplatePath { get; set; }
/// <summary>
/// 最小匹配分数
/// </summary>
public double MinScore { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace MainShell.Vision
{
/// <summary>
/// 视觉算法参数基类
/// </summary>
public abstract class VisionAlgorithmParameters
{
}
}

View File

@@ -0,0 +1,234 @@
using MainShell.Common;
using System;
namespace MainShell.Vision
{
/// <summary>
/// <20>Ӿ<EFBFBD>ʧ<EFBFBD>ܷ<EFBFBD><DCB7>
/// </summary>
public enum VisionFailureCategory
{
None = 0,
Validation = 1,
Cancelled = 2,
Capture = 3,
Algorithm = 4,
Driver = 5,
}
/// <summary>
/// <20>Ӿ<EFBFBD><D3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public enum VisionErrorCode
{
None = 0,
RequestInvalid = 1000,
TimeoutInvalid = 1001,
TemplatePathEmpty = 1002,
RoiNameEmpty = 1003,
MinScoreInvalid = 1004,
OperationCancelled = 1100,
CameraNotFound = 1200,
CameraNotOpen = 1201,
CameraNotGrabbing = 1202,
CaptureTimeout = 1203,
NoFrame = 1204,
SoftTriggerFailed = 1205,
DriverError = 1206,
ImageNull = 1207,
TemplateAlgorithmNotImplemented = 1300,
TemplateMatchFailed = 1301,
TemplateResultInvalid = 1302,
CommonAlgorithmNotSupported = 1400,
CommonAlgorithmExecutionFailed = 1401,
ChipMapSortInputInvalid = 1402,
}
/// <summary>
/// Vision <20><><EFBFBD><EFBFBD><EFBFBD>Ŷ<EFBFBD><C5B6>
/// </summary>
public static class VisionAlarmIds
{
public const int CameraNotFound = 13000;
public const int CameraNotOpen = 13001;
public const int CameraNotGrabbing = 13002;
public const int CaptureTimeout = 13003;
public const int NoFrame = 13004;
public const int SoftTriggerFailed = 13005;
public const int DriverError = 13006;
public const int ImageNull = 13007;
public const int RequestInvalid = 13008;
public const int TemplatePathEmpty = 13100;
public const int TemplateRoiInvalid = 13103;
public const int TemplateMinScoreInvalid = 13104;
public const int TemplateAlgorithmNotImplemented = 13105;
public const int TemplateMatchFailed = 13106;
public const int TemplateResultInvalid = 13107;
public const int CommonAlgorithmNotSupported = 13200;
public const int CommonAlgorithmExecutionFailed = 13201;
public const int ChipMapSortInputInvalid = 13202;
}
/// <summary>
/// Vision <20><><EFBFBD><EFBFBD>ӳ<EFBFBD><D3B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public static class VisionResultMapper
{
public static VisionProcessResult CreateCaptureFailure(ImageCaptureResult captureResult)
{
if (captureResult == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.NoFrame,
VisionAlarmIds.NoFrame,
MessageKey.VisionNoFrameReturned,
"Vision capture result is null.");
}
switch (captureResult.Status)
{
case ImageCaptureStatus.Cancelled:
return VisionProcessResult.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
captureResult.Message,
captureResult.Exception);
case ImageCaptureStatus.CameraNotFound:
return VisionProcessResult.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.CameraNotFound,
VisionAlarmIds.CameraNotFound,
MessageKey.VisionCameraNotFound,
captureResult.Message,
captureResult.Exception);
case ImageCaptureStatus.CameraNotOpen:
return VisionProcessResult.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.CameraNotOpen,
VisionAlarmIds.CameraNotOpen,
MessageKey.VisionCameraNotOpen,
captureResult.Message,
captureResult.Exception);
case ImageCaptureStatus.CameraNotGrabbing:
return VisionProcessResult.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.CameraNotGrabbing,
VisionAlarmIds.CameraNotGrabbing,
MessageKey.VisionCameraNotGrabbing,
captureResult.Message,
captureResult.Exception);
case ImageCaptureStatus.Timeout:
return VisionProcessResult.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.CaptureTimeout,
VisionAlarmIds.CaptureTimeout,
MessageKey.VisionCaptureTimeout,
captureResult.Message,
captureResult.Exception);
case ImageCaptureStatus.NoFrame:
return VisionProcessResult.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.NoFrame,
VisionAlarmIds.NoFrame,
MessageKey.VisionNoFrameReturned,
captureResult.Message,
captureResult.Exception);
case ImageCaptureStatus.SoftTriggerFailed:
return VisionProcessResult.Failure(
VisionFailureCategory.Driver,
VisionErrorCode.SoftTriggerFailed,
VisionAlarmIds.SoftTriggerFailed,
MessageKey.VisionSoftTriggerFailed,
captureResult.Message,
captureResult.Exception);
default:
return VisionProcessResult.Failure(
VisionFailureCategory.Driver,
VisionErrorCode.DriverError,
VisionAlarmIds.DriverError,
MessageKey.VisionDriverError,
captureResult.Message,
captureResult.Exception);
}
}
public static int? GetAlarmId(VisionErrorCode errorCode)
{
switch (errorCode)
{
case VisionErrorCode.RequestInvalid:
case VisionErrorCode.TimeoutInvalid:
return VisionAlarmIds.RequestInvalid;
case VisionErrorCode.TemplatePathEmpty:
return VisionAlarmIds.TemplatePathEmpty;
case VisionErrorCode.RoiNameEmpty:
return VisionAlarmIds.TemplateRoiInvalid;
case VisionErrorCode.MinScoreInvalid:
return VisionAlarmIds.TemplateMinScoreInvalid;
case VisionErrorCode.CameraNotFound:
return VisionAlarmIds.CameraNotFound;
case VisionErrorCode.CameraNotOpen:
return VisionAlarmIds.CameraNotOpen;
case VisionErrorCode.CameraNotGrabbing:
return VisionAlarmIds.CameraNotGrabbing;
case VisionErrorCode.CaptureTimeout:
return VisionAlarmIds.CaptureTimeout;
case VisionErrorCode.NoFrame:
return VisionAlarmIds.NoFrame;
case VisionErrorCode.SoftTriggerFailed:
return VisionAlarmIds.SoftTriggerFailed;
case VisionErrorCode.DriverError:
return VisionAlarmIds.DriverError;
case VisionErrorCode.ImageNull:
return VisionAlarmIds.ImageNull;
case VisionErrorCode.TemplateAlgorithmNotImplemented:
return VisionAlarmIds.TemplateAlgorithmNotImplemented;
case VisionErrorCode.TemplateMatchFailed:
return VisionAlarmIds.TemplateMatchFailed;
case VisionErrorCode.TemplateResultInvalid:
return VisionAlarmIds.TemplateResultInvalid;
case VisionErrorCode.CommonAlgorithmNotSupported:
return VisionAlarmIds.CommonAlgorithmNotSupported;
case VisionErrorCode.CommonAlgorithmExecutionFailed:
return VisionAlarmIds.CommonAlgorithmExecutionFailed;
case VisionErrorCode.ChipMapSortInputInvalid:
return VisionAlarmIds.ChipMapSortInputInvalid;
default:
return null;
}
}
}
}

View File

@@ -0,0 +1,45 @@
namespace MainShell.Vision
{
/// <summary>
/// ƥ<><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class VisionMatchResult
{
public VisionMatchResult()
{
}
public VisionMatchResult(double offsetX, double offsetY, double angle = 0.0, double score = 1.0)
{
OffsetX = offsetX;
OffsetY = offsetY;
Angle = angle;
Score = score;
}
/// <summary>
/// X ƫ<><C6AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public double OffsetX { get; set; }
/// <summary>
/// Y ƫ<><C6AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public double OffsetY { get; set; }
/// <summary>
/// <20>Ƕȡ<C7B6>
/// </summary>
public double Angle { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public double Score { get; set; }
/// <summary>
/// <20><>ǩ<EFBFBD><C7A9>
/// </summary>
public string Tag { get; set; }
}
}

View File

@@ -0,0 +1,72 @@
using MainShell.Common;
using System;
namespace MainShell.Vision
{
/// <summary>
/// <20>Ӿ<EFBFBD><D3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class VisionProcessResult
{
/// <summary>
/// <20>Ƿ<EFBFBD>ִ<EFBFBD>гɹ<D0B3>
/// </summary>
public bool Succeeded { get; set; }
public VisionFailureCategory FailureCategory { get; set; }
public VisionErrorCode ErrorCode { get; set; }
public int? AlarmId { get; set; }
public MessageKey UserMessageKey { get; set; }
/// <summary>
/// <20><><EFBFBD>û<EFBFBD><C3BB><EFBFBD><E9BFB4><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>Ϣ
/// </summary>
public string Message { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><ECB3A3><EFBFBD><EFBFBD>
/// </summary>
public Exception Exception { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public static VisionProcessResult Success()
{
return new VisionProcessResult
{
Succeeded = true,
FailureCategory = VisionFailureCategory.None,
ErrorCode = VisionErrorCode.None,
AlarmId = null,
UserMessageKey = MessageKey.None,
};
}
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʧ<EFBFBD>ܽ<EFBFBD><DCBD><EFBFBD>
/// </summary>
public static VisionProcessResult Failure(
VisionFailureCategory failureCategory,
VisionErrorCode errorCode,
int? alarmId,
MessageKey userMessageKey,
string message,
Exception exception = null)
{
return new VisionProcessResult
{
Succeeded = false,
FailureCategory = failureCategory,
ErrorCode = errorCode,
AlarmId = alarmId,
UserMessageKey = userMessageKey,
Message = message,
Exception = exception,
};
}
}
}

View File

@@ -0,0 +1,57 @@
using MainShell.Common;
using System;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݵ<EFBFBD><DDB5>Ӿ<EFBFBD><D3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <typeparam name="TData"><3E>Ӿ<EFBFBD><D3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD><D8B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></typeparam>
public class VisionProcessResult<TData> : VisionProcessResult
{
/// <summary>
/// <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public TData Data { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public static VisionProcessResult<TData> Success(TData data)
{
return new VisionProcessResult<TData>
{
Succeeded = true,
FailureCategory = VisionFailureCategory.None,
ErrorCode = VisionErrorCode.None,
AlarmId = null,
UserMessageKey = MessageKey.None,
Data = data,
};
}
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʧ<EFBFBD>ܽ<EFBFBD><DCBD><EFBFBD>
/// </summary>
public new static VisionProcessResult<TData> Failure(
VisionFailureCategory failureCategory,
VisionErrorCode errorCode,
int? alarmId,
MessageKey userMessageKey,
string message,
Exception exception = null)
{
return new VisionProcessResult<TData>
{
Succeeded = false,
FailureCategory = failureCategory,
ErrorCode = errorCode,
AlarmId = alarmId,
UserMessageKey = userMessageKey,
Message = message,
Exception = exception,
Data = default(TData),
};
}
}
}

View File

@@ -0,0 +1,324 @@
using JM1Vision;
using MainShell.Common;
using MainShell.Log;
using MainShell.Vision.Common;
using MwFramework.Device;
using MwFramework.Device.Model;
using MwFramework.ManagerService;
using SemiconductorVisionAlgorithm.SemiCalib;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Jm1RecheckDiePoint = JM1.JM1Params.RecheckDiePoint;
using SemiPoint = SemiconductorVisionAlgorithm.SemiParams.Point;
namespace MainShell.Vision
{
/// <summary>
/// 通用视觉算法服务。
/// </summary>
public class CommonVisionAlgorithmService : ICommonVisionAlgorithmService
{
public Task<VisionProcessResult> GenWaferMapXAsync(
string fileDirectory,
double[] edgePtRow,
double[] edgePtCol,
double[] edgePtAngle,
double[] inPtRow,
double[] inPtCol,
double[] inPtAngle,
double upThresholdAngle,
double upRowDistance,
double upColDistance,
int upRowNumber,
int upColNumber,
double firstPosRow,
double firstPosCol,
double distanceThreshold,
double angleThreshold,
double rotateAngle,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(GenWaferMapXAsync),
delegate ()
{
return JM1Manager.Instance.gen_wafer_map_x(
fileDirectory,
edgePtRow,
edgePtCol,
edgePtAngle,
inPtRow,
inPtCol,
inPtAngle,
upThresholdAngle,
upRowDistance,
upColDistance,
upRowNumber,
upColNumber,
firstPosRow,
firstPosCol,
distanceThreshold,
angleThreshold,
rotateAngle);
},
cancellationToken);
}
public Task<VisionProcessResult> RecheckPosRgbAsync(
List<Jm1RecheckDiePoint> edgeDiePoints,
List<Jm1RecheckDiePoint> inDiePoints,
double threshold,
List<Jm1RecheckDiePoint> existingPoints,
RecheckDieMap padPos,
double diePitch,
int rowNumber,
int colNumber,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(RecheckPosRgbAsync),
delegate ()
{
Dictionary<int, Dictionary<int, Jm1RecheckDiePoint>> rawPadPos = padPos;
return JM1Manager.Instance.recheck_posRGB(
edgeDiePoints,
inDiePoints,
threshold,
existingPoints,
rawPadPos,
diePitch,
rowNumber,
colNumber);
},
cancellationToken);
}
public Task<VisionProcessResult> ClearRecheckDiePosAsync(
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(ClearRecheckDiePosAsync),
delegate ()
{
return JM1Manager.Instance.ClearRecheckdiepos();
},
cancellationToken);
}
public Task<VisionProcessResult> ClearMapAsync(
int flag,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(ClearMapAsync),
delegate ()
{
return JM1Manager.Instance.ClearMap(flag);
},
cancellationToken);
}
public Task<VisionProcessResult<(RecheckDieMap UpMap, List<Jm1RecheckDiePoint> PatchPos, List<Jm1RecheckDiePoint> ExceptPos)>> GetMapAsync(
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteDataAlgorithmAsync(
nameof(GetMapAsync),
delegate ()
{
Dictionary<int, Dictionary<int, Jm1RecheckDiePoint>> upMap;
List<Jm1RecheckDiePoint> patchPos;
List<Jm1RecheckDiePoint> exceptPos;
bool executeResult = JM1Manager.Instance.GetMap(out upMap, out patchPos, out exceptPos);
EnsureExecutionSucceeded(nameof(GetMapAsync), executeResult);
return (new RecheckDieMap(upMap), patchPos, exceptPos);
},
cancellationToken);
}
public Task<VisionProcessResult<(RecheckDieMap Map, List<Jm1RecheckDiePoint> ExposedPoints)>> GetRecheckDiePosAsync(
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteDataAlgorithmAsync(
nameof(GetRecheckDiePosAsync),
delegate ()
{
Dictionary<int, Dictionary<int, Jm1RecheckDiePoint>> map;
List<Jm1RecheckDiePoint> exposedPoints;
bool executeResult = JM1Manager.Instance.GetRecheckdiepos(out map, out exposedPoints);
EnsureExecutionSucceeded(nameof(GetRecheckDiePosAsync), executeResult);
return (new RecheckDieMap(map), exposedPoints);
},
cancellationToken);
}
public Task<VisionProcessResult> CalProductAffineAsync(
List<SemiPoint> sourcePoints,
List<SemiPoint> targetPoints,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(CalProductAffineAsync),
delegate ()
{
return JM1Manager.Instance.cal_product_affine(sourcePoints, targetPoints);
},
cancellationToken);
}
public Task<VisionProcessResult<double>> CalculateLineAngleAsync(
SemiPoint point1,
SemiPoint point2,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteDataAlgorithmAsync(
nameof(CalculateLineAngleAsync),
delegate ()
{
double deg;
bool executeResult = JM1Manager.Instance.calculate_line_angle(point1, point2, out deg);
EnsureExecutionSucceeded(nameof(CalculateLineAngleAsync), executeResult);
return deg;
},
cancellationToken);
}
public Task<VisionProcessResult> SetWorkAreaAsync(
string fileDirectory,
double heightY,
double widthX,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(SetWorkAreaAsync),
delegate ()
{
return JM1Manager.Instance.set_workarea(fileDirectory, heightY, widthX);
},
cancellationToken);
}
public Task<VisionProcessResult> SetDirectionAsync(
string fileDirectory,
int xDirection,
int yDirection,
CancellationToken cancellationToken = default(CancellationToken))
{
return ExecuteBoolAlgorithmAsync(
nameof(SetDirectionAsync),
delegate ()
{
return JM1Manager.Instance.set_direction(fileDirectory, xDirection, yDirection);
},
cancellationToken);
}
private static Task<VisionProcessResult> ExecuteBoolAlgorithmAsync(
string algorithmName,
Func<bool> execute,
CancellationToken cancellationToken)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
bool executeResult = execute();
EnsureExecutionSucceeded(algorithmName, executeResult);
string message = $"CommonVisionAlgorithmService: algorithm {algorithmName} completed.";
message.LogInfo();
return Task.FromResult(VisionProcessResult.Success());
}
catch (OperationCanceledException ex)
{
string message = $"CommonVisionAlgorithmService: algorithm {algorithmName} was cancelled.";
message.LogInfo();
return Task.FromResult(VisionProcessResult.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"CommonVisionAlgorithmService: algorithm {algorithmName} failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.CommonAlgorithmExecutionFailed,
VisionAlarmIds.CommonAlgorithmExecutionFailed,
MessageKey.VisionCommonAlgorithmExecutionFailed,
message,
ex));
}
}
private static Task<VisionProcessResult<TData>> ExecuteDataAlgorithmAsync<TData>(
string algorithmName,
Func<TData> execute,
CancellationToken cancellationToken)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
TData result = execute();
string message = $"CommonVisionAlgorithmService: algorithm {algorithmName} completed.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<TData>.Success(result));
}
catch (OperationCanceledException ex)
{
string message = $"CommonVisionAlgorithmService: algorithm {algorithmName} was cancelled.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<TData>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"CommonVisionAlgorithmService: algorithm {algorithmName} failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<TData>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.CommonAlgorithmExecutionFailed,
VisionAlarmIds.CommonAlgorithmExecutionFailed,
MessageKey.VisionCommonAlgorithmExecutionFailed,
message,
ex));
}
}
private static void EnsureExecutionSucceeded(string algorithmName, bool executeResult)
{
if (!executeResult)
{
throw new InvalidOperationException($"CommonVisionAlgorithmService: algorithm {algorithmName} returned false.");
}
}
}
}

View File

@@ -0,0 +1,83 @@
using MwFramework.Device.Model;
using SemiconductorVisionAlgorithm.SemiParams;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jm1RecheckDiePoint = JM1.JM1Params.RecheckDiePoint;
using SemiPoint = SemiconductorVisionAlgorithm.SemiParams.Point;
namespace MainShell.Vision
{
/// <summary>
/// 通用视觉算法服务接口。
/// </summary>
public interface ICommonVisionAlgorithmService
{
Task<VisionProcessResult> GenWaferMapXAsync(
string fileDirectory,
double[] edgePtRow,
double[] edgePtCol,
double[] edgePtAngle,
double[] inPtRow,
double[] inPtCol,
double[] inPtAngle,
double upThresholdAngle,
double upRowDistance,
double upColDistance,
int upRowNumber,
int upColNumber,
double firstPosRow,
double firstPosCol,
double distanceThreshold,
double angleThreshold,
double rotateAngle,
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult> RecheckPosRgbAsync(
List<Jm1RecheckDiePoint> edgeDiePoints,
List<Jm1RecheckDiePoint> inDiePoints,
double threshold,
List<Jm1RecheckDiePoint> existingPoints,
RecheckDieMap padPos,
double diePitch,
int rowNumber,
int colNumber,
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult> ClearRecheckDiePosAsync(
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult> ClearMapAsync(
int flag,
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult<(RecheckDieMap UpMap, List<Jm1RecheckDiePoint> PatchPos, List<Jm1RecheckDiePoint> ExceptPos)>> GetMapAsync(
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult<(RecheckDieMap Map, List<Jm1RecheckDiePoint> ExposedPoints)>> GetRecheckDiePosAsync(
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult> CalProductAffineAsync(
List<SemiPoint> sourcePoints,
List<SemiPoint> targetPoints,
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult<double>> CalculateLineAngleAsync(
SemiPoint point1,
SemiPoint point2,
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult> SetWorkAreaAsync(
string fileDirectory,
double heightY,
double widthX,
CancellationToken cancellationToken = default(CancellationToken));
Task<VisionProcessResult> SetDirectionAsync(
string fileDirectory,
int xDirection,
int yDirection,
CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Jm1RecheckDiePoint = JM1.JM1Params.RecheckDiePoint;
namespace MainShell.Vision
{
/// <summary>
/// 复检 Die Map。
/// </summary>
public class RecheckDieMap : Dictionary<int, Dictionary<int, Jm1RecheckDiePoint>>
{
public RecheckDieMap()
{
}
public RecheckDieMap(IDictionary<int, Dictionary<int, Jm1RecheckDiePoint>> source)
: base(source ?? new Dictionary<int, Dictionary<int, Jm1RecheckDiePoint>>())
{
}
}
}

View File

@@ -0,0 +1,26 @@
using MainShell.Models;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class FindCenterImageRequest
{
public FindCenterImageRequest()
{
TimeoutMilliseconds = 3000;
Parameters = new FindCenterParameters();
}
/// <summary>
/// <20><EFBFBD><E3B7A8>ʱʱ<CAB1><EFBFBD><E4A3A8><EFBFBD><EFBFBD><EBA3A9>
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public FindCenterParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD>ĵ<EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public sealed class FindCenterParameters : TemplateVisionAlgorithmParameters
{
/// <summary>
/// <20>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD> ROI
/// </summary>
public bool UseRoi { get; set; }
/// <summary>
/// ROI <20><><EFBFBD>ƻ<EFBFBD><C6BB><EFBFBD>ʶ
/// </summary>
public string RoiName { get; set; }
}
}

View File

@@ -0,0 +1,49 @@
using MainShell.Common;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class FindCenterRequest
{
private CameraCaptureOptions _captureOptions;
public FindCenterRequest()
{
_captureOptions = new CameraCaptureOptions();
TimeoutMilliseconds = 3000;
Parameters = new FindCenterParameters();
}
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>
/// </summary>
public CameraType CameraSource { get; set; }
/// <summary>
/// <20><>ͼѡ<CDBC>
/// </summary>
public CameraCaptureOptions CaptureOptions
{
get
{
return _captureOptions;
}
set
{
_captureOptions = value ?? new CameraCaptureOptions();
}
}
/// <summary>
/// <20><EFBFBD><E3B7A8>ʱʱ<CAB1><EFBFBD><E4A3A8><EFBFBD><EFBFBD><EBA3A9>
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public FindCenterParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class FindCenterResult
{
/// <summary>
/// <20><><EFBFBD><EFBFBD> X <20><><EFBFBD>
/// </summary>
public double CenterX { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD> Y <20><><EFBFBD>
/// </summary>
public double CenterY { get; set; }
/// <summary>
/// ʶ<><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public double Score { get; set; }
}
}

View File

@@ -0,0 +1,193 @@
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>֡<EFBFBD>
/// </summary>
[Singleton]
public class FindCenterService : IFindCenterService
{
private readonly IImageCaptureService _imageCaptureService;
public FindCenterService(IImageCaptureService imageCaptureService)
{
_imageCaptureService = imageCaptureService ?? throw new ArgumentNullException(nameof(imageCaptureService));
}
public async Task<VisionProcessResult<FindCenterResult>> ProcessAsync(
FindCenterRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
ImageCaptureResult captureResult = null;
MxImage image = null;
try
{
captureResult = await _imageCaptureService.CaptureAsync(
request.CameraSource,
request.CaptureOptions,
cancellationToken).ConfigureAwait(false);
if (captureResult == null)
{
string message = string.Format(
"FindCenterService: image capture returned null result for camera '{0}'.",
request.CameraSource);
message.LogSysError();
return VisionProcessResult<FindCenterResult>.Failure(message);
}
if (!captureResult.Succeeded)
{
return VisionProcessResult<FindCenterResult>.Failure(captureResult.Message, captureResult.Exception);
}
image = captureResult.Image;
if (image == null)
{
string message = string.Format(
"FindCenterService: image capture succeeded but returned null image for camera '{0}'.",
request.CameraSource);
message.LogSysError();
return VisionProcessResult<FindCenterResult>.Failure(message);
}
FindCenterImageRequest imageRequest = new FindCenterImageRequest();
imageRequest.TimeoutMilliseconds = request.TimeoutMilliseconds;
imageRequest.Parameters = request.Parameters;
return await ProcessImageAsync(image, imageRequest, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
string message = "FindCenterService: process was cancelled.";
message.LogInfo();
return VisionProcessResult<FindCenterResult>.Failure(message, ex);
}
catch (Exception ex)
{
string message = string.Format("FindCenterService: process failed. {0}", ex.Message);
message.LogSysError();
return VisionProcessResult<FindCenterResult>.Failure(message, ex);
}
finally
{
if (image != null)
{
image.Dispose();
}
}
}
public Task<VisionProcessResult<FindCenterResult>> ProcessImageAsync(
MxImage image,
FindCenterImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindCenterImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(VisionProcessResult<FindCenterResult>.Failure(validationResult.Message, validationResult.Exception));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
string message = string.Format(
"FindCenterService: algorithm is not implemented. RoiName={0}, UseRoi={1}, MinScore={2}.",
normalizedRequest.Parameters != null ? normalizedRequest.Parameters.RoiName : null,
normalizedRequest.Parameters != null && normalizedRequest.Parameters.UseRoi,
normalizedRequest.Parameters != null ? normalizedRequest.Parameters.MinScore : 0.0);
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindCenterResult>.Failure(message));
}
catch (OperationCanceledException ex)
{
string message = string.Format(
"FindCenterService: process image was cancelled. Timeout={0} ms.",
normalizedRequest.TimeoutMilliseconds);
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindCenterResult>.Failure(message, ex));
}
catch (Exception ex)
{
string message = string.Format("FindCenterService: process image failed. {0}", ex.Message);
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindCenterResult>.Failure(message, ex));
}
}
private static FindCenterImageRequest NormalizeRequest(FindCenterImageRequest request)
{
FindCenterImageRequest normalizedRequest = new FindCenterImageRequest();
normalizedRequest.TimeoutMilliseconds = request.TimeoutMilliseconds > 0 ? request.TimeoutMilliseconds : 3000;
normalizedRequest.Parameters = CloneParameters(request.Parameters);
return normalizedRequest;
}
private static VisionProcessResult ValidateRequest(FindCenterImageRequest request)
{
if (request.Parameters == null)
{
return VisionProcessResult.Failure("FindCenterService: parameters cannot be null.");
}
if (request.TimeoutMilliseconds <= 0 || request.TimeoutMilliseconds > 60000)
{
return VisionProcessResult.Failure(
string.Format("FindCenterService: TimeoutMilliseconds {0} is invalid. Expected range is [1, 60000].", request.TimeoutMilliseconds));
}
if (request.Parameters.MinScore <= 0d || request.Parameters.MinScore > 1d)
{
return VisionProcessResult.Failure(
string.Format("FindCenterService: MinScore {0} is invalid. Expected range is (0, 1].", request.Parameters.MinScore));
}
if (request.Parameters.UseRoi && string.IsNullOrWhiteSpace(request.Parameters.RoiName))
{
return VisionProcessResult.Failure("FindCenterService: RoiName cannot be empty when UseRoi is true.");
}
return null;
}
private static FindCenterParameters CloneParameters(FindCenterParameters parameters)
{
if (parameters == null)
{
return new FindCenterParameters();
}
FindCenterParameters clone = new FindCenterParameters();
clone.UseRoi = parameters.UseRoi;
clone.RoiName = parameters.RoiName;
clone.MinScore = parameters.MinScore;
return clone;
}
}
}

View File

@@ -0,0 +1,27 @@
using MainShell.Models;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӿڡ<D3BF>
/// </summary>
public interface IFindCenterService
{
/// <summary>
/// <20><>ͼ<EFBFBD><CDBC>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD>
/// </summary>
Task<VisionProcessResult<FindCenterResult>> ProcessAsync(
FindCenterRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD>
/// </summary>
Task<VisionProcessResult<FindCenterResult>> ProcessImageAsync(
MxImage image,
FindCenterImageRequest request,
CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@@ -0,0 +1,24 @@
namespace MainShell.Vision
{
/// <summary>
/// Die 定位图像处理请求。
/// </summary>
public class FindDieImageRequest
{
public FindDieImageRequest()
{
TimeoutMilliseconds = 3000;
Parameters = new FindDieParameters();
}
/// <summary>
/// 算法超时时间(毫秒)。
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// Die 定位参数。
/// </summary>
public FindDieParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,55 @@
namespace MainShell.Vision
{
/// <summary>
/// Die 定位参数
/// </summary>
public sealed class FindDieParameters : TemplateVisionAlgorithmParameters
{
public FindDieParameters()
{
ScoreThreshold = MinScore;
SerialNumber = string.Empty;
CurrentRuler = new SemiconductorVisionAlgorithm.SemiParams.Point(0.0, 0.0);
}
/// <summary>
/// 标定序列号。
/// </summary>
public string SerialNumber { get; set; }
/// <summary>
/// 当前尺坐标。
/// </summary>
public SemiconductorVisionAlgorithm.SemiParams.Point CurrentRuler { get; set; }
/// <summary>
/// X 方向重叠比例。
/// </summary>
public double OverlapX { get; set; }
/// <summary>
/// Y 方向重叠比例。
/// </summary>
public double OverlapY { get; set; }
/// <summary>
/// Die 宽度。
/// </summary>
public double DieWidth { get; set; }
/// <summary>
/// Die 高度。
/// </summary>
public double DieHeight { get; set; }
/// <summary>
/// 分数阈值。
/// </summary>
public double ScoreThreshold { get; set; }
/// <summary>
/// 允许角度偏差(度)
/// </summary>
public double? AngleTolerance { get; set; }
}
}

View File

@@ -0,0 +1,49 @@
using MainShell.Common;
namespace MainShell.Vision
{
/// <summary>
/// Die 定位请求。
/// </summary>
public class FindDieRequest
{
private CameraCaptureOptions _captureOptions;
public FindDieRequest()
{
_captureOptions = new CameraCaptureOptions();
TimeoutMilliseconds = 3000;
Parameters = new FindDieParameters();
}
/// <summary>
/// 相机源。
/// </summary>
public CameraType CameraSource { get; set; }
/// <summary>
/// 取图选项。
/// </summary>
public CameraCaptureOptions CaptureOptions
{
get
{
return _captureOptions;
}
set
{
_captureOptions = value ?? new CameraCaptureOptions();
}
}
/// <summary>
/// 算法超时时间(毫秒)。
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// Die 定位参数。
/// </summary>
public FindDieParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
namespace MainShell.Vision
{
/// <summary>
/// Die 定位结果。
/// </summary>
public class FindDieResult
{
/// <summary>
/// 中心 X 坐标。
/// </summary>
public double CenterX { get; set; }
/// <summary>
/// 中心 Y 坐标。
/// </summary>
public double CenterY { get; set; }
/// <summary>
/// 匹配角度。
/// </summary>
public double Angle { get; set; }
/// <summary>
/// 像素 X 坐标。
/// </summary>
public double PixelX { get; set; }
/// <summary>
/// 像素 Y 坐标。
/// </summary>
public double PixelY { get; set; }
/// <summary>
/// 是否为重叠区域点。
/// </summary>
public bool IsOverlap { get; set; }
/// <summary>
/// 匹配扩展信息。
/// </summary>
public VisionMatchResult Match { get; set; }
}
/// <summary>
/// Die 像素结果。
/// </summary>
public class FindDiePixelResult
{
/// <summary>
/// 像素 X 坐标。
/// </summary>
public double PixelX { get; set; }
/// <summary>
/// 像素 Y 坐标。
/// </summary>
public double PixelY { get; set; }
/// <summary>
/// 匹配角度。
/// </summary>
public double Angle { get; set; }
}
}

View File

@@ -0,0 +1,669 @@
using JM1Vision;
using MainShell.Common;
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// Die 定位服务。
/// </summary>
public class FindDieService : IFindDieService
{
private readonly IImageCaptureService _imageCaptureService;
public FindDieService(IImageCaptureService imageCaptureService)
{
_imageCaptureService = imageCaptureService ?? throw new ArgumentNullException(nameof(imageCaptureService));
}
public async Task<VisionProcessResult<FindDieResult>> ProcessAsync(
FindDieRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessImageAsync,
cancellationToken).ConfigureAwait(false);
}
public Task<VisionProcessResult<FindDieResult>> ProcessImageAsync(
MxImage image,
FindDieImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult<FindDieResult>(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindDieResult> result = ExecuteSingleMatch(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindDieService: process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindDieResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindDieService: process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindDieResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex));
}
}
public async Task<VisionProcessResult<FindDiesResult>> ProcessMultipleAsync(
FindDieRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessMultipleImageAsync,
cancellationToken).ConfigureAwait(false);
}
public Task<VisionProcessResult<FindDiesResult>> ProcessMultipleImageAsync(
MxImage image,
FindDieImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult<FindDiesResult>(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindDiesResult> result = ExecuteMultipleMatch(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindDieService: multiple die process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindDieService: multiple die process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex));
}
}
private static FindDieImageRequest NormalizeRequest(FindDieImageRequest request)
{
FindDieImageRequest normalizedRequest = new FindDieImageRequest();
normalizedRequest.TimeoutMilliseconds = request.TimeoutMilliseconds > 0 ? request.TimeoutMilliseconds : 3000;
normalizedRequest.Parameters = CloneParameters(request.Parameters);
return normalizedRequest;
}
private static VisionProcessResult ValidateRequest(FindDieImageRequest request)
{
if (request.Parameters == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindDieService: parameters cannot be null.");
}
if (request.TimeoutMilliseconds <= 0 || request.TimeoutMilliseconds > 60000)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TimeoutInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionTimeoutInvalid,
$"FindDieService: TimeoutMilliseconds {request.TimeoutMilliseconds} is invalid. Expected range is [1, 60000].");
}
if (string.IsNullOrWhiteSpace(request.Parameters.TemplatePath))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TemplatePathEmpty,
VisionAlarmIds.TemplatePathEmpty,
MessageKey.VisionTemplatePathEmpty,
"FindDieService: TemplatePath cannot be empty.");
}
if (string.IsNullOrWhiteSpace(request.Parameters.SerialNumber))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindDieService: SerialNumber cannot be empty.");
}
if (request.Parameters.CurrentRuler == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindDieService: CurrentRuler cannot be null.");
}
if (request.Parameters.DieWidth <= 0d || request.Parameters.DieHeight <= 0d)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
$"FindDieService: DieWidth {request.Parameters.DieWidth} and DieHeight {request.Parameters.DieHeight} must be greater than 0.");
}
if (request.Parameters.ScoreThreshold <= 0d || request.Parameters.ScoreThreshold > 1d)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.MinScoreInvalid,
VisionAlarmIds.TemplateMinScoreInvalid,
MessageKey.VisionTemplateMinScoreInvalid,
$"FindDieService: ScoreThreshold {request.Parameters.ScoreThreshold} is invalid. Expected range is (0, 1].");
}
return null;
}
private static FindDieParameters CloneParameters(FindDieParameters parameters)
{
if (parameters == null)
{
return new FindDieParameters();
}
FindDieParameters clone = new FindDieParameters();
clone.MinScore = parameters.MinScore;
clone.TemplatePath = parameters.TemplatePath;
clone.SerialNumber = parameters.SerialNumber;
clone.CurrentRuler = parameters.CurrentRuler == null
? null
: new Point(parameters.CurrentRuler.X, parameters.CurrentRuler.Y);
clone.OverlapX = parameters.OverlapX;
clone.OverlapY = parameters.OverlapY;
clone.DieWidth = parameters.DieWidth;
clone.DieHeight = parameters.DieHeight;
clone.ScoreThreshold = parameters.ScoreThreshold > 0d ? parameters.ScoreThreshold : parameters.MinScore;
clone.AngleTolerance = parameters.AngleTolerance;
return clone;
}
private static FindDieImageRequest CreateImageRequest(FindDieRequest request)
{
FindDieImageRequest imageRequest = new FindDieImageRequest();
imageRequest.TimeoutMilliseconds = request.TimeoutMilliseconds;
imageRequest.Parameters = CloneParameters(request.Parameters);
return imageRequest;
}
private static VisionProcessResult<FindDieResult> ExecuteSingleMatch(MxImage image, FindDieImageRequest request)
{
VisionProcessResult<FindDiesResult> multipleResult = ExecuteMultipleMatch(image, request);
if (!multipleResult.Succeeded)
{
return CreateFailureResult<FindDieResult>(multipleResult);
}
FindDieResult result = SelectPrimaryResult(multipleResult.Data);
if (result == null)
{
return VisionProcessResult<FindDieResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: no valid die result was found. TemplatePath={request.Parameters.TemplatePath}.");
}
return VisionProcessResult<FindDieResult>.Success(result);
}
private static VisionProcessResult<FindDiesResult> ExecuteMultipleMatch(MxImage image, FindDieImageRequest request)
{
Camera camera = image.Image;
if (camera == null)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
"FindDieService: input image does not contain a valid camera object.");
}
double[] uniqueRows = null;
double[] uniqueCols = null;
double[] uniqueAngles = null;
double[] overlapRows = null;
double[] overlapCols = null;
double[] overlapAngles = null;
double[] pixelRows = null;
double[] pixelCols = null;
double[] pixelAngles = null;
bool succeeded = JM1Manager.Instance.get_target_pos_nodisp(
camera,
request.Parameters.TemplatePath,
request.Parameters.SerialNumber,
request.Parameters.CurrentRuler,
request.Parameters.OverlapX,
request.Parameters.OverlapY,
request.Parameters.DieWidth,
request.Parameters.DieHeight,
request.Parameters.ScoreThreshold,
out uniqueRows,
out uniqueCols,
out uniqueAngles,
out overlapRows,
out overlapCols,
out overlapAngles,
out pixelRows,
out pixelCols,
out pixelAngles);
if (!succeeded)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die match failed. TemplatePath={request.Parameters.TemplatePath}.");
}
if (!HasConsistentResult(uniqueRows, uniqueCols, uniqueAngles) ||
!HasConsistentResult(overlapRows, overlapCols, overlapAngles) ||
!HasConsistentResult(pixelRows, pixelCols, pixelAngles))
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die match returned inconsistent result data. TemplatePath={request.Parameters.TemplatePath}.");
}
if (uniqueRows.Length + overlapRows.Length == 0)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die match returned no results. TemplatePath={request.Parameters.TemplatePath}.");
}
FindDiesResult result = CreateMultipleResult(uniqueRows, uniqueCols, uniqueAngles, overlapRows, overlapCols, overlapAngles, pixelRows, pixelCols, pixelAngles);
ApplyAngleTolerance(result, request.Parameters.AngleTolerance);
if (result.Items.Count == 0)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die results were empty after angle filtering. TemplatePath={request.Parameters.TemplatePath}.");
}
return VisionProcessResult<FindDiesResult>.Success(result);
}
private static FindDiesResult CreateMultipleResult(
double[] uniqueRows,
double[] uniqueCols,
double[] uniqueAngles,
double[] overlapRows,
double[] overlapCols,
double[] overlapAngles,
double[] pixelRows,
double[] pixelCols,
double[] pixelAngles)
{
FindDiesResult result = new FindDiesResult();
AppendDieResults(result.Items, uniqueRows, uniqueCols, uniqueAngles, false);
AppendDieResults(result.Items, overlapRows, overlapCols, overlapAngles, true);
AppendPixelResults(result.PixelItems, pixelRows, pixelCols, pixelAngles);
return result;
}
private static void AppendDieResults(
IList<FindDieResult> destination,
double[] rows,
double[] cols,
double[] angles,
bool isOverlap)
{
if (destination == null || rows == null || cols == null || angles == null)
{
return;
}
for (int i = 0; i < rows.Length; i++)
{
FindDieResult item = new FindDieResult();
item.CenterX = cols[i];
item.CenterY = rows[i];
item.Angle = angles[i];
item.PixelX = double.NaN;
item.PixelY = double.NaN;
item.IsOverlap = isOverlap;
item.Match = CreateMatchResult(angles[i], isOverlap);
destination.Add(item);
}
}
private static void AppendPixelResults(
IList<FindDiePixelResult> destination,
double[] rows,
double[] cols,
double[] angles)
{
if (destination == null || rows == null || cols == null || angles == null)
{
return;
}
for (int i = 0; i < rows.Length; i++)
{
FindDiePixelResult item = new FindDiePixelResult();
item.PixelX = cols[i];
item.PixelY = rows[i];
item.Angle = angles[i];
destination.Add(item);
}
}
private static VisionMatchResult CreateMatchResult(double angle, bool isOverlap)
{
VisionMatchResult match = new VisionMatchResult();
match.OffsetX = 0d;
match.OffsetY = 0d;
match.Angle = angle;
match.Score = 0d;
match.Tag = isOverlap ? "Overlap" : "Unique";
return match;
}
private static void ApplyAngleTolerance(FindDiesResult result, double? angleTolerance)
{
if (result == null || !angleTolerance.HasValue || angleTolerance.Value <= 0d)
{
return;
}
List<double> itemAngles = new List<double>();
foreach (FindDieResult item in result.Items)
{
itemAngles.Add(item.Angle * 180.0d / Math.PI);
}
if (itemAngles.Count == 0)
{
return;
}
double averageAngle = itemAngles.Average();
List<FindDieResult> filteredItems = new List<FindDieResult>();
foreach (FindDieResult item in result.Items)
{
double angleDegree = item.Angle * 180.0d / Math.PI;
if (Math.Abs(angleDegree - averageAngle) <= angleTolerance.Value)
{
filteredItems.Add(item);
}
}
List<FindDiePixelResult> filteredPixelItems = new List<FindDiePixelResult>();
foreach (FindDiePixelResult item in result.PixelItems)
{
double angleDegree = item.Angle * 180.0d / Math.PI;
if (Math.Abs(angleDegree - averageAngle) <= angleTolerance.Value)
{
filteredPixelItems.Add(item);
}
}
result.Items.Clear();
foreach (FindDieResult item in filteredItems)
{
result.Items.Add(item);
}
result.PixelItems.Clear();
foreach (FindDiePixelResult item in filteredPixelItems)
{
result.PixelItems.Add(item);
}
}
private static FindDieResult SelectPrimaryResult(FindDiesResult result)
{
if (result == null || result.Items == null || result.Items.Count == 0)
{
return null;
}
foreach (FindDieResult item in result.Items)
{
if (!item.IsOverlap)
{
return item;
}
}
return result.Items[0];
}
private static bool HasConsistentResult(double[] rows, double[] cols, double[] angles)
{
if (rows == null || cols == null || angles == null)
{
return false;
}
if (rows.Length != cols.Length || rows.Length != angles.Length)
{
return false;
}
for (int i = 0; i < rows.Length; i++)
{
if (double.IsNaN(rows[i]) || double.IsInfinity(rows[i]) ||
double.IsNaN(cols[i]) || double.IsInfinity(cols[i]) ||
double.IsNaN(angles[i]) || double.IsInfinity(angles[i]))
{
return false;
}
}
return true;
}
private async Task<VisionProcessResult<TResult>> ExecuteWithCaptureAsync<TResult>(
CameraType cameraSource,
CameraCaptureOptions captureOptions,
FindDieImageRequest imageRequest,
Func<MxImage, FindDieImageRequest, CancellationToken, Task<VisionProcessResult<TResult>>> imageProcessor,
CancellationToken cancellationToken)
{
ImageCaptureResult captureResult = null;
MxImage image = null;
try
{
captureResult = await _imageCaptureService.CaptureAsync(
cameraSource,
captureOptions,
cancellationToken).ConfigureAwait(false);
if (captureResult == null)
{
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.NoFrame,
VisionAlarmIds.NoFrame,
MessageKey.VisionNoFrameReturned,
$"FindDieService: image capture returned null result for camera '{cameraSource}'.");
}
if (!captureResult.Succeeded)
{
return CreateFailureResult<TResult>(VisionResultMapper.CreateCaptureFailure(captureResult));
}
image = captureResult.Image;
if (image == null)
{
string message = $"FindDieService: image capture succeeded but returned null image for camera '{cameraSource}'.";
message.LogSysError();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
message);
}
return await imageProcessor(image, imageRequest, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
string message = $"FindDieService: capture orchestration was cancelled for camera '{cameraSource}'.";
message.LogInfo();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex);
}
catch (Exception ex)
{
string message = $"FindDieService: capture orchestration failed for camera '{cameraSource}'. {ex.Message}";
message.LogSysError();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex);
}
finally
{
if (image != null)
{
image.Dispose();
}
}
}
private static VisionProcessResult<TData> CreateFailureResult<TData>(VisionProcessResult result)
{
return VisionProcessResult<TData>.Failure(
result.FailureCategory,
result.ErrorCode,
result.AlarmId,
result.UserMessageKey,
result.Message,
result.Exception);
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace MainShell.Vision
{
/// <summary>
/// Die 定位结果集合。
/// </summary>
public class FindDiesResult
{
public FindDiesResult()
{
Items = new List<FindDieResult>();
PixelItems = new List<FindDiePixelResult>();
}
/// <summary>
/// 实际坐标结果集合。
/// </summary>
public IList<FindDieResult> Items { get; private set; }
/// <summary>
/// 像素坐标结果集合。
/// </summary>
public IList<FindDiePixelResult> PixelItems { get; private set; }
}
}

View File

@@ -0,0 +1,42 @@
using MainShell.Models;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// Die 定位服务接口。
/// </summary>
public interface IFindDieService
{
/// <summary>
/// 采集图像并执行单颗 Die 定位。
/// </summary>
Task<VisionProcessResult<FindDieResult>> ProcessAsync(
FindDieRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// 对输入图像直接执行单颗 Die 定位。
/// </summary>
Task<VisionProcessResult<FindDieResult>> ProcessImageAsync(
MxImage image,
FindDieImageRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// 采集图像并执行多颗 Die 定位。
/// </summary>
Task<VisionProcessResult<FindDiesResult>> ProcessMultipleAsync(
FindDieRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// 对输入图像直接执行多颗 Die 定位。
/// </summary>
Task<VisionProcessResult<FindDiesResult>> ProcessMultipleImageAsync(
MxImage image,
FindDieImageRequest request,
CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace MainShell.Vision
{
/// <summary>
/// 抓圆图像请求
/// </summary>
public class FindEdgeCircleImageRequest
{
public FindEdgeCircleImageRequest()
{
TimeoutMilliseconds = 3000;
Parameters = new FindEdgeCircleParameters();
}
/// <summary>
/// 算法超时时间(毫秒)
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// 抓圆参数
/// </summary>
public FindEdgeCircleParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
using SemiconductorVisionAlgorithm.SemiParams;
namespace MainShell.Vision
{
/// <summary>
/// 抓圆参数
/// </summary>
public class FindEdgeCircleParameters : VisionAlgorithmParameters
{
/// <summary>
/// 是否启用 ROI
/// </summary>
public bool UseRoi { get; set; }
/// <summary>
/// ROI 名称或标识
/// </summary>
public string RoiName { get; set; }
/// <summary>
/// 算法文件目录
/// </summary>
public string FileDirectory { get; set; }
/// <summary>
/// 相机ID
/// </summary>
public string CameraId { get; set; }
/// <summary>
/// ROI 矩形区域(可选)
/// </summary>
public Rectangle1 RoiRect { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
using MainShell.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// 默认抓圆请求。
/// </summary>
public class FindEdgeCircleRequest
{
private CameraCaptureOptions _captureOptions;
public FindEdgeCircleRequest()
{
_captureOptions = new CameraCaptureOptions();
TimeoutMilliseconds = 3000;
Parameters = new FindEdgeCircleParameters();
}
/// <summary>
/// 相机来源。
/// </summary>
public CameraType CameraSource { get; set; }
/// <summary>
/// 采图选项。
/// </summary>
public CameraCaptureOptions CaptureOptions
{
get
{
return _captureOptions;
}
set
{
_captureOptions = value ?? new CameraCaptureOptions();
}
}
/// <summary>
/// 算法超时时间(毫秒)。
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// 抓圆参数。
/// </summary>
public FindEdgeCircleParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace MainShell.Vision
{
/// <summary>
/// 抓圆结果
/// </summary>
public class FindEdgeCircleResult
{
/// <summary>
/// 圆心 X 坐标
/// </summary>
public double CenterX { get; set; }
/// <summary>
/// 圆心 Y 坐标
/// </summary>
public double CenterY { get; set; }
/// <summary>
/// 圆半径
/// </summary>
public double Radius { get; set; }
/// <summary>
/// 置信度分数
/// </summary>
public double Score { get; set; }
}
}

View File

@@ -0,0 +1,353 @@
using MainShell.Common;
using MainShell.Log;
using MainShell.Models;
using MainShell.Vision.Common;
using MaxwellFramework.Core.Attributes;
using SemiconductorVisionAlgorithm.SemiCalib;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// 抓圆服务实现
/// </summary>
[Singleton]
public class FindEdgeCircleService : IFindEdgeCircleService
{
private readonly IImageCaptureService _imageCaptureService;
public FindEdgeCircleService(IImageCaptureService imageCaptureService)
{
_imageCaptureService = imageCaptureService ?? throw new ArgumentNullException(nameof(imageCaptureService));
}
/// <summary>
/// 采集图像并执行抓圆
/// </summary>
public async Task<VisionProcessResult<FindEdgeCircleResult>> ProcessAsync(
FindEdgeCircleRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindEdgeCircleImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessImageAsync,
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// 基于已有图像直接执行抓圆
/// </summary>
public Task<VisionProcessResult<FindEdgeCircleResult>> ProcessImageAsync(
MxImage image,
FindEdgeCircleImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindEdgeCircleImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindEdgeCircleResult> result = ExecuteEdgeCircle(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindEdgeCircleService: process image was cancelled.Timeout ={ normalizedRequest.TimeoutMilliseconds}ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindEdgeCircleService: process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.CommonAlgorithmExecutionFailed,
VisionAlarmIds.CommonAlgorithmExecutionFailed,
MessageKey.VisionCommonAlgorithmExecutionFailed,
message,
ex));
}
}
/// <summary>
/// 执行边缘圆检测算法
/// </summary>
private static VisionProcessResult<FindEdgeCircleResult> ExecuteEdgeCircle(
MxImage image,
FindEdgeCircleImageRequest request)
{
Camera camera = image.Image;
if (camera == null)
{
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
"FindEdgeCircleService: input image does not contain a valid camera object.");
}
var parameters = request.Parameters;
double[] hv_Parameter;
bool flag;
if (parameters.RoiRect != null)
{
// 使用 ROI 区域进行抓圆
flag = CalibManager.Instance.calib_edge_circle_by_roi(
camera,
parameters.CameraId,
0,
parameters.RoiRect,
parameters.FileDirectory,
out hv_Parameter);
}
else
{
// 不使用 ROI 进行抓圆
flag = CalibManager.Instance.calib_edge_circle(
camera,
parameters.CameraId,
0,
parameters.FileDirectory,
out hv_Parameter);
}
if (!flag)
{
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.CommonAlgorithmExecutionFailed,
VisionAlarmIds.CommonAlgorithmExecutionFailed,
MessageKey.VisionCommonAlgorithmExecutionFailed,
"FindEdgeCircleService: algorithm calib_edge_circle returned false.");
}
if (hv_Parameter == null || hv_Parameter.Length < 2)
{
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.CommonAlgorithmExecutionFailed,
VisionAlarmIds.CommonAlgorithmExecutionFailed,
MessageKey.VisionCommonAlgorithmExecutionFailed,
"FindEdgeCircleService: algorithm returned invalid parameters.");
}
FindEdgeCircleResult result = new FindEdgeCircleResult();
result.CenterX = hv_Parameter[0];
result.CenterY = hv_Parameter[1];
return VisionProcessResult<FindEdgeCircleResult>.Success(result);
}
private static FindEdgeCircleImageRequest NormalizeRequest(FindEdgeCircleImageRequest request)
{
FindEdgeCircleImageRequest normalizedRequest = new FindEdgeCircleImageRequest();
normalizedRequest.TimeoutMilliseconds = request.TimeoutMilliseconds > 0 ? request.TimeoutMilliseconds :3000;
normalizedRequest.Parameters = CloneParameters(request.Parameters);
return normalizedRequest;
}
private static VisionProcessResult ValidateRequest(FindEdgeCircleImageRequest request)
{
if (request.Parameters == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindEdgeCircleService: parameters cannot be null.");
}
if (request.TimeoutMilliseconds <= 0 || request.TimeoutMilliseconds > 60000)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TimeoutInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionTimeoutInvalid,
string.Format("FindEdgeCircleService: TimeoutMilliseconds {0} is invalid. Expected range is [1,60000].", request.TimeoutMilliseconds));
}
if (string.IsNullOrWhiteSpace(request.Parameters.FileDirectory))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindEdgeCircleService: FileDirectory cannot be empty.");
}
if (string.IsNullOrWhiteSpace(request.Parameters.CameraId))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindEdgeCircleService: CameraId cannot be empty.");
}
return null;
}
private static FindEdgeCircleParameters CloneParameters(FindEdgeCircleParameters parameters)
{
if (parameters == null)
{
return new FindEdgeCircleParameters();
}
FindEdgeCircleParameters clone = new FindEdgeCircleParameters();
clone.UseRoi = parameters.UseRoi;
clone.RoiName = parameters.RoiName;
clone.FileDirectory = parameters.FileDirectory;
clone.CameraId = parameters.CameraId;
clone.RoiRect = parameters.RoiRect;
return clone;
}
private static FindEdgeCircleImageRequest CreateImageRequest(FindEdgeCircleRequest request)
{
FindEdgeCircleImageRequest imageRequest = new FindEdgeCircleImageRequest();
imageRequest.TimeoutMilliseconds = request.TimeoutMilliseconds;
imageRequest.Parameters = CloneParameters(request.Parameters);
return imageRequest;
}
private async Task<VisionProcessResult<FindEdgeCircleResult>> ExecuteWithCaptureAsync(
CameraType cameraSource,
CameraCaptureOptions captureOptions,
FindEdgeCircleImageRequest imageRequest,
Func<MxImage, FindEdgeCircleImageRequest, CancellationToken,
Task<VisionProcessResult<FindEdgeCircleResult>>> imageProcessor,
CancellationToken cancellationToken)
{
ImageCaptureResult captureResult = null;
MxImage image = null;
try
{
captureResult = await _imageCaptureService.CaptureAsync(
cameraSource,
captureOptions,
cancellationToken).ConfigureAwait(false);
if (captureResult == null)
{
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.NoFrame,
VisionAlarmIds.NoFrame,
MessageKey.VisionNoFrameReturned,
string.Format(
"FindEdgeCircleService: image capture returned null result for camera '{0}'.",
cameraSource));
}
if (!captureResult.Succeeded)
{
return CreateFailureResult(VisionResultMapper.CreateCaptureFailure(captureResult));
}
image = captureResult.Image;
if (image == null)
{
string message = string.Format(
"FindEdgeCircleService: image capture succeeded but returned null image for camera '{0}'.",
cameraSource);
message.LogSysError();
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
message);
}
return await imageProcessor(image, imageRequest, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
string message = $"FindEdgeCircleService: capture orchestration was cancelled for camera '{cameraSource}'.";
message.LogInfo();
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex);
}
catch (Exception ex)
{
string message = $"FindEdgeCircleService: capture orchestration failed for camera '{cameraSource}'. { ex.Message} ";
message.LogSysError();
return VisionProcessResult<FindEdgeCircleResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.CommonAlgorithmExecutionFailed,
VisionAlarmIds.CommonAlgorithmExecutionFailed,
MessageKey.VisionCommonAlgorithmExecutionFailed,
message,
ex);
}
finally
{
if (image != null)
{
image.Dispose();
}
}
}
private static VisionProcessResult<FindEdgeCircleResult> CreateFailureResult(VisionProcessResult result)
{
return VisionProcessResult<FindEdgeCircleResult>.Failure(
result.FailureCategory,
result.ErrorCode,
result.AlarmId,
result.UserMessageKey,
result.Message,
result.Exception);
}
}
}

View File

@@ -0,0 +1,27 @@
using MainShell.Models;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// 抓圆服务接口
/// </summary>
public interface IFindEdgeCircleService
{
/// <summary>
/// 采集图像并执行抓圆
/// </summary>
Task<VisionProcessResult<FindEdgeCircleResult>> ProcessAsync(
FindEdgeCircleRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// 基于已有图像直接执行抓圆
/// </summary>
Task<VisionProcessResult<FindEdgeCircleResult>> ProcessImageAsync(
MxImage image,
FindEdgeCircleImageRequest request,
CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@@ -0,0 +1,18 @@
namespace MainShell.Vision
{
/// <summary>
/// Mark 点识别参数
/// </summary>
public sealed class FindMarkParameters : TemplateVisionAlgorithmParameters
{
/// <summary>
/// 是否启用 ROI
/// </summary>
public bool UseRoi { get; set; }
/// <summary>
/// ROI 名称或标识
/// </summary>
public string RoiName { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using JM1Vision;
using MainShell.Models;
namespace MainShell.Vision
{
/// <summary>
/// ģ<><C4A3>ƥ<EFBFBD><C6A5>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class FindTemplateImageRequest
{
public FindTemplateImageRequest()
{
TimeoutMilliseconds = 3000;
Parameters = new FindTemplateParameters();
}
/// <summary>
/// <20><EFBFBD><E3B7A8>ʱʱ<CAB1><EFBFBD><E4A3A8><EFBFBD><EFBFBD><EBA3A9>
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// ģ<><C4A3>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public FindTemplateParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace MainShell.Vision
{
/// <summary>
/// 模板匹配参数。
/// </summary>
public sealed class FindTemplateParameters : TemplateVisionAlgorithmParameters
{
/// <summary>
/// 是否启用 ROI。
/// </summary>
public bool UseRoi { get; set; }
/// <summary>
/// ROI 名称或标识。
/// </summary>
public string RoiName { get; set; }
}
}

View File

@@ -0,0 +1,49 @@
using MainShell.Common;
namespace MainShell.Vision
{
/// <summary>
/// ģ<><C4A3>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class FindTemplateRequest
{
private CameraCaptureOptions _captureOptions;
public FindTemplateRequest()
{
_captureOptions = new CameraCaptureOptions();
TimeoutMilliseconds = 3000;
Parameters = new FindTemplateParameters();
}
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>
/// </summary>
public CameraType CameraSource { get; set; }
/// <summary>
/// <20><>ͼѡ<CDBC>
/// </summary>
public CameraCaptureOptions CaptureOptions
{
get
{
return _captureOptions;
}
set
{
_captureOptions = value ?? new CameraCaptureOptions();
}
}
/// <summary>
/// <20><EFBFBD><E3B7A8>ʱʱ<CAB1><EFBFBD><E4A3A8><EFBFBD><EFBFBD><EBA3A9>
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// ģ<><C4A3>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public FindTemplateParameters Parameters { get; set; }
}
}

View File

@@ -0,0 +1,101 @@
using System.Collections.Generic;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class FindTemplateResult
{
public FindTemplateResult()
{
Circles = new List<FindTemplateCircleResult>();
Lines = new List<FindTemplateLineResult>();
Rectangles = new List<FindTemplateRectangleResult>();
}
/// <summary>
/// ƥ<><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD> X <20><><EFBFBD>
/// </summary>
public double CenterX { get; set; }
/// <summary>
/// ƥ<><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Y <20><><EFBFBD>
/// </summary>
public double CenterY { get; set; }
/// <summary>
/// ƥ<><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public double Score { get; set; }
/// <summary>
/// <20><>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD>չƥ<D5B9><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public VisionMatchResult Match { get; set; }
/// <summary>
/// ƥ<><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public FindTemplateContourResult Contour { get; set; }
/// <summary>
/// Բ<><D4B2>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public IList<FindTemplateCircleResult> Circles { get; private set; }
/// <summary>
/// ֱ<><D6B1>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public IList<FindTemplateLineResult> Lines { get; private set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public IList<FindTemplateRectangleResult> Rectangles { get; private set; }
}
public class FindTemplateContourResult
{
public double StartX { get; set; }
public double StartY { get; set; }
public double EndX { get; set; }
public double EndY { get; set; }
}
public class FindTemplateCircleResult
{
public double CenterX { get; set; }
public double CenterY { get; set; }
public double Radius { get; set; }
}
public class FindTemplateLineResult
{
public double StartX { get; set; }
public double StartY { get; set; }
public double EndX { get; set; }
public double EndY { get; set; }
}
public class FindTemplateRectangleResult
{
public double StartX { get; set; }
public double StartY { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double Angle { get; set; }
}
}

View File

@@ -0,0 +1,705 @@
using MainShell.Common;
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using SemiconductorVisionAlgorithm.SemiParams;
using SemiconductorVisionAlgorithm.SemiWaferRecip;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// ?????????????
/// </summary>
[Singleton]
public class FindTemplateService : IFindTemplateService
{
private readonly IImageCaptureService _imageCaptureService;
public FindTemplateService(IImageCaptureService imageCaptureService)
{
_imageCaptureService = imageCaptureService ?? throw new ArgumentNullException(nameof(imageCaptureService));
}
public async Task<VisionProcessResult<FindTemplateResult>> ProcessAsync(
FindTemplateRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindTemplateImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessImageAsync,
cancellationToken).ConfigureAwait(false);
}
public Task<VisionProcessResult<FindTemplateResult>> ProcessImageAsync(
MxImage image,
FindTemplateImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindTemplateImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult<FindTemplateResult>(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindTemplateResult> result = ExecuteSingleMatch(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindTemplateService: process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindTemplateResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindTemplateService: process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindTemplateResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex));
}
}
public async Task<VisionProcessResult<FindTemplatesResult>> ProcessMultipleAsync(
FindTemplateRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindTemplateImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessMultipleImageAsync,
cancellationToken).ConfigureAwait(false);
}
public Task<VisionProcessResult<FindTemplatesResult>> ProcessMultipleImageAsync(
MxImage image,
FindTemplateImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindTemplateImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult<FindTemplatesResult>(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindTemplatesResult> result = ExecuteMultipleMatch(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindTemplateService: multiple template process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindTemplatesResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindTemplateService: multiple template process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindTemplatesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex));
}
}
private static FindTemplateImageRequest NormalizeRequest(FindTemplateImageRequest request)
{
FindTemplateImageRequest normalizedRequest = new FindTemplateImageRequest();
normalizedRequest.TimeoutMilliseconds = request.TimeoutMilliseconds > 0 ? request.TimeoutMilliseconds : 3000;
normalizedRequest.Parameters = CloneParameters(request.Parameters);
return normalizedRequest;
}
private static VisionProcessResult ValidateRequest(FindTemplateImageRequest request)
{
if (request.Parameters == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindTemplateService: parameters cannot be null.");
}
if (request.TimeoutMilliseconds <= 0 || request.TimeoutMilliseconds > 600000)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TimeoutInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionTimeoutInvalid,
string.Format("FindTemplateService: TimeoutMilliseconds {0} is invalid. Expected range is [1, 60000].", request.TimeoutMilliseconds));
}
if (request.Parameters.MinScore <= 0d || request.Parameters.MinScore > 1d)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.MinScoreInvalid,
VisionAlarmIds.TemplateMinScoreInvalid,
MessageKey.VisionTemplateMinScoreInvalid,
string.Format("FindTemplateService: MinScore {0} is invalid. Expected range is (0, 1].", request.Parameters.MinScore));
}
if (string.IsNullOrWhiteSpace(request.Parameters.TemplatePath))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TemplatePathEmpty,
VisionAlarmIds.TemplatePathEmpty,
MessageKey.VisionTemplatePathEmpty,
"FindTemplateService: TemplatePath cannot be empty.");
}
if (request.Parameters.UseRoi && string.IsNullOrWhiteSpace(request.Parameters.RoiName))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RoiNameEmpty,
VisionAlarmIds.TemplateRoiInvalid,
MessageKey.VisionTemplateRoiInvalid,
"FindTemplateService: RoiName cannot be empty when UseRoi is true.");
}
return null;
}
private static FindTemplateParameters CloneParameters(FindTemplateParameters parameters)
{
if (parameters == null)
{
return new FindTemplateParameters();
}
FindTemplateParameters clone = new FindTemplateParameters();
clone.UseRoi = parameters.UseRoi;
clone.RoiName = parameters.RoiName;
clone.MinScore = parameters.MinScore;
clone.TemplatePath = parameters.TemplatePath;
return clone;
}
private static FindTemplatesResult CreateMultipleResult(IList<FindTemplateResult> matches)
{
FindTemplatesResult result = new FindTemplatesResult();
if (matches == null)
{
return result;
}
foreach (FindTemplateResult match in matches)
{
result.Items.Add(match);
}
return result;
}
private static FindTemplateImageRequest CreateImageRequest(FindTemplateRequest request)
{
FindTemplateImageRequest imageRequest = new FindTemplateImageRequest();
imageRequest.TimeoutMilliseconds = request.TimeoutMilliseconds;
imageRequest.Parameters = CloneParameters(request.Parameters);
return imageRequest;
}
private static VisionProcessResult<FindTemplateResult> ExecuteSingleMatch(
MxImage image,
FindTemplateImageRequest request)
{
Camera camera = image.Image;
if (camera == null)
{
return VisionProcessResult<FindTemplateResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
"FindTemplateService: input image does not contain a valid camera object.");
}
Template template = null;
double score = 0d;
Rectangle1 contour = null;
List<Circle> circles = null;
List<LineSeg> lines = null;
List<Rectangle2> rectangles = null;
int matchCode = WaferRecips.Instance.match_image(
camera,
request.Parameters.TemplatePath,
out template,
out score,
out contour,
out circles,
out lines,
out rectangles);
if (matchCode != 0)
{
return VisionProcessResult<FindTemplateResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindTemplateService: single template match failed. Code={matchCode}, TemplatePath={request.Parameters.TemplatePath}.");
}
if (template == null || double.IsNaN(score) || double.IsInfinity(score))
{
return VisionProcessResult<FindTemplateResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindTemplateService: single template match returned an invalid result. TemplatePath={request.Parameters.TemplatePath}, Score={score}.");
}
if (score < request.Parameters.MinScore)
{
return VisionProcessResult<FindTemplateResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindTemplateService: single template match score {score} is lower than required {request.Parameters.MinScore}. TemplatePath={request.Parameters.TemplatePath}.");
}
FindTemplateResult result = CreateSingleResult(template, score, contour, circles, lines, rectangles);
return VisionProcessResult<FindTemplateResult>.Success(result);
}
private static VisionProcessResult<FindTemplatesResult> ExecuteMultipleMatch(
MxImage image,
FindTemplateImageRequest request)
{
Camera camera = image.Image;
if (camera == null)
{
return VisionProcessResult<FindTemplatesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
"FindTemplateService: input image does not contain a valid camera object.");
}
double[] rows = null;
double[] cols = null;
double[] angles = null;
double[] scores = null;
List<Rectangle1> contours = null;
bool succeeded = WaferRecips.Instance.match_multi_0bject(
camera,
request.Parameters.TemplatePath,
out rows,
out cols,
out angles,
out scores,
out contours);
if (!succeeded)
{
return VisionProcessResult<FindTemplatesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindTemplateService: multiple template match failed. TemplatePath={request.Parameters.TemplatePath}.");
}
if (!HasConsistentMultipleResult(rows, cols, angles, scores, contours))
{
return VisionProcessResult<FindTemplatesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindTemplateService: multiple template match returned inconsistent result data. TemplatePath={request.Parameters.TemplatePath}.");
}
FindTemplatesResult result = CreateMultipleResult(rows, cols, angles, scores, contours);
return VisionProcessResult<FindTemplatesResult>.Success(result);
}
private static FindTemplateResult CreateSingleResult(
Template template,
double score,
Rectangle1 contour,
IList<Circle> circles,
IList<LineSeg> lines,
IList<Rectangle2> rectangles)
{
FindTemplateResult result = new FindTemplateResult();
result.CenterX = template.X;
result.CenterY = template.Y;
result.Score = score;
result.Match = CreateMatchResult(template.Phi, score, contour, circles, lines, rectangles);
result.Contour = CreateContourResult(contour);
AppendCircleResults(result.Circles, circles);
AppendLineResults(result.Lines, lines);
AppendRectangleResults(result.Rectangles, rectangles);
return result;
}
private static FindTemplatesResult CreateMultipleResult(
double[] rows,
double[] cols,
double[] angles,
double[] scores,
IList<Rectangle1> contours)
{
FindTemplatesResult result = new FindTemplatesResult();
for (int i = 0; i < rows.Length; i++)
{
FindTemplateResult item = new FindTemplateResult();
item.CenterX = cols[i];
item.CenterY = rows[i];
item.Score = scores[i];
item.Match = CreateMatchResult(angles[i], scores[i], contours[i], null, null, null);
item.Contour = CreateContourResult(contours[i]);
result.Items.Add(item);
}
return result;
}
private static VisionMatchResult CreateMatchResult(
double angle,
double score,
Rectangle1 contour,
IList<Circle> circles,
IList<LineSeg> lines,
IList<Rectangle2> rectangles)
{
VisionMatchResult match = new VisionMatchResult();
match.Angle = angle;
match.Score = score;
match.Tag = CreateMatchTag(contour, circles, lines, rectangles);
return match;
}
private static FindTemplateContourResult CreateContourResult(Rectangle1 contour)
{
if (contour == null)
{
return null;
}
FindTemplateContourResult result = new FindTemplateContourResult();
result.StartX = contour.Start_X;
result.StartY = contour.Start_Y;
result.EndX = contour.End_X;
result.EndY = contour.End_Y;
return result;
}
private static void AppendCircleResults(IList<FindTemplateCircleResult> destination, IList<Circle> circles)
{
if (destination == null || circles == null)
{
return;
}
foreach (Circle circle in circles)
{
if (circle == null)
{
continue;
}
FindTemplateCircleResult item = new FindTemplateCircleResult();
item.CenterX = circle.X;
item.CenterY = circle.Y;
item.Radius = circle.R;
destination.Add(item);
}
}
private static void AppendLineResults(IList<FindTemplateLineResult> destination, IList<LineSeg> lines)
{
if (destination == null || lines == null)
{
return;
}
foreach (LineSeg line in lines)
{
if (line == null)
{
continue;
}
FindTemplateLineResult item = new FindTemplateLineResult();
item.StartX = line.Start_X;
item.StartY = line.Start_Y;
item.EndX = line.End_X;
item.EndY = line.End_Y;
destination.Add(item);
}
}
private static void AppendRectangleResults(IList<FindTemplateRectangleResult> destination, IList<Rectangle2> rectangles)
{
if (destination == null || rectangles == null)
{
return;
}
foreach (Rectangle2 rectangle in rectangles)
{
if (rectangle == null)
{
continue;
}
FindTemplateRectangleResult item = new FindTemplateRectangleResult();
item.StartX = rectangle.XStart;
item.StartY = rectangle.YStart;
item.Width = rectangle.Width;
item.Height = rectangle.Height;
item.Angle = rectangle.Phi;
destination.Add(item);
}
}
private static string CreateMatchTag(
Rectangle1 contour,
IList<Circle> circles,
IList<LineSeg> lines,
IList<Rectangle2> rectangles)
{
if (contour == null && circles == null && lines == null && rectangles == null)
{
return null;
}
int circleCount = circles != null ? circles.Count : 0;
int lineCount = lines != null ? lines.Count : 0;
int rectangleCount = rectangles != null ? rectangles.Count : 0;
if (contour == null)
{
return $"Circles={circleCount};Lines={lineCount};Rects={rectangleCount}";
}
return $"Contour=({contour.Start_X},{contour.Start_Y})-({contour.End_X},{contour.End_Y});Circles={circleCount};Lines={lineCount};Rects={rectangleCount}";
}
private static bool HasConsistentMultipleResult(
double[] rows,
double[] cols,
double[] angles,
double[] scores,
IList<Rectangle1> contours)
{
if (rows == null || cols == null || angles == null || scores == null || contours == null)
{
return false;
}
if (rows.Length == 0)
{
return false;
}
if (rows.Length != cols.Length || rows.Length != angles.Length || rows.Length != scores.Length)
{
return false;
}
if (rows.Length != contours.Count)
{
return false;
}
for (int i = 0; i < rows.Length; i++)
{
if (double.IsNaN(rows[i]) || double.IsInfinity(rows[i]) ||
double.IsNaN(cols[i]) || double.IsInfinity(cols[i]) ||
double.IsNaN(angles[i]) || double.IsInfinity(angles[i]) ||
double.IsNaN(scores[i]) || double.IsInfinity(scores[i]))
{
return false;
}
}
return true;
}
private async Task<VisionProcessResult<TResult>> ExecuteWithCaptureAsync<TResult>(
MainShell.Common.CameraType cameraSource,
CameraCaptureOptions captureOptions,
FindTemplateImageRequest imageRequest,
Func<MxImage, FindTemplateImageRequest, CancellationToken, Task<VisionProcessResult<TResult>>> imageProcessor,
CancellationToken cancellationToken)
{
ImageCaptureResult captureResult = null;
MxImage image = null;
try
{
captureResult = await _imageCaptureService.CaptureAsync(
cameraSource,
captureOptions,
cancellationToken).ConfigureAwait(false);
if (captureResult == null)
{
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.NoFrame,
VisionAlarmIds.NoFrame,
MessageKey.VisionNoFrameReturned,
string.Format(
"FindTemplateService: image capture returned null result for camera '{0}'.",
cameraSource));
}
if (!captureResult.Succeeded)
{
return CreateFailureResult<TResult>(VisionResultMapper.CreateCaptureFailure(captureResult));
}
image = captureResult.Image;
if (image == null)
{
string message = string.Format(
"FindTemplateService: image capture succeeded but returned null image for camera '{0}'.",
cameraSource);
message.LogSysError();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
message);
}
return await imageProcessor(image, imageRequest, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
string message = $"FindTemplateService: capture orchestration was cancelled for camera '{cameraSource}'.";
message.LogInfo();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex);
}
catch (Exception ex)
{
string message = $"FindTemplateService: capture orchestration failed for camera '{cameraSource}'. {ex.Message}";
message.LogSysError();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex);
}
finally
{
if (image != null)
{
image.Dispose();
}
}
}
private static VisionProcessResult<TData> CreateFailureResult<TData>(VisionProcessResult result)
{
return VisionProcessResult<TData>.Failure(
result.FailureCategory,
result.ErrorCode,
result.AlarmId,
result.UserMessageKey,
result.Message,
result.Exception);
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD>
/// </summary>
public class FindTemplatesResult
{
public FindTemplatesResult()
{
Items = new List<FindTemplateResult>();
}
/// <summary>
/// ƥ<><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD>
/// </summary>
public IList<FindTemplateResult> Items { get; private set; }
}
}

View File

@@ -0,0 +1,42 @@
using MainShell.Models;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// ģ<><C4A3>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӿڡ<D3BF>
/// </summary>
public interface IFindTemplateService
{
/// <summary>
/// <20>ɼ<EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD>ִ<EFBFBD>е<EFBFBD><D0B5><EFBFBD>ģ<EFBFBD><C4A3>ƥ<EFBFBD>
/// </summary>
Task<VisionProcessResult<FindTemplateResult>> ProcessAsync(
FindTemplateRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC>ֱ<EFBFBD><D6B1>ִ<EFBFBD>е<EFBFBD><D0B5><EFBFBD>ģ<EFBFBD><C4A3>ƥ<EFBFBD>
/// </summary>
Task<VisionProcessResult<FindTemplateResult>> ProcessImageAsync(
MxImage image,
FindTemplateImageRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// <20>ɼ<EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD>ִ<EFBFBD>ж<EFBFBD><D0B6><EFBFBD>ģ<EFBFBD><C4A3>ƥ<EFBFBD>
/// </summary>
Task<VisionProcessResult<FindTemplatesResult>> ProcessMultipleAsync(
FindTemplateRequest request,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC>ֱ<EFBFBD><D6B1>ִ<EFBFBD>ж<EFBFBD><D0B6><EFBFBD>ģ<EFBFBD><C4A3>ƥ<EFBFBD>
/// </summary>
Task<VisionProcessResult<FindTemplatesResult>> ProcessMultipleImageAsync(
MxImage image,
FindTemplateImageRequest request,
CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@@ -0,0 +1,18 @@
namespace MainShell.Vision
{
/// <summary>
/// 图像采集模式
/// </summary>
public enum CameraCaptureMode
{
/// <summary>
/// 从连续流中取图相机持续采图从内部缓冲区取最新一帧SnapImage
/// </summary>
Stream,
/// <summary>
/// 软触发单帧采集发出软触发命令后等待相机响应帧ExcuteSoftTrigger
/// </summary>
SoftTrigger,
}
}

View File

@@ -0,0 +1,68 @@
using MwFramework.Device;
namespace MainShell.Vision
{
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼѡ<CDBC>
/// <20>ö<EFBFBD><C3B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><C4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ץ<EFBFBD><D7A5>״̬<D7B4><CCAC>Ӳ<EFBFBD><D3B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽӦ<CABD><D3A6><EFBFBD>ϲ<EFBFBD><CFB2>ڲ<EFBFBD>ͼǰ<CDBC><C7B0><EFBFBD><EFBFBD>׼<EFBFBD><D7BC><EFBFBD><EFBFBD>
/// </summary>
public class CameraCaptureOptions
{
public static CameraCaptureOptions CreateSoftTrigger(int timeoutMilliseconds = 5000)
{
CameraCaptureOptions options = new CameraCaptureOptions();
options.CaptureMode = CameraCaptureMode.SoftTrigger;
options.TimeoutMilliseconds = timeoutMilliseconds;
return options;
}
public static CameraCaptureOptions CreateStream(int timeoutMilliseconds = 5000)
{
CameraCaptureOptions options = new CameraCaptureOptions();
options.CaptureMode = CameraCaptureMode.Stream;
options.TimeoutMilliseconds = timeoutMilliseconds;
return options;
}
public CameraCaptureOptions()
{
CaptureMode = CameraCaptureMode.Stream;
TimeoutMilliseconds = 5000;
AutoStartGrabbing = false;
TriggerMode = null;
TriggerSource = null;
TriggerDelay = null;
}
/// <summary>
/// <20>ɼ<EFBFBD><C9BC><EFBFBD>ʽ<EFBFBD><CABD>
/// </summary>
public CameraCaptureMode CaptureMode { get; set; }
/// <summary>
/// <20><>ʱʱ<CAB1><EFBFBD><E4A3A8><EFBFBD><EFBFBD><EBA3A9>
/// </summary>
public int TimeoutMilliseconds { get; set; }
/// <summary>
/// <20><><EFBFBD>ݾɵ<DDBE><C9B5>ñ<EFBFBD><C3B1><EFBFBD><EFBFBD>ֶΡ<D6B6><CEA1><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٸ<EFBFBD><D9B8>ݸ<EFBFBD>ֵ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>ץ<EFBFBD><D7A5><EFBFBD><EFBFBD>
/// </summary>
public bool AutoStartGrabbing { get; set; }
/// <summary>
/// <20><><EFBFBD>ݾɵ<DDBE><C9B5>ñ<EFBFBD><C3B1><EFBFBD><EFBFBD>ֶΡ<D6B6><CEA1><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٸ<EFBFBD><D9B8>ݸ<EFBFBD>ֵ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>ô<EFBFBD><C3B4><EFBFBD>ģʽ<C4A3><CABD>
/// </summary>
public CameraTriggerMode? TriggerMode { get; set; }
/// <summary>
/// <20><><EFBFBD>ݾɵ<DDBE><C9B5>ñ<EFBFBD><C3B1><EFBFBD><EFBFBD>ֶΡ<D6B6><CEA1><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٸ<EFBFBD><D9B8>ݸ<EFBFBD>ֵ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>ô<EFBFBD><C3B4><EFBFBD>Դ<EFBFBD><D4B4>
/// </summary>
public CameraTriggerSource? TriggerSource { get; set; }
/// <summary>
/// <20><><EFBFBD>ݾɵ<DDBE><C9B5>ñ<EFBFBD><C3B1><EFBFBD><EFBFBD>ֶΡ<D6B6><CEA1><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٸ<EFBFBD><D9B8>ݸ<EFBFBD>ֵ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1>
/// </summary>
public double? TriggerDelay { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using MainShell.Common;
using System.Threading;
using System.Threading.Tasks;
using AppCameraType = MainShell.Common.CameraType;
namespace MainShell.Vision
{
/// <summary>
/// ͼ<><CDBC><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӿڡ<D3BF>
/// </summary>
public interface IImageCaptureService
{
/// <summary>
/// <20><>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ򻯲<CDBC><F2BBAFB2><EFBFBD><EFBFBD>ɼ<EFBFBD>һ֡ͼ<D6A1><CDBC><EFBFBD><EFBFBD>
/// </summary>
Task<ImageCaptureResult> CaptureAsync(
AppCameraType source,
int timeoutMilliseconds,
CameraCaptureMode captureMode = CameraCaptureMode.Stream,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// <20><>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>һ֡ͼ<D6A1><CDBC><EFBFBD><EFBFBD>
/// </summary>
Task<ImageCaptureResult> CaptureAsync(
AppCameraType source,
CameraCaptureOptions options,
CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@@ -0,0 +1,119 @@
using MainShell.Common;
using MainShell.Models;
using System;
using AppCameraType = MainShell.Common.CameraType;
namespace MainShell.Vision
{
/// <summary>
/// ????????????
/// </summary>
public enum ImageCaptureStatus
{
Success = 0,
Cancelled = 1,
CameraNotFound = 2,
CameraNotOpen = 3,
CameraNotGrabbing = 4,
Timeout = 5,
NoFrame = 6,
SoftTriggerFailed = 7,
DriverError = 8,
}
/// <summary>
/// ??????????
/// </summary>
public class ImageCaptureResult
{
public ImageCaptureResult()
{
Status = ImageCaptureStatus.DriverError;
ErrorCode = VisionErrorCode.DriverError;
FailureCategory = VisionFailureCategory.Driver;
Message = string.Empty;
UserMessageKey = MessageKey.None;
}
public ImageCaptureStatus Status { get; set; }
public VisionErrorCode ErrorCode { get; set; }
public VisionFailureCategory FailureCategory { get; set; }
public int? AlarmId { get; set; }
public MessageKey UserMessageKey { get; set; }
public AppCameraType CameraSource { get; set; }
public CameraCaptureMode CaptureMode { get; set; }
public string Message { get; set; }
public Exception Exception { get; set; }
public MxImage Image { get; set; }
public bool Succeeded
{
get
{
return Status == ImageCaptureStatus.Success;
}
}
public bool IsCancelled
{
get
{
return Status == ImageCaptureStatus.Cancelled;
}
}
public static ImageCaptureResult Success(AppCameraType source, CameraCaptureMode captureMode, MxImage image)
{
return new ImageCaptureResult
{
Status = ImageCaptureStatus.Success,
ErrorCode = VisionErrorCode.None,
FailureCategory = VisionFailureCategory.None,
AlarmId = null,
UserMessageKey = MessageKey.None,
CameraSource = source,
CaptureMode = captureMode,
Image = image,
Message = string.Format(
"ImageCaptureService: capture succeeded from '{0}' with mode '{1}'.",
source,
captureMode),
};
}
public static ImageCaptureResult Failure(
ImageCaptureStatus status,
VisionErrorCode errorCode,
VisionFailureCategory failureCategory,
int? alarmId,
MessageKey userMessageKey,
AppCameraType source,
CameraCaptureMode captureMode,
string message,
Exception exception = null)
{
return new ImageCaptureResult
{
Status = status,
ErrorCode = errorCode,
FailureCategory = failureCategory,
AlarmId = alarmId,
UserMessageKey = userMessageKey,
CameraSource = source,
CaptureMode = captureMode,
Message = message,
Exception = exception,
Image = null,
};
}
}
}

View File

@@ -0,0 +1,287 @@
using MainShell.Common;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using MwFramework.Device;
using MwFramework.Device.Model;
using System;
using System.Threading;
using System.Threading.Tasks;
using AppCameraType = MainShell.Common.CameraType;
namespace MainShell.Vision
{
/// <summary>
/// ???????????????? HardwareManager ??????????????????????????<3F><>???????
/// </summary>
[Singleton]
public class ImageCaptureService : IImageCaptureService
{
private readonly HardwareManager _hardwareManager;
private readonly object _upCameraCaptureLock = new object();
private readonly object _downCameraCaptureLock = new object();
private readonly object _mapCameraCaptureLock = new object();
private readonly object _upWsCameraCaptureLock = new object();
private readonly object _upWideCameraCaptureLock = new object();
public ImageCaptureService(HardwareManager hardwareManager)
{
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
}
/// <summary>
/// ??????????????????????
/// </summary>
public Task<ImageCaptureResult> CaptureAsync(
AppCameraType source,
int timeoutMilliseconds,
CameraCaptureMode captureMode = CameraCaptureMode.Stream,
CancellationToken cancellationToken = default(CancellationToken))
{
CameraCaptureOptions options = new CameraCaptureOptions();
options.TimeoutMilliseconds = timeoutMilliseconds;
options.CaptureMode = captureMode;
return CaptureAsync(source, options, cancellationToken);
}
/// <summary>
/// ?????????????????
/// ????????????????????????????????????????????????????????<3F><>??
/// </summary>
public Task<ImageCaptureResult> CaptureAsync(
AppCameraType source,
CameraCaptureOptions options,
CancellationToken cancellationToken = default(CancellationToken))
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
int timeoutMilliseconds = options.TimeoutMilliseconds;
if (timeoutMilliseconds <= 0)
{
timeoutMilliseconds = 5000;
}
MwCamera camera = GetCamera(source);
if (camera == null)
{
string message = string.Format("ImageCaptureService: camera '{0}' not found in HardwareManager.", source);
message.LogSysError();
return Task.FromResult(ImageCaptureResult.Failure(
ImageCaptureStatus.CameraNotFound,
VisionErrorCode.CameraNotFound,
VisionFailureCategory.Capture,
VisionAlarmIds.CameraNotFound,
MessageKey.VisionCameraNotFound,
source,
options.CaptureMode,
message));
}
if (!camera.IsOpen)
{
string message = string.Format("ImageCaptureService: camera '{0}' is not open.", source);
message.LogSysError();
return Task.FromResult(ImageCaptureResult.Failure(
ImageCaptureStatus.CameraNotOpen,
VisionErrorCode.CameraNotOpen,
VisionFailureCategory.Capture,
VisionAlarmIds.CameraNotOpen,
MessageKey.VisionCameraNotOpen,
source,
options.CaptureMode,
message));
}
if (!camera.IsGrabbing)
{
string message = string.Format(
"ImageCaptureService: camera '{0}' is not grabbing. Capture mode '{1}' requires grabbing to be prepared by caller.",
source,
options.CaptureMode);
message.LogSysError();
return Task.FromResult(ImageCaptureResult.Failure(
ImageCaptureStatus.CameraNotGrabbing,
VisionErrorCode.CameraNotGrabbing,
VisionFailureCategory.Capture,
VisionAlarmIds.CameraNotGrabbing,
MessageKey.VisionCameraNotGrabbing,
source,
options.CaptureMode,
message));
}
string.Format(
"ImageCaptureService: capturing from '{0}' (mode={1}, timeout={2}ms).",
source,
options.CaptureMode,
timeoutMilliseconds).LogInfo();
object captureLock = GetCaptureLock(source);
try
{
CameraData data;
lock (captureLock)
{
cancellationToken.ThrowIfCancellationRequested();
switch (options.CaptureMode)
{
case CameraCaptureMode.SoftTrigger:
data = ExecuteSoftTriggerSync(camera, source, timeoutMilliseconds, out bool softTriggerFailed);
if (softTriggerFailed)
{
string softTriggerFailureMessage = string.Format(
"ImageCaptureService: soft trigger capture failed for camera '{0}' (timeout={1}ms).",
source,
timeoutMilliseconds);
softTriggerFailureMessage.LogSysError();
return Task.FromResult(ImageCaptureResult.Failure(
ImageCaptureStatus.SoftTriggerFailed,
VisionErrorCode.SoftTriggerFailed,
VisionFailureCategory.Driver,
VisionAlarmIds.SoftTriggerFailed,
MessageKey.VisionSoftTriggerFailed,
source,
options.CaptureMode,
softTriggerFailureMessage));
}
break;
case CameraCaptureMode.Stream:
data = camera.SnapImage(timeoutMilliseconds);
break;
default:
throw new NotSupportedException(string.Format("ImageCaptureService: capture mode '{0}' is not supported.", options.CaptureMode));
}
}
cancellationToken.ThrowIfCancellationRequested();
if (data == null)
{
string message = string.Format(
"ImageCaptureService: no frame received from '{0}' with capture mode '{1}'.",
source,
options.CaptureMode);
message.LogSysError();
ImageCaptureStatus noFrameStatus = options.CaptureMode == CameraCaptureMode.SoftTrigger
? ImageCaptureStatus.Timeout
: ImageCaptureStatus.NoFrame;
VisionErrorCode noFrameErrorCode = options.CaptureMode == CameraCaptureMode.SoftTrigger
? VisionErrorCode.CaptureTimeout
: VisionErrorCode.NoFrame;
int? noFrameAlarmId = options.CaptureMode == CameraCaptureMode.SoftTrigger
? (int?)VisionAlarmIds.CaptureTimeout
: (int?)VisionAlarmIds.NoFrame;
MessageKey noFrameMessageKey = options.CaptureMode == CameraCaptureMode.SoftTrigger
? MessageKey.VisionCaptureTimeout
: MessageKey.VisionNoFrameReturned;
return Task.FromResult(ImageCaptureResult.Failure(
noFrameStatus,
noFrameErrorCode,
VisionFailureCategory.Capture,
noFrameAlarmId,
noFrameMessageKey,
source,
options.CaptureMode,
message));
}
return Task.FromResult(ImageCaptureResult.Success(source, options.CaptureMode, new MxImage(data)));
}
catch (OperationCanceledException ex)
{
string message = string.Format("ImageCaptureService: capture from '{0}' was cancelled by caller.", source);
message.LogInfo();
return Task.FromResult(ImageCaptureResult.Failure(
ImageCaptureStatus.Cancelled,
VisionErrorCode.OperationCancelled,
VisionFailureCategory.Cancelled,
null,
MessageKey.VisionOperationCancelled,
source,
options.CaptureMode,
message,
ex));
}
catch (Exception ex)
{
string message = string.Format("ImageCaptureService: capture from '{0}' failed. {1}", source, ex.Message);
message.LogSysError();
return Task.FromResult(ImageCaptureResult.Failure(
ImageCaptureStatus.DriverError,
VisionErrorCode.DriverError,
VisionFailureCategory.Driver,
VisionAlarmIds.DriverError,
MessageKey.VisionDriverError,
source,
options.CaptureMode,
message,
ex));
}
}
private static CameraData ExecuteSoftTriggerSync(MwCamera camera, AppCameraType source, int timeoutMilliseconds, out bool softTriggerFailed)
{
softTriggerFailed = false;
CameraData data = new CameraData();
DriverLibResult result = camera.ExcuteSoftTrigger(ref data);
if (result != DriverLibResult.DriverLibNoError)
{
softTriggerFailed = true;
string.Format(
"ImageCaptureService: soft trigger capture failed for camera '{0}' (timeout={1}ms), driver result={2}.",
source,
timeoutMilliseconds,
result).LogSysError();
return null;
}
if (data == null)
{
string.Format(
"ImageCaptureService: soft trigger capture returned null frame for camera '{0}' (timeout={1}ms).",
source,
timeoutMilliseconds).LogSysError();
return null;
}
return data;
}
private MwCamera GetCamera(AppCameraType source)
{
return _hardwareManager.GetCamera(source).Camera;
}
private object GetCaptureLock(AppCameraType source)
{
switch (source)
{
case AppCameraType.TopPositionCamera:
return _upCameraCaptureLock;
case AppCameraType.TopWideCamera:
return _mapCameraCaptureLock;
case AppCameraType.MapCamera:
return _downCameraCaptureLock;
case AppCameraType.TopWsCamera:
return _upWsCameraCaptureLock;
case AppCameraType.TopWideWsCamera:
return _upWideCameraCaptureLock;
default:
throw new ArgumentOutOfRangeException(nameof(source), source, "ImageCaptureService: unknown camera type for capture lock.");
}
}
}
}

View File

@@ -0,0 +1,430 @@
# Vision 模块使用文档
## 1. 文档范围
本文档说明当前 [`MainShell/Vision`](MainShell/Vision) 模块在“一个算法一个文件夹”的结构下的职责划分和推荐使用方式,重点覆盖:
- [`IImageCaptureService`](MainShell/Vision/ImageCapture/IImageCaptureService.cs) / [`ImageCaptureService`](MainShell/Vision/ImageCapture/ImageCaptureService.cs):负责按当前已准备好的相机模式执行采图
- [`IFindTemplateService`](MainShell/Vision/FindTemplate/IFindTemplateService.cs:10) / [`FindTemplateService`](MainShell/Vision/FindTemplate/FindTemplateService.cs:14):负责模板匹配算法的采图与处理
- [`VisionParController`](MainShell/Models/VisionParController.cs):负责视觉前置硬件参数准备,包括相机参数、触发模式和抓流状态
- [`FindTemplateRequest`](MainShell/Vision/FindTemplate/FindTemplateRequest.cs:8) / [`FindTemplateImageRequest`](MainShell/Vision/FindTemplate/FindTemplateImageRequest.cs:6):负责模板匹配请求建模
- [`VisionProcessResult`](MainShell/Vision/Common/VisionProcessResult.cs:8) / [`VisionProcessResult<TData>`](MainShell/Vision/Common/VisionProcessResultOfT.cs:9):负责承载通用处理状态与强类型算法结果
- [`ImageCaptureResult`](MainShell/Vision/ImageCapture/ImageCaptureResult.cs:25) / [`ImageCaptureStatus`](MainShell/Vision/ImageCapture/ImageCaptureResult.cs:11):负责承载采图状态、消息与图像对象
当前目录划分如下:
- `Common/`:共享结果模型与算法参数基类
- `ImageCapture/`:采图接口、实现与采图相关模型
- `FindTemplate/`:模板匹配算法接口、实现、请求、参数、结果
- `FindMark/`Mark 识别参数与后续算法扩展入口
- `FindDie/`Die 定位参数与后续算法扩展入口
- `FindCenter/`:中心识别草稿算法骨架
## 2. 当前模块职责边界
### 2.1 [`VisionParController`](MainShell/Models/VisionParController.cs)
负责视觉前置硬件准备:
- 设置曝光和增益
- 设置触发模式
- 设置触发源
- 设置触发延时
- 启动或停止抓流
- 设置光源参数
### 2.2 [`ImageCaptureService`](MainShell/Vision/ImageCapture/ImageCaptureService.cs)
只负责执行采图动作:
- 不再自动切换触发模式
- 不再自动切换触发源
- 不再自动设置触发延时
- 不再自动启停抓流
- 同一相机的采图请求在服务内部按相机粒度串行执行
- 通过 [`ImageCaptureResult`](MainShell/Vision/ImageCapture/ImageCaptureResult.cs:25) 返回成功、取消、无帧或设备状态异常等结果
调用方必须在采图前通过 [`VisionParController`](MainShell/Models/VisionParController.cs) 或其他上层硬件控制流程准备好相机状态。
### 2.3 [`FindTemplateService`](MainShell/Vision/FindTemplate/FindTemplateService.cs:14)
负责:
- 模板匹配参数校验
- 模板匹配采图流程组织
- 单个模板匹配结果构建
- 多个模板匹配结果集合构建
- 返回 [`VisionProcessResult<FindTemplateResult>`](MainShell/Vision/Common/VisionProcessResultOfT.cs:9) 或 [`VisionProcessResult<FindTemplatesResult>`](MainShell/Vision/Common/VisionProcessResultOfT.cs:9)
当前模板匹配算法本体仍为占位实现,尚未接入真实视觉算法库。当前占位实现会明确返回失败,避免上层流程误判为识别成功。
## 3. 当前推荐结构
当前推荐使用“一个算法一个实现”的结构,即:
- 每个算法拥有独立接口,例如 [`IFindTemplateService`](MainShell/Vision/FindTemplate/IFindTemplateService.cs:10)
- 每个算法拥有独立实现,例如 [`FindTemplateService`](MainShell/Vision/FindTemplate/FindTemplateService.cs:14)
- 每个算法拥有独立请求、参数、结果模型
- 不再使用统一算法总入口服务
推荐未来新增算法继续按相同方式组织,例如:
- `FindMark/IFindMarkService.cs`
- `FindMark/FindMarkService.cs`
- `FindMark/FindMarkRequest.cs`
- `FindMark/FindMarkResult.cs`
如果算法需要“已有图像直接处理”的场景,可按需再增加 `XXXImageRequest.cs`
## 4. 推荐调用顺序
推荐顺序如下:
1. 先通过 [`VisionParController`](MainShell/Models/VisionParController.cs) 准备相机与光源参数
2. 再通过 [`IImageCaptureService`](MainShell/Vision/ImageCapture/IImageCaptureService.cs) 执行采图,或由具体算法服务内部采图
3. 最后通过具体算法服务,例如 [`IFindTemplateService`](MainShell/Vision/FindTemplate/IFindTemplateService.cs:10),执行算法
## 5. 采图前提说明
### 5.1 连续流采图
当 [`CameraCaptureMode.Stream`](MainShell/Vision/ImageCapture/CameraCaptureMode.cs) 用于采图时,调用前应确保:
- 相机已打开
- 相机已处于 grabbing 状态
- 相机已被配置为适合连续流取图的工作模式
### 5.2 软触发采图
当 [`CameraCaptureMode.SoftTrigger`](MainShell/Vision/ImageCapture/CameraCaptureMode.cs) 用于采图时,调用前应确保:
- 相机已打开
- 相机已处于 grabbing 状态
- 相机已被配置为软触发模式
- 触发源已设置为软件触发
如果上述前提不满足,[`ImageCaptureService`](MainShell/Vision/ImageCapture/ImageCaptureService.cs) 不会代替调用方修改硬件状态,而是直接失败并记录日志。
## 6. 请求模型
模板匹配当前使用独立请求模型:
- [`FindTemplateRequest`](MainShell/Vision/FindTemplate/FindTemplateRequest.cs:8):用于“采图 + 算法”流程
- [`FindTemplateImageRequest`](MainShell/Vision/FindTemplate/FindTemplateImageRequest.cs:6):用于“已有图像 + 算法”流程
推荐新算法默认最小模型集为:
- `XXXRequest`
- `XXXResult`
只有在算法确实需要时,再增加:
- `XXXParameters`
- `XXXImageRequest`
## 7. 结果模型
当前结果模型已调整为四层:
- [`VisionProcessResult`](MainShell/Vision/Common/VisionProcessResult.cs:8):仅承载成功/失败/异常/消息
- [`VisionProcessResult<TData>`](MainShell/Vision/Common/VisionProcessResultOfT.cs:9):承载强类型算法结果数据
- [`ImageCaptureResult`](MainShell/Vision/ImageCapture/ImageCaptureResult.cs:25):承载采图动作结果、状态、消息和图像对象
- [`VisionMatchResult`](MainShell/Vision/Common/VisionMatchResult.cs:6):承载扩展匹配信息
模板匹配当前返回:
- [`VisionProcessResult<FindTemplateResult>`](MainShell/Vision/Common/VisionProcessResultOfT.cs:9)
- [`VisionProcessResult<FindTemplatesResult>`](MainShell/Vision/Common/VisionProcessResultOfT.cs:9)
[`FindTemplateResult`](MainShell/Vision/FindTemplate/FindTemplateResult.cs:6) 当前包含:
- `CenterX`
- `CenterY`
- `Score`
- `Match`
[`FindTemplatesResult`](MainShell/Vision/FindTemplate/FindTemplatesResult.cs:6) 当前包含:
- `Items`
## 8. 当前算法实现状态
当前状态如下:
- 采图服务已按新职责边界实现
- 模板匹配保持独立接口与独立服务实现
- 采图结果已区分成功、取消、设备状态错误、无帧和驱动异常
- 已不再使用统一视觉算法入口服务
- [`FindTemplateService`](MainShell/Vision/FindTemplate/FindTemplateService.cs:14) 当前仍为占位实现,尚未接入真实算法
- 已预留单个模板匹配与多个模板匹配两套接口
- 多个模板匹配当前已明确约定输出规则:保留底层原始顺序,不做得分筛选、不做去重、不做聚类合并
因此本模块当前适合作为:
- 视觉接入骨架
- 采图与参数联调骨架
- 按算法拆服务的长期结构基础
## 9. 取消与超时说明
[`ImageCaptureService`](MainShell/Vision/ImageCapture/ImageCaptureService.cs) 当前采图仍基于底层同步驱动接口。
这意味着:
- `CancellationToken` 主要控制外层任务流程
- 取消仅能在进入驱动前或驱动返回后生效
- 不一定能真正中断驱动内部阻塞调用
- 软触发底层超时仍受驱动实现限制
[`FindTemplateService`](MainShell/Vision/FindTemplate/FindTemplateService.cs:14) 当前会区分:
- 调用方主动取消
- 参数校验失败
- 算法未实现
- 采图阶段失败
## 10. 图像对象生命周期说明
- 通过 [`FindTemplateService.ProcessAsync()`](MainShell/Vision/FindTemplate/FindTemplateService.cs:22) 内部采集到的图像,由服务内部释放
- 通过 [`IFindTemplateService.ProcessImageAsync()`](MainShell/Vision/FindTemplate/IFindTemplateService.cs:22) 或 [`IFindTemplateService.ProcessMultipleImageAsync()`](MainShell/Vision/FindTemplate/IFindTemplateService.cs:36) 传入的已有图像,由调用方负责释放
- 调用方不应在算法执行期间提前释放正在使用的图像对象
## 11. 多模板匹配规则
当前多个模板匹配算法接口约定如下:
1. 输入一张图像和一组模板匹配参数
2. 调用底层模板匹配能力,获取原始匹配结果集合
3. 不按 `MinScore` 做二次筛选
4. 不做最佳值选优
5. 不做结果去重
6. 不做聚类合并
7. 保持底层算法返回顺序
8. 将原始匹配结果逐个映射为 [`FindTemplateResult`](MainShell/Vision/FindTemplate/FindTemplateResult.cs:6) 并输出到 [`FindTemplatesResult`](MainShell/Vision/FindTemplate/FindTemplatesResult.cs:6)
## 12. 新增算法模板建议
推荐新增一个普通算法时,默认最小文件集合为:
- `IXXXService.cs`
- `XXXService.cs`
- `XXXRequest.cs`
- `XXXResult.cs`
复杂算法再按需增加:
- `XXXParameters.cs`
- `XXXImageRequest.cs`
这样可以兼顾:
- 一算法一实现的清晰边界
- 文件数量不过度膨胀
- 后续维护和问题定位的可读性
## 13. 代码使用示例
### 13.1 示例一:由算法服务内部采图后执行单个模板匹配
这是推荐的业务调用方式。先通过 [`VisionParController`](MainShell/Models/VisionParController.cs) 准备相机状态,再调用 [`IFindTemplateService.ProcessAsync()`](MainShell/Vision/FindTemplate/IFindTemplateService.cs:14)。
```csharp
using MainShell.Common;
using MainShell.Vision;
using System.Threading;
using System.Threading.Tasks;
public class VisionUsageExample
{
private readonly IFindTemplateService _findTemplateService;
public VisionUsageExample(IFindTemplateService findTemplateService)
{
_findTemplateService = findTemplateService;
}
public async Task ExecuteAsync(CancellationToken cancellationToken)
{
FindTemplateRequest request = new FindTemplateRequest();
request.CameraSource = CameraType.UpCamera;
request.TimeoutMilliseconds = 3000;
request.CaptureOptions.TimeoutMilliseconds = 2000;
request.CaptureOptions.CaptureMode = CameraCaptureMode.Stream;
request.Parameters.UseRoi = true;
request.Parameters.RoiName = "DieCenterRoi";
request.Parameters.TemplatePath = "Template/DieCenter";
request.Parameters.MinScore = 0.8d;
VisionProcessResult<FindTemplateResult> result = await _findTemplateService.ProcessAsync(
request,
cancellationToken).ConfigureAwait(false);
if (!result.Succeeded)
{
string failureMessage = result.Message;
return;
}
FindTemplateResult data = result.Data;
double centerX = data.CenterX;
double centerY = data.CenterY;
double score = data.Score;
}
}
```
### 13.2 示例二:对已有图像执行多个模板匹配
这个方式适合调试、联调或者需要复用同一帧图像的场景。
```csharp
using MainShell.Common;
using MainShell.Models;
using MainShell.Vision;
using System.Threading;
using System.Threading.Tasks;
public class VisionImageUsageExample
{
private readonly IImageCaptureService _imageCaptureService;
private readonly IFindTemplateService _findTemplateService;
public VisionImageUsageExample(
IImageCaptureService imageCaptureService,
IFindTemplateService findTemplateService)
{
_imageCaptureService = imageCaptureService;
_findTemplateService = findTemplateService;
}
public async Task ExecuteAsync(CancellationToken cancellationToken)
{
CameraCaptureOptions captureOptions = new CameraCaptureOptions();
captureOptions.TimeoutMilliseconds = 2000;
captureOptions.CaptureMode = CameraCaptureMode.Stream;
ImageCaptureResult captureResult = await _imageCaptureService.CaptureAsync(
CameraType.UpCamera,
captureOptions,
cancellationToken).ConfigureAwait(false);
if (!captureResult.Succeeded)
{
string captureFailureMessage = captureResult.Message;
return;
}
MxImage image = captureResult.Image;
try
{
FindTemplateImageRequest request = new FindTemplateImageRequest();
request.TimeoutMilliseconds = 3000;
request.Parameters.UseRoi = false;
request.Parameters.TemplatePath = "Template/DieCenter";
request.Parameters.MinScore = 0.8d;
VisionProcessResult<FindTemplatesResult> result = await _findTemplateService.ProcessMultipleImageAsync(
image,
request,
cancellationToken).ConfigureAwait(false);
if (!result.Succeeded)
{
string failureMessage = result.Message;
return;
}
FindTemplatesResult data = result.Data;
int count = data.Items.Count;
}
finally
{
if (image != null)
{
image.Dispose();
}
}
}
}
```
### 13.3 示例三:如何判断采图失败原因
[`ImageCaptureResult`](MainShell/Vision/ImageCapture/ImageCaptureResult.cs:25) 适合在上层做更细的失败分流。
```csharp
using MainShell.Common;
using MainShell.Vision;
using System.Threading;
using System.Threading.Tasks;
public class CaptureResultExample
{
private readonly IImageCaptureService _imageCaptureService;
public CaptureResultExample(IImageCaptureService imageCaptureService)
{
_imageCaptureService = imageCaptureService;
}
public async Task ExecuteAsync(CancellationToken cancellationToken)
{
ImageCaptureResult captureResult = await _imageCaptureService.CaptureAsync(
CameraType.UpCamera,
2000,
CameraCaptureMode.Stream,
cancellationToken).ConfigureAwait(false);
if (captureResult.Succeeded)
{
return;
}
switch (captureResult.Status)
{
case ImageCaptureStatus.Cancelled:
break;
case ImageCaptureStatus.CameraNotFound:
case ImageCaptureStatus.CameraNotOpen:
case ImageCaptureStatus.CameraNotGrabbing:
case ImageCaptureStatus.NoFrame:
case ImageCaptureStatus.DriverError:
string message = captureResult.Message;
break;
default:
break;
}
}
}
```
### 13.4 示例四:新增一个新算法时的推荐骨架
`FindMark` 为例,推荐至少准备:
- `FindMark/IFindMarkService.cs`
- `FindMark/FindMarkService.cs`
- `FindMark/FindMarkRequest.cs`
- `FindMark/FindMarkResult.cs`
如果需要已有图像处理,再增加:
- `FindMark/FindMarkImageRequest.cs`
如果参数较复杂,再增加:
- `FindMark/FindMarkParameters.cs`
服务实现建议沿用 [`FindTemplateService`](MainShell/Vision/FindTemplate/FindTemplateService.cs:14) 的结构:
- `ProcessAsync()` 负责“采图 + 单个模板匹配”
- `ProcessImageAsync()` 负责“已有图像 + 单个模板匹配”
- `ProcessMultipleAsync()` 负责“采图 + 多个模板匹配”
- `ProcessMultipleImageAsync()` 负责“已有图像 + 多个模板匹配”
- `NormalizeRequest()` 负责标准化
- `ValidateRequest()` 负责参数校验
- `CloneParameters()` 负责避免共享可变输入对象
这样可以保证不同算法之间结构一致,便于维护和扩展。

View File

@@ -0,0 +1,24 @@
using MainShell.Filewritable;
using MwFramework.Controls.SystemCalib;
using SemiconductorVisionAlgorithm.SemiCalib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Vision
{
public interface IVisionStartupInitializationService
{
void Initialize();
}
public class VisionStartupInitializationService : IVisionStartupInitializationService
{
public void Initialize()
{
JM1Vision.JM1Manager.Instance.Load(Paths.CalibPath);
CalibManager.Instance.init_calib_params(UIDataManager.CalibDataDir);
}
}
}