CalibrationService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ToolBlock;
  3. using MathNet.Numerics.LinearAlgebra;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Collections.ObjectModel;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Windows.Forms;
  10. using TeamAAS_VP.Core;
  11. using TeamAAS_VP.Enums;
  12. using TeamAAS_VP.Interfaces;
  13. using TeamAAS_VP.Models.Calibration;
  14. using TeamAAS_VP.Resources.Languages;
  15. namespace TeamAAS_VP.Services
  16. {
  17. /// <summary>
  18. /// Calibration 管理服务,负责校准文件的读取与保存
  19. /// </summary>
  20. public class CalibrationService : ICalibrationService
  21. {
  22. private static class Paths
  23. {
  24. public static readonly string CalibrationPath = "..//Calibration";
  25. public static string CalibrationListFilePath => Path.Combine(CalibrationPath ?? string.Empty, "CalibrationList.cfg");
  26. }
  27. private readonly object _sync = new object();
  28. private ObservableCollection<CalibrationInfo> _calibrations = new ObservableCollection<CalibrationInfo>();
  29. public CalibrationService()
  30. {
  31. // 不在构造中自动加载,调用者可以选择 LoadAll
  32. }
  33. public IReadOnlyCollection<CalibrationInfo> GetAllCalibrations()
  34. {
  35. lock (_sync)
  36. {
  37. return _calibrations.ToList().AsReadOnly();
  38. }
  39. }
  40. public CalibrationInfo GetCalibration(Guid id)
  41. {
  42. lock (_sync)
  43. {
  44. return _calibrations.FirstOrDefault(c => c.Id == id);
  45. }
  46. }
  47. public CalibrationInfo AddOrUpdateCalibration(CalibrationInfo calib)
  48. {
  49. if (calib == null) return null;
  50. lock (_sync)
  51. {
  52. var exist = _calibrations.FirstOrDefault(c => c.Id == calib.Id);
  53. if (exist != null)
  54. {
  55. var idx = _calibrations.IndexOf(exist);
  56. _calibrations[idx] = calib;
  57. }
  58. else
  59. {
  60. calib.Index = _calibrations.Count + 1;
  61. _calibrations.Add(calib);
  62. }
  63. }
  64. calib.DateTime = DateTime.Now;
  65. string folder = Path.Combine(Paths.CalibrationPath, calib.Name);
  66. string calibFile = Path.Combine(folder, calib.Name + ".cfg");
  67. if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
  68. // 写入单个校准文件
  69. FileHelper.WriteJsonFile(calib, calibFile);
  70. // 更新索引列表
  71. var list = new Dictionary<Guid, string>();
  72. lock (_sync)
  73. {
  74. foreach (var item in _calibrations)
  75. {
  76. list[item.Id] = item.Name;
  77. }
  78. }
  79. FileHelper.WriteJsonFile(list, Paths.CalibrationListFilePath);
  80. // 保存ToolBlock
  81. string prcPath = Path.Combine(folder, calib.Name + ".vpp");
  82. if (calib.ToolBlock != null)
  83. {
  84. CogSerializer.SaveObjectToFile(calib.ToolBlock, prcPath);
  85. }
  86. else
  87. {
  88. if (File.Exists(prcPath))
  89. {
  90. calib.ToolBlock = CogSerializer.LoadObjectFromFile(prcPath) as CogToolBlock;
  91. }
  92. else if (File.Exists("..//Vision Template//Calibration.vpp"))
  93. {
  94. calib.ToolBlock = CogSerializer.LoadObjectFromFile("..//Vision Template//Calibration.vpp") as CogToolBlock;
  95. CogSerializer.SaveObjectToFile(calib.ToolBlock, prcPath);
  96. }
  97. }
  98. return calib;
  99. }
  100. public bool RemoveCalibration(Guid id)
  101. {
  102. bool result = false;
  103. CalibrationInfo removed = null;
  104. lock (_sync)
  105. {
  106. removed = _calibrations.FirstOrDefault(c => c.Id == id);
  107. if (removed != null) result = _calibrations.Remove(removed);
  108. }
  109. // 更新列表文件
  110. var list = new Dictionary<Guid, string>();
  111. lock (_sync)
  112. {
  113. foreach (var item in _calibrations)
  114. {
  115. list[item.Id] = item.Name;
  116. }
  117. }
  118. FileHelper.WriteJsonFile(list, Paths.CalibrationListFilePath);
  119. //删除后,对所有校准重新排序索引
  120. //先按照Index进行排序
  121. var calibs = _calibrations.OrderBy(c => c.Index).ToList();
  122. int index= 1;
  123. foreach (var item in calibs)
  124. {
  125. item.Index = index;
  126. //保存更新后的索引
  127. string folder = Path.Combine(Paths.CalibrationPath, item.Name);
  128. string calibFile = Path.Combine(folder, item.Name + ".cfg");
  129. FileHelper.WriteJsonFile(item, calibFile);
  130. index++;
  131. }
  132. _calibrations = new ObservableCollection<CalibrationInfo>(calibs);
  133. // 可选:删除物理文件和toolblock目录(不自动删除,除非明确需求)
  134. return result;
  135. }
  136. public void LoadAll()
  137. {
  138. lock (_sync)
  139. {
  140. if (!Directory.Exists(Paths.CalibrationPath)) Directory.CreateDirectory(Paths.CalibrationPath);
  141. _calibrations.Clear();
  142. if (!File.Exists(Paths.CalibrationListFilePath))
  143. {
  144. FileHelper.WriteJsonFile(new Dictionary<Guid, string>(), Paths.CalibrationListFilePath);
  145. return;
  146. }
  147. var list = FileHelper.ReadJsonFile<Dictionary<Guid, string>>(Paths.CalibrationListFilePath);
  148. if (list == null) return;
  149. foreach (var item in list)
  150. {
  151. try
  152. {
  153. string filePath = Path.Combine(FilePath.CalibrationPath, item.Value, item.Value + ".cfg");
  154. if (File.Exists(filePath))
  155. {
  156. var calib = FileHelper.ReadJsonFile<CalibrationInfo>(filePath);
  157. // 尝试加载toolblock
  158. string prcPath = Path.Combine(FilePath.CalibrationPath, calib.Name, calib.Name + ".vpp");
  159. if (File.Exists(prcPath))
  160. {
  161. calib.ToolBlock = CogSerializer.LoadObjectFromFile(prcPath) as CogToolBlock;
  162. }
  163. _calibrations.Add(calib);
  164. }
  165. }
  166. catch
  167. {
  168. // 忽略单个文件错误,继续加载其他文件
  169. }
  170. }
  171. }
  172. }
  173. public void SaveAll()
  174. {
  175. lock (_sync)
  176. {
  177. var list = new Dictionary<Guid, string>();
  178. foreach (var item in _calibrations)
  179. {
  180. list[item.Id] = item.Name;
  181. string folder = Path.Combine(FilePath.CalibrationPath, item.Name);
  182. if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
  183. string filePath = Path.Combine(folder, item.Name + ".cfg");
  184. FileHelper.WriteJsonFile(item, filePath);
  185. if (item.ToolBlock != null)
  186. {
  187. string prcPath = Path.Combine(folder, item.Name + ".vpp");
  188. CogSerializer.SaveObjectToFile(item.ToolBlock, prcPath);
  189. }
  190. }
  191. FileHelper.WriteJsonFile(list, Paths.CalibrationListFilePath);
  192. }
  193. }
  194. /// <summary>
  195. /// 校准转换,将像素坐标转换位置坐标
  196. /// </summary>
  197. /// <param name="pixelCoord"></param>
  198. /// <param name="robotCoord"></param>
  199. /// <param name="calib"></param>
  200. /// <returns></returns>
  201. public (bool IsSucceed,double X, double Y, double U) ConvertPixelToPosition((double X, double Y, double angle) pixelCoord, double[] robotCoord, CalibrationInfo calib, RobotBrand robotBrand = RobotBrand.Default)
  202. {
  203. double angle= pixelCoord.angle;
  204. //得到校准矩阵
  205. Matrix<double> matrix = Matrix<double>.Build.DenseOfArray(calib.AffineTransformationMaterial);
  206. if (calib.CameraMount == CameraMount.FixedDown || calib.CameraMount == CameraMount.MobileJ2 || calib.CameraMount == CameraMount.MobileJ4)
  207. {
  208. angle *= -1;
  209. }
  210. //将像素坐标转换成机器人坐标
  211. var rpos = matrix * Vector<double>.Build.Dense(new double[] { pixelCoord.X, pixelCoord.Y, 1 });
  212. if (calib.CameraMount == Enums.CameraMount.MobileJ4)
  213. {
  214. //像素直接转换成mm时的点位
  215. double Robot_X, Robot_Y;
  216. Robot_X = rpos[0];
  217. Robot_Y = rpos[1];
  218. //机器人当前TOOL 0下的坐标
  219. double curpos_x = 0, curpos_y = 0, curpos_u = 0;
  220. curpos_x = robotCoord[0];
  221. curpos_y = robotCoord[1];
  222. curpos_u = robotCoord[2];
  223. //得到旋转矩阵
  224. double angle1 = Math.PI * (curpos_u - calib.MarkPoint.U) / 180;
  225. // 创建T矩阵
  226. Matrix<double> rotationMatrix = Matrix<double>.Build.DenseOfArray(new double[,]
  227. {
  228. { Math.Cos(angle1), -Math.Sin(angle1) },
  229. { Math.Sin(angle1), Math.Cos(angle1) }
  230. });
  231. Vector<double> p3 = Vector<double>.Build.Dense(new double[] { Robot_X - calib.CalibPoints[0].Robot.X, Robot_Y - calib.CalibPoints[0].Robot.Y });
  232. Vector<double> phere = Vector<double>.Build.Dense(new double[] { curpos_x, curpos_y });
  233. var cc = rotationMatrix * p3 + phere;
  234. rpos = Vector<double>.Build.Dense(new double[] { cc[0], cc[1] });
  235. }
  236. else if (calib.CameraMount == Enums.CameraMount.MobileDown_XYPlatform)
  237. {
  238. //像素直接转换成mm时的点位
  239. double Robot_X, Robot_Y;
  240. Robot_X = rpos[0];
  241. Robot_Y = rpos[1];
  242. //模组当前TOOL 0下的坐标(相当于模组吸嘴坐标)
  243. double curpos_x = 0, curpos_y = 0, curpos_u = 0;
  244. curpos_x = robotCoord[0];
  245. curpos_y = robotCoord[1];
  246. curpos_u = robotCoord[2];
  247. //得到旋转矩阵
  248. //double angle1 = Math.PI * (curpos_u - calib.MarkPoint.U) / 180;
  249. double angle1 = 0;
  250. // 创建T矩阵
  251. Matrix<double> rotationMatrix = Matrix<double>.Build.DenseOfArray(new double[,]
  252. {
  253. { Math.Cos(angle1), -Math.Sin(angle1) },
  254. { Math.Sin(angle1), Math.Cos(angle1) }
  255. });
  256. Vector<double> p3 = Vector<double>.Build.Dense(new double[] { Robot_X - calib.CalibPoints[0].Robot.X, Robot_Y - calib.CalibPoints[0].Robot.Y });
  257. Vector<double> phere = Vector<double>.Build.Dense(new double[] { curpos_x, curpos_y });
  258. var cc = rotationMatrix * p3 + phere;
  259. rpos = Vector<double>.Build.Dense(new double[] { cc[0], cc[1] });//Mark点在tool0下的的坐标
  260. }
  261. else
  262. {
  263. //当需要直接转换工具坐标时
  264. if (robotCoord!=null)
  265. {
  266. double curposX = robotCoord[0];
  267. double curposY = robotCoord[1];
  268. double curposU = robotCoord[2];
  269. //根据机器人当前的点位计算工具坐标
  270. ToolCoord tool = new ToolCoord();
  271. if (robotBrand == RobotBrand.Schneider)
  272. {
  273. curposU *= -1;
  274. }
  275. tool.ComputeTool(curposX, curposY, curposU, rpos[0], rpos[1]);
  276. rpos = Vector<double>.Build.Dense(new double[] { tool.X, tool.Y });
  277. }
  278. }
  279. return (true, rpos[0], rpos[1], angle);
  280. }
  281. /// <summary>
  282. /// 校准转换,将像素坐标转换位置坐标
  283. /// </summary>
  284. /// <param name="pixelCoord"></param>
  285. /// <param name="robotCoord"></param>
  286. /// <param name="calibId"></param>
  287. /// <returns></returns>
  288. public (bool IsSucceed, double X, double Y, double U) ConvertPixelToPosition((double X, double Y, double angle) pixelCoord, double[] robotCoord, Guid calibId, RobotBrand robotBrand = RobotBrand.Default)
  289. {
  290. var calib = GetCalibration(calibId);
  291. if (calib == null)
  292. {
  293. return (false, 0, 0, 0);
  294. }
  295. var result = ConvertPixelToPosition(pixelCoord, robotCoord, calib, robotBrand);
  296. return result;
  297. }
  298. public void Dispose()
  299. {
  300. SaveAll();
  301. GC.SuppressFinalize(this);
  302. }
  303. }
  304. }