301 lines
14 KiB
C#
301 lines
14 KiB
C#
/*
|
|
* Copyright (c) 2025 LG Electronics Inc.
|
|
* SPDX-License-Identifier: LicenseRef-LGE-Proprietary
|
|
*/
|
|
using Aurora.Api;
|
|
using MilCommon;
|
|
using VisionCommon;
|
|
using Hermes;
|
|
using Matrox.MatroxImagingLibrary;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using Aurora.Api.DataType;
|
|
|
|
namespace MilPattern {
|
|
public enum eMilCodeType {
|
|
DataMatrix = MIL.M_DATAMATRIX, // 코드 타입 : 2D DataMatrix
|
|
QRCode = MIL.M_QRCODE, // 코드 타입 : 2D QR
|
|
Barcode_Any = MIL.M_ANY, // 코드 타입 : 1D Any (자동 탐지 후 검출. 직접 지정 방식보다 속도는 느림.)
|
|
Barcode_BC412 = MIL.M_BC412, // 코드 타입 : 1D BC412
|
|
Barcode_CODABAR = MIL.M_CODABAR, // 코드 타입 : 1D CODABAR
|
|
Barcode_CODE39 = MIL.M_CODE39, // 코드 타입 : 1D CODE39
|
|
Barcode_CODE93 = MIL.M_CODE93, // 코드 타입 : 1D CODE93
|
|
Barcode_CODE128 = MIL.M_CODE128, // 코드 타입 : 1D CODE128
|
|
Barcode_EAN8 = MIL.M_EAN8, // 코드 타입 : 1D EAN8
|
|
Barcode_EAN13 = MIL.M_EAN13, // 코드 타입 : 1D EAN13
|
|
Barcode_EAN14 = MIL.M_EAN14, // 코드 타입 : 1D EAN14
|
|
Barcode_GS1_128 = MIL.M_GS1_128, // 코드 타입 : 1D GS1_128
|
|
Barcode_INDUSTRIAL25 = MIL.M_INDUSTRIAL25, // 코드 타입 : 1D INDUSTRIAL25
|
|
Barcode_INTERLEAVED25 = MIL.M_INTERLEAVED25, // 코드 타입 : 1D INTERLEAVED25
|
|
Barcode_UPC_A = MIL.M_UPC_A, // 코드 타입 : 1D UPC_A
|
|
Barcode_UPC_E = MIL.M_UPC_E // 코드 타입 : 1D UPC_E
|
|
}
|
|
|
|
public enum eMilThresholdMode {
|
|
Adaptive = MIL.M_ADAPTIVE,
|
|
GlobalSeg = MIL.M_GLOBAL_SEGMENTATION,
|
|
GlobalWithLocalReSeg = MIL.M_GLOBAL_WITH_LOCAL_RESEGMENTATION,
|
|
}
|
|
|
|
public class CodeRunParam : IRunParam, ICloneable {
|
|
public eMilSpeed Speed { get; set; } = eMilSpeed.Default;
|
|
public eMilThresholdMode ThresholdMode { get; set; } = eMilThresholdMode.Adaptive;
|
|
public bool AutoThreshold { get; set; } = true;
|
|
public int Threshold { get; set; } = 10;
|
|
public CodeParamExt CodeParamExt { get; set; } = new CodeParamExt();
|
|
|
|
public bool IsValidFindRoi(ImageSize imageSize) {
|
|
return (0 < CodeParamExt.FindROI.Width && CodeParamExt.FindROI.Width < imageSize.Width) &&
|
|
(0 < CodeParamExt.FindROI.Height && CodeParamExt.FindROI.Height < imageSize.Height);
|
|
}
|
|
|
|
public ROI GetFindRoi() {
|
|
return CodeParamExt.FindROI.Clone() as ROI;
|
|
}
|
|
|
|
public void SetFindROI(ROI findRoi) {
|
|
CodeParamExt.FindROI = findRoi;
|
|
}
|
|
|
|
public object Clone() {
|
|
return new CodeRunParam() {
|
|
Speed = Speed,
|
|
ThresholdMode = ThresholdMode,
|
|
AutoThreshold = AutoThreshold,
|
|
Threshold = Threshold,
|
|
CodeParamExt = CodeParamExt.Clone() as CodeParamExt
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// // Barcode, QRCode, DataMatrix 검출
|
|
/// </summary>
|
|
internal static class MilCodeRead {
|
|
private static Logger _logger = new Logger(typeof(MilCodeRead).Name);
|
|
|
|
public static MIL_ID CreateContext(MIL_ID milSystem) {
|
|
MIL_ID contextId = MIL.M_NULL;
|
|
MIL.McodeAlloc(milSystem, MIL.M_DEFAULT, MIL.M_DEFAULT, ref contextId);
|
|
|
|
return contextId;
|
|
}
|
|
|
|
public static MIL_ID LoadContext(MIL_ID milSystem, string filePath) {
|
|
MIL_ID contextId = MIL.M_NULL;
|
|
MIL.McodeRestore(filePath, milSystem, MIL.M_DEFAULT, ref contextId);
|
|
|
|
return contextId;
|
|
}
|
|
|
|
public static int SaveContext(MIL_ID contextId, string filePath) {
|
|
if (contextId != MIL.M_NULL && filePath != null) {
|
|
MIL.McodeSave(filePath, contextId, MIL.M_DEFAULT);
|
|
return E1.OK;
|
|
}
|
|
|
|
return E1.GENERIC;
|
|
}
|
|
|
|
public static void FreeContext(MIL_ID contextId) {
|
|
if (contextId != MIL.M_NULL) {
|
|
MIL.McodeFree(contextId);
|
|
}
|
|
}
|
|
|
|
public static MIL_ID CreateResultContext(MIL_ID contextId) {
|
|
var milSystem = GetSystemOfContext(contextId);
|
|
|
|
return MIL.McodeAllocResult(milSystem, MIL.M_DEFAULT, MIL.M_NULL);
|
|
}
|
|
|
|
private static MIL_ID GetSystemOfContext(MIL_ID contextId) {
|
|
return (MIL_ID)MIL.McodeInquire(contextId, MIL.M_OWNER_SYSTEM, MIL.M_NULL);
|
|
}
|
|
|
|
public static CodeRunParam GetRunParam(MIL_ID contextId) {
|
|
var codeRunParam = new CodeRunParam();
|
|
|
|
try {
|
|
double codeType = MIL.McodeInquire(contextId, MIL.M_CODE_TYPE, MIL.M_NULL);
|
|
if (codeType != MIL.M_NULL && codeType != MIL.M_DEFAULT) {
|
|
codeRunParam.CodeParamExt.CodeType = (eMilCodeType)codeType;
|
|
}
|
|
|
|
double thresholdMode = MIL.McodeInquire(contextId, MIL.M_THRESHOLD_MODE, MIL.M_NULL);
|
|
if (thresholdMode != MIL.M_DEFAULT) {
|
|
codeRunParam.ThresholdMode = (eMilThresholdMode)thresholdMode;
|
|
}
|
|
|
|
double threshold = MIL.McodeInquire(contextId, MIL.M_THRESHOLD_VALUE, MIL.M_NULL);
|
|
codeRunParam.AutoThreshold = (threshold == MIL.M_AUTO_COMPUTE);
|
|
codeRunParam.Threshold = (int)threshold;
|
|
codeRunParam.Speed = (eMilSpeed)Convert.ToInt32(MIL.McodeInquire(contextId, MIL.M_SPEED, MIL.M_NULL));
|
|
}
|
|
catch (Exception e) {
|
|
_logger.FuncE(e.Message);
|
|
}
|
|
|
|
return codeRunParam;
|
|
}
|
|
|
|
public static int SetRunParam(MIL_ID contextId, IRunParam runParam) {
|
|
if (runParam is CodeRunParam) {
|
|
var param = runParam as CodeRunParam;
|
|
MIL.McodeControl(contextId, MIL.M_INITIALIZATION_MODE, MIL.M_IMPROVED_RECOGNITION);
|
|
MIL.McodeControl(contextId, MIL.M_THRESHOLD_MODE, (double)param.ThresholdMode);
|
|
MIL.McodeControl(contextId, MIL.M_SPEED, (double)param.Speed);
|
|
|
|
if (param.AutoThreshold) {
|
|
MIL.McodeControl(contextId, MIL.M_THRESHOLD_VALUE, MIL.M_AUTO_COMPUTE);
|
|
}
|
|
else {
|
|
MIL.McodeControl(contextId, MIL.M_THRESHOLD_VALUE, param.Threshold);
|
|
}
|
|
|
|
return E1.OK;
|
|
}
|
|
|
|
return E1.GENERIC;
|
|
}
|
|
|
|
public static bool HasModel(MIL_ID contextId) {
|
|
return MIL.McodeInquire(contextId, MIL.M_NUMBER_OF_CODE_MODELS, MIL.M_NULL) > 0;
|
|
}
|
|
|
|
public static PatternResult Find(MIL_ID contextId, MIL_ID resultContextId, MIL_ID image, CodeParamExt codeParamExt) {
|
|
MIL.MappTimer(MIL.M_DEFAULT, MIL.M_TIMER_RESET + MIL.M_SYNCHRONOUS, MIL.M_NULL);
|
|
|
|
DetectCodeModels(contextId, image, codeParamExt);
|
|
MIL.McodeRead(contextId, image, resultContextId);
|
|
|
|
double tactTime = 0;
|
|
MIL.MappTimer(MIL.M_DEFAULT, MIL.M_TIMER_READ + MIL.M_SYNCHRONOUS, ref tactTime);
|
|
|
|
var finderResult = ExtractResults(resultContextId, MilEnvironment.GetImageCenter(image), codeParamExt);
|
|
finderResult.TactTimeInMilliSec = MathUtil.ConvertUnitSecToMilli(tactTime);
|
|
|
|
return finderResult;
|
|
}
|
|
|
|
private static void DetectCodeModels(MIL_ID contextId, MIL_ID image, CodeParamExt codeParamExt) {
|
|
var milSystem = GetSystemOfContext(contextId);
|
|
MIL_ID milCodeResult = MIL.McodeAllocResult(milSystem, MIL.M_CODE_DETECT_RESULT, MIL.M_NULL);
|
|
|
|
if (codeParamExt.CodeType != eMilCodeType.Barcode_Any) {
|
|
const int STRING_LENGTH_MAX = 64;
|
|
MIL_INT codeType = (int)codeParamExt.CodeType;
|
|
|
|
// 지정된 코드 타입 감지
|
|
MIL.McodeDetect(image, STRING_LENGTH_MAX, codeType, codeParamExt.FindNumber, MIL.M_DEFAULT, MIL.M_DEFAULT, milCodeResult);
|
|
MIL.McodeModel(contextId, MIL.M_ADD, codeType, MIL.M_NULL, milCodeResult, MIL.M_NULL);
|
|
}
|
|
else {
|
|
// 자동으로 코드 타입 감지
|
|
MIL.McodeDetect(image, 0, MIL.M_NULL, codeParamExt.FindNumber, MIL.M_DEFAULT, MIL.M_DEFAULT, milCodeResult);
|
|
MIL.McodeModel(contextId, MIL.M_RESET_FROM_DETECTED_RESULTS, MIL.M_NULL, MIL.M_ALL, milCodeResult, MIL.M_NULL);
|
|
|
|
#region 디버깅용 코드
|
|
//MIL_INT codeDetectedNum = 0;
|
|
//MIL.McodeGetResult(milCodeResult, MIL.M_GENERAL, MIL.M_GENERAL, MIL.M_NUMBER + MIL.M_TYPE_MIL_INT, ref codeDetectedNum);
|
|
|
|
//for (MIL_INT i = 0; i < codeDetectedNum; i++) {
|
|
// MIL_INT[] codeTypes = new MIL_INT[codeDetectedNum];
|
|
// MIL.McodeGetResult(milCodeResult, MIL.M_ALL, MIL.M_GENERAL, MIL.M_CODE_TYPE + MIL.M_TYPE_MIL_INT, ref codeTypes[i]);
|
|
|
|
// MIL_INT codeTypeStringSize = 0;
|
|
// MIL.McodeGetResult(milCodeResult, i, MIL.M_GENERAL, MIL.M_CODE_TYPE_NAME + MIL.M_STRING_SIZE + MIL.M_TYPE_MIL_INT, ref codeTypeStringSize);
|
|
|
|
// StringBuilder codeTypeString = new StringBuilder((int)codeTypeStringSize);
|
|
// MIL.McodeGetResult(milCodeResult, i, MIL.M_GENERAL, MIL.M_CODE_TYPE_NAME + MIL.M_TYPE_STRING, codeTypeString);
|
|
//}
|
|
#endregion
|
|
}
|
|
|
|
MIL.McodeControl(contextId, MIL.M_NUMBER, MIL.M_ALL);
|
|
MIL.McodeFree(milCodeResult);
|
|
}
|
|
|
|
private static PatternResult ExtractResults(MIL_ID contextResult, ImagePoint imageCenter, CodeParamExt codeParamExt) {
|
|
List<CodeResult> results = new List<CodeResult>();
|
|
MIL_INT resultStatus = 0;
|
|
|
|
MIL.McodeGetResult(contextResult, MIL.M_GENERAL, MIL.M_GENERAL, MIL.M_STATUS + MIL.M_TYPE_MIL_INT, ref resultStatus);
|
|
|
|
if (resultStatus == MIL.M_STATUS_READ_OK) {
|
|
MIL_INT codeReadNum = 0;
|
|
MIL.McodeGetResult(contextResult, MIL.M_GENERAL, MIL.M_GENERAL, MIL.M_NUMBER + MIL.M_TYPE_MIL_INT, ref codeReadNum);
|
|
for (MIL_INT i = 0; i < codeReadNum; i++) {
|
|
MIL_INT codeStringSize = 0;
|
|
MIL.McodeGetResult(contextResult, i, MIL.M_GENERAL, MIL.M_STRING + MIL.M_STRING_SIZE + MIL.M_TYPE_MIL_INT, ref codeStringSize);
|
|
StringBuilder codeString = new StringBuilder((int)codeStringSize);
|
|
MIL.McodeGetResult(contextResult, i, MIL.M_GENERAL, MIL.M_STRING, codeString);
|
|
|
|
double posX = 0, posY = 0;
|
|
double score = 0;
|
|
double angleDeg = 0;
|
|
MIL.McodeGetResult(contextResult, i, MIL.M_GENERAL, MIL.M_POSITION_X, ref posX);
|
|
MIL.McodeGetResult(contextResult, i, MIL.M_GENERAL, MIL.M_POSITION_Y, ref posY);
|
|
MIL.McodeGetResult(contextResult, i, MIL.M_GENERAL, MIL.M_SCORE, ref score);
|
|
MIL.McodeGetResult(contextResult, i, MIL.M_GENERAL, MIL.M_ANGLE, ref angleDeg); // Warning : Counter Clockwise
|
|
|
|
results.Add(new CodeResult(
|
|
codeType: codeParamExt.CodeType.ToString(),
|
|
position: new ImagePoint(posX, posY),
|
|
offset: (new ImagePoint(posX, posY) - imageCenter) as ImagePoint,
|
|
angle: Angle.NormalizeAngle(Angle.CreateWithDegree(angleDeg)),
|
|
score: score * 100,
|
|
code: codeString.ToString()));
|
|
}
|
|
}
|
|
|
|
|
|
return new PatternResult() {
|
|
PatternType = ePatternType.Code,
|
|
Status = (results.Count > 0) ? eFoundStatus.FoundOK : eFoundStatus.NotFound,
|
|
Results = results,
|
|
FindRoi = codeParamExt.FindROI.Clone() as ROI,
|
|
};
|
|
}
|
|
|
|
public static PatternResult GetNotFoundPatternResult() {
|
|
return new PatternResult() {
|
|
Status = eFoundStatus.NotFound,
|
|
Results = new List<CodeResult>()
|
|
};
|
|
}
|
|
|
|
public static void Draw(PatternResult patternResult, IMilCameraDisplay milCameraDisplay) {
|
|
MIL_ID staticGraphicList = 0;
|
|
MIL.MdispInquire(milCameraDisplay.GetMilDisplayId(), MIL.M_ASSOCIATED_GRAPHIC_LIST_ID, ref staticGraphicList);
|
|
|
|
if (patternResult.Status == eFoundStatus.FoundOK) {
|
|
var results = patternResult.Results as List<CodeResult>;
|
|
var crosses = new List<Cross>();
|
|
|
|
foreach (var result in results) {
|
|
var color = GraphicColors.PatternFoundOK;
|
|
var cross = new Cross(result.ImagePosition.Clone() as ImagePoint, 50);
|
|
crosses.Add(cross);
|
|
|
|
milCameraDisplay.SetPatternTracking(cross);
|
|
milCameraDisplay.AddCross(cross, color);
|
|
|
|
var msgs = new List<string>();
|
|
msgs.Add(string.Format("CodeType : {0}", result.CodeType));
|
|
msgs.Add(string.Format("X : {0:0.00}", result.ImagePosition.X));
|
|
msgs.Add(string.Format("Y : {0:0.00}", result.ImagePosition.Y));
|
|
msgs.Add(string.Format("Angle : {0:0.00}", result.Angle.Degree));
|
|
msgs.Add(string.Format("Score : {0:0.00}", result.Score));
|
|
msgs.Add(string.Format("TactTime : {0} ms", Math.Round(patternResult.TactTimeInMilliSec)));
|
|
msgs.Add(string.Format("Code : {0}", result.Code));
|
|
milCameraDisplay.AddTextList(
|
|
GraphicColors.PatternFoundOK, new ImagePoint(result.ImagePosition.X, result.ImagePosition.Y), msgs);
|
|
}
|
|
milCameraDisplay.DrawPatternTrackings(crosses);
|
|
}
|
|
}
|
|
}
|
|
}
|