393 lines
18 KiB
C#
393 lines
18 KiB
C#
using Aurora;
|
|
using Aurora.Api;
|
|
using Aurora.Api.Control;
|
|
using Hermes.Functional;
|
|
using Matrox.MatroxImagingLibrary;
|
|
using MilCommon;
|
|
using MilPattern;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.Remoting.Messaging;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
|
|
using VisionCommon;
|
|
using VisionCommonUI;
|
|
using VisionCommonUI.Resources;
|
|
using VisionCommonUI.UserGuide;
|
|
|
|
|
|
namespace MilUI {
|
|
/// <summary>
|
|
/// Interaction logic for CamCalSettingView.xaml
|
|
/// </summary>
|
|
///
|
|
public class McaFileItem {
|
|
public string ImgFileName { get; set; }
|
|
public int PointCount { get; set; }
|
|
public double AvgError { get; set; }
|
|
public string MCAFullPath { get; set; }
|
|
}
|
|
|
|
public class CalibPointPair {
|
|
public double RawX, RawY;
|
|
public double CalibX, CalibY;
|
|
}
|
|
public partial class CamCalSettingView : UserControl, IDisposable {
|
|
private string _calPath = Path.Combine(ApplicationExt.DataPath, "_config_vision", "CalData");
|
|
private string _backupDir;
|
|
private ObservableCollection<McaFileItem> McaFileItems = new ObservableCollection<McaFileItem>();
|
|
|
|
private Logger _Logger;
|
|
private CameraBase _CameraDev;
|
|
private FovInfo _FovInfo;
|
|
private FovRecipe _FovRecipe;
|
|
private CameraDisplay _CameraDisplay;
|
|
protected PatternAgentBase _PatternAgent = null;
|
|
|
|
private MIL_ID _SysId;
|
|
private MIL_ID MilCalContext = MIL.M_NULL;
|
|
|
|
public bool IsDeviceRunning { get; set; } = false;
|
|
public bool IsCalUsed { get; set; } = false;
|
|
public bool IsCalEnabled { get; set; } = false;
|
|
public bool IsCalLoaded { get; set; } = false;
|
|
|
|
List<CalibPointPair> _calibrationPoints;
|
|
|
|
public CamCalSettingView(FovInfo fovinfo, UserControl camview) {
|
|
_FovInfo = fovinfo;
|
|
_FovRecipe = VisionRecipeManager.GetFovRecipe(_FovInfo);
|
|
_Logger = new Logger(string.Format("CameraCalibration for [{0}]", _FovRecipe.FovName));
|
|
_CameraDev = VisionEnv.AuroraEnv.DeviceManager[_FovRecipe.CamRecipe.CamId.ToString()] as CameraBase;
|
|
_CameraDisplay = (camview as CameraView).GetDisplay() as CameraDisplay;
|
|
_PatternAgent = PatternAgentManager.GetAgent(_FovInfo);
|
|
|
|
var camConfig = VisionEnv.AuroraEnv.DeviceManager.GetDeviceConfig(_FovRecipe.CamRecipe.CamId.ToString()) as CamConfig;
|
|
_SysId = _PatternAgent.GetMilSystemId(_FovRecipe.CamRecipe.CamId);
|
|
InitializeComponent();
|
|
|
|
if (!Directory.Exists(_calPath)) {
|
|
Directory.CreateDirectory(_calPath);
|
|
}
|
|
_backupDir = Path.Combine(_calPath, "Backup", _FovInfo.ToString());
|
|
if (!Directory.Exists(_backupDir)) {
|
|
Directory.CreateDirectory(_backupDir);
|
|
}
|
|
SettingPanel.Visibility = Visibility.Collapsed;
|
|
Loaded += CameraCalibrationSettingView_Loaded;
|
|
}
|
|
|
|
private void CameraCalibrationSettingView_Loaded(object sender, RoutedEventArgs e) {
|
|
UpdateSignalLights();
|
|
}
|
|
private void SetSignalLight(System.Windows.Shapes.Ellipse light, bool state) {
|
|
light.Fill = state ? Brushes.Green : Brushes.Gray;
|
|
}
|
|
|
|
private void UpdateSignalLights() {
|
|
IsCalUsed = _FovRecipe.CamCalibrationRecipe.Used;
|
|
IsCalEnabled = _FovRecipe.CamCalibrationRecipe.Enabled;
|
|
IsCalLoaded = _FovRecipe.CamCalibrationRecipe.Loaded;
|
|
|
|
SetSignalLight(LightUsed, IsCalUsed);
|
|
SetSignalLight(LightEnabled, IsCalEnabled);
|
|
SetSignalLight(LightLoaded, IsCalLoaded);
|
|
}
|
|
|
|
private void StartCalibration_Click(object sender, RoutedEventArgs e) {
|
|
if (IsDeviceRunning && IsLoaded) {
|
|
MessageBoxUtil.ShowMessageBoxWarning("이미 보정 중입니다. 보정기능을 끄기위해 장치를 재시작합니다.");
|
|
StopDevice();
|
|
_FovRecipe.CamCalibrationRecipe.Used = false;
|
|
RecipeFileManager.SaveVisionRecipe();
|
|
StartDevice();
|
|
}
|
|
SettingPanel.Visibility = Visibility.Visible;
|
|
UpdateSignalLights();
|
|
}
|
|
|
|
|
|
private void BtnSaveCalibration_Click(object sender, RoutedEventArgs e) {
|
|
_CameraDisplay.ClearAllGraphics();
|
|
MIL_ID image = _CameraDisplay.GetImage();
|
|
if (image == null) {
|
|
MessageBoxUtil.ShowMessageBoxError(StrTokens.MSG_E_ImageNotReady);
|
|
return;
|
|
}
|
|
int r = (int)TxtRows.Value;
|
|
int c = (int)TxtCols.Value;
|
|
double sx = (double)TxtSpacingX.Value;
|
|
double sy = (double)TxtSpacingY.Value;
|
|
|
|
if(r * c * sx * sy == 0 ) {
|
|
MessageBoxUtil.ShowMessageBoxInfo("입력값이 정확하지 않습니다.");
|
|
return;
|
|
}
|
|
MIL.McalAlloc(_SysId, MIL.M_LINEAR_INTERPOLATION, MIL.M_DEFAULT, ref MilCalContext);
|
|
MIL.McalGrid(MilCalContext, image, 0.0, 0.0, 0.0, r, c, sx, sy, MIL.M_FULL_CALIBRATION, MIL.M_CIRCLE_GRID);
|
|
//DropBox 분기
|
|
//MIL.McalGrid(MilCalContext, image, 0.0, 0.0, 0.0, r, c, sx, sy, MIL.M_FULL_CALIBRATION, MIL.M_CHESSBOARD_GRID);
|
|
var (isValid, usedPoints, error) = IsCalValid(MilCalContext, r*c);
|
|
if (isValid) {
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
string backupMca = Path.Combine(_backupDir, $"backup_{timestamp}.mca");
|
|
string backupImg = Path.Combine(_backupDir, $"backup_{timestamp}.jpg");
|
|
MIL.McalSave(backupMca, MilCalContext, MIL.M_DEFAULT);
|
|
MilEnvironment.SaveImage(backupImg, image);
|
|
|
|
McaFileItems.Add(new McaFileItem {
|
|
MCAFullPath = backupMca,
|
|
PointCount = usedPoints,
|
|
AvgError = error,
|
|
ImgFileName = backupImg
|
|
});
|
|
McaFileGrid.ItemsSource = null;
|
|
McaFileGrid.ItemsSource = McaFileItems;
|
|
MessageBoxUtil.ShowMessageBoxInfo("MCA 추출 완료 및 백업 완료");
|
|
}
|
|
else {
|
|
MessageBoxUtil.ShowMessageBoxInfo("MCA 추출 실패");
|
|
}
|
|
UpdateSignalLights();
|
|
}
|
|
|
|
private (bool, int, double) IsCalValid(MIL_ID calId, int expectedPointCount, double maxAcceptableError = 0.5) {
|
|
double error = 0.0;
|
|
MIL.McalInquire(calId, MIL.M_AVERAGE_PIXEL_ERROR, ref error);
|
|
double usedPoints = 0;
|
|
MIL.McalInquire(calId, MIL.M_NUMBER_OF_CALIBRATION_POINTS, ref usedPoints);
|
|
bool res = expectedPointCount > 0 && usedPoints >= expectedPointCount * 0.8 && error < maxAcceptableError;
|
|
return (res, (int)usedPoints, error);
|
|
}
|
|
|
|
private void McaFileGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) {
|
|
MIL.MgraClear(_CameraDisplay.GetGraphicContextId(), _CameraDisplay.GetGraphicListId());
|
|
if (McaFileGrid.SelectedItem is McaFileItem selected) {
|
|
string jpgPath = selected.ImgFileName;
|
|
if (!File.Exists(jpgPath)) {
|
|
MessageBoxUtil.ShowMessageBoxInfo("⚠️ 해당 MCA에 대응하는 이미지 파일이 존재하지 않습니다.");
|
|
return;
|
|
}
|
|
|
|
// 사용자 알림
|
|
MessageBoxUtil.ShowMessageBoxInfo("📷 칼리브레이션 시 사용된 이미지를 불러옵니다.");
|
|
|
|
MIL_ID imageId = MIL.M_NULL;
|
|
MIL.MbufRestore(jpgPath, _SysId, ref imageId);
|
|
_CameraDisplay.SetImage(imageId); // display에 연결
|
|
|
|
MIL.McalRestore(selected.MCAFullPath, _SysId, MIL.M_DEFAULT, ref MilCalContext);
|
|
MIL.MgraColor(
|
|
_CameraDisplay.GetGraphicContextId(), // 그래픽 컨텍스트
|
|
MIL.M_RGB888(255, 0, 0) // 빨간색 (예: R=255, G=0, B=0)
|
|
);
|
|
MIL.McalDraw(
|
|
_CameraDisplay.GetGraphicContextId(), // 🔸 대상 그래픽 리스트 (Display에 연결된)
|
|
MilCalContext, // 🔸 Calibration Context
|
|
_CameraDisplay.GetGraphicListId(), // 🔸 Drawing 대상: 이 경우 Display 그래픽 오버레이
|
|
MIL.M_DRAW_IMAGE_POINTS + MIL.M_DRAW_CALIBRATION_ERROR, // 🔸 원본 이미지 상의 포인트를 그림
|
|
MIL.M_DEFAULT,
|
|
MIL.M_DEFAULT
|
|
);
|
|
|
|
MIL.MbufFree(imageId); // 이미지 연결 후 해제 가능 (Display 내부에서 유지됨)
|
|
}
|
|
}
|
|
|
|
private void BtnComparePreview_Click(object sender, RoutedEventArgs e) {
|
|
if (IsCalLoaded) {
|
|
MessageBoxUtil.ShowMessageBoxInfo($"MCA파일이 이미 Load되어 있습니다.\nUnload이후 비교해주세요");
|
|
return;
|
|
}
|
|
if (McaFileGrid.SelectedItem is McaFileItem selected) {
|
|
MIL_INT result = MIL.McalRestore(selected.MCAFullPath, _SysId, MIL.M_DEFAULT, ref MilCalContext);
|
|
MIL_ID image = _CameraDisplay.GetImage();
|
|
MIL_ID transformed = MilEnvironment.GetCompatibleGrayImageBuffer(image);
|
|
|
|
MIL.McalTransformImage(
|
|
image,
|
|
transformed,
|
|
MilCalContext,
|
|
MIL.M_BILINEAR,
|
|
MIL.M_FULL_CORRECTION,
|
|
MIL.M_WARP_IMAGE
|
|
);
|
|
_CameraDisplay.SetImage(transformed);
|
|
|
|
MIL.MgraColor(
|
|
_CameraDisplay.GetGraphicContextId(),
|
|
MIL.M_RGB888(0, 255, 0)
|
|
);
|
|
|
|
MIL.McalDraw(
|
|
_CameraDisplay.GetGraphicContextId(),
|
|
MilCalContext,
|
|
_CameraDisplay.GetGraphicListId(),
|
|
MIL.M_DRAW_WORLD_POINTS,
|
|
MIL.M_DEFAULT,
|
|
MIL.M_DEFAULT
|
|
);
|
|
}
|
|
GenerateClipboard(MilCalContext);
|
|
}
|
|
|
|
private void BtnSetAsRecipe_Click(object sender, RoutedEventArgs e) {
|
|
if (McaFileGrid.SelectedItem is McaFileItem selected) {
|
|
MIL_INT result = MIL.McalRestore(selected.MCAFullPath, _SysId, MIL.M_DEFAULT, ref MilCalContext);
|
|
if (result != MIL.M_NULL) {
|
|
var file_name = _calPath + $@"/{_FovInfo}.mca";
|
|
MIL.McalSave(file_name, MilCalContext, MIL.M_DEFAULT);
|
|
StopDevice();
|
|
_FovRecipe.CamCalibrationRecipe.Used = true;
|
|
_FovRecipe.CamCalibrationRecipe.Enabled = true;
|
|
RecipeFileManager.SaveVisionRecipe();
|
|
StartDevice();
|
|
MessageBoxUtil.ShowMessageBoxInfo($"⚠️ 선택된 MCA를 레시피에 적용합니다.");
|
|
UpdateSignalLights();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private void BtnLoadCalibration_Click(object sender, RoutedEventArgs e) {
|
|
var dialog = new System.Windows.Forms.OpenFileDialog {
|
|
Title = "백업된 MCA 파일 선택",
|
|
InitialDirectory = _backupDir,
|
|
Filter = "MCA Files (*.mca)|*.mca",
|
|
CheckFileExists = true,
|
|
Multiselect = false
|
|
};
|
|
|
|
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
|
FileInfo selectedMcaFile = new FileInfo(dialog.FileName);
|
|
string matchingJpgPath = Path.ChangeExtension(selectedMcaFile.FullName, ".jpg");
|
|
|
|
if (File.Exists(matchingJpgPath)) {
|
|
FileInfo matchingJpgFile = new FileInfo(matchingJpgPath);
|
|
MIL_INT result = MIL.McalRestore(selectedMcaFile.FullName, _SysId, MIL.M_DEFAULT, ref MilCalContext);
|
|
|
|
if (result != MIL.M_NULL) {
|
|
double calibrationStatus = 0;
|
|
MIL.McalInquire(MilCalContext, MIL.M_CALIBRATION_STATUS, ref calibrationStatus);
|
|
|
|
if (calibrationStatus == MIL.M_CALIBRATED) {
|
|
double averagePixelError = 0.0;
|
|
MIL.McalInquire(MilCalContext, MIL.M_AVERAGE_PIXEL_ERROR, ref averagePixelError); ;
|
|
double nbCalibrationPoints = 0;
|
|
MIL.McalInquire(MilCalContext, MIL.M_NUMBER_OF_CALIBRATION_POINTS, ref nbCalibrationPoints);
|
|
McaFileItems.Add(new McaFileItem {
|
|
MCAFullPath = selectedMcaFile.FullName,
|
|
PointCount = (int)nbCalibrationPoints,
|
|
AvgError = averagePixelError,
|
|
ImgFileName = matchingJpgPath
|
|
});
|
|
}
|
|
else {
|
|
MessageBoxUtil.ShowMessageBoxInfo($"⚠️ MCA복원실패");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
MessageBoxUtil.ShowMessageBoxInfo($"⚠️ 선택된 MCA에 대응하는 JPG 이미지가 없습니다.");
|
|
}
|
|
}
|
|
|
|
McaFileGrid.ItemsSource = null;
|
|
McaFileGrid.ItemsSource = McaFileItems;
|
|
}
|
|
|
|
|
|
private void StopDevice() {
|
|
|
|
}
|
|
|
|
private void StartDevice() {
|
|
|
|
}
|
|
|
|
private void BtnHelp_Click(object sender, RoutedEventArgs e) {
|
|
var guide = new GuideManager(this);
|
|
guide.AddStep(startbutton, "보정을 원하시면 버튼을 눌러 시작하세요!\n이미 칼리브레이션이 동작중이라면 Camera가 자동으로 껏다켜집니다.");
|
|
guide.AddStep(AddButton, "기존 백업된 mca파일들을 Load해서 비교 할 수 있습니다.", placement: MessagePlacement.Above);
|
|
guide.AddStep(McaFileGrid, "평균 픽셀 오차는(AVG Error), Mil Calibrator가 좌표를 얼마나 잘 복원해냈는가를 평가하는 지표\n즉 정답지(row,col,spacing)기준으로 모델링 했을때 정답지/칼리브레이션결과의 평균 차이를 뜻함\n OpenCV의 ReProjectionError와 같은 맥락(최적화 알고리즘)", placement: MessagePlacement.Above);
|
|
guide.Start();
|
|
}
|
|
|
|
private void BtnSetting_Help_Click(object sender, RoutedEventArgs e) {
|
|
var guide = new GuideManager(this);
|
|
guide.AddStep(SettingPanel, "좌측 화면의 이미지를 기준으로 칼리브레이션을 하게됩니다. 아래와 같은 이미지를 로드하거나 grab하세요", "cal.PNG");
|
|
guide.AddStep(TxtRows, "체커보드의 행 수를 입력하세요");
|
|
guide.AddStep(TxtCols, "체커보드의 열 수를 입력하세요");
|
|
guide.AddStep(SpacingXY, "mm기준의 거리값을 입력합니다.");
|
|
guide.AddStep(BtnSaveCalibration, "Calibration이 되는 경우 백업파일을 만들고 아래 리스트박스에 사용대기형태로 추가됩니다.");
|
|
guide.AddStep(McaFileGrid, "리스트에 업로드된 파일을 더블클릭하면 칼리브레이션 당시 사용한 이미지를 볼 수 있습니다.", "cal1.png",placement: MessagePlacement.Above);
|
|
guide.AddStep(McaFileGrid, "Action의 preview버튼으로 이미지변환 결과를 볼 수 있습니다.", "cal2.png", placement: MessagePlacement.Above);
|
|
guide.AddStep(McaFileGrid, "Action의 Recipe버튼을 클릭하면 해당Calibration이 앞으로 현재 FOV에 적용됩니다.", "cal3.png", placement: MessagePlacement.Above);
|
|
guide.Start();
|
|
}
|
|
|
|
public void Dispose() {
|
|
if (MilCalContext != null) {
|
|
MIL.McalFree(MilCalContext);
|
|
}
|
|
}
|
|
|
|
public void GenerateClipboard(MIL_ID calContext) {
|
|
// 포인트 개수 조회
|
|
double numPoints = 0;
|
|
MIL.McalInquire(calContext, MIL.M_NUMBER_OF_CALIBRATION_POINTS, ref numPoints);
|
|
|
|
if (numPoints <= 0) {
|
|
MessageBoxUtil.ShowMessageBoxWarning("⚠️ 캘리브레이션 포인트가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
// 포인트 배열 선언
|
|
double[] measuredXs = new double[(int)numPoints];
|
|
double[] measuredYs = new double[(int)numPoints];
|
|
double[] worldXs = new double[(int)numPoints];
|
|
double[] worldYs = new double[(int)numPoints];
|
|
|
|
// 포인트 정보 추출
|
|
MIL.McalInquire(calContext, MIL.M_CALIBRATION_IMAGE_POINTS_X, measuredXs);
|
|
MIL.McalInquire(calContext, MIL.M_CALIBRATION_IMAGE_POINTS_Y, measuredYs);
|
|
MIL.McalInquire(calContext, MIL.M_CALIBRATION_WORLD_POINTS_X, worldXs);
|
|
MIL.McalInquire(calContext, MIL.M_CALIBRATION_WORLD_POINTS_Y, worldYs);
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("Index\tMeasuredX\tMeasuredY\tWorldX\tWorldY\tPredictedX\tPredictedY\tPixelError");
|
|
|
|
for (int i = 0; i < (int)numPoints; i++) {
|
|
double predictedX = 0, predictedY = 0;
|
|
|
|
MIL.McalTransformCoordinate(
|
|
calContext,
|
|
MIL.M_WORLD_TO_PIXEL,
|
|
worldXs[i],
|
|
worldYs[i],
|
|
ref predictedX,
|
|
ref predictedY
|
|
);
|
|
|
|
double error = Math.Sqrt(
|
|
Math.Pow(predictedX - measuredXs[i], 2) +
|
|
Math.Pow(predictedY - measuredYs[i], 2)
|
|
);
|
|
|
|
sb.AppendLine($"{i}\t{measuredXs[i]:F3}\t{measuredYs[i]:F3}\t{worldXs[i]:F3}\t{worldYs[i]:F3}\t{predictedX:F3}\t{predictedY:F3}\t{error:F3}");
|
|
}
|
|
|
|
Clipboard.SetText(sb.ToString());
|
|
MessageBoxUtil.ShowMessageBoxInfo($"📋 평균 픽셀 오차 리포트가 클립보드에 복사되었습니다.\n총 포인트: {numPoints}");
|
|
}
|
|
}
|
|
}
|