MvCamera.cs 46 KB

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