添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MainShell.Vision
|
||||
{
|
||||
/// <summary>
|
||||
/// 视觉算法参数基类
|
||||
/// </summary>
|
||||
public abstract class VisionAlgorithmParameters
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>>())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace MainShell.Vision
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像采集模式
|
||||
/// </summary>
|
||||
public enum CameraCaptureMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 从连续流中取图:相机持续采图,从内部缓冲区取最新一帧(SnapImage)
|
||||
/// </summary>
|
||||
Stream,
|
||||
|
||||
/// <summary>
|
||||
/// 软触发单帧采集:发出软触发命令后等待相机响应帧(ExcuteSoftTrigger)
|
||||
/// </summary>
|
||||
SoftTrigger,
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
430
MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Vision/README.md
Normal file
430
MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Vision/README.md
Normal 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()` 负责避免共享可变输入对象
|
||||
|
||||
这样可以保证不同算法之间结构一致,便于维护和扩展。
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user