CameraCalibrationService.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. using MaterialDesignThemes.Wpf;
  2. using MathNet.Numerics;
  3. using MathNet.Numerics.LinearAlgebra;
  4. using OpenCvSharp;
  5. using Prism.Services.Dialogs;
  6. using System;
  7. using System.Drawing;
  8. using System.Security.Cryptography;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using TeamAAS_VP.Controls;
  12. using TeamAAS_VP.Core;
  13. using TeamAAS_VP.Interfaces;
  14. using TeamAAS_VP.Models;
  15. using TeamAAS_VP.Models.Calibration;
  16. using TeamAAS_VP.Models.Robot;
  17. using TeamAAS_VP.Resources.Languages;
  18. namespace TeamAAS_VP.Services
  19. {
  20. /// <summary>
  21. /// 相机标定服务的骨架实现。实际的标定算法和数据持久化由后续实现补充。
  22. /// </summary>
  23. public class CameraCalibrationService : ICameraCalibrationService
  24. {
  25. /// <summary>
  26. /// 触发相机拍照并进行图像处理的回调函数
  27. /// 在标定过程中需要获取图像特征点时调用此回调
  28. /// Tuple 字段含义:
  29. /// Item1 - IsSuccess: 拍照及图像处理是否成功;
  30. /// Item2 - X: 图像处理后得到的机器人目标 X(mm);
  31. /// Item3 - Y: 图像处理后得到的机器人目标 Y(mm);
  32. /// Item4 - U: 图像处理后得到的机器人目标 U(工具朝向)。
  33. /// </summary>
  34. public Func<Task<(bool IsSuccess, double X, double Y, double U, int ImageWidth, int ImageHeight)>> CaptureAndProcessCallback { get; set; }
  35. /// <summary>
  36. /// 固定向下相机校准工具坐标。
  37. /// 该方法用于在相机固定、朝下安装且工具不随 J4 移动的情况下标定相机与机器人工具坐标系之间的关系。
  38. /// </summary>
  39. /// <param name="calibrationInfo">包含标定时所需的相机参数、图像处理配置与标定点信息的对象。</param>
  40. /// <param name="robot">实现了 <see cref="IRobot"/> 的机器人抽象,用于获取/移动机器人位姿。</param>
  41. /// <param name="cancellationToken">用于取消操作的标记。</param>
  42. /// <returns>
  43. /// 一个包含以下字段的元组:
  44. /// IsSuccess - 标定是否成功;
  45. /// Matrix<double> - 图像与像素坐标系之间的旋转(或仿射)矩阵(行列顺序与具体实现约定);
  46. /// ToolX - 标定得到的工具坐标系在机器人基坐标系下的 X(单位与机器人一致);
  47. /// ToolY - 标定得到的工具坐标系在机器人基坐标系下的 Y(单位与机器人一致);
  48. /// PixelScaleX - 像素到物理距离在 X 方向的缩放因子(如 mm/px);
  49. /// PixelScaleY - 像素到物理距离在 Y 方向的缩放因子(如 mm/px)。
  50. /// </returns>
  51. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateFixedDownCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  52. {
  53. double pixelScaleX = 0;
  54. double pixelScaleY = 0;
  55. Matrix<double> RobotCameraRotationMatrix = null;
  56. //移动机器人至待机位置
  57. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  58. if (cancellationToken.IsCancellationRequested)
  59. {
  60. return (false, null, 0, 0, 0, 0);
  61. }
  62. if (calibrationInfo.Pick.PickPlaceModel == Enums.PickPlaceModel.Inhal)
  63. {
  64. //打开吸气
  65. await robot.CalibOutIOAsync(true);
  66. }
  67. else
  68. {
  69. //张开夹爪
  70. //夹爪张开为false,夹爪闭合为True
  71. await robot.CalibOutIOAsync(false);
  72. }
  73. var view = new ShowMessage(Lang.提示, Lang.请将校准块放置在吸嘴中心);
  74. var result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
  75. if (!((bool)result)) return (false, null, 0, 0, 0, 0);
  76. if (calibrationInfo.Pick.PickPlaceModel == Enums.PickPlaceModel.Clamp)
  77. {
  78. //夹紧夹爪
  79. await robot.CalibOutIOAsync(true);
  80. }
  81. //计算相机与机器人坐标系的旋转矩阵
  82. var robotCameraRotationMatrix = await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  83. if (!robotCameraRotationMatrix.IsSuccess)
  84. {
  85. return (false, null, 0, 0, 0, 0);
  86. }
  87. pixelScaleX = robotCameraRotationMatrix.PixelScaleX;
  88. pixelScaleY = robotCameraRotationMatrix.PixelScaleY;
  89. RobotCameraRotationMatrix = robotCameraRotationMatrix.Item2;
  90. //相机拍照并处理图像
  91. var photoResult = await CaptureAndProcessCallback();
  92. if (!photoResult.IsSuccess)
  93. {
  94. return (false, null, 0, 0, 0, 0);
  95. }
  96. PointF P0 = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  97. RPoint RobotU = calibrationInfo.CenterPoint.Clone();
  98. RobotU.U += (float)calibrationInfo.Angle;
  99. //移动机器人中心点
  100. await robot.CalibMotionAsync(calibrationInfo.CenterPoint, 0);
  101. if (cancellationToken.IsCancellationRequested)
  102. {
  103. return (false, null, 0, 0, 0, 0);
  104. }
  105. //打开吸气
  106. await robot.CalibOutIOAsync(true);
  107. //机器人U轴旋转
  108. await robot.CalibMotionAsync(RobotU, 0);
  109. if (cancellationToken.IsCancellationRequested)
  110. {
  111. return (false, null, 0, 0, 0, 0);
  112. }
  113. //关闭吸气
  114. await robot.CalibOutIOAsync(false);
  115. //移动机器人至待机位置
  116. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  117. if (cancellationToken.IsCancellationRequested)
  118. {
  119. return (false, null, 0, 0, 0, 0);
  120. }
  121. //相机拍照并处理图像
  122. photoResult = await CaptureAndProcessCallback();
  123. if (!photoResult.IsSuccess)
  124. {
  125. return (false, null, 0, 0, 0, 0);
  126. }
  127. PointF curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  128. bool IsFinish = false;
  129. for (int i = 0; i < 10; i++)
  130. {
  131. //计算需要移动的偏移量,目的是逼近中心点
  132. double offsetx = (P0.X - curPixel.X) * pixelScaleX;
  133. double offsety = (P0.Y - curPixel.Y) * pixelScaleY;
  134. var pos = RobotCameraRotationMatrix.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
  135. //移动机器人取标定块
  136. await robot.CalibMotionAsync(RobotU, 0);
  137. if (cancellationToken.IsCancellationRequested)
  138. {
  139. return (false, null, 0, 0, 0, 0);
  140. }
  141. //打开吸气
  142. await robot.CalibOutIOAsync(true);
  143. RobotU.X += (float)pos[0];
  144. RobotU.Y += (float)pos[1];
  145. //机器人移动
  146. await robot.CalibMotionAsync(RobotU, 0);
  147. if (cancellationToken.IsCancellationRequested)
  148. {
  149. return (false, null, 0, 0, 0, 0);
  150. }
  151. //关闭吸气
  152. await robot.CalibOutIOAsync(false);
  153. //移动机器人至待机位置
  154. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  155. if (cancellationToken.IsCancellationRequested)
  156. {
  157. return (false, null, 0, 0, 0, 0);
  158. }
  159. photoResult = await CaptureAndProcessCallback();
  160. if (!photoResult.IsSuccess)
  161. {
  162. return (false, null, 0, 0, 0, 0);
  163. }
  164. curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  165. offsetx = P0.X - curPixel.X;
  166. offsety = P0.Y - curPixel.Y;
  167. //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
  168. if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
  169. {
  170. //移动机器人取标定块
  171. await robot.CalibMotionAsync(RobotU, 0);
  172. if (cancellationToken.IsCancellationRequested)
  173. {
  174. return (false, null, 0, 0, 0, 0);
  175. }
  176. //打开吸气
  177. await robot.CalibOutIOAsync(true);
  178. //移动机器人至待机位置
  179. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  180. IsFinish = true;
  181. break;
  182. }
  183. if (cancellationToken.IsCancellationRequested)
  184. {
  185. return (false, null, 0, 0, 0, 0);
  186. }
  187. }
  188. if (IsFinish)
  189. {
  190. Vector<double> p0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  191. Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
  192. ToolCoord toolCoord = new ToolCoord();
  193. double u1 = calibrationInfo.CenterPoint.U;
  194. double u2 = RobotU.U;
  195. if (robot.Brand == Enums.RobotBrand.Schneider)
  196. {
  197. u1 *= -1;
  198. u2 *= -1;
  199. }
  200. toolCoord.ComputeTool(p0, p1, u1, u2);
  201. if (calibrationInfo.Tool == null)
  202. {
  203. calibrationInfo.Tool = new RobotTool();
  204. }
  205. calibrationInfo.Tool.X = toolCoord.X;
  206. calibrationInfo.Tool.Y = toolCoord.Y;
  207. return (true, RobotCameraRotationMatrix, calibrationInfo.Tool.X, calibrationInfo.Tool.Y, pixelScaleX, pixelScaleY);
  208. }
  209. else
  210. {
  211. return (false, null, 0, 0, 0, 0);
  212. }
  213. }
  214. /// <summary>
  215. /// J4 移动的向下相机校准工具坐标。
  216. /// 用于相机或工具随机器人第 4 轴(J4)运动时的标定,需考虑 J4 引入的偏转影响。
  217. /// </summary>
  218. /// <param name="calibrationInfo">标定所需配置信息。</param>
  219. /// <param name="robot">机器人接口。</param>
  220. /// <param name="cancellationToken">用于取消操作的标记。</param>
  221. /// <returns>同 <see cref="CalibrateFixedDownCameraTool"/> 的返回约定。</returns>
  222. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateJ4MovingDownCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  223. {
  224. double pixelScaleX = 0;
  225. double pixelScaleY = 0;
  226. Matrix<double> RobotCameraRotationMatrix = null;
  227. //移动机器人至中心位置
  228. await robot.CalibMotionAsync(calibrationInfo.CenterPoint, 0);
  229. //计算相机与机器人坐标系的旋转矩阵
  230. var robotCameraRotationMatrix = await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  231. if (!robotCameraRotationMatrix.IsSuccess)
  232. {
  233. return (false, null, 0, 0, 0, 0);
  234. }
  235. pixelScaleX = robotCameraRotationMatrix.PixelScaleX;
  236. pixelScaleY = robotCameraRotationMatrix.PixelScaleY;
  237. RobotCameraRotationMatrix = robotCameraRotationMatrix.Item2;
  238. //自动移动机器人至图像中心点位置
  239. var autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  240. if (!autoMoveResult.IsSuccess)
  241. {
  242. return (false, null, 0, 0, 0, 0);
  243. }
  244. calibrationInfo.CenterPoint.X = (float)autoMoveResult.X;
  245. calibrationInfo.CenterPoint.Y = (float)autoMoveResult.Y;
  246. RPoint RobotU = calibrationInfo.CenterPoint.Clone();
  247. RobotU.U += (float)calibrationInfo.Angle;
  248. //机器人U轴旋转
  249. await robot.CalibMotionAsync(RobotU, RobotU.Z);
  250. if (cancellationToken.IsCancellationRequested)
  251. {
  252. return (false, null, 0, 0, 0, 0);
  253. }
  254. //相机拍照并处理图像
  255. var photoResult = await CaptureAndProcessCallback();
  256. if (!photoResult.IsSuccess)
  257. {
  258. return (false, null, 0, 0, 0, 0);
  259. }
  260. PointF curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  261. autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  262. if (!autoMoveResult.IsSuccess)
  263. {
  264. return (false, null, 0, 0, 0, 0);
  265. }
  266. RobotU.X = (float)autoMoveResult.X;
  267. RobotU.Y = (float)autoMoveResult.Y;
  268. Vector<double> p0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  269. Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
  270. ToolCoord toolCoord = new ToolCoord();
  271. double u1 = calibrationInfo.CenterPoint.U;
  272. double u2 = RobotU.U;
  273. if (robot.Brand == Enums.RobotBrand.Schneider)
  274. {
  275. u1 *= -1;
  276. u2 *= -1;
  277. }
  278. toolCoord.ComputeTool(p0, p1, u1, u2);
  279. if (calibrationInfo.Tool == null)
  280. {
  281. calibrationInfo.Tool = new RobotTool();
  282. }
  283. calibrationInfo.Tool.X = toolCoord.X;
  284. calibrationInfo.Tool.Y = toolCoord.Y;
  285. return (true, RobotCameraRotationMatrix, calibrationInfo.Tool.X, calibrationInfo.Tool.Y, pixelScaleX, pixelScaleY);
  286. }
  287. /// <summary>
  288. /// 固定向上相机校准工具坐标。
  289. /// 该方法用于相机固定、朝上安装的情况,标定过程与向下相机类似但需要考虑图像翻转/镜像等差异。
  290. /// </summary>
  291. /// <param name="calibrationInfo">标定配置信息。</param>
  292. /// <param name="robot">机器人接口。</param>
  293. /// <param name="cancellationToken">用于取消操作的标记。</param>
  294. /// <returns>同 <see cref="CalibrateFixedDownCameraTool"/> 的返回约定。</returns>
  295. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateFixedUpCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  296. {
  297. double pixelScaleX = 0;
  298. double pixelScaleY = 0;
  299. Matrix<double> RobotCameraRotationMatrix = null;
  300. //移动机器人至中心位置
  301. await robot.CalibMotionAsync(calibrationInfo.CenterPoint, 0);
  302. if (calibrationInfo.Pick.PickPlaceModel == Enums.PickPlaceModel.Inhal)
  303. {
  304. //打开吸气
  305. await robot.CalibOutIOAsync(true);
  306. }
  307. else
  308. {
  309. //张开夹爪
  310. await robot.CalibOutIOAsync(false);
  311. }
  312. var view = new ShowMessage(Lang.提示, Lang.请将校准块放置在吸嘴中心);
  313. var result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
  314. if (!((bool)result)) return (false, null, 0, 0, 0, 0);
  315. if (calibrationInfo.Pick.PickPlaceModel == Enums.PickPlaceModel.Clamp)
  316. {
  317. //夹紧夹爪
  318. await robot.CalibOutIOAsync(true);
  319. }
  320. //计算相机与机器人坐标系的旋转矩阵
  321. var robotCameraRotationMatrix= await AutoIdentifyRobotCameraRotationMatrix(calibrationInfo, robot, cancellationToken);
  322. if (!robotCameraRotationMatrix.IsSuccess)
  323. {
  324. return (false, null, 0, 0, 0, 0);
  325. }
  326. pixelScaleX= robotCameraRotationMatrix.PixelScaleX;
  327. pixelScaleY= robotCameraRotationMatrix.PixelScaleY;
  328. RobotCameraRotationMatrix= robotCameraRotationMatrix.Item2;
  329. //自动移动机器人至图像中心点位置
  330. var autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  331. if (!autoMoveResult.IsSuccess)
  332. {
  333. return (false, null, 0, 0, 0, 0);
  334. }
  335. calibrationInfo.CenterPoint.X= (float)autoMoveResult.X;
  336. calibrationInfo.CenterPoint.Y= (float)autoMoveResult.Y;
  337. RPoint RobotU = calibrationInfo.CenterPoint.Clone();
  338. RobotU.U += (float)calibrationInfo.Angle;
  339. //机器人U轴旋转
  340. await robot.CalibMotionAsync(RobotU, RobotU.Z);
  341. if(cancellationToken.IsCancellationRequested)
  342. {
  343. return (false, null, 0, 0, 0, 0);
  344. }
  345. //相机拍照并处理图像
  346. var photoResult = await CaptureAndProcessCallback();
  347. if (!photoResult.IsSuccess)
  348. {
  349. return (false, null, 0, 0, 0, 0);
  350. }
  351. PointF curPixel = new PointF((float)(photoResult.X), (float)(photoResult.Y));
  352. autoMoveResult = await AutoMoveRobotToImageCenter(robot, RobotCameraRotationMatrix, pixelScaleX, pixelScaleY, cancellationToken);
  353. if (!autoMoveResult.IsSuccess)
  354. {
  355. return (false, null, 0, 0, 0, 0);
  356. }
  357. RobotU.X = (float)autoMoveResult.X;
  358. RobotU.Y = (float)autoMoveResult.Y;
  359. Vector<double> p0 = Vector<double>.Build.Dense(new double[] { calibrationInfo.CenterPoint.X, calibrationInfo.CenterPoint.Y });
  360. Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
  361. ToolCoord toolCoord = new ToolCoord();
  362. double u1 = calibrationInfo.CenterPoint.U;
  363. double u2 = RobotU.U;
  364. if (robot.Brand == Enums.RobotBrand.Schneider)
  365. {
  366. u1 *= -1;
  367. u2 *= -1;
  368. }
  369. toolCoord.ComputeTool(p0, p1, u1, u2);
  370. if (calibrationInfo.Tool == null)
  371. {
  372. calibrationInfo.Tool = new RobotTool();
  373. }
  374. calibrationInfo.Tool.X = toolCoord.X;
  375. calibrationInfo.Tool.Y = toolCoord.Y;
  376. return (true, RobotCameraRotationMatrix, calibrationInfo.Tool.X, calibrationInfo.Tool.Y, pixelScaleX, pixelScaleY);
  377. }
  378. /// <summary>
  379. /// 通过未标定的相机校准工具坐标。
  380. /// 当相机内参未知或未可靠标定时,通过已知机器人位姿与图像特征的对应关系反推工具坐标与像素尺度。
  381. /// </summary>
  382. /// <param name="calibrationInfo">标定配置信息(可能包含初始猜测或参考点)。</param>
  383. /// <param name="robot">机器人接口。</param>
  384. /// <param name="cancellationToken">用于取消操作的标记。</param>
  385. /// <returns>同 <see cref="CalibrateFixedDownCameraTool"/> 的返回约定。</returns>
  386. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY)> CalibrateUncalibratedCameraTool(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  387. {
  388. if (calibrationInfo == null) return (false, null, 0, 0, 0, 0);
  389. if (robot == null || !robot.IsConnected) return (false, null, 0, 0, 0, 0);
  390. double pixelScaleX = 0;
  391. double pixelScaleY = 0;
  392. Matrix<double> RobotCameraRotationMatrix = null;
  393. //发送校准的参数
  394. await robot.CalibParameAsync(calibrationInfo.Pick, calibrationInfo.Speed, calibrationInfo.Accel, calibrationInfo.Power, calibrationInfo.WaitSuction, calibrationInfo.WaitBlow);
  395. (bool IsSuccess, Matrix<double> RotationMatrix, double ToolX, double ToolY, double PixelScaleX, double PixelScaleY) calibresult;
  396. if (calibrationInfo.CameraMount == Enums.CameraMount.FixedDown)
  397. {
  398. calibresult = await CalibrateFixedDownCameraTool(calibrationInfo,robot,cancellationToken);
  399. }
  400. else if (calibrationInfo.CameraMount == Enums.CameraMount.FixedUp)
  401. {
  402. calibresult = await CalibrateFixedUpCameraTool(calibrationInfo, robot, cancellationToken);
  403. }
  404. else if (calibrationInfo.CameraMount == Enums.CameraMount.MobileJ4)
  405. {
  406. calibresult = await CalibrateJ4MovingDownCameraTool(calibrationInfo, robot, cancellationToken);
  407. }
  408. else
  409. {
  410. return (false, null, 0, 0, 0, 0);
  411. }
  412. if (!calibresult.IsSuccess)
  413. {
  414. return (false, null, 0, 0, 0, 0);
  415. }
  416. pixelScaleX= calibresult.PixelScaleX;
  417. pixelScaleY= calibresult.PixelScaleY;
  418. RobotCameraRotationMatrix= calibresult.RotationMatrix;
  419. return (true, RobotCameraRotationMatrix, calibresult.ToolX, calibresult.ToolY, pixelScaleX, pixelScaleY);
  420. }
  421. /// <summary>
  422. /// 自动识别机器人与相机图像坐标系的旋转矩阵。
  423. /// 使用若干已知的机器人位姿与对应图像坐标点,估计二者之间的旋转变换与像素尺度。
  424. /// </summary>
  425. /// <param name="calibrationInfo">包含用于识别的点集与图像处理配置。</param>
  426. /// <param name="robot">机器人接口,用于获取对应位姿或移动机器人到指定位置以采集数据。</param>
  427. /// <param name="cancellationToken">用于取消操作的标记。</param>
  428. /// <returns>
  429. /// 元组字段:
  430. /// IsSuccess - 是否成功识别;
  431. /// Matrix<double> - 旋转(或仿射)矩阵;
  432. /// PixelScaleX - X 方向像素尺度(如 mm/px);
  433. /// PixelScaleY - Y 方向像素尺度(如 mm/px)。
  434. /// </returns>
  435. public async Task<(bool IsSuccess, Matrix<double> RotationMatrix, double PixelScaleX, double PixelScaleY)> AutoIdentifyRobotCameraRotationMatrix(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  436. {
  437. //移动距离,单位mm
  438. float distance = ((float)calibrationInfo.Height) / 3;
  439. //-------------------------------------------P0-----------------------------------
  440. //移动机器人至中心点
  441. if (calibrationInfo.CameraMount == Enums.CameraMount.FixedDown)
  442. {
  443. await robot.CalibMotionAsync(calibrationInfo.CenterPoint, 0);
  444. //关闭吸气
  445. await robot.CalibOutIOAsync(false);
  446. }
  447. else
  448. {
  449. await robot.CalibMotionAsync(calibrationInfo.CenterPoint, calibrationInfo.CenterPoint.Z);
  450. }
  451. RPoint RobotP0 = await robot.GetRobotPosAsync();
  452. RPoint RobotP1 = RobotP0.Clone();
  453. RPoint RobotP2 = RobotP0.Clone();
  454. RobotP1.X += distance;
  455. RobotP2.Y += distance;
  456. if (calibrationInfo.CameraMount == Enums.CameraMount.FixedDown)
  457. {
  458. //移动机器人至待机位置
  459. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  460. }
  461. //相机拍照并处理图像
  462. var result = await CaptureAndProcessCallback();
  463. if (!result.IsSuccess)
  464. {
  465. return (false, null, 0, 0);
  466. }
  467. PointF P0 = new PointF((float)result.X, (float)result.Y);
  468. //-------------------------------------------P1-----------------------------------
  469. if (calibrationInfo.CameraMount == Enums.CameraMount.FixedDown)
  470. {
  471. //移动机器人至P0
  472. await robot.CalibMotionAsync(RobotP0, 0);
  473. if(cancellationToken.IsCancellationRequested)
  474. {
  475. return (false, null, 0, 0);
  476. }
  477. //打开吸气
  478. await robot.CalibOutIOAsync(true);
  479. //移动机器人至P1
  480. await robot.CalibMotionAsync(RobotP1, 0);
  481. if (cancellationToken.IsCancellationRequested)
  482. {
  483. return (false, null, 0, 0);
  484. }
  485. //关闭吸气
  486. await robot.CalibOutIOAsync(false);
  487. //移动机器人至待机位置
  488. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  489. }
  490. else
  491. {
  492. //移动机器人至P1
  493. await robot.CalibMotionAsync(RobotP1, RobotP1.Z);
  494. }
  495. if (cancellationToken.IsCancellationRequested)
  496. {
  497. return (false, null, 0, 0);
  498. }
  499. //相机拍照并处理图像
  500. result = await CaptureAndProcessCallback();
  501. if (!result.IsSuccess)
  502. {
  503. return (false, null, 0, 0);
  504. }
  505. PointF P1 = new PointF((float)result.X, (float)result.Y);
  506. //-------------------------------------------P2-----------------------------------
  507. if (calibrationInfo.CameraMount == Enums.CameraMount.FixedDown)
  508. {
  509. //移动机器人至P1
  510. await robot.CalibMotionAsync(RobotP1, 0);
  511. if (cancellationToken.IsCancellationRequested)
  512. {
  513. return (false, null, 0, 0);
  514. }
  515. //打开吸气
  516. await robot.CalibOutIOAsync(true);
  517. //移动机器人至P2
  518. await robot.CalibMotionAsync(RobotP2, 0);
  519. if (cancellationToken.IsCancellationRequested)
  520. {
  521. return (false, null, 0, 0);
  522. }
  523. //关闭吸气
  524. await robot.CalibOutIOAsync(false);
  525. //移动机器人至待机位置
  526. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  527. }
  528. else
  529. {
  530. //移动机器人至P2
  531. await robot.CalibMotionAsync(RobotP2, RobotP2.Z);
  532. }
  533. if (cancellationToken.IsCancellationRequested)
  534. {
  535. return (false, null, 0, 0);
  536. }
  537. //相机拍照并处理图像
  538. result = await CaptureAndProcessCallback();
  539. if (!result.IsSuccess)
  540. {
  541. return (false, null, 0, 0);
  542. }
  543. PointF P2 = new PointF((float)result.X, (float)result.Y);
  544. //-------------------------------------------P0--------------------------------------
  545. if (calibrationInfo.CameraMount == Enums.CameraMount.FixedDown)
  546. {
  547. //移动机器人至P2
  548. await robot.CalibMotionAsync(RobotP2, 0);
  549. if (cancellationToken.IsCancellationRequested)
  550. {
  551. return (false, null, 0, 0);
  552. }
  553. //打开吸气
  554. await robot.CalibOutIOAsync(true);
  555. //移动机器人至P0
  556. await robot.CalibMotionAsync(RobotP0, 0);
  557. if (cancellationToken.IsCancellationRequested)
  558. {
  559. return (false, null, 0, 0);
  560. }
  561. //关闭吸气
  562. await robot.CalibOutIOAsync(false);
  563. //移动机器人至待机位置
  564. await robot.CalibMotionAsync(calibrationInfo.HomePoint, 0);
  565. }
  566. else
  567. {
  568. //移动机器人至P0
  569. await robot.CalibMotionAsync(RobotP0, RobotP0.Z);
  570. }
  571. if (cancellationToken.IsCancellationRequested)
  572. {
  573. return (false, null, 0, 0);
  574. }
  575. //-----------------------------------------计算旋转矩阵------------------------------
  576. Matrix<double> M = CalculateImageRotationMatrix(P0, P1, P2);
  577. //Theta = Acos(M11)
  578. //相机与机器人坐标系角度:Theta * 180 / PI
  579. //-----------------------------------------计算像素和毫米比例-----------------------
  580. Vector<double> P11 = Vector<double>.Build.Dense(new double[] { distance, 0 });
  581. Vector<double> P21 = Vector<double>.Build.Dense(new double[] { P1.X, P1.Y }) - Vector<double>.Build.Dense(new double[] { P0.X, P0.Y });
  582. double PixelScaleX = Math.Abs((M * P11)[0] / P21[0]); //mm/pixel
  583. double PixelScaleY = Math.Abs((M * P11)[1] / P21[1]); //mm/pixel
  584. return (true, M, PixelScaleX, PixelScaleY);
  585. }
  586. /// <summary>
  587. /// 根据图像上的三个点计算图像坐标系的旋转矩阵。
  588. /// 通常通过已知三点在像素坐标系中的位置计算出局部旋转/仿射变换,用于后续坐标换算。
  589. /// </summary>
  590. /// <param name="P0">图像中的点 0(像素坐标)。</param>
  591. /// <param name="P1">图像中的点 1(像素坐标)。</param>
  592. /// <param name="P2">图像中的点 2(像素坐标)。</param>
  593. /// <returns>表示图像旋转或仿射变换的 3x3 或 2x2 矩阵(具体尺寸由实现决定)。</returns>
  594. public Matrix<double> CalculateImageRotationMatrix(PointF P0, PointF P1, PointF P2)
  595. {
  596. //1.将点转换为向量表示:
  597. Vector<double> Point0 = Vector<double>.Build.Dense(new double[] { (double)P0.X, (double)P0.Y });
  598. Vector<double> Point1 = Vector<double>.Build.Dense(new double[] { (double)P1.X, (double)P1.Y });
  599. Vector<double> Point2 = Vector<double>.Build.Dense(new double[] { (double)P2.X, (double)P2.Y });
  600. //2.计算坐标系的基向量:
  601. Vector<double> E1 = (Point1 - Point0) / (Point1 - Point0).L2Norm();
  602. Vector<double> E2 = (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1) / (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1).L2Norm();
  603. //3.计算旋转矩阵:我们可以使用基向量组成的矩阵作为旋转矩阵。具体地,我们可以将基向量 e1 和 e2 分别作为新坐标系下的 $x$ 和 $y$ 轴,然后组成一个矩阵,再对这个矩阵求逆即可得到旋转矩阵。代码实现:
  604. Matrix<double> R = Matrix<double>.Build.DenseOfColumnVectors(E1, E2);
  605. return R;
  606. }
  607. /// <summary>
  608. /// 自动移动机器人至图像中心点位置。
  609. /// 典型用例为将相机视野中心对应的工作点转换为机器人坐标并移动机器人到该点以便校准或抓取。
  610. /// </summary>
  611. /// <param name="robot">机器人接口,用于执行移动命令并返回当前位置。</param>
  612. /// <param name="matrix">图像坐标系到机器人坐标系的转换矩阵(旋转/仿射)。</param>
  613. /// <param name="PixelScaleX">像素到物理距离在 X 方向的缩放因子(如 mm/px)。</param>
  614. /// <param name="PixelScaleY">像素到物理距离在 Y 方向的缩放因子(如 mm/px)。</param>
  615. /// <param name="cancellationToken">用于取消操作的标记。</param>
  616. /// <returns>
  617. /// 元组字段:
  618. /// IsSuccess - 是否成功移动到图像中心;
  619. /// X - 移动后机器人在基坐标系下的 X;
  620. /// Y - 移动后机器人在基坐标系下的 Y;
  621. /// U - 移动后机器人的朝向(工具角度)。
  622. /// </returns>
  623. public async Task<(bool IsSuccess, double X, double Y, double U)> AutoMoveRobotToImageCenter(IRobot robot, Matrix<double> matrix, double PixelScaleX, double PixelScaleY, CancellationToken cancellationToken)
  624. {
  625. bool IsFinish = false;
  626. //相机拍照并处理图像
  627. var result = await CaptureAndProcessCallback();
  628. if (!result.IsSuccess)
  629. {
  630. return (false, 0, 0, 0);
  631. }
  632. PointF curPixel = new PointF((float)result.X, (float)result.Y);
  633. //中心点像素坐标
  634. PointF P0 = new PointF((float)(result.ImageWidth / 2), (float)(result.ImageHeight / 2));
  635. var curpos = robot.GetRobotPos();
  636. for (int i = 0; i < 10; i++)
  637. {
  638. //计算需要移动的偏移量,目的是逼近中心点
  639. double offsetx = (P0.X - curPixel.X) * PixelScaleX;
  640. double offsety = (P0.Y - curPixel.Y) * PixelScaleY;
  641. var pos = matrix.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
  642. curpos.X += (float)pos[0];
  643. curpos.Y += (float)pos[1];
  644. //机器人移动
  645. await robot.CalibMotionAsync(curpos, curpos.Z);
  646. result = await CaptureAndProcessCallback();
  647. if (!result.IsSuccess)
  648. {
  649. return (false, 0, 0, 0);
  650. }
  651. curPixel = new PointF((float)result.X, (float)result.Y);
  652. offsetx = P0.X - curPixel.X;
  653. offsety = P0.Y - curPixel.Y;
  654. //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
  655. if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
  656. {
  657. IsFinish = true;
  658. break;
  659. }
  660. if (cancellationToken.IsCancellationRequested)
  661. {
  662. return (false, 0, 0, 0);
  663. }
  664. }
  665. if (IsFinish)
  666. {
  667. var finalPos = robot.GetRobotPos();
  668. return (true, finalPos.X, finalPos.Y, finalPos.U);
  669. }
  670. else
  671. {
  672. return (false, 0, 0, 0);
  673. }
  674. }
  675. /// <summary>
  676. /// 自动九点标定。
  677. /// 使用 9 个采样点进行仿射/透视变换求解,以提高标定精度并生成验证用的九点数据结构。
  678. /// </summary>
  679. /// <param name="calibrationInfo">九点标定的配置信息(点列、采集顺序、图像处理参数等)。</param>
  680. /// <param name="robot">机器人接口。</param>
  681. /// <param name="cancellationToken">用于取消操作的标记。</param>
  682. /// <returns>
  683. /// 元组字段:
  684. /// IsSuccess - 标定是否成功;
  685. /// AffineTransformationMaterial - 求解得到的仿射变换矩阵;
  686. /// NinePoint - 九点标定结果封装(像素点与对应的机器人位姿);
  687. /// Result - 详细的标定结果与统计信息(误差、残差等)。
  688. /// </returns>
  689. public async Task<(bool IsSuccess, Matrix<double> AffineTransformationMaterial, RobotPixelPoint NinePoint, CalibrationResult Result)> AutoNinePointCalibration(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  690. {
  691. }
  692. /// <summary>
  693. /// 执行校准验证。
  694. /// 使用当前标定参数在一组验证点上计算误差并返回测试结果,用于评估标定质量。
  695. /// </summary>
  696. /// <param name="calibrationInfo">用于验证的点集与验收标准配置。</param>
  697. /// <param name="robot">机器人接口,用于在验证过程中获取或移动到参考位姿。</param>
  698. /// <param name="cancellationToken">用于取消操作的标记。</param>
  699. /// <returns>元组字段:IsSuccess 表示验证是否通过;Result 包含详细的验证统计与误差分析。</returns>
  700. public async Task<(bool IsSuccess, CalibrationTestResult Result)> ExecuteCalibrationValidation(CalibrationInfo calibrationInfo, IRobot robot, CancellationToken cancellationToken)
  701. {
  702. }
  703. }
  704. }