MvCamera.cs 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. using Cognex.VisionPro;
  2. using LampInspectionMachine.Interfaces;
  3. using LampInspectionMachine.Log4xml;
  4. using LampInspectionMachine.Model;
  5. using Microsoft.Win32;
  6. using MvCamCtrl.NET;
  7. using MvCameraControl;
  8. using SqlSugar.DistributedSystem.Snowflake;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Collections.ObjectModel;
  12. using System.ComponentModel;
  13. using System.Data.SqlTypes;
  14. using System.Diagnostics;
  15. using System.Drawing.Imaging;
  16. using System.Drawing;
  17. using System.Linq;
  18. using System.Runtime.CompilerServices;
  19. using System.Runtime.InteropServices;
  20. using System.Text;
  21. using System.Threading;
  22. using System.Threading.Tasks;
  23. using System.Windows.Media.Media3D;
  24. using System.Configuration;
  25. namespace LampInspectionMachine.Cameralibs.HKCamera
  26. {
  27. public class MvCamera : ICamera, INotifyPropertyChanged
  28. {
  29. #region 字段
  30. private IDevice m_MyCamera = null;
  31. private Thread m_hReceiveThread;
  32. #endregion
  33. #region 属性
  34. public CameraBrand CameraBrand { get => CameraBrand.HikRobot_MVS; }
  35. public string Name { get; private set; }
  36. public Guid ID { get; private set; }
  37. public string ManufacturerName { get; private set; }
  38. public string ModelName { get; private set; }
  39. public string SerialNumber { get; private set; }
  40. public CameraType CameraType { get; private set; }
  41. public IDeviceInfo CameraInfo { get; private set; }
  42. public UInt32 ImageWidth { get; private set; }
  43. public UInt32 ImageHeight { get; private set; }
  44. public MvGvspPixelType PixelType { get; private set; }
  45. private bool _IsGrabbing;
  46. /// <summary>
  47. /// 正在采集
  48. /// </summary>
  49. public bool IsGrabbing
  50. {
  51. get { return _IsGrabbing; }
  52. private set { SetProperty(ref _IsGrabbing, value); }
  53. }
  54. private ICogImage _Image;
  55. public ICogImage Image
  56. {
  57. get { return _Image; }
  58. private set { SetProperty(ref _Image, value); }
  59. }
  60. public bool IsConnected { get; private set; }
  61. private bool IsHaveCamera = false;
  62. /// <summary>
  63. /// 采集用时
  64. /// </summary>
  65. public TimeSpan TotalTime { get; private set; }
  66. /// <summary>
  67. /// 错误信息
  68. /// </summary>
  69. public string ErrorMessage { get; private set; }
  70. #endregion
  71. #region 事件
  72. /// <summary>
  73. /// 手动采集图像回调事件
  74. /// </summary>
  75. public event Action<ICogImage, TimeSpan, string> ImageCallbackEvent;
  76. /// <summary>
  77. /// 触发取图回调事件
  78. /// </summary>
  79. public event Action<ICogImage> GrabImageCallbackEvent;
  80. public event Action<Guid, bool> CameraConnectChangedEvent;
  81. #endregion
  82. public MvCamera(Guid _ID, string _Name, string _SerialNumber)
  83. {
  84. ID = _ID;
  85. Name = _Name;
  86. SerialNumber = _SerialNumber;
  87. // ch: 初始化 SDK | en: Initialize SDK
  88. SDKSystem.Initialize();
  89. List<IDeviceInfo> devInfoList = new List<IDeviceInfo>();
  90. // ch:枚举设备 | en:Enum device
  91. int nRet = DeviceEnumerator.EnumDevices(DeviceTLayerType.MvGigEDevice | DeviceTLayerType.MvVirGigEDevice | DeviceTLayerType.MvGenTLGigEDevice | DeviceTLayerType.MvUsbDevice | DeviceTLayerType.MvVirUsbDevice, out devInfoList);
  92. if (nRet != MvError.MV_OK)
  93. {
  94. throw new Exception($"Enumerate devices fail: {nRet:x8}");
  95. }
  96. foreach (var devInfo in devInfoList)
  97. {
  98. if (devInfo.TLayerType == DeviceTLayerType.MvGigEDevice || devInfo.TLayerType == DeviceTLayerType.MvVirGigEDevice || devInfo.TLayerType == DeviceTLayerType.MvGenTLGigEDevice)
  99. {
  100. IGigEDeviceInfo gigeDevInfo = devInfo as IGigEDeviceInfo;
  101. uint nIp1 = ((gigeDevInfo.CurrentIp & 0xff000000) >> 24);
  102. uint nIp2 = ((gigeDevInfo.CurrentIp & 0x00ff0000) >> 16);
  103. uint nIp3 = ((gigeDevInfo.CurrentIp & 0x0000ff00) >> 8);
  104. uint nIp4 = (gigeDevInfo.CurrentIp & 0x000000ff);
  105. Console.WriteLine("DevIP: {0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4);
  106. if (string.Equals(devInfo.SerialNumber, _SerialNumber))
  107. {
  108. ManufacturerName = devInfo.ManufacturerName;
  109. ModelName = devInfo.ModelName;
  110. SerialNumber = devInfo.SerialNumber;
  111. CameraType = CameraType.GIGE;
  112. CameraInfo = devInfo;
  113. IsHaveCamera = true;
  114. break;
  115. }
  116. }
  117. else if (devInfo.TLayerType == DeviceTLayerType.MvUsbDevice || devInfo.TLayerType == DeviceTLayerType.MvVirUsbDevice)
  118. {
  119. if (string.Equals(devInfo.SerialNumber, _SerialNumber))
  120. {
  121. ManufacturerName = devInfo.ManufacturerName;
  122. ModelName = devInfo.ModelName;
  123. SerialNumber = devInfo.SerialNumber;
  124. CameraType = CameraType.USB;
  125. CameraInfo = devInfo;
  126. IsHaveCamera = true;
  127. break;
  128. }
  129. }
  130. }
  131. IsConnected = false;
  132. }
  133. #region 方法
  134. /// <summary>
  135. /// 获取设备
  136. /// </summary>
  137. /// <returns></returns>
  138. public static CameraInfo[] GetDevices()
  139. {
  140. List<CameraInfo> cameras = new List<CameraInfo>();
  141. try
  142. {
  143. if (!CheckSoftwareInstalled().isInstalled)
  144. {
  145. //LogHelper.WriteLogInfo("未安装MVS");
  146. return cameras.ToArray();
  147. }
  148. // ch: 初始化 SDK | en: Initialize SDK
  149. SDKSystem.Initialize();
  150. List<IDeviceInfo> devInfoList = new List<IDeviceInfo>();
  151. // ch:枚举设备 | en:Enum device
  152. int nRet = DeviceEnumerator.EnumDevices(DeviceTLayerType.MvGigEDevice | DeviceTLayerType.MvVirGigEDevice | DeviceTLayerType.MvGenTLGigEDevice | DeviceTLayerType.MvUsbDevice | DeviceTLayerType.MvVirUsbDevice, out devInfoList);
  153. if (nRet != MvError.MV_OK)
  154. {
  155. throw new Exception($"Enumerate devices fail: {nRet:x8}");
  156. }
  157. foreach (var devInfo in devInfoList)
  158. {
  159. if (devInfo.TLayerType == DeviceTLayerType.MvGigEDevice || devInfo.TLayerType == DeviceTLayerType.MvVirGigEDevice || devInfo.TLayerType == DeviceTLayerType.MvGenTLGigEDevice)
  160. {
  161. IGigEDeviceInfo gigeDevInfo = devInfo as IGigEDeviceInfo;
  162. uint nIp1 = ((gigeDevInfo.CurrentIp & 0xff000000) >> 24);
  163. uint nIp2 = ((gigeDevInfo.CurrentIp & 0x00ff0000) >> 16);
  164. uint nIp3 = ((gigeDevInfo.CurrentIp & 0x0000ff00) >> 8);
  165. uint nIp4 = (gigeDevInfo.CurrentIp & 0x000000ff);
  166. Console.WriteLine("DevIP: {0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4);
  167. cameras.Add(new CameraInfo()
  168. {
  169. CameraName = "",
  170. CameraBrand = CameraBrand.HikRobot_MVS,
  171. CameraType = CameraType.GIGE,
  172. Id = Guid.NewGuid(),
  173. ManufacturerName = devInfo.ManufacturerName,
  174. Model = devInfo.ModelName,
  175. SerialNumber = devInfo.SerialNumber,
  176. CameraIp = $"{nIp1}.{nIp2}.{nIp3}.{nIp4}",
  177. });
  178. }
  179. else if (devInfo.TLayerType == DeviceTLayerType.MvUsbDevice || devInfo.TLayerType == DeviceTLayerType.MvVirUsbDevice)
  180. {
  181. cameras.Add(new CameraInfo()
  182. {
  183. CameraName = "",
  184. CameraBrand = CameraBrand.HikRobot_MVS,
  185. CameraType = CameraType.USB,
  186. Id = Guid.NewGuid(),
  187. ManufacturerName = devInfo.ManufacturerName,
  188. Model = devInfo.ModelName,
  189. SerialNumber = devInfo.SerialNumber,
  190. CameraIp = "",
  191. });
  192. }
  193. }
  194. }
  195. catch (Exception ex)
  196. {
  197. // LogHelper.WriteLogError("搜索海康相机列表时出错!", ex);
  198. }
  199. return cameras.ToArray();
  200. }
  201. /// <summary>
  202. /// 检查相机软件是否安装
  203. /// </summary>
  204. /// <returns></returns>
  205. public static (bool isInstalled, string version) CheckSoftwareInstalled()
  206. {
  207. string softwareName = "MVS";
  208. string softwareVersion = "4.5.0";
  209. string[] registryPaths = new[]
  210. {
  211. @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
  212. @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
  213. };
  214. foreach (var path in registryPaths)
  215. {
  216. using (RegistryKey key = Registry.LocalMachine.OpenSubKey(path))
  217. {
  218. if (key == null) continue;
  219. foreach (string subKeyName in key.GetSubKeyNames())
  220. {
  221. using (RegistryKey subKey = key.OpenSubKey(subKeyName))
  222. {
  223. string displayName = subKey.GetValue("DisplayName")?.ToString();
  224. if (displayName != null && displayName.Contains(softwareName))
  225. {
  226. string version = subKey.GetValue("DisplayVersion")?.ToString();
  227. Version v1 = new Version(version);
  228. Version v2 = new Version(softwareVersion);
  229. if (v1 >= v2)
  230. {
  231. return (true, version);
  232. }
  233. else
  234. {
  235. return (false, version);
  236. }
  237. }
  238. }
  239. }
  240. }
  241. }
  242. return (false, "");
  243. }
  244. /// <summary>
  245. /// 打开相机
  246. /// </summary>
  247. /// <returns></returns>
  248. /// <exception cref="Exception"></exception>
  249. public bool OpenDevice()
  250. {
  251. if (!IsHaveCamera)
  252. {
  253. List<IDeviceInfo> devInfoList = new List<IDeviceInfo>();
  254. // ch:枚举设备 | en:Enum device
  255. int nRet = DeviceEnumerator.EnumDevices(DeviceTLayerType.MvGigEDevice | DeviceTLayerType.MvVirGigEDevice | DeviceTLayerType.MvGenTLGigEDevice | DeviceTLayerType.MvUsbDevice | DeviceTLayerType.MvVirUsbDevice, out devInfoList);
  256. if (nRet != MvError.MV_OK)
  257. {
  258. throw new Exception($"Enumerate devices fail: {nRet:x8}");
  259. }
  260. foreach (var devInfo in devInfoList)
  261. {
  262. if (devInfo.TLayerType == DeviceTLayerType.MvGigEDevice || devInfo.TLayerType == DeviceTLayerType.MvVirGigEDevice || devInfo.TLayerType == DeviceTLayerType.MvGenTLGigEDevice)
  263. {
  264. IGigEDeviceInfo gigeDevInfo = devInfo as IGigEDeviceInfo;
  265. uint nIp1 = ((gigeDevInfo.CurrentIp & 0xff000000) >> 24);
  266. uint nIp2 = ((gigeDevInfo.CurrentIp & 0x00ff0000) >> 16);
  267. uint nIp3 = ((gigeDevInfo.CurrentIp & 0x0000ff00) >> 8);
  268. uint nIp4 = (gigeDevInfo.CurrentIp & 0x000000ff);
  269. Console.WriteLine("DevIP: {0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4);
  270. if (string.Equals(devInfo.SerialNumber, SerialNumber))
  271. {
  272. ManufacturerName = devInfo.ManufacturerName;
  273. ModelName = devInfo.ModelName;
  274. SerialNumber = devInfo.SerialNumber;
  275. CameraType = CameraType.GIGE;
  276. CameraInfo = devInfo;
  277. IsHaveCamera = true;
  278. break;
  279. }
  280. }
  281. else if (devInfo.TLayerType == DeviceTLayerType.MvUsbDevice || devInfo.TLayerType == DeviceTLayerType.MvVirUsbDevice)
  282. {
  283. if (string.Equals(devInfo.SerialNumber, SerialNumber))
  284. {
  285. ManufacturerName = devInfo.ManufacturerName;
  286. ModelName = devInfo.ModelName;
  287. SerialNumber = devInfo.SerialNumber;
  288. CameraType = CameraType.USB;
  289. CameraInfo = devInfo;
  290. IsHaveCamera = true;
  291. break;
  292. }
  293. }
  294. }
  295. if (!IsHaveCamera)
  296. {
  297. throw new Exception("没有发现相机");
  298. }
  299. }
  300. if (m_MyCamera == null)
  301. {
  302. // ch:创建设备 | en:Create device
  303. try
  304. {
  305. m_MyCamera = DeviceFactory.CreateDevice(CameraInfo);
  306. }
  307. catch (Exception)
  308. {
  309. return false;
  310. }
  311. //m_MyCamera.RegisterExceptionCallBack(pCallBackFunc, IntPtr.Zero);
  312. }
  313. int nRet1 = m_MyCamera.Open(DeviceAccessMode.AccessControl, 0);
  314. if (nRet1 != MvError.MV_OK)
  315. {
  316. m_MyCamera.Dispose();
  317. return false;
  318. }
  319. CameraConnectChangedEvent?.Invoke(ID, true);
  320. // ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
  321. if (CameraType == CameraType.GIGE)
  322. {
  323. int packetSize;
  324. int ret = (m_MyCamera as IGigEDevice).GetOptimalPacketSize(out packetSize);
  325. if (packetSize > 0)
  326. {
  327. ret = m_MyCamera.Parameters.SetIntValue("GevSCPSPacketSize", packetSize);
  328. if (ret != MvError.MV_OK)
  329. {
  330. Console.WriteLine("Warning: Set Packet Size failed {0:x8}", ret);
  331. }
  332. else
  333. {
  334. Console.WriteLine("Set PacketSize to {0}", packetSize);
  335. }
  336. }
  337. else
  338. {
  339. Console.WriteLine("Warning: Get Packet Size failed {0:x8}", ret);
  340. }
  341. }
  342. //连续采集
  343. m_MyCamera.Parameters.SetEnumValueByString("TriggerMode", "Off");
  344. // ch:触发源选择:0 - Line0; | en:Trigger source select:0 - Line0;
  345. // 1 - Line1;
  346. // 2 - Line2;
  347. // 3 - Line3;
  348. // 4 - Counter;
  349. // 7 - Software;
  350. // m_MyCamera.Parameters.SetEnumValueByString("TriggerSource", "Software");
  351. m_MyCamera.Parameters.SetEnumValueByString("AcquisitionMode", "Continuous");
  352. //m_MyCamera.Parameters.SetCommandValue("TriggerSoftware");
  353. // ch:注册回调函数 | en:Register image callback
  354. // m_MyCamera.StreamGrabber.FrameGrabedEvent += FrameGrabedEventHandler;
  355. IsConnected = m_MyCamera.IsConnected;
  356. return IsConnected;
  357. }
  358. /// <summary>
  359. /// 关闭相机
  360. /// </summary>
  361. public void CloseDevice()
  362. {
  363. m_MyCamera.StreamGrabber.FrameGrabedEvent -= FrameGrabedEventHandler;
  364. // ch:关闭设备 | en:Close Device
  365. m_MyCamera.Close();
  366. m_MyCamera.Dispose();
  367. m_MyCamera = null;
  368. if (IsConnected)
  369. {
  370. IsConnected = false;
  371. CameraConnectChangedEvent?.Invoke(ID, IsConnected);
  372. }
  373. }
  374. /// <summary>
  375. /// 取图前的必要操作步骤
  376. /// </summary>
  377. /// <returns></returns>
  378. private Int32 NecessaryOperBeforeGrab()
  379. {
  380. // ch:取图像宽 | en:Get Iamge Width
  381. IIntValue pcWidth = null;
  382. int nRet = m_MyCamera.Parameters.GetIntValue("Width", out pcWidth);
  383. if (nRet != MvError.MV_OK)
  384. {
  385. return nRet;
  386. }
  387. ImageWidth = (UInt32)pcWidth.CurValue;
  388. // ch:取图像高 | en:Get Iamge Height
  389. IIntValue pcHeight = null;
  390. nRet = m_MyCamera.Parameters.GetIntValue("Height", out pcHeight);
  391. if (nRet != MvError.MV_OK)
  392. {
  393. return nRet;
  394. }
  395. ImageHeight = (UInt32)pcHeight.CurValue;
  396. // ch:取像素格式 | en:Get Pixel Format
  397. IEnumValue pcPixelFormat = null;
  398. nRet = m_MyCamera.Parameters.GetEnumValue("PixelFormat", out pcPixelFormat);
  399. if (nRet != MvError.MV_OK)
  400. {
  401. return nRet;
  402. }
  403. PixelType = (MvCameraControl.MvGvspPixelType)pcPixelFormat.CurEnumEntry.Value;
  404. return MvError.MV_OK;
  405. }
  406. /// <summary>
  407. /// 采集图像
  408. /// </summary>
  409. /// <returns></returns>
  410. public ICogImage Grab()
  411. {
  412. m_MyCamera.StreamGrabber.StartGrabbing();
  413. m_MyCamera.Parameters.SetEnumValueByString("AcquisitionMode", "Continuous");
  414. m_MyCamera.Parameters.SetEnumValueByString("TriggerMode", "Off");
  415. m_MyCamera.Parameters.SetFloatValue("ExposureTime", 5000); // 单位μs
  416. m_MyCamera.Parameters.SetIntValue("GevSCPSPacketSize", 8164);
  417. m_MyCamera.Parameters.SetIntValue("GevSCPD", 12000); // 缓冲区大小
  418. m_MyCamera.Parameters.SetEnumValueByString("PixelFormat", "RGB8");
  419. IFrameOut frameOut = null;
  420. bool Succeed = false;
  421. int nRet = -1;
  422. Stopwatch sw = Stopwatch.StartNew();
  423. for (int i = 0; i < 5; i++)
  424. {
  425. try
  426. {
  427. if (m_MyCamera == null || !m_MyCamera.IsConnected)
  428. {
  429. OpenDevice();
  430. }
  431. ErrorMessage = "";
  432. // ch:前置配置 | en:pre-operation
  433. nRet = NecessaryOperBeforeGrab();
  434. if (nRet != MvError.MV_OK)
  435. {
  436. CloseDevice();
  437. OpenDevice();
  438. ErrorMessage = $"{nRet: x8}";
  439. continue;
  440. }
  441. // ch:开始采集 | en:Start Grabbing
  442. nRet = m_MyCamera.StreamGrabber.StartGrabbing();
  443. if (nRet != MvError.MV_OK)
  444. {
  445. CloseDevice();
  446. OpenDevice();
  447. ErrorMessage = $"{nRet: x8}";
  448. continue;
  449. }
  450. nRet = m_MyCamera.StreamGrabber.GetImageBuffer(20000, out frameOut);
  451. m_MyCamera.StreamGrabber.StopGrabbing();
  452. if (nRet != MvError.MV_OK)
  453. {
  454. CloseDevice();
  455. OpenDevice();
  456. ErrorMessage = $"{nRet: x8}";
  457. continue;
  458. }
  459. m_MyCamera.StreamGrabber.FreeImageBuffer(frameOut);
  460. Succeed = true;
  461. break;
  462. }
  463. catch (Exception ex)
  464. {
  465. CloseDevice();
  466. OpenDevice();
  467. ErrorMessage = ex.Message;
  468. }
  469. }
  470. if (!Succeed)
  471. {
  472. ImageCallbackEvent?.Invoke(Image, TotalTime, ErrorMessage);
  473. return null;
  474. }
  475. if( frameOut !=null)
  476. Image = AnalyticImage(frameOut);
  477. TotalTime = sw.Elapsed;
  478. sw.Stop();
  479. ImageCallbackEvent?.Invoke(Image, TotalTime, ErrorMessage);
  480. GrabImageCallbackEvent?.Invoke(Image);
  481. return Image;
  482. }
  483. /// <summary>
  484. /// 开始采集图像
  485. /// </summary>
  486. public void StartGrabbing()
  487. {
  488. if (IsGrabbing)
  489. return;
  490. IsGrabbing = true;
  491. m_hReceiveThread = new Thread(GetStreamThreadProc) { IsBackground = true };
  492. m_hReceiveThread.Start();
  493. }
  494. /// <summary>
  495. /// 开始触发模式采集图像
  496. /// </summary>
  497. public void StartTriggerGrabbing()
  498. {
  499. IsGrabbing = true;
  500. m_MyCamera.StreamGrabber.StartGrabbing();
  501. }
  502. /// <summary>
  503. /// 停止采集图像
  504. /// </summary>
  505. public void StopGrabbing()
  506. {
  507. try
  508. {
  509. if ( IsGrabbing )
  510. {
  511. IsGrabbing = false;
  512. Thread.Sleep(1000);
  513. }
  514. if (m_hReceiveThread != null)
  515. {
  516. m_hReceiveThread.Abort();
  517. m_hReceiveThread = null;
  518. }
  519. }
  520. catch (Exception)
  521. {
  522. }
  523. }
  524. private void FrameGrabedEventHandler(object sender, FrameGrabbedEventArgs e)
  525. {
  526. GrabImageCallbackEvent?.Invoke(AnalyticImage(e.FrameOut));
  527. //Console.WriteLine("Get one frame: Width[{0}] , Height[{1}] , ImageSize[{2}], FrameNum[{3}]", e.FrameOut.Image.Width, e.FrameOut.Image.Height, e.FrameOut.Image.ImageSize, e.FrameOut.FrameNum);
  528. }
  529. /// <summary>
  530. /// Converts the image data from the specified <see cref="IFrameOut"/> object into an <see cref="ICogImage"/>
  531. /// format.
  532. /// </summary>
  533. /// <remarks>This method processes both color and monochrome images, converting them to a
  534. /// compatible format for further analysis. Unsupported pixel formats are not processed, and the method will
  535. /// return <see langword="null"/> in such cases. The caller is responsible for ensuring that the <paramref
  536. /// name="frameOut"/> parameter is valid and properly initialized.</remarks>
  537. /// <param name="frameOut">The frame output containing the image data to be analyzed and converted.</param>
  538. /// <returns>An <see cref="ICogImage"/> object representing the converted image. Returns <see langword="null"/> if the
  539. /// image format is unsupported or if an error occurs during conversion.</returns>
  540. private ICogImage AnalyticImage(IFrameOut frameOut)
  541. {
  542. ICogImage image = null;
  543. int nRet = -1;
  544. //IntPtr pTemp = IntPtr.Zero;
  545. IImage pImage = frameOut.Image;
  546. MvCameraControl.MvGvspPixelType pixelType = frameOut.Image.PixelType;
  547. if (IsColorPixelFormat(frameOut.Image.PixelType)) // 彩色图像处理
  548. {
  549. if (frameOut.Image.PixelType == MvCameraControl.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed)
  550. {
  551. //pTemp = frameOut.Image.PixelDataPtr;
  552. }
  553. else
  554. {
  555. nRet = m_MyCamera.PixelTypeConverter.ConvertPixelType(frameOut.Image, out pImage, MvCameraControl.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed);
  556. if (nRet != MvError.MV_OK)
  557. {
  558. frameOut.Dispose();
  559. return null;
  560. }
  561. pixelType = MvCameraControl.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed;
  562. }
  563. }
  564. else if (IsMonoPixelFormat(frameOut.Image.PixelType)) // Mono图像处理
  565. {
  566. if (frameOut.Image.PixelType == MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono8)
  567. {
  568. //pTemp = frameOut.Image.PixelDataPtr;
  569. }
  570. else
  571. {
  572. // 其他格式Mono转为Mono8
  573. nRet = m_MyCamera.PixelTypeConverter.ConvertPixelType(frameOut.Image, out pImage, MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono8);
  574. if (nRet != MvError.MV_OK)
  575. {
  576. frameOut.Dispose();
  577. return null;
  578. }
  579. }
  580. pixelType = MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono8;
  581. }
  582. else
  583. {
  584. return null; // 不支持的图像格式
  585. }
  586. // 直接转换为CogImage,避免不必要的内存拷贝
  587. image = ConvertToICogImage(pImage.Height,pImage.Width,
  588. pImage.PixelDataPtr, pixelType);
  589. ////2.申请 byte[]
  590. //byte[] m_BufForDriver1 = new byte[pImage.ImageSize];
  591. ////3.海康相机取流 指针转 byte[]
  592. //Marshal.Copy(pImage.PixelDataPtr, m_BufForDriver1, 0, ((int)pImage.ImageSize));
  593. ////4.转换成 CogImage
  594. //image = ConvertToICogImage(pImage.Width, pImage.Height, pImage.PixelDataPtr, pixelType);
  595. pImage.Dispose();
  596. frameOut.Dispose();
  597. return image;
  598. }
  599. /// <summary>
  600. /// 设置曝光时间
  601. /// </summary>
  602. /// <param name="ExposureTime"></param>
  603. /// <returns></returns>
  604. public bool SetExposureTime(float ExposureTime)
  605. {
  606. int nRet = m_MyCamera.Parameters.SetFloatValue("ExposureTime", ExposureTime);
  607. if (nRet != MvError.MV_OK)
  608. {
  609. return false;
  610. }
  611. else
  612. {
  613. return true;
  614. }
  615. }
  616. /// <summary>
  617. /// 获取曝光时间
  618. /// </summary>
  619. /// <returns></returns>
  620. public float GetExposureTime()
  621. {
  622. IFloatValue pcFloatValue = null;
  623. int nRet = m_MyCamera.Parameters.GetFloatValue("ExposureTime", out pcFloatValue);
  624. if (nRet == MvError.MV_OK)
  625. {
  626. return pcFloatValue.CurValue;
  627. }
  628. else
  629. {
  630. return 0;
  631. }
  632. }
  633. /// <summary>
  634. /// 设置增益
  635. /// </summary>
  636. /// <param name="Gain"></param>
  637. /// <returns></returns>
  638. public bool SetGain(float Gain)
  639. {
  640. int nRet = m_MyCamera.Parameters.SetFloatValue("Gain", Gain);
  641. if (nRet != MvError.MV_OK)
  642. {
  643. return false;
  644. }
  645. else
  646. {
  647. return true;
  648. }
  649. }
  650. /// <summary>
  651. /// 获取增益
  652. /// </summary>
  653. /// <returns></returns>
  654. public float GetGain()
  655. {
  656. IFloatValue pcFloatValue = null;
  657. int nRet = m_MyCamera.Parameters.GetFloatValue("Gain", out pcFloatValue);
  658. if (nRet == MvError.MV_OK)
  659. {
  660. return pcFloatValue.CurValue;
  661. }
  662. else
  663. {
  664. return 0;
  665. }
  666. }
  667. /// <summary>
  668. /// ch:获取触发模式 | en:Get Trigger Mode
  669. /// </summary>
  670. /// <returns>On/Off</returns>
  671. public bool GetTriggerMode()
  672. {
  673. IEnumValue enumValue;
  674. int result = m_MyCamera.Parameters.GetEnumValue("TriggerMode", out enumValue);
  675. if (result == MvError.MV_OK)
  676. {
  677. if (enumValue.CurEnumEntry.Symbolic == "On")
  678. {
  679. return true;
  680. }
  681. return false;
  682. }
  683. return false;
  684. }
  685. /// <summary>
  686. /// 设置触发模式
  687. /// </summary>
  688. /// <param name="mode">触发模式</param>
  689. /// <param name="triggerSource">触发源0 - Line0;1 - Line1;2 - Line2;3 - Line3;4 - Counter;7 - Software;</param>
  690. /// <returns></returns>
  691. public bool SetTriggerMode(bool mode, int triggerSource)
  692. {
  693. string strmode = mode ? "On" : "Off";
  694. int nRet = m_MyCamera.Parameters.SetEnumValueByString("TriggerMode", strmode);
  695. if (nRet != MvError.MV_OK)
  696. {
  697. return false;
  698. }
  699. if (mode)
  700. {
  701. // ch:注册回调函数 | en:Register image callback
  702. m_MyCamera.StreamGrabber.FrameGrabedEvent -= FrameGrabedEventHandler;
  703. m_MyCamera.StreamGrabber.FrameGrabedEvent += FrameGrabedEventHandler;
  704. return SetTriggerSource(triggerSource);
  705. }
  706. else
  707. {
  708. m_MyCamera.StreamGrabber.FrameGrabedEvent -= FrameGrabedEventHandler;
  709. nRet = m_MyCamera.Parameters.SetEnumValueByString("AcquisitionMode", "Continuous");
  710. if (nRet != MvError.MV_OK)
  711. {
  712. return false;
  713. }
  714. }
  715. return true;
  716. }
  717. /// <summary>
  718. /// Sets the trigger source for the camera.
  719. /// </summary>
  720. /// <param name="source">0 - Line0;1 - Line1;2 - Line2;3 - Line3;4 - Counter;7 - Software;</param>
  721. /// <returns></returns>
  722. /// <exception cref="ArgumentOutOfRangeException"></exception>
  723. public bool SetTriggerSource(int source)
  724. {
  725. // ch:触发源选择:0 - Line0; | en:Trigger source select:0 - Line0;
  726. // 1 - Line1;
  727. // 2 - Line2;
  728. // 3 - Line3;
  729. // 4 - Counter;
  730. // 7 - Software;
  731. string sourceStr;
  732. switch (source)
  733. {
  734. case 0: sourceStr = "Line0"; break;
  735. case 1: sourceStr = "Line1"; break;
  736. case 2: sourceStr = "Line2"; break;
  737. case 3: sourceStr = "Line3"; break;
  738. case 4: sourceStr = "Counter"; break;
  739. case 7: sourceStr = "Software"; break;
  740. default: throw new ArgumentOutOfRangeException(nameof(source), "Invalid trigger source value");
  741. }
  742. int nRet = m_MyCamera.Parameters.SetEnumValueByString("TriggerSource", sourceStr);
  743. if (nRet != MvError.MV_OK)
  744. {
  745. return false;
  746. }
  747. return true;
  748. }
  749. /// <summary>
  750. /// Retrieves the current trigger source setting of the camera.
  751. /// </summary>
  752. /// <remarks>This method queries the camera's parameters to determine the current trigger source.
  753. /// If the retrieval is unsuccessful or the trigger source is not recognized, the method returns -1.</remarks>
  754. /// <returns>An integer representing the trigger source: <list type="bullet"> <item><description>0 for
  755. /// "Line0".</description></item> <item><description>1 for "Line1".</description></item> <item><description>2
  756. /// for "Line2".</description></item> <item><description>3 for "Line3".</description></item>
  757. /// <item><description>4 for "Counter".</description></item> <item><description>7 for
  758. /// "Software".</description></item> <item><description>-1 if the trigger source is unknown or if the retrieval
  759. /// fails.</description></item> </list></returns>
  760. public int GetTriggerSource()
  761. {
  762. IEnumValue enumValue;
  763. int result = m_MyCamera.Parameters.GetEnumValue("TriggerSource", out enumValue);
  764. if (result == MvError.MV_OK)
  765. {
  766. switch (enumValue.CurEnumEntry.Symbolic)
  767. {
  768. case "Line0": return 0;
  769. case "Line1": return 1;
  770. case "Line2": return 2;
  771. case "Line3": return 3;
  772. case "Counter": return 4;
  773. case "Software": return 7;
  774. default: return -1; // 未知触发源
  775. }
  776. }
  777. return -1; // 获取失败
  778. }
  779. /// <summary>
  780. /// Sends a software trigger command to the camera.
  781. /// </summary>
  782. /// <remarks>This method triggers the camera to capture an image or perform an action based on
  783. /// its current configuration. Ensure the camera is properly initialized and configured to respond to software
  784. /// triggers before calling this method.</remarks>
  785. public void TriggerSoftware()
  786. {
  787. // ch:触发软件 | en:Trigger Software
  788. m_MyCamera.Parameters.SetCommandValue("TriggerSoftware");
  789. }
  790. private void GetStreamThreadProc()
  791. {
  792. m_MyCamera.StreamGrabber.StartGrabbing();
  793. m_MyCamera.Parameters.SetEnumValueByString("AcquisitionMode", "Continuous");
  794. m_MyCamera.Parameters.SetEnumValueByString("TriggerMode", "Off");
  795. m_MyCamera.Parameters.SetFloatValue("ExposureTime", 5000); // 单位μs
  796. m_MyCamera.Parameters.SetIntValue("GevSCPSPacketSize", 8164);
  797. m_MyCamera.Parameters.SetIntValue("GevSCPD", 12000); // 缓冲区大小
  798. m_MyCamera.Parameters.SetEnumValueByString("PixelFormat", "RGB8");
  799. while (IsGrabbing)
  800. {
  801. Stopwatch sw = Stopwatch.StartNew();
  802. try
  803. {
  804. IFrameOut frameOut = null;
  805. int nRet = m_MyCamera.StreamGrabber.GetImageBuffer(500, out frameOut);
  806. double time1 = sw.Elapsed.TotalMilliseconds;
  807. Console.WriteLine($"获取图像:{time1}ms");
  808. if ( nRet == MvError.MV_OK )
  809. {
  810. Image = AnalyticImage(frameOut);
  811. TotalTime = sw.Elapsed;
  812. ImageCallbackEvent?.Invoke(Image, TotalTime, ErrorMessage);
  813. m_MyCamera.StreamGrabber.FreeImageBuffer(frameOut);
  814. }
  815. else
  816. {
  817. TotalTime = sw.Elapsed;
  818. Thread.Sleep(5);
  819. }
  820. }
  821. catch (Exception ex)
  822. {
  823. TotalTime = sw.Elapsed;
  824. ErrorMessage = ex.Message;
  825. ImageCallbackEvent?.Invoke(Image, TotalTime, ErrorMessage);
  826. continue;
  827. }
  828. }
  829. IsGrabbing = false;
  830. }
  831. /// <summary>
  832. /// Converts raw image data into an <see cref="ICogImage"/> object, supporting both monochrome and color pixel
  833. /// formats.
  834. /// </summary>
  835. /// <remarks>This method supports both monochrome (PixelType_Gvsp_Mono8) and color pixel formats.
  836. /// For color images, the method processes the image data as a planar color format.</remarks>
  837. /// <param name="nHeight">The height of the image in pixels.</param>
  838. /// <param name="nWidth">The width of the image in pixels.</param>
  839. /// <param name="pImageBuf">A pointer to the buffer containing the raw image data.</param>
  840. /// <param name="enPixelType">The pixel format of the image, specified as a <see cref="MvCameraControl.MvGvspPixelType"/> value.</param>
  841. /// <returns>An <see cref="ICogImage"/> object representing the converted image. Returns <see langword="null"/> if the
  842. /// conversion fails.</returns>
  843. private ICogImage ConvertToICogImage(UInt32 nHeight, UInt32 nWidth, IntPtr pImageBuf, MvCameraControl.MvGvspPixelType enPixelType)
  844. {
  845. ICogImage cogImage = null;
  846. // ch:获取步长 || en: Get nRowStep
  847. uint m_nRowStep = nWidth * nHeight;
  848. // ch: 显示 || display
  849. try
  850. {
  851. if (enPixelType == MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono8)
  852. {
  853. CogImage8Root cogImage8Root = new CogImage8Root();
  854. cogImage8Root.Initialize((Int32)nWidth, (Int32)nHeight, pImageBuf, (Int32)nWidth, null);
  855. CogImage8Grey cogImage8Grey = new CogImage8Grey();
  856. cogImage8Grey.SetRoot(cogImage8Root);
  857. cogImage = cogImage8Grey.ScaleImage((int)nWidth, (int)nHeight);
  858. System.GC.Collect();
  859. }
  860. else
  861. {
  862. CogImage8Root image0 = new CogImage8Root();
  863. IntPtr ptr0 = new IntPtr(pImageBuf.ToInt64());
  864. image0.Initialize((int)nWidth, (int)nHeight, ptr0, (int)nWidth, null);
  865. CogImage8Root image1 = new CogImage8Root();
  866. IntPtr ptr1 = new IntPtr(pImageBuf.ToInt64() + m_nRowStep);
  867. image1.Initialize((int)nWidth, (int)nHeight, ptr1, (int)nWidth, null);
  868. CogImage8Root image2 = new CogImage8Root();
  869. IntPtr ptr2 = new IntPtr(pImageBuf.ToInt64() + m_nRowStep * 2);
  870. image2.Initialize((int)nWidth, (int)nHeight, ptr2, (int)nWidth, null);
  871. CogImage24PlanarColor colorImage = new CogImage24PlanarColor();
  872. colorImage.SetRoots(image0, image1, image2);
  873. cogImage = colorImage.ScaleImage((int)nWidth, (int)nHeight);
  874. System.GC.Collect();
  875. }
  876. }
  877. catch (System.Exception ex)
  878. {
  879. ErrorMessage = $"转换ICogImage出错: {ex.Message}";
  880. return null;
  881. }
  882. return cogImage;
  883. }
  884. /// <summary>
  885. /// 图像是否为Mono格式
  886. /// </summary>
  887. /// <param name="enType"></param>
  888. /// <returns></returns>
  889. private bool IsMonoPixelFormat(MvCameraControl.MvGvspPixelType enType)
  890. {
  891. switch (enType)
  892. {
  893. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono8:
  894. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono10:
  895. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono10_Packed:
  896. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono12:
  897. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono12_Packed:
  898. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_Mono16:
  899. return true;
  900. default:
  901. return false;
  902. }
  903. }
  904. /// <summary>
  905. /// 图像是否为彩色
  906. /// </summary>
  907. /// <param name="enType"></param>
  908. /// <returns></returns>
  909. private bool IsColorPixelFormat(MvCameraControl.MvGvspPixelType enType)
  910. {
  911. switch (enType)
  912. {
  913. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed:
  914. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BGR8_Packed:
  915. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_RGBA8_Packed:
  916. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BGRA8_Packed:
  917. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_YUV422_Packed:
  918. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_YUV422_YUYV_Packed:
  919. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGR8:
  920. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRG8:
  921. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGB8:
  922. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerBG8:
  923. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRBGG8:
  924. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGB10:
  925. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGB10_Packed:
  926. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerBG10:
  927. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerBG10_Packed:
  928. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRG10:
  929. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRG10_Packed:
  930. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGR10:
  931. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGR10_Packed:
  932. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGB12:
  933. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGB12_Packed:
  934. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerBG12:
  935. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerBG12_Packed:
  936. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRG12:
  937. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRG12_Packed:
  938. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGR12:
  939. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGR12_Packed:
  940. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGR16:
  941. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerRG16:
  942. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerGB16:
  943. case MvCameraControl.MvGvspPixelType.PixelType_Gvsp_BayerBG16:
  944. return true;
  945. default:
  946. return false;
  947. }
  948. }
  949. #endregion
  950. #region 属性通知
  951. /// <summary>
  952. /// Occurs when a property value changes.
  953. /// </summary>
  954. public event PropertyChangedEventHandler PropertyChanged;
  955. /// <summary>
  956. /// Checks if a property already matches a desired value. Sets the property and
  957. /// notifies listeners only when necessary.
  958. /// </summary>
  959. /// <typeparam name="T">Type of the property.</typeparam>
  960. /// <param name="storage">Reference to a property with both getter and setter.</param>
  961. /// <param name="value">Desired value for the property.</param>
  962. /// <param name="propertyName">Name of the property used to notify listeners. This
  963. /// value is optional and can be provided automatically when invoked from compilers that
  964. /// support CallerMemberName.</param>
  965. /// <returns>True if the value was changed, false if the existing value matched the
  966. /// desired value.</returns>
  967. protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
  968. {
  969. if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
  970. storage = value;
  971. RaisePropertyChanged(propertyName);
  972. return true;
  973. }
  974. /// <summary>
  975. /// Checks if a property already matches a desired value. Sets the property and
  976. /// notifies listeners only when necessary.
  977. /// </summary>
  978. /// <typeparam name="T">Type of the property.</typeparam>
  979. /// <param name="storage">Reference to a property with both getter and setter.</param>
  980. /// <param name="value">Desired value for the property.</param>
  981. /// <param name="propertyName">Name of the property used to notify listeners. This
  982. /// value is optional and can be provided automatically when invoked from compilers that
  983. /// support CallerMemberName.</param>
  984. /// <param name="onChanged">Action that is called after the property value has been changed.</param>
  985. /// <returns>True if the value was changed, false if the existing value matched the
  986. /// desired value.</returns>
  987. protected virtual bool SetProperty<T>(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null)
  988. {
  989. if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
  990. storage = value;
  991. onChanged?.Invoke();
  992. RaisePropertyChanged(propertyName);
  993. return true;
  994. }
  995. /// <summary>
  996. /// Raises this object's PropertyChanged event.
  997. /// </summary>
  998. /// <param name="propertyName">Name of the property used to notify listeners. This
  999. /// value is optional and can be provided automatically when invoked from compilers
  1000. /// that support <see cref="CallerMemberNameAttribute"/>.</param>
  1001. protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
  1002. {
  1003. OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
  1004. }
  1005. /// <summary>
  1006. /// Raises this object's PropertyChanged event.
  1007. /// </summary>
  1008. /// <param name="args">The PropertyChangedEventArgs</param>
  1009. protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
  1010. {
  1011. PropertyChanged?.Invoke(this, args);
  1012. }
  1013. public bool CheckGrabImageCallbackEventIsHas(Action<ICogImage> action)
  1014. {
  1015. if ( GrabImageCallbackEvent == null ) return false;
  1016. return GrabImageCallbackEvent.GetInvocationList().Contains(action);
  1017. }
  1018. public bool CheckImageCallbackEventIsHas(Action<ICogImage, TimeSpan, string> action)
  1019. {
  1020. if ( ImageCallbackEvent == null ) return false;
  1021. return ImageCallbackEvent.GetInvocationList().Contains(action);
  1022. }
  1023. public bool CheckCameraConnectChangedEventIsHas(Action<Guid, bool> action)
  1024. {
  1025. if ( CameraConnectChangedEvent == null ) return false;
  1026. return CameraConnectChangedEvent.GetInvocationList().Contains(action);
  1027. }
  1028. #endregion
  1029. }
  1030. }