CameraCalibrationService.cs 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. using MathNet.Numerics.LinearAlgebra;
  2. using OpenCvSharp;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using TeamAAS.Robot.Core;
  10. using TeamAAS.Robot.Enums;
  11. using TeamAAS.Robot.Interfaces;
  12. using TeamAAS.Robot.Models;
  13. using TeamAAS.Robot.Models.Robot;
  14. using TeamAAS.Vision.Calibration.Enums;
  15. using TeamAAS.Vision.Calibration.Models;
  16. namespace TeamAAS.Vision.Calibration.Services
  17. {
  18. /// <summary>
  19. /// 相机标定算法服务(自 TeamAAS-2.0 移植,保持数值行为一致)。
  20. ///
  21. /// 与视觉库/相机/UI 解耦:
  22. /// - 机器人动作走注入的 <see cref="IRobot"/>;
  23. /// - 拍照取点走 <see cref="CaptureAndProcessCallback"/>(由宿主用 ICamera + 视觉引擎实现);
  24. /// - 需要人工摆放标定块的提示走 <see cref="PromptPlaceCalibrationBlockCallback"/>;
  25. /// - 线性代数用 MathNet(工具坐标换算复用 Robot 层 ToolCoord),九点仿射用 OpenCvSharp。
  26. /// </summary>
  27. public class CameraCalibrationService
  28. {
  29. /// <summary>
  30. /// 触发相机拍照并进行图像处理的回调。
  31. /// 字段含义:IsSuccess 是否成功;X/Y 特征点像素坐标;U 特征朝向;ImageWidth/ImageHeight 图像尺寸。
  32. /// </summary>
  33. public Func<Task<(bool IsSuccess, double X, double Y, double U, int ImageWidth, int ImageHeight)>> CaptureAndProcessCallback { get; set; }
  34. /// <summary>
  35. /// 提示人工"将标定块放到中心/吸嘴下"并等待确认的回调;返回 true 表示已摆好可继续。
  36. /// 未设置时视为自动跳过(无需人工确认)。
  37. /// </summary>
  38. public Func<Task<bool>> PromptPlaceCalibrationBlockCallback { get; set; }
  39. /// <summary>由 <see cref="RPoint"/> 模板 + 平面坐标构造一个标定点(Z/U/V/W/Hand/Local/Tool 沿用模板)。</summary>
  40. private static RPoint MakePoint(int number, Vector<double> p, RPoint template)
  41. {
  42. return new RPoint
  43. {
  44. Number = number,
  45. X = (float)p[0],
  46. Y = (float)p[1],
  47. Z = template.Z,
  48. U = template.U,
  49. V = template.V,
  50. W = template.W,
  51. Hand = template.Hand,
  52. Local = template.Local,
  53. Tool = template.Tool
  54. };
  55. }
  56. private async Task<bool> PromptPlaceBlockAsync()
  57. {
  58. if (PromptPlaceCalibrationBlockCallback == null) return true;
  59. return await PromptPlaceCalibrationBlockCallback();
  60. }
  61. /// <summary>
  62. /// 固定向下相机校准工具坐标(相机固定俯视、工具不随 J4 移动)。
  63. /// </summary>
  64. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateFixedDownCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  65. {
  66. double pixelScaleX = 0;
  67. double pixelScaleY = 0;
  68. Matrix<double> RobotCameraRotationMatrix = null;
  69. // 移动机器人至待机位置。
  70. bool isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  71. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  72. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  73. if (calibrationInfo.Pick.PickPlaceModel == PickPlaceModel.Suction)
  74. {
  75. // 打开吸气。
  76. await robot.CalibOutIOAsync(true);
  77. }
  78. else
  79. {
  80. // 张开夹爪(张开为 false,闭合为 true)。
  81. await robot.CalibOutIOAsync(false);
  82. }
  83. if (!await PromptPlaceBlockAsync()) return (false, null, 0, 0, 0, 0);
  84. if (calibrationInfo.Pick.PickPlaceModel == PickPlaceModel.Clamp)
  85. {
  86. // 夹紧夹爪。
  87. await robot.CalibOutIOAsync(true);
  88. }
  89. // 计算相机与机器人坐标系的旋转矩阵。
  90. var robotCameraRotationMatrix = await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  91. if (!robotCameraRotationMatrix.IsSuccess) return (false, null, 0, 0, 0, 0);
  92. pixelScaleX = robotCameraRotationMatrix.PixelScaleX;
  93. pixelScaleY = robotCameraRotationMatrix.PixelScaleY;
  94. RobotCameraRotationMatrix = robotCameraRotationMatrix.RotationMatrix;
  95. // 相机拍照并处理图像。
  96. var photoResult = await CaptureAndProcessCallback();
  97. if (!photoResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  98. PointF P0 = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  99. RPoint RobotU = calibrationInfo.CenterPoint.Clone();
  100. RobotU.U += (float)calibrationInfo.Angle;
  101. // 移动机器人至中心点。
  102. isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, null);
  103. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  104. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  105. // 打开吸气。
  106. await robot.CalibOutIOAsync(true);
  107. // 机器人 U 轴旋转。
  108. isSuccess = await robot.CalibMotionAsync(RobotU, null);
  109. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  110. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  111. // 关闭吸气。
  112. await robot.CalibOutIOAsync(false);
  113. // 移动机器人至待机位置。
  114. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  115. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  116. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  117. // 相机拍照并处理图像。
  118. photoResult = await CaptureAndProcessCallback();
  119. if (!photoResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  120. PointF curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  121. bool IsFinish = false;
  122. for (int i = 0; i < 10; i++)
  123. {
  124. // 计算需要移动的偏移量,逼近中心点。
  125. double offsetx = (P0.X - curPixel.X) * pixelScaleX;
  126. double offsety = (P0.Y - curPixel.Y) * pixelScaleY;
  127. var pos = RobotCameraRotationMatrix.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
  128. // 移动机器人取标定块。
  129. isSuccess = await robot.CalibMotionAsync(RobotU, null);
  130. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  131. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  132. // 打开吸气。
  133. await robot.CalibOutIOAsync(true);
  134. RobotU.X += (float)pos[0];
  135. RobotU.Y += (float)pos[1];
  136. // 机器人移动。
  137. isSuccess = await robot.CalibMotionAsync(RobotU, null);
  138. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  139. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  140. // 关闭吸气。
  141. await robot.CalibOutIOAsync(false);
  142. // 移动机器人至待机位置。
  143. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  144. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  145. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  146. photoResult = await CaptureAndProcessCallback();
  147. if (!photoResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  148. curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  149. offsetx = P0.X - curPixel.X;
  150. offsety = P0.Y - curPixel.Y;
  151. if (Math.Abs(offsetx) < 1 && Math.Abs(offsety) < 1)
  152. {
  153. // 移动机器人取标定块。
  154. isSuccess = await robot.CalibMotionAsync(RobotU, null);
  155. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  156. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  157. // 打开吸气。
  158. await robot.CalibOutIOAsync(true);
  159. // 移动机器人至待机位置。
  160. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  161. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  162. IsFinish = true;
  163. break;
  164. }
  165. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  166. }
  167. if (IsFinish)
  168. {
  169. Vector<double> p0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  170. Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
  171. ToolCoord toolCoord = new ToolCoord();
  172. double u1 = calibrationInfo.CenterPoint.U;
  173. double u2 = RobotU.U;
  174. if (robot.Brand == RobotBrand.Schneider)
  175. {
  176. u1 *= -1;
  177. u2 *= -1;
  178. }
  179. toolCoord.ComputeTool(p0, p1, u1, u2);
  180. if (calibrationInfo.Tool == null) calibrationInfo.Tool = new RobotTool();
  181. calibrationInfo.Tool.X = toolCoord.X;
  182. calibrationInfo.Tool.Y = toolCoord.Y;
  183. return (true, RobotCameraRotationMatrix, calibrationInfo.Tool.X, calibrationInfo.Tool.Y, pixelScaleX, pixelScaleY);
  184. }
  185. return (false, null, 0, 0, 0, 0);
  186. }
  187. /// <summary>
  188. /// J4 移动向下相机校准工具坐标(相机/工具随 J4 运动,需考虑 J4 旋转偏转)。
  189. /// </summary>
  190. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateJ4MovingDownCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  191. {
  192. double pixelScaleX = 0;
  193. double pixelScaleY = 0;
  194. Matrix<double> RobotCameraRotationMatrix = null;
  195. // 移动机器人至中心点。
  196. bool isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, null);
  197. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  198. // 计算相机与机器人坐标系的旋转矩阵。
  199. var robotCameraRotationMatrix = await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  200. if (!robotCameraRotationMatrix.IsSuccess) return (false, null, 0, 0, 0, 0);
  201. pixelScaleX = robotCameraRotationMatrix.PixelScaleX;
  202. pixelScaleY = robotCameraRotationMatrix.PixelScaleY;
  203. RobotCameraRotationMatrix = robotCameraRotationMatrix.RotationMatrix;
  204. // 自动移动机器人至图像中心位置。
  205. var autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  206. if (!autoMoveResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  207. calibrationInfo.CenterPoint.X = (float)autoMoveResult.X;
  208. calibrationInfo.CenterPoint.Y = (float)autoMoveResult.Y;
  209. RPoint RobotU = calibrationInfo.CenterPoint.Clone();
  210. RobotU.U += (float)calibrationInfo.Angle;
  211. // 机器人 U 轴旋转。
  212. isSuccess = await robot.CalibMotionAsync(RobotU, RobotU.Z);
  213. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  214. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  215. // 相机拍照并处理图像。
  216. var photoResult = await CaptureAndProcessCallback();
  217. if (!photoResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  218. autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  219. if (!autoMoveResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  220. RobotU.X = (float)autoMoveResult.X;
  221. RobotU.Y = (float)autoMoveResult.Y;
  222. Vector<double> p0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  223. Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
  224. ToolCoord toolCoord = new ToolCoord();
  225. double u1 = calibrationInfo.CenterPoint.U;
  226. double u2 = RobotU.U;
  227. if (robot.Brand == RobotBrand.Schneider)
  228. {
  229. u1 *= -1;
  230. u2 *= -1;
  231. }
  232. toolCoord.ComputeTool(p0, p1, u1, u2);
  233. if (calibrationInfo.Tool == null) calibrationInfo.Tool = new RobotTool();
  234. calibrationInfo.Tool.X = toolCoord.X;
  235. calibrationInfo.Tool.Y = toolCoord.Y;
  236. return (true, RobotCameraRotationMatrix, calibrationInfo.Tool.X, calibrationInfo.Tool.Y, pixelScaleX, pixelScaleY);
  237. }
  238. /// <summary>
  239. /// 固定向上相机校准工具坐标。
  240. /// </summary>
  241. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateFixedUpCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  242. {
  243. double pixelScaleX = 0;
  244. double pixelScaleY = 0;
  245. Matrix<double> RobotCameraRotationMatrix = null;
  246. // 移动机器人至中心点。
  247. bool isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, null);
  248. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  249. if (calibrationInfo.Pick.PickPlaceModel == PickPlaceModel.Suction)
  250. {
  251. // 打开吸气。
  252. await robot.CalibOutIOAsync(true);
  253. }
  254. else
  255. {
  256. // 张开夹爪。
  257. await robot.CalibOutIOAsync(false);
  258. }
  259. if (!await PromptPlaceBlockAsync()) return (false, null, 0, 0, 0, 0);
  260. if (calibrationInfo.Pick.PickPlaceModel == PickPlaceModel.Clamp)
  261. {
  262. // 夹紧夹爪。
  263. await robot.CalibOutIOAsync(true);
  264. }
  265. // 计算相机与机器人坐标系的旋转矩阵。
  266. var robotCameraRotationMatrix = await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  267. if (!robotCameraRotationMatrix.IsSuccess) return (false, null, 0, 0, 0, 0);
  268. pixelScaleX = robotCameraRotationMatrix.PixelScaleX;
  269. pixelScaleY = robotCameraRotationMatrix.PixelScaleY;
  270. RobotCameraRotationMatrix = robotCameraRotationMatrix.RotationMatrix;
  271. // 自动移动机器人至图像中心位置。
  272. var autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  273. if (!autoMoveResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  274. calibrationInfo.CenterPoint.X = (float)autoMoveResult.X;
  275. calibrationInfo.CenterPoint.Y = (float)autoMoveResult.Y;
  276. RPoint RobotU = calibrationInfo.CenterPoint.Clone();
  277. RobotU.U += (float)calibrationInfo.Angle;
  278. // 机器人 U 轴旋转。
  279. isSuccess = await robot.CalibMotionAsync(RobotU, RobotU.Z);
  280. if (!isSuccess) return (false, null, 0, 0, 0, 0);
  281. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0, 0, 0);
  282. // 相机拍照并处理图像。
  283. var photoResult = await CaptureAndProcessCallback();
  284. if (!photoResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  285. autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  286. if (!autoMoveResult.IsSuccess) return (false, null, 0, 0, 0, 0);
  287. RobotU.X = (float)autoMoveResult.X;
  288. RobotU.Y = (float)autoMoveResult.Y;
  289. Vector<double> p0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  290. Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
  291. ToolCoord toolCoord = new ToolCoord();
  292. double u1 = calibrationInfo.CenterPoint.U;
  293. double u2 = RobotU.U;
  294. if (robot.Brand == RobotBrand.Schneider)
  295. {
  296. u1 *= -1;
  297. u2 *= -1;
  298. }
  299. toolCoord.ComputeTool(p0, p1, u1, u2);
  300. if (calibrationInfo.Tool == null) calibrationInfo.Tool = new RobotTool();
  301. calibrationInfo.Tool.X = toolCoord.X;
  302. calibrationInfo.Tool.Y = toolCoord.Y;
  303. return (true, RobotCameraRotationMatrix, calibrationInfo.Tool.X, calibrationInfo.Tool.Y, pixelScaleX, pixelScaleY);
  304. }
  305. /// <summary>
  306. /// 通过未标定相机校准工具坐标:按 <see cref="CameraMount"/> 分发到对应标定方法。
  307. /// </summary>
  308. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateUncalibratedCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  309. {
  310. if (calibrationInfo == null) return (false, null, 0, 0, 0, 0);
  311. if (robot == null || !robot.IsConnected) return (false, null, 0, 0, 0, 0);
  312. // 设置校准参数。
  313. await robot.CalibParameAsync(calibrationInfo.Pick, calibrationInfo.Speed, calibrationInfo.Accel, calibrationInfo.Power, calibrationInfo.WaitSuction, calibrationInfo.WaitBlow);
  314. (bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY) calibresult;
  315. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  316. {
  317. calibresult = await CalibrateFixedDownCameraTool(calibrationInfo, robot, cancellationToken);
  318. }
  319. else if (calibrationInfo.CameraMount == CameraMount.FixedUp)
  320. {
  321. calibresult = await CalibrateFixedUpCameraTool(calibrationInfo, robot, cancellationToken);
  322. }
  323. else if (calibrationInfo.CameraMount == CameraMount.MobileJ4)
  324. {
  325. calibresult = await CalibrateJ4MovingDownCameraTool(calibrationInfo, robot, cancellationToken);
  326. }
  327. else
  328. {
  329. return (false, null, 0, 0, 0, 0);
  330. }
  331. if (!calibresult.IsSuccess) return (false, null, 0, 0, 0, 0);
  332. return (true, calibresult.RotationMatrix, calibresult.ToolX, calibresult.ToolY, calibresult.PixelScaleX, calibresult.PixelScaleY);
  333. }
  334. /// <summary>
  335. /// 自动识别机器人坐标系与图像坐标系的旋转矩阵(三点法)与像素尺度。
  336. /// </summary>
  337. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double PixelScaleX, double PixelScaleY)> AutoIdentifyRobotCameraRotationMatrix(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  338. {
  339. // 移动距离,单位 mm。
  340. float distance = ((float)calibrationInfo.Height) / 3;
  341. //-------------------------------------------P0-----------------------------------
  342. bool isSuccess;
  343. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  344. {
  345. isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, null);
  346. if (!isSuccess) return (false, null, 0, 0);
  347. // 关闭吸气。
  348. await robot.CalibOutIOAsync(false);
  349. }
  350. else
  351. {
  352. isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, calibrationInfo.CenterPoint.Z);
  353. if (!isSuccess) return (false, null, 0, 0);
  354. }
  355. RPoint RobotP0 = await robot.GetRobotPosAsync();
  356. RPoint RobotP1 = RobotP0.Clone();
  357. RPoint RobotP2 = RobotP0.Clone();
  358. RobotP1.X += distance;
  359. RobotP2.Y += distance;
  360. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  361. {
  362. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  363. if (!isSuccess) return (false, null, 0, 0);
  364. }
  365. var result = await CaptureAndProcessCallback();
  366. if (!result.IsSuccess) return (false, null, 0, 0);
  367. PointF P0 = new PointF((float)result.X, (float)result.Y);
  368. //-------------------------------------------P1-----------------------------------
  369. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  370. {
  371. isSuccess = await robot.CalibMotionAsync(RobotP0, null);
  372. if (!isSuccess) return (false, null, 0, 0);
  373. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  374. await robot.CalibOutIOAsync(true);
  375. isSuccess = await robot.CalibMotionAsync(RobotP1, null);
  376. if (!isSuccess) return (false, null, 0, 0);
  377. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  378. await robot.CalibOutIOAsync(false);
  379. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  380. if (!isSuccess) return (false, null, 0, 0);
  381. }
  382. else
  383. {
  384. isSuccess = await robot.CalibMotionAsync(RobotP1, RobotP1.Z);
  385. if (!isSuccess) return (false, null, 0, 0);
  386. }
  387. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  388. result = await CaptureAndProcessCallback();
  389. if (!result.IsSuccess) return (false, null, 0, 0);
  390. PointF P1 = new PointF((float)result.X, (float)result.Y);
  391. //-------------------------------------------P2-----------------------------------
  392. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  393. {
  394. isSuccess = await robot.CalibMotionAsync(RobotP1, null);
  395. if (!isSuccess) return (false, null, 0, 0);
  396. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  397. await robot.CalibOutIOAsync(true);
  398. isSuccess = await robot.CalibMotionAsync(RobotP2, null);
  399. if (!isSuccess) return (false, null, 0, 0);
  400. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  401. await robot.CalibOutIOAsync(false);
  402. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  403. if (!isSuccess) return (false, null, 0, 0);
  404. }
  405. else
  406. {
  407. isSuccess = await robot.CalibMotionAsync(RobotP2, RobotP2.Z);
  408. if (!isSuccess) return (false, null, 0, 0);
  409. }
  410. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  411. result = await CaptureAndProcessCallback();
  412. if (!result.IsSuccess) return (false, null, 0, 0);
  413. PointF P2 = new PointF((float)result.X, (float)result.Y);
  414. //-------------------------------------------P0--------------------------------------
  415. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  416. {
  417. isSuccess = await robot.CalibMotionAsync(RobotP2, null);
  418. if (!isSuccess) return (false, null, 0, 0);
  419. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  420. await robot.CalibOutIOAsync(true);
  421. isSuccess = await robot.CalibMotionAsync(RobotP0, null);
  422. if (!isSuccess) return (false, null, 0, 0);
  423. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  424. await robot.CalibOutIOAsync(false);
  425. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  426. if (!isSuccess) return (false, null, 0, 0);
  427. }
  428. else
  429. {
  430. isSuccess = await robot.CalibMotionAsync(RobotP0, RobotP0.Z);
  431. if (!isSuccess) return (false, null, 0, 0);
  432. }
  433. if (cancellationToken.IsCancellationRequested) return (false, null, 0, 0);
  434. //-----------------------------------------计算旋转矩阵------------------------------
  435. Matrix<double> M = CalculateImageRotationMatrix(P0, P1, P2);
  436. //-----------------------------------------计算像素和毫米比例-----------------------
  437. Vector<double> P11 = Vector<double>.Build.Dense(new double[] { distance, 0 });
  438. Vector<double> P21 = Vector<double>.Build.Dense(new double[] { P1.X, P1.Y }) - Vector<double>.Build.Dense(new double[] { P0.X, P0.Y });
  439. double PixelScaleX = Math.Abs((M * P11)[0] / P21[0]); // mm/pixel
  440. double PixelScaleY = Math.Abs((M * P11)[1] / P21[1]); // mm/pixel
  441. return (true, M, PixelScaleX, PixelScaleY);
  442. }
  443. /// <summary>
  444. /// 计算图像坐标系到机器人坐标系的旋转矩阵(三点基向量法)。
  445. /// </summary>
  446. public Matrix<double> CalculateImageRotationMatrix(PointF P0, PointF P1, PointF P2)
  447. {
  448. Vector<double> Point0 = Vector<double>.Build.Dense(new double[] { (double)P0.X, (double)P0.Y });
  449. Vector<double> Point1 = Vector<double>.Build.Dense(new double[] { (double)P1.X, (double)P1.Y });
  450. Vector<double> Point2 = Vector<double>.Build.Dense(new double[] { (double)P2.X, (double)P2.Y });
  451. Vector<double> E1 = (Point1 - Point0) / (Point1 - Point0).L2Norm();
  452. Vector<double> E2 = (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1) / (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1).L2Norm();
  453. Matrix<double> R = Matrix<double>.Build.DenseOfColumnVectors(E1, E2);
  454. return R;
  455. }
  456. /// <summary>
  457. /// 自动移动机器人到图像中心位置(视觉伺服逼近)。
  458. /// </summary>
  459. public async Task<(bool IsSuccess, double X, double Y, double U)> AutoMoveRobotToImageCenter(IRobot robot, Matrix<double> matrix, double PixelScaleX, double PixelScaleY, CancellationToken cancellationToken)
  460. {
  461. bool IsFinish = false;
  462. var result = await CaptureAndProcessCallback();
  463. if (!result.IsSuccess) return (false, 0, 0, 0);
  464. PointF curPixel = new PointF((float)result.X, (float)result.Y);
  465. PointF P0 = new PointF((float)(result.ImageWidth / 2), (float)(result.ImageHeight / 2));
  466. var curpos = robot.GetRobotPos();
  467. for (int i = 0; i < 10; i++)
  468. {
  469. double offsetx = (P0.X - curPixel.X) * PixelScaleX;
  470. double offsety = (P0.Y - curPixel.Y) * PixelScaleY;
  471. var pos = matrix.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
  472. curpos.X += (float)pos[0];
  473. curpos.Y += (float)pos[1];
  474. bool isSuccess = await robot.CalibMotionAsync(curpos, curpos.Z);
  475. if (!isSuccess) return (false, 0, 0, 0);
  476. result = await CaptureAndProcessCallback();
  477. if (!result.IsSuccess) return (false, 0, 0, 0);
  478. curPixel = new PointF((float)result.X, (float)result.Y);
  479. offsetx = P0.X - curPixel.X;
  480. offsety = P0.Y - curPixel.Y;
  481. if (Math.Abs(offsetx) < 1 && Math.Abs(offsety) < 1)
  482. {
  483. IsFinish = true;
  484. break;
  485. }
  486. if (cancellationToken.IsCancellationRequested) return (false, 0, 0, 0);
  487. }
  488. if (IsFinish)
  489. {
  490. var finalPos = robot.GetRobotPos();
  491. return (true, finalPos.X, finalPos.Y, finalPos.U);
  492. }
  493. return (false, 0, 0, 0);
  494. }
  495. /// <summary>
  496. /// 自动九点标定:以中心点为原点生成 3x3 九点,逐点运动+拍照,OpenCV 求仿射,计算精度指标。
  497. /// </summary>
  498. public async Task<(bool IsSuccess, Matrix<double> AffineTransformationMaterial, RobotPixelPoint[] NinePoint, CalibrationResult Result)> AutoNinePointCalibration(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  499. {
  500. if (calibrationInfo == null) return (false, null, null, null);
  501. if (robot == null || !robot.IsConnected) return (false, null, null, null);
  502. double IntervalX = calibrationInfo.Width / 2;
  503. double IntervalY = calibrationInfo.Height / 2;
  504. Vector<double>[] grid =
  505. {
  506. Vector<double>.Build.Dense(new double[] { -IntervalX, -IntervalY }),
  507. Vector<double>.Build.Dense(new double[] { 0, -IntervalY }),
  508. Vector<double>.Build.Dense(new double[] { IntervalX, -IntervalY }),
  509. Vector<double>.Build.Dense(new double[] { IntervalX, 0 }),
  510. Vector<double>.Build.Dense(new double[] { 0, 0 }),
  511. Vector<double>.Build.Dense(new double[] { -IntervalX, 0 }),
  512. Vector<double>.Build.Dense(new double[] { -IntervalX, IntervalY }),
  513. Vector<double>.Build.Dense(new double[] { 0, IntervalY }),
  514. Vector<double>.Build.Dense(new double[] { IntervalX, IntervalY }),
  515. };
  516. // 设置校准参数。
  517. await robot.CalibParameAsync(calibrationInfo.Pick, calibrationInfo.Speed, calibrationInfo.Accel, calibrationInfo.Power, calibrationInfo.WaitSuction, calibrationInfo.WaitBlow);
  518. bool isSuccess;
  519. var P0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  520. if (robot.Brand == RobotBrand.XYZ_Platform)
  521. {
  522. // 移动机器人至中心点。
  523. isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, null);
  524. if (!isSuccess) return (false, null, null, null);
  525. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  526. // 计算相机与机器人坐标系的旋转矩阵。
  527. var robotCameraRotationMatrix = await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  528. if (!robotCameraRotationMatrix.IsSuccess) return (false, null, null, null);
  529. // 自动移动机器人至图像中心位置。
  530. var autoMoveResult = await AutoMoveRobotToImageCenter(robot, robotCameraRotationMatrix.RotationMatrix, robotCameraRotationMatrix.PixelScaleX, robotCameraRotationMatrix.PixelScaleY, cancellationToken);
  531. if (!autoMoveResult.IsSuccess) return (false, null, null, null);
  532. calibrationInfo.CenterPoint.X = (float)autoMoveResult.X;
  533. calibrationInfo.CenterPoint.Y = (float)autoMoveResult.Y;
  534. if (calibrationInfo.CameraMount != CameraMount.MobileDown_XYPlatform)
  535. {
  536. calibrationInfo.MarkPoint = calibrationInfo.CenterPoint.Clone();
  537. }
  538. calibrationInfo.RobotCameraRotationMatrix = robotCameraRotationMatrix.RotationMatrix.ToArray();
  539. }
  540. // 机器人坐标系与图像坐标系之间的旋转矩阵。
  541. Matrix<double> RobotCameraRotationMatrix = Matrix<double>.Build.DenseOfArray(calibrationInfo.RobotCameraRotationMatrix);
  542. // 将九点相对偏移旋转到机器人坐标系并叠加中心点。
  543. List<RPoint> rPoints = new List<RPoint>();
  544. for (int i = 0; i < grid.Length; i++)
  545. {
  546. var p = RobotCameraRotationMatrix.Inverse() * grid[i] + P0;
  547. rPoints.Add(MakePoint(i + 1, p, calibrationInfo.CenterPoint));
  548. }
  549. List<PointF> PixelPoslist = new List<PointF>();
  550. if (calibrationInfo.CalibPoints == null)
  551. {
  552. calibrationInfo.CalibPoints = new System.Collections.ObjectModel.ObservableCollection<RobotPixelPoint>();
  553. }
  554. else
  555. {
  556. calibrationInfo.CalibPoints.Clear();
  557. }
  558. // 移动相机标定时,需要先记住标定块的绝对坐标(相对机器人基坐标系)P0。
  559. if (calibrationInfo.CameraMount == CameraMount.MobileJ4)
  560. {
  561. isSuccess = await robot.CalibMotionAsync(calibrationInfo.CenterPoint, null);
  562. if (!isSuccess) return (false, null, null, null);
  563. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  564. // 将当前吸嘴在 Tool 0 下的坐标转换为工具坐标系下的坐标,并记录。
  565. ToolCoord toolCoord = new ToolCoord();
  566. toolCoord.SetTool(calibrationInfo.Tool.X, calibrationInfo.Tool.Y);
  567. Vector<double> p = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  568. var pn = toolCoord.GetToolnCoord(p, calibrationInfo.CenterPoint.U);
  569. if (calibrationInfo.MarkPoint == null) calibrationInfo.MarkPoint = new RPoint();
  570. calibrationInfo.MarkPoint = calibrationInfo.CenterPoint.Clone();
  571. calibrationInfo.MarkPoint.X = (float)pn[0];
  572. calibrationInfo.MarkPoint.Y = (float)pn[1];
  573. }
  574. for (int i = 0; i < rPoints.Count; i++)
  575. {
  576. // 移动机器人至第 i 个标定点。
  577. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  578. {
  579. isSuccess = await robot.CalibMotionAsync(rPoints[i], null);
  580. if (!isSuccess) return (false, null, null, null);
  581. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  582. // 关闭吸气。
  583. await robot.CalibOutIOAsync(false);
  584. // 移动机器人至待机位置。
  585. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  586. if (!isSuccess) return (false, null, null, null);
  587. }
  588. else
  589. {
  590. isSuccess = await robot.CalibMotionAsync(rPoints[i], rPoints[i].Z);
  591. if (!isSuccess) return (false, null, null, null);
  592. }
  593. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  594. // 相机拍照并处理图像。
  595. var photoResult = await CaptureAndProcessCallback();
  596. if (!photoResult.IsSuccess) return (false, null, null, null);
  597. PointF curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  598. PixelPoslist.Add(curPixel);
  599. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  600. {
  601. // 移动机器人至第 i 个标定点。
  602. isSuccess = await robot.CalibMotionAsync(rPoints[i], null);
  603. if (!isSuccess) return (false, null, null, null);
  604. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  605. // 打开吸气。
  606. await robot.CalibOutIOAsync(true);
  607. }
  608. calibrationInfo.CalibPoints.Add(new RobotPixelPoint()
  609. {
  610. Number = i + 1,
  611. Robot = new PointF(rPoints[i].X, rPoints[i].Y),
  612. Pixel = new PointF(PixelPoslist[i].X, PixelPoslist[i].Y)
  613. });
  614. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  615. }
  616. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  617. {
  618. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  619. if (!isSuccess) return (false, null, null, null);
  620. }
  621. if (cancellationToken.IsCancellationRequested) return (false, null, null, null);
  622. Mat matPixel = new Mat(9, 2, MatType.CV_64F);
  623. Mat matRobot = new Mat(9, 2, MatType.CV_64F);
  624. for (int i = 0; i < 9; i++)
  625. {
  626. matPixel.Set<double>(i, 0, PixelPoslist[i].X);
  627. matPixel.Set<double>(i, 1, PixelPoslist[i].Y);
  628. // 移动相机模式需要根据机器人九点运动的反向变换,使 Mark 点移动到相机中心。
  629. if (calibrationInfo.CameraMount == CameraMount.MobileJ4 || calibrationInfo.CameraMount == CameraMount.MobileDown_XYPlatform)
  630. {
  631. double ox = rPoints[i].X - rPoints[0].X;
  632. double oy = rPoints[i].Y - rPoints[0].Y;
  633. matRobot.Set<double>(i, 0, calibrationInfo.MarkPoint.X - ox);
  634. matRobot.Set<double>(i, 1, calibrationInfo.MarkPoint.Y - oy);
  635. }
  636. else
  637. {
  638. // 将 Tool 0 下的坐标转换为 Tool n 下的坐标。
  639. if (calibrationInfo.Tool != null)
  640. {
  641. ToolCoord toolCoord = new ToolCoord();
  642. toolCoord.SetTool(calibrationInfo.Tool.X, calibrationInfo.Tool.Y);
  643. double u1 = rPoints[i].U;
  644. if (robot.Brand == RobotBrand.Schneider) u1 *= -1;
  645. var trans = toolCoord.GetToolnCoord(Vector<double>.Build.Dense(new double[] { rPoints[i].X, rPoints[i].Y }), u1);
  646. matRobot.Set<double>(i, 0, trans[0]);
  647. matRobot.Set<double>(i, 1, trans[1]);
  648. }
  649. else
  650. {
  651. matRobot.Set<double>(i, 0, rPoints[i].X);
  652. matRobot.Set<double>(i, 1, rPoints[i].Y);
  653. }
  654. }
  655. }
  656. Mat mat2d = Cv2.EstimateAffine2D(matPixel, matRobot);
  657. if (mat2d == null || mat2d.Empty()) return (false, null, null, null);
  658. double a = mat2d.Get<double>(0, 0);
  659. double b = mat2d.Get<double>(0, 1);
  660. double tx = mat2d.Get<double>(0, 2);
  661. double c = mat2d.Get<double>(1, 0);
  662. double d = mat2d.Get<double>(1, 1);
  663. double ty = mat2d.Get<double>(1, 2);
  664. double[,] vv = { { a, b, tx }, { c, d, ty } };
  665. Matrix<double> matrix = Matrix<double>.Build.DenseOfArray(vv);
  666. calibrationInfo.AffineTransformationMaterial = matrix.ToArray();
  667. // 2x2 仿射部分的列向量分别表示图像 X/Y 轴在机器人坐标系中的方向和比例。
  668. double pixelScaleX = Math.Sqrt(a * a + c * c);
  669. double pixelScaleY = Math.Sqrt(b * b + d * d);
  670. double rotateFromXAxis = ToDegree(Math.Atan2(c, a));
  671. double rotateFromYAxis = ToDegree(Math.Atan2(-b, d));
  672. double rotate = AverageAngle(rotateFromXAxis, rotateFromYAxis);
  673. double dot = a * b + c * d;
  674. double axisLengthProduct = pixelScaleX * pixelScaleY;
  675. double axisAngle = axisLengthProduct > 0 ? ToDegree(Math.Acos(Clamp(dot / axisLengthProduct, -1, 1))) : 90;
  676. double shearAngle = axisAngle - 90;
  677. double determinant = a * d - b * c;
  678. // RMS(均方根值) 标定偏差。
  679. double sumX = 0, sumY = 0;
  680. List<double> differenceX = new List<double>();
  681. List<double> differenceY = new List<double>();
  682. for (int i = 0; i < 9; i++)
  683. {
  684. var pixel = Vector<double>.Build.Dense(new double[] { PixelPoslist[i].X, PixelPoslist[i].Y, 1 });
  685. if (calibrationInfo.CameraMount == CameraMount.MobileJ4 || calibrationInfo.CameraMount == CameraMount.MobileDown_XYPlatform)
  686. {
  687. double ox = rPoints[i].X - rPoints[0].X;
  688. double oy = rPoints[i].Y - rPoints[0].Y;
  689. differenceX.Add((calibrationInfo.MarkPoint.X - ox) - (matrix * pixel)[0]);
  690. differenceY.Add((calibrationInfo.MarkPoint.Y - oy) - (matrix * pixel)[1]);
  691. }
  692. else
  693. {
  694. if (calibrationInfo.Tool != null)
  695. {
  696. ToolCoord toolCoord = new ToolCoord();
  697. toolCoord.SetTool(calibrationInfo.Tool.X, calibrationInfo.Tool.Y);
  698. double u1 = rPoints[i].U;
  699. if (robot.Brand == RobotBrand.Schneider) u1 *= -1;
  700. var trans = toolCoord.GetToolnCoord(Vector<double>.Build.Dense(new double[] { rPoints[i].X, rPoints[i].Y }), u1);
  701. differenceX.Add(trans[0] - (matrix * pixel)[0]);
  702. differenceY.Add(trans[1] - (matrix * pixel)[1]);
  703. }
  704. else
  705. {
  706. differenceX.Add(rPoints[i].X - (matrix * pixel)[0]);
  707. differenceY.Add(rPoints[i].Y - (matrix * pixel)[1]);
  708. }
  709. }
  710. sumX += Math.Pow(differenceX[differenceX.Count - 1], 2);
  711. sumY += Math.Pow(differenceY[differenceY.Count - 1], 2);
  712. }
  713. double rmsX = Math.Sqrt(sumX / 9);
  714. double rmsY = Math.Sqrt(sumY / 9);
  715. var errorDistances = differenceX.Zip(differenceY, (dx, dy) => Math.Sqrt(dx * dx + dy * dy)).ToList();
  716. CalibrationResult calibrationResult = new CalibrationResult();
  717. calibrationResult.ScaleX = pixelScaleX;
  718. calibrationResult.ScaleY = pixelScaleY;
  719. calibrationResult.TranslationX = tx;
  720. calibrationResult.TranslationY = ty;
  721. calibrationResult.Rotate = rotate;
  722. calibrationResult.ImageXAxisAngle = rotateFromXAxis;
  723. calibrationResult.ImageYAxisAngle = rotateFromYAxis;
  724. calibrationResult.ShearAngle = shearAngle;
  725. calibrationResult.ScaleRatio = pixelScaleY == 0 ? 0 : pixelScaleX / pixelScaleY;
  726. calibrationResult.RMSX = rmsX;
  727. calibrationResult.RMSY = rmsY;
  728. calibrationResult.RMS = Math.Sqrt(errorDistances.Select(value => value * value).Average());
  729. calibrationResult.MeanError = errorDistances.Average();
  730. calibrationResult.MaxErrorValue = errorDistances.Max();
  731. calibrationResult.MeanErrorX = differenceX.Average();
  732. calibrationResult.MeanErrorY = differenceY.Average();
  733. calibrationResult.MaxErrorValueX = differenceX.Select(Math.Abs).Max();
  734. calibrationResult.MaxErrorValueY = differenceY.Select(Math.Abs).Max();
  735. calibrationResult.Determinant = determinant;
  736. calibrationInfo.CalibrationResult = calibrationResult;
  737. return (true, matrix, calibrationInfo.CalibPoints.ToArray(), calibrationResult);
  738. }
  739. /// <summary>
  740. /// 执行校准验证:在 5 个验证点上复算误差,评估标定质量。
  741. /// </summary>
  742. public async Task<(bool IsSuccess, CalibrationTestResult Result)> ExecuteCalibrationValidation(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  743. {
  744. if (calibrationInfo == null) return (false, null);
  745. if (robot == null || !robot.IsConnected) return (false, null);
  746. double IntervalX = calibrationInfo.Width * 0.90 / 2;
  747. double IntervalY = calibrationInfo.Height * 0.90 / 2;
  748. Vector<double>[] grid =
  749. {
  750. Vector<double>.Build.Dense(new double[] { -IntervalX, -IntervalY }),
  751. Vector<double>.Build.Dense(new double[] { IntervalX, -IntervalY }),
  752. Vector<double>.Build.Dense(new double[] { 0, 0 }),
  753. Vector<double>.Build.Dense(new double[] { -IntervalX, IntervalY }),
  754. Vector<double>.Build.Dense(new double[] { IntervalX, IntervalY }),
  755. };
  756. var P0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  757. Matrix<double> RobotCameraRotationMatrix = Matrix<double>.Build.DenseOfArray(calibrationInfo.RobotCameraRotationMatrix);
  758. List<RPoint> rPoints = new List<RPoint>();
  759. for (int i = 0; i < grid.Length; i++)
  760. {
  761. var p = RobotCameraRotationMatrix.Inverse() * grid[i] + P0;
  762. rPoints.Add(MakePoint(i + 1, p, calibrationInfo.CenterPoint));
  763. }
  764. bool isSuccess;
  765. List<Vector<double>> TestPos = new List<Vector<double>>();
  766. for (int i = 0; i < rPoints.Count; i++)
  767. {
  768. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  769. {
  770. isSuccess = await robot.CalibMotionAsync(rPoints[i], null);
  771. if (!isSuccess) return (false, null);
  772. if (cancellationToken.IsCancellationRequested) return (false, null);
  773. await robot.CalibOutIOAsync(false);
  774. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  775. if (!isSuccess) return (false, null);
  776. }
  777. else
  778. {
  779. isSuccess = await robot.CalibMotionAsync(rPoints[i], rPoints[i].Z);
  780. if (!isSuccess) return (false, null);
  781. }
  782. if (cancellationToken.IsCancellationRequested) return (false, null);
  783. var photoResult = await CaptureAndProcessCallback();
  784. if (!photoResult.IsSuccess) return (false, null);
  785. PointF pixelpos = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  786. Matrix<double> matrix = Matrix<double>.Build.DenseOfArray(calibrationInfo.AffineTransformationMaterial);
  787. var rpos = matrix * Vector<double>.Build.Dense(new double[] { pixelpos.X, pixelpos.Y, 1 });
  788. TestPos.Add(rpos);
  789. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  790. {
  791. isSuccess = await robot.CalibMotionAsync(rPoints[i], null);
  792. if (!isSuccess) return (false, null);
  793. await robot.CalibOutIOAsync(true);
  794. }
  795. if (cancellationToken.IsCancellationRequested) return (false, null);
  796. }
  797. if (calibrationInfo.CameraMount == CameraMount.FixedDown)
  798. {
  799. isSuccess = await robot.CalibMotionAsync(calibrationInfo.HomePoint, null);
  800. if (!isSuccess) return (false, null);
  801. }
  802. // RMS(均方根值)标定偏差。
  803. double sumX = 0, sumY = 0;
  804. List<double> differenceX = new List<double>();
  805. List<double> differenceY = new List<double>();
  806. for (int i = 0; i < 5; i++)
  807. {
  808. if (calibrationInfo.CameraMount == CameraMount.MobileJ4 || calibrationInfo.CameraMount == CameraMount.MobileDown_XYPlatform)
  809. {
  810. double Robot_X = TestPos[i][0];
  811. double Robot_Y = TestPos[i][1];
  812. double curpos_x = rPoints[i].X;
  813. double curpos_y = rPoints[i].Y;
  814. double curpos_u = rPoints[i].U;
  815. double angle = Math.PI * (curpos_u - calibrationInfo.MarkPoint.U) / 180;
  816. Matrix<double> rotationMatrix = Matrix<double>.Build.DenseOfArray(new double[,]
  817. {
  818. { Math.Cos(angle), -Math.Sin(angle) },
  819. { Math.Sin(angle), Math.Cos(angle) }
  820. });
  821. Vector<double> p3 = Vector<double>.Build.Dense(new double[] { Robot_X - calibrationInfo.CalibPoints[0].Robot.X, Robot_Y - calibrationInfo.CalibPoints[0].Robot.Y });
  822. Vector<double> phere = Vector<double>.Build.Dense(new double[] { curpos_x, curpos_y });
  823. var cc = rotationMatrix * p3 + phere;
  824. differenceX.Add(cc[0] - calibrationInfo.MarkPoint.X);
  825. differenceY.Add(cc[1] - calibrationInfo.MarkPoint.Y);
  826. }
  827. else
  828. {
  829. if (calibrationInfo.Tool != null)
  830. {
  831. ToolCoord toolCoord = new ToolCoord();
  832. toolCoord.SetTool(calibrationInfo.Tool.X, calibrationInfo.Tool.Y);
  833. double u1 = rPoints[i].U;
  834. if (robot.Brand == RobotBrand.Schneider) u1 *= -1;
  835. var trans = toolCoord.GetToolnCoord(Vector<double>.Build.Dense(new double[] { rPoints[i].X, rPoints[i].Y }), u1);
  836. differenceX.Add(trans[0] - TestPos[i][0]);
  837. differenceY.Add(trans[1] - TestPos[i][1]);
  838. }
  839. else
  840. {
  841. differenceX.Add(rPoints[i].X - TestPos[i][0]);
  842. differenceY.Add(rPoints[i].Y - TestPos[i][1]);
  843. }
  844. }
  845. sumX += Math.Pow(differenceX[differenceX.Count - 1], 2);
  846. sumY += Math.Pow(differenceY[differenceY.Count - 1], 2);
  847. }
  848. double rmsX = Math.Sqrt(sumX / 5);
  849. double rmsY = Math.Sqrt(sumY / 5);
  850. CalibrationTestResult calibrationTestResult = new CalibrationTestResult();
  851. calibrationTestResult.MaxErrorValueX = differenceX.Max();
  852. calibrationTestResult.MinErrorValueX = differenceX.Min();
  853. calibrationTestResult.MaxErrorValueY = differenceY.Max();
  854. calibrationTestResult.MinErrorValueY = differenceY.Min();
  855. calibrationTestResult.RMSEX = rmsX;
  856. calibrationTestResult.RMSEY = rmsY;
  857. return (true, calibrationTestResult);
  858. }
  859. private static double ToDegree(double radians) => radians * 180 / Math.PI;
  860. private static double AverageAngle(double angle1, double angle2)
  861. => NormalizeAngle(angle1 + NormalizeAngle(angle2 - angle1) / 2);
  862. private static double NormalizeAngle(double angle)
  863. {
  864. while (angle > 180) angle -= 360;
  865. while (angle <= -180) angle += 360;
  866. return angle;
  867. }
  868. private static double Clamp(double value, double min, double max)
  869. {
  870. if (value < min) return min;
  871. return value > max ? max : value;
  872. }
  873. }
  874. }