OptCamera.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. using Microsoft.Win32;
  2. using System.ComponentModel;
  3. using SciCamera.Net;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Drawing;
  8. using System.Drawing.Imaging;
  9. using System.Linq;
  10. using System.Runtime.InteropServices;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using TeamAAS.Camera.Images;
  14. using TeamAAS.Camera.Models;
  15. using TeamAAS.Camera.Attributes;
  16. using TeamAAS.Camera.Enums;
  17. namespace TeamAAS.Camera.Cameras
  18. {
  19. /// <summary>
  20. /// 奥普特相机(SciCamera.Net SDK)
  21. /// </summary>
  22. [Camera("OPT", "奥普特")]
  23. public class OptCamera : CameraBase
  24. {
  25. #region 字段
  26. private readonly object _stateLock = new object();
  27. private TaskCompletionSource<GenImage> _tcs;
  28. SciCam mvCameraAcq;
  29. [Browsable(false)]
  30. public SciCam.SCI_DEVICE_INFO CameraInfo { get; private set; }
  31. [Browsable(false)]
  32. public SciCam.SciCamPixelType PixelType { get; private set; }
  33. [Browsable(false)]
  34. public SciCam.SciCamGrabStrategy CurrentGrabStrategy { get; private set; } = SciCam.SciCamGrabStrategy.SciCam_GrabStrategy_Latest;
  35. private bool IsHaveCamera = false;
  36. #endregion
  37. [Browsable(false)]
  38. public override string CameraBrand => "奥普特";
  39. public OptCamera(Guid id, int index, string name, string serialNumber, string cameraIp, bool isFindByIp)
  40. : base(id, index, name, serialNumber, cameraIp, isFindByIp)
  41. {
  42. mvCameraAcq = new SciCam();
  43. SciCam.SCI_DEVICE_INFO_LIST stDeviceList = new SciCam.SCI_DEVICE_INFO_LIST();
  44. uint nReVal = SciCam.DiscoveryDevices(ref stDeviceList, (uint)(SciCam.SciCamTLType.SciCam_TLType_Gige));
  45. if (nReVal != SciCam.SCI_CAMERA_OK)
  46. {
  47. return;
  48. }
  49. for (int i = 0; i < stDeviceList.count; i++)
  50. {
  51. SciCam.SCI_DEVICE_INFO device = stDeviceList.pDevInfo[i];
  52. SciCam.SciCamTLType devTlType = device.tlType;
  53. if (devTlType == SciCam.SciCamTLType.SciCam_TLType_Gige)
  54. {
  55. SciCam.SCI_DEVICE_GIGE_INFO gigeDevInfo = (SciCam.SCI_DEVICE_GIGE_INFO)SciCam.ByteToStruct(device.info.gigeInfo, typeof(SciCam.SCI_DEVICE_GIGE_INFO));
  56. uint ip1 = gigeDevInfo.ip;
  57. int nIp1 = (int)(ip1 & 0x000000ff);
  58. int nIp2 = (int)((ip1 & 0x0000ff00) >> 8);
  59. int nIp3 = (int)((ip1 & 0x00ff0000) >> 16);
  60. int nIp4 = (int)((ip1 & 0xff000000) >> 24);
  61. string ip = string.Format("{0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4);
  62. if (IsFindByIp)
  63. {
  64. if (CameraIp == ip)
  65. {
  66. ManufacturerName = gigeDevInfo.manufactureName;
  67. ModelName = gigeDevInfo.modelName;
  68. SerialNumber = gigeDevInfo.serialNumber;
  69. CameraType = CameraType.GIGE;
  70. CameraInfo = device;
  71. IsHaveCamera = true;
  72. break;
  73. }
  74. }
  75. else
  76. {
  77. if (string.Equals(gigeDevInfo.serialNumber, serialNumber))
  78. {
  79. ManufacturerName = gigeDevInfo.manufactureName;
  80. ModelName = gigeDevInfo.modelName;
  81. SerialNumber = gigeDevInfo.serialNumber;
  82. CameraType = CameraType.GIGE;
  83. CameraInfo = device;
  84. IsHaveCamera = true;
  85. break;
  86. }
  87. }
  88. }
  89. }
  90. IsConnected = false;
  91. }
  92. #region 设备发现
  93. public static CameraInfo[] GetDevices()
  94. {
  95. List<CameraInfo> cameras = new List<CameraInfo>();
  96. try
  97. {
  98. if (!CheckSoftwareInstalled().isInstalled)
  99. {
  100. return cameras.ToArray();
  101. }
  102. SciCam.SCI_DEVICE_INFO_LIST stDeviceList = new SciCam.SCI_DEVICE_INFO_LIST();
  103. uint nReVal = SciCam.DiscoveryDevices(ref stDeviceList, (uint)(SciCam.SciCamTLType.SciCam_TLType_Gige));
  104. if (nReVal != SciCam.SCI_CAMERA_OK)
  105. {
  106. return cameras.ToArray();
  107. }
  108. for (int i = 0; i < stDeviceList.count; i++)
  109. {
  110. SciCam.SCI_DEVICE_INFO device = stDeviceList.pDevInfo[i];
  111. SciCam.SciCamTLType devTlType = device.tlType;
  112. if (devTlType == SciCam.SciCamTLType.SciCam_TLType_Gige)
  113. {
  114. SciCam.SCI_DEVICE_GIGE_INFO gigeDevInfo = (SciCam.SCI_DEVICE_GIGE_INFO)SciCam.ByteToStruct(device.info.gigeInfo, typeof(SciCam.SCI_DEVICE_GIGE_INFO));
  115. uint ip1 = gigeDevInfo.ip;
  116. int nIp1 = (int)(ip1 & 0x000000ff);
  117. int nIp2 = (int)((ip1 & 0x0000ff00) >> 8);
  118. int nIp3 = (int)((ip1 & 0x00ff0000) >> 16);
  119. int nIp4 = (int)((ip1 & 0xff000000) >> 24);
  120. string ip = string.Format("{0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4);
  121. cameras.Add(new CameraInfo()
  122. {
  123. CameraNo = i + 1,
  124. CameraName = gigeDevInfo.name,
  125. CameraBrand = "奥普特",
  126. CameraType = CameraType.GIGE,
  127. ManufacturerName = gigeDevInfo.manufactureName,
  128. Model = gigeDevInfo.modelName,
  129. SerialNumber = gigeDevInfo.serialNumber,
  130. CameraIp = ip,
  131. IsFindByIp = true,
  132. });
  133. }
  134. }
  135. }
  136. catch (Exception)
  137. {
  138. }
  139. return cameras.ToArray();
  140. }
  141. public static (bool isInstalled, string version) CheckSoftwareInstalled()
  142. {
  143. return (true, "4.0.0.3");
  144. }
  145. #endregion
  146. #region SDK 实现
  147. public override bool OpenDevice()
  148. {
  149. using (WriteLockScope())
  150. {
  151. try
  152. {
  153. SciCam.SCI_DEVICE_INFO stDeviceInfo = CameraInfo;
  154. uint nReVal = mvCameraAcq.CreateDevice(ref stDeviceInfo);
  155. if (nReVal != SciCam.SCI_CAMERA_OK)
  156. {
  157. throw new Exception("Create Device Fail!");
  158. }
  159. nReVal = mvCameraAcq.OpenDevice();
  160. // 按当前触发模式下发(默认软触发,与历史硬编码行为一致);AcquisitionMode 由该方法一并置 Continuous
  161. ApplyTriggerToHardware(TriggerMode);
  162. nReVal = mvCameraAcq.SetGrabStrategy(CurrentGrabStrategy);
  163. if (!IsConnected)
  164. {
  165. OnConnectChanged(true);
  166. }
  167. mvCameraAcq.SetGrabTimeout(5000);
  168. IsConnected = mvCameraAcq.IsDeviceOpen();
  169. if (IsConnected)
  170. {
  171. mvCameraAcq.StartGrabbing();
  172. }
  173. return IsConnected;
  174. }
  175. catch (Exception ex)
  176. {
  177. ErrorMessage = ex.Message;
  178. IsConnected = false;
  179. return false;
  180. }
  181. }
  182. }
  183. public override void CloseDevice()
  184. {
  185. using (WriteLockScope())
  186. {
  187. try
  188. {
  189. if (mvCameraAcq != null)
  190. {
  191. if (mvCameraAcq.IsDeviceOpen())
  192. {
  193. mvCameraAcq.StopGrabbing();
  194. mvCameraAcq.CloseDevice();
  195. }
  196. }
  197. }
  198. catch { }
  199. IsConnected = false;
  200. OnConnectChanged(false);
  201. }
  202. }
  203. public override GenImage Grab()
  204. {
  205. using (WriteLockScope())
  206. {
  207. var sw = Stopwatch.StartNew();
  208. try
  209. {
  210. if (mvCameraAcq == null || !mvCameraAcq.IsDeviceOpen())
  211. {
  212. ErrorMessage = "相机未连接";
  213. return null;
  214. }
  215. if (!mvCameraAcq.IsGrabbing())
  216. {
  217. mvCameraAcq.StartGrabbing();
  218. }
  219. IntPtr payload = IntPtr.Zero;
  220. // 仅软触发模式需主动发触发命令;连续采集自由出图,硬触发等外部信号
  221. bool triggerOk = true;
  222. if (IsSoftwareTrigger)
  223. triggerOk = mvCameraAcq.SetCommandValueEx(
  224. SciCam.SciCamDeviceXmlType.SciCam_DeviceXml_Camera, "TriggerSoftware") == SciCam.SCI_CAMERA_OK;
  225. if (triggerOk)
  226. {
  227. uint nReVal = mvCameraAcq.Grab(ref payload);
  228. if (nReVal == SciCam.SCI_CAMERA_OK)
  229. {
  230. Image = GetConvertedInfo(payload);
  231. mvCameraAcq.FreePayload(payload);
  232. if (Image != null)
  233. {
  234. ImageWidth = (uint)Image.Width;
  235. ImageHeight = (uint)Image.Height;
  236. ApplyGammaToImage(Image);
  237. TotalTime = sw.Elapsed;
  238. ErrorMessage = "";
  239. return Image;
  240. }
  241. }
  242. else
  243. {
  244. CloseDevice();
  245. OpenDevice();
  246. ErrorMessage = "图像采集失败!";
  247. }
  248. }
  249. else
  250. {
  251. CloseDevice();
  252. OpenDevice();
  253. ErrorMessage = "软触发失败!";
  254. }
  255. }
  256. catch (Exception ex)
  257. {
  258. CloseDevice();
  259. OpenDevice();
  260. ErrorMessage = ex.Message;
  261. }
  262. return null;
  263. }
  264. }
  265. #region 触发模式
  266. /// <summary>OPT(SciCamera)支持连续/软触发/硬触发(Line0)</summary>
  267. public override IReadOnlyList<CameraTriggerMode> SupportedTriggerModes => FullTriggerModes;
  268. /// <summary>历史行为:OpenDevice 硬编码 TriggerMode=On + TriggerSource=Software,故默认软触发</summary>
  269. protected override CameraTriggerMode DefaultTriggerMode => CameraTriggerMode.软触发;
  270. protected override bool ApplyTriggerMode(CameraTriggerMode mode)
  271. {
  272. using (WriteLockScope())
  273. return ApplyTriggerToHardware(mode);
  274. }
  275. /// <summary>
  276. /// 把触发模式下发到 SciCamera SDK(GenICam 字符串接口)。
  277. /// 调用方需已持有写锁(OpenDevice/Grab 内部已在锁内;锁可重入)。
  278. /// </summary>
  279. private bool ApplyTriggerToHardware(CameraTriggerMode mode)
  280. {
  281. if (mvCameraAcq == null || !mvCameraAcq.IsDeviceOpen()) return false;
  282. var xml = SciCam.SciCamDeviceXmlType.SciCam_DeviceXml_Camera;
  283. mvCameraAcq.SetEnumValueByString("AcquisitionMode", "Continuous");
  284. switch (mode)
  285. {
  286. case CameraTriggerMode.连续采集:
  287. return mvCameraAcq.SetEnumValueByStringEx(xml, "TriggerMode", "Off") == SciCam.SCI_CAMERA_OK;
  288. case CameraTriggerMode.软触发:
  289. mvCameraAcq.SetEnumValueByString("TriggerSource", "Software");
  290. return mvCameraAcq.SetEnumValueByStringEx(xml, "TriggerMode", "On") == SciCam.SCI_CAMERA_OK;
  291. case CameraTriggerMode.硬触发上升沿:
  292. case CameraTriggerMode.硬触发下降沿:
  293. mvCameraAcq.SetEnumValueByString("TriggerSource", "Line0");
  294. mvCameraAcq.SetEnumValueByString("TriggerActivation",
  295. mode == CameraTriggerMode.硬触发上升沿 ? "Rising" : "Falling");
  296. return mvCameraAcq.SetEnumValueByStringEx(xml, "TriggerMode", "On") == SciCam.SCI_CAMERA_OK;
  297. default: // 跟随相机:不下发,沿用相机自身配置
  298. return true;
  299. }
  300. }
  301. #endregion
  302. public override bool SetExposureTime(float exposureTime)
  303. {
  304. using (WriteLockScope())
  305. {
  306. try
  307. {
  308. if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen())
  309. {
  310. mvCameraAcq.SetFloatValue("ExposureTime", exposureTime);
  311. }
  312. return true;
  313. }
  314. catch { return false; }
  315. }
  316. }
  317. public override float GetExposureTime()
  318. {
  319. using (ReadLockScope())
  320. {
  321. try
  322. {
  323. if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen())
  324. {
  325. return 0;
  326. }
  327. }
  328. catch { }
  329. return 0;
  330. }
  331. }
  332. public override bool SetGain(float gain)
  333. {
  334. using (WriteLockScope())
  335. {
  336. try
  337. {
  338. if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen())
  339. {
  340. mvCameraAcq.SetFloatValue("Gain", gain);
  341. }
  342. return true;
  343. }
  344. catch { return false; }
  345. }
  346. }
  347. public override float GetGain()
  348. {
  349. using (ReadLockScope())
  350. {
  351. try
  352. {
  353. if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen())
  354. {
  355. return 0;
  356. }
  357. }
  358. catch { }
  359. return 0;
  360. }
  361. }
  362. public override bool SetGamma(float gamma)
  363. {
  364. using (WriteLockScope())
  365. {
  366. try
  367. {
  368. if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen())
  369. {
  370. mvCameraAcq.SetFloatValue("Gamma", gamma);
  371. return true;
  372. }
  373. }
  374. catch { }
  375. UpdateGammaSnapshot(gamma);
  376. return true;
  377. }
  378. }
  379. public override float GetGamma()
  380. {
  381. using (ReadLockScope())
  382. {
  383. try
  384. {
  385. if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen())
  386. {
  387. return Gamma;
  388. }
  389. }
  390. catch { }
  391. return Gamma;
  392. }
  393. }
  394. #endregion
  395. #region 辅助方法
  396. void ImageCallBack(IntPtr payload, IntPtr tag)
  397. {
  398. try
  399. {
  400. if (payload == IntPtr.Zero) return;
  401. uint nReVal = SciCam.SCI_CAMERA_OK;
  402. SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE attribute = new SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE();
  403. nReVal = SciCam.PayloadGetAttribute(payload, ref attribute);
  404. if (nReVal != SciCam.SCI_CAMERA_OK || !attribute.isComplete)
  405. {
  406. _tcs?.TrySetException(new Exception($"Failed to get image attributes or frame is incomplete! nRet: {nReVal}"));
  407. }
  408. else
  409. {
  410. Image = GetConvertedInfo(payload);
  411. _tcs?.TrySetResult(Image);
  412. }
  413. }
  414. catch (Exception ex)
  415. {
  416. _tcs?.TrySetException(new Exception($"相机采集错误: {ex.Message}"));
  417. }
  418. }
  419. private GenImage GetConvertedInfo(IntPtr payload)
  420. {
  421. if (payload == IntPtr.Zero)
  422. {
  423. return null;
  424. }
  425. SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE payloadAttribute = new SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE();
  426. uint nReVal = SciCam.PayloadGetAttribute(payload, ref payloadAttribute);
  427. if (nReVal != SciCam.SCI_CAMERA_OK)
  428. {
  429. return null;
  430. }
  431. bool imgIsComplete = payloadAttribute.isComplete;
  432. SciCam.SciCamPayloadMode payloadMode = payloadAttribute.payloadMode;
  433. SciCam.SciCamPixelType imgPixelType = payloadAttribute.imgAttr.pixelType;
  434. ulong imgWidth = payloadAttribute.imgAttr.width;
  435. ulong imgHeight = payloadAttribute.imgAttr.height;
  436. ulong framID = payloadAttribute.frameID;
  437. if (!imgIsComplete || payloadMode != SciCam.SciCamPayloadMode.SciCam_PayloadMode_2D)
  438. {
  439. return null;
  440. }
  441. IntPtr imgData = IntPtr.Zero;
  442. nReVal = SciCam.PayloadGetImage(payload, ref imgData);
  443. if (nReVal != SciCam.SCI_CAMERA_OK)
  444. {
  445. return null;
  446. }
  447. long destImgSize = 0;
  448. if (imgPixelType == SciCam.SciCamPixelType.Mono1p ||
  449. imgPixelType == SciCam.SciCamPixelType.Mono2p ||
  450. imgPixelType == SciCam.SciCamPixelType.Mono4p ||
  451. imgPixelType == SciCam.SciCamPixelType.Mono8s ||
  452. imgPixelType == SciCam.SciCamPixelType.Mono8 ||
  453. imgPixelType == SciCam.SciCamPixelType.Mono10 ||
  454. imgPixelType == SciCam.SciCamPixelType.Mono10p ||
  455. imgPixelType == SciCam.SciCamPixelType.Mono12 ||
  456. imgPixelType == SciCam.SciCamPixelType.Mono12p ||
  457. imgPixelType == SciCam.SciCamPixelType.Mono14 ||
  458. imgPixelType == SciCam.SciCamPixelType.Mono16 ||
  459. imgPixelType == SciCam.SciCamPixelType.Mono10Packed ||
  460. imgPixelType == SciCam.SciCamPixelType.Mono12Packed ||
  461. imgPixelType == SciCam.SciCamPixelType.Mono14p)
  462. {
  463. nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.Mono8, IntPtr.Zero, ref destImgSize, true);
  464. if (nReVal == SciCam.SCI_CAMERA_OK)
  465. {
  466. IntPtr destImg = Marshal.AllocHGlobal((int)destImgSize);
  467. try
  468. {
  469. nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.Mono8, destImg, ref destImgSize, true);
  470. if (nReVal == SciCam.SCI_CAMERA_OK)
  471. {
  472. // destImg 此时为 Mono8 交错的灰度数据;GenImage.FromIntPtr 内部会 Marshal.Copy 出独立副本,可在 finally 中安全释放 destImg
  473. return GenImage.FromIntPtr((int)imgWidth, (int)imgHeight, Images.PixelType.Grey8, destImg, (int)imgWidth);
  474. }
  475. }
  476. finally
  477. {
  478. Marshal.FreeHGlobal(destImg);
  479. }
  480. }
  481. }
  482. else
  483. {
  484. nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.RGB8, IntPtr.Zero, ref destImgSize, true);
  485. if (nReVal == SciCam.SCI_CAMERA_OK)
  486. {
  487. IntPtr destImg = Marshal.AllocHGlobal((int)destImgSize);
  488. try
  489. {
  490. nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.RGB8, destImg, ref destImgSize, true);
  491. if (nReVal == SciCam.SCI_CAMERA_OK)
  492. {
  493. // destImg 此时为交错 RGB8(R-G-B)数据,可直接用于 GenImage.RGB24
  494. return GenImage.FromIntPtr((int)imgWidth, (int)imgHeight, Images.PixelType.RGB24, destImg, (int)imgWidth * 3);
  495. }
  496. }
  497. finally
  498. {
  499. Marshal.FreeHGlobal(destImg);
  500. }
  501. }
  502. }
  503. return null;
  504. }
  505. #endregion
  506. }
  507. }