ShuLiClass.cs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352
  1. using CCDCount.DLL.AlarmTools;
  2. using CCDCount.MODEL.ConfigModel;
  3. using CCDCount.MODEL.ResultModel;
  4. using CCDCount.MODEL.ShuLiModel;
  5. using LogClass;
  6. using MvCameraControl;
  7. using System;
  8. using System.Collections.Concurrent;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.Drawing;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Runtime.InteropServices.WindowsRuntime;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. namespace CCDCount.DLL
  18. {
  19. public class ShuLiClass
  20. {
  21. #region 变量
  22. /// <summary>
  23. /// 当活跃物体转变为历史物体时的回调事件
  24. /// </summary>
  25. public event EventHandler<ActiveObjectEventArgsClass> WorkCompleted;
  26. private List<ActiveObjectClass> activeObjects = new List<ActiveObjectClass>(); // 当前跟踪中的物体
  27. private List<ActiveObjectClass> historyActiveObjects = new List<ActiveObjectClass>(); // 历史物体
  28. private ConcurrentQueue<IImage> IFrameDatas = new ConcurrentQueue<IImage>(); //图像数据队列
  29. private Thread IdentifyImageProcessThread = null; // 识别线程
  30. private bool IsIdentify = false; //线程是否开始识别的标志
  31. private long currentLine = 0; //行数记录
  32. private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件
  33. public List<int> ChannelsRoi { get { return _ChannelsRoi; } }
  34. private List<int> _ChannelsRoi = new List<int>();
  35. private int ChannelWidth = 0;//每个区域的宽度
  36. private int IdentifyImageWidth = -1;
  37. private int ObjectNum = 0;
  38. public int ImageNum { get { return IFrameDatas.Count; } }
  39. private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
  40. private double XCoefficient = 1;
  41. private double YCoefficient = 2;
  42. private DateTime[] ChannelIntervalTime = new DateTime[8];
  43. #endregion
  44. #region 公共方法
  45. /// <summary>
  46. /// 初始化构造方法
  47. /// </summary>
  48. public ShuLiClass()
  49. {
  50. // 加载默认参数
  51. shuLiConfig = new ShuLiConfigClass()
  52. {
  53. Channel = 8,
  54. PandingCode = 2
  55. };
  56. for(int i = 0; i < ChannelIntervalTime.Length; i++)
  57. {
  58. ChannelIntervalTime[i] = DateTime.Now;
  59. }
  60. }
  61. /// <summary>
  62. /// 带参数的构造方法
  63. /// </summary>
  64. public ShuLiClass(ShuLiConfigClass config)
  65. {
  66. if (config.IsLoadCanfig)
  67. {
  68. // 加载传出的参数
  69. shuLiConfig = config;
  70. XCoefficient = shuLiConfig.ScaleX;
  71. YCoefficient = shuLiConfig.ScaleY;
  72. }
  73. else
  74. {
  75. // 加载默认参数
  76. shuLiConfig = new ShuLiConfigClass()
  77. {
  78. Channel = 8,
  79. PandingCode = 2
  80. };
  81. }
  82. InitChannel();
  83. }
  84. public long InitCurrentLine(int line)
  85. {
  86. currentLine = line;
  87. return currentLine;
  88. }
  89. public int InitNum(int num)
  90. {
  91. ObjectNum = num;
  92. return ObjectNum;
  93. }
  94. List<ActiveObjectClass> lostObjects = new List<ActiveObjectClass>();
  95. /// <summary>
  96. /// 处理图像序列的主入口
  97. /// </summary>
  98. /// <param name="image">图像像素数据</param>
  99. /// <param name="ImageWidth">图像宽</param>
  100. /// <param name="currentLine">当前行数</param>
  101. /// <returns>检测到的物体总数</returns>
  102. public bool ProcessImageSequence(IImage image)
  103. {
  104. bool result = false;
  105. for (int i = 0; i < image.Height; i++)
  106. {
  107. result = ProcessLine(image, i);
  108. currentLine += 1;
  109. }
  110. Stopwatch stopwatch = Stopwatch.StartNew();
  111. //识别到结果并输出
  112. //lock (_lockObj)
  113. //{
  114. _rwLock.EnterReadLock();
  115. // 清理超时未更新的物体
  116. lostObjects = activeObjects
  117. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  118. .ToList();
  119. _rwLock.ExitReadLock();
  120. //}
  121. if (stopwatch.ElapsedMilliseconds > 1)
  122. {
  123. FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果筛选,此次识别耗时:{stopwatch.Elapsed}");
  124. }
  125. List<ActiveObjectClass> OneActive = new List<ActiveObjectClass>();
  126. // 有物体转变为活跃物体,返回值转为true
  127. if (lostObjects.Count > 0)
  128. {
  129. result = true;
  130. stopwatch.Restart();
  131. foreach (var item in lostObjects)
  132. {
  133. //噪点判定
  134. if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  135. {
  136. item.StateCode = 9;
  137. //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6);
  138. continue;
  139. }
  140. //转为历史物体,添加缺少的参数
  141. item.Num = Interlocked.Increment(ref ObjectNum) + 1;
  142. item.ChannelNO = ActiveChannel(item);
  143. item.EndCheckTime = DateTime.Now;
  144. if(item.StartLine == item.LastSeenLine)
  145. {
  146. item.MaxLength = item.MaxEndCol - item.MinStartCol;
  147. item.StateCode = 10;
  148. LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num));
  149. }
  150. else
  151. {
  152. var CalculationResult = SizeCalculation(item.RowsData);
  153. item.MaxLength = CalculationResult.Height;
  154. item.hasSignificantConcavity = CalculationResult.hasSignificantConcavity;
  155. item.concavityRatio = CalculationResult.concavityRatio;
  156. if(CalculationResult.hasSignificantConcavity)
  157. {
  158. if(CalculationResult.concavityRatio>0.3)
  159. {
  160. item.StateCode= 11;
  161. }
  162. }
  163. }
  164. if ((item.LastSeenLine - item.StartLine) > shuLiConfig.MAX_Idetify_Height&&item.StateCode != -1)
  165. {
  166. item.StateCode = 7;
  167. //FaultLog.RecordLogMessage("ShuLiClass-ProcessLine:非颗粒,视野异常", 3);
  168. //LOG.log(string.Format("ShuLiClass-ProcessLine:非颗粒,视野异常"), 6);
  169. //Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常");
  170. }
  171. else if (shuLiConfig.PandingCode != -1)
  172. {
  173. if (item.StateCode != -1)
  174. {
  175. if (item.StateCode == 8)
  176. {
  177. LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  178. }
  179. }
  180. else if (item.Area < shuLiConfig.MinArea
  181. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  182. {
  183. item.StateCode = 5;
  184. LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num));
  185. Console.WriteLine("颗粒编号{0}:面积过小", item.Num);
  186. }
  187. else if (item.Area > shuLiConfig.MaxArea
  188. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  189. {
  190. item.StateCode = 6;
  191. LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  192. Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
  193. }
  194. else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH
  195. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  196. {
  197. item.StateCode = 2;
  198. LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  199. Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
  200. }
  201. else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH
  202. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  203. {
  204. item.StateCode = 1;
  205. LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  206. Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
  207. }
  208. else
  209. {
  210. item.StateCode = 0;
  211. LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  212. Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
  213. }
  214. }
  215. OneActive.Add(item);
  216. }
  217. stopwatch.Stop();
  218. //if (stopwatch.ElapsedMilliseconds > 1)
  219. //{
  220. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
  221. //}
  222. //stopwatch.Restart();
  223. if (OneActive.Count > 0)
  224. {
  225. //触发回调事件
  226. OnWorkCompleted(OneActive);
  227. //ThreadPool.QueueUserWorkItem(_ =>OnWorkCompleted(OneActive));
  228. }
  229. stopwatch.Stop();
  230. //if (stopwatch.ElapsedMilliseconds > 1)
  231. //{
  232. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果完成回调函数,此次识别耗时:{stopwatch.Elapsed}");
  233. //}
  234. }
  235. else
  236. {
  237. OneActive.Clear();
  238. }
  239. _rwLock.EnterWriteLock();
  240. // 累加到总数并从活跃物体转移到历史物体
  241. OneActive.ForEach(o => TryAdd(historyActiveObjects, o, 2500));
  242. //lostObjects.Where(o => o.StateCode != 7 && o.StateCode != 9).ToList().ForEach(o => TryAdd(historyActiveObjects, o, 2500));
  243. lostObjects.ForEach(o => activeObjects.Remove(o));
  244. _rwLock.ExitWriteLock();
  245. return result;
  246. }
  247. /// <summary>
  248. /// 返回最后一个历史物品
  249. /// </summary>
  250. /// <returns></returns>
  251. public ActiveObjectClass GetLastActive()
  252. {
  253. if (historyActiveObjects.Count() == 0)
  254. return null;
  255. return historyActiveObjects.Last();
  256. }
  257. /// <summary>
  258. /// 返回历史物品
  259. /// </summary>
  260. /// <returns></returns>
  261. public List<ActiveObjectClass> GetHistoryActive()
  262. {
  263. //lock (_lockObj) // 加锁
  264. //{
  265. _rwLock.EnterReadLock();
  266. var result = historyActiveObjects.ToList();
  267. _rwLock.ExitReadLock();
  268. return result;
  269. //}
  270. }
  271. /// <summary>
  272. /// 返回缓存在内存的历史物品的总数量
  273. /// </summary>
  274. /// <returns></returns>
  275. public int GetHistoryActiveNum()
  276. {
  277. //lock (_lockObj) // 加锁
  278. _rwLock.EnterReadLock();
  279. var result = historyActiveObjects.Count;
  280. _rwLock.ExitReadLock();
  281. return result;
  282. }
  283. /// <summary>
  284. /// 获取历史数据中,正常数据数量
  285. /// </summary>
  286. /// <returns></returns>
  287. public int GetOkHistoryNum()
  288. {
  289. //lock (_lockObj)
  290. _rwLock.EnterReadLock();
  291. var result = historyActiveObjects.Where(o => o.StateCode == 0).ToList().Count;
  292. _rwLock.ExitReadLock();
  293. return result;
  294. }
  295. /// <summary>
  296. /// 获取历史数据中,异常数据数量
  297. /// </summary>
  298. /// <returns></returns>
  299. public int GetNgHistoryNum()
  300. {
  301. //lock (_lockObj)
  302. _rwLock.EnterReadLock();
  303. var result = historyActiveObjects.Where(o => o.StateCode != 0).ToList().Count;
  304. _rwLock.ExitReadLock();
  305. return result;
  306. }
  307. /// <summary>
  308. /// 清除历史数据
  309. /// </summary>
  310. /// <returns></returns>
  311. public bool ClearHistoryActive()
  312. {
  313. //lock (_lockObj)
  314. //{
  315. _rwLock.EnterWriteLock();
  316. historyActiveObjects.Clear();
  317. _rwLock.ExitWriteLock();
  318. return true;
  319. //}
  320. }
  321. /// <summary>
  322. /// 开启识别
  323. /// </summary>
  324. public void StartIdentifyFuntion(int ImaageWidth)
  325. {
  326. UpdateIdentifyImageWidth(ImaageWidth);
  327. InitChannel();
  328. try
  329. {
  330. // 标志位置位true
  331. IsIdentify = true;
  332. // 打开识别线程
  333. IdentifyImageProcessThread = new Thread(IdentifyImageProcess)
  334. {
  335. Priority = ThreadPriority.Highest
  336. };
  337. IdentifyImageProcessThread.Start();
  338. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程启动失败);
  339. }
  340. catch (Exception ex)
  341. {
  342. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程启动失败, "Start thread failed!, " + ex.Message, "DLL:ShuLiClass-StartIdentifyFuntion");
  343. //FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message);
  344. throw;
  345. }
  346. }
  347. /// <summary>
  348. /// 关闭识别
  349. /// </summary>
  350. public void StopIdentifyFuntion()
  351. {
  352. try
  353. {
  354. // 标志位设为false
  355. IsIdentify = false;
  356. if (IdentifyImageProcessThread != null && IdentifyImageProcessThread.IsAlive)
  357. IdentifyImageProcessThread.Join();
  358. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败);
  359. }
  360. catch (Exception ex)
  361. {
  362. //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
  363. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败, "Stop thread failed!, " + ex.Message, "DLL:ShuLiClass-StopIdentifyFuntion");
  364. throw;
  365. }
  366. }
  367. /// <summary>
  368. /// 向识别队列添加一个数据
  369. /// </summary>
  370. /// <param name="items"></param>
  371. public void SetOnceIdentifyImageData(IImage items)
  372. {
  373. IFrameDatas.Enqueue(items.Clone() as IImage);
  374. }
  375. /// <summary>
  376. /// 保存参数
  377. /// </summary>
  378. public void SaveConfig()
  379. {
  380. if (!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\");
  381. XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml");
  382. }
  383. /// <summary>
  384. /// 更新检测宽度信息
  385. /// </summary>
  386. /// <param name="Width"></param>
  387. public void UpdateIdentifyImageWidth(int Width)
  388. {
  389. IdentifyImageWidth = Width;
  390. }
  391. /// <summary>
  392. /// 初始化通道划分
  393. /// </summary>
  394. /// <param name="ImageWidth"></param>
  395. public void InitChannel()
  396. {
  397. _ChannelsRoi.Clear();
  398. shuLiConfig.ImageWidth = IdentifyImageWidth == -1 ? shuLiConfig.ImageWidth : IdentifyImageWidth;
  399. if (shuLiConfig.Channel > 0)
  400. {
  401. if (shuLiConfig.IsIdentifyRoiOpen)
  402. {
  403. ChannelWidth = (shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX) / shuLiConfig.Channel;
  404. }
  405. else
  406. {
  407. ChannelWidth = shuLiConfig.ImageWidth / shuLiConfig.Channel;
  408. }
  409. for (int i = 0; i < shuLiConfig.Channel; i++)
  410. {
  411. _ChannelsRoi.Add(ChannelWidth + i * ChannelWidth);
  412. }
  413. }
  414. }
  415. /// <summary>
  416. /// 获取配置信息
  417. /// </summary>
  418. /// <returns></returns>
  419. public ShuLiConfigClass GetConfigValue()
  420. {
  421. ShuLiConfigClass result = shuLiConfig;
  422. return result;
  423. }
  424. public int GetConfigImageWidth()
  425. {
  426. int result = -1;
  427. if (shuLiConfig != null)
  428. result = shuLiConfig.ImageWidth;
  429. return result;
  430. }
  431. /// <summary>
  432. /// 获取坐标转换系数
  433. /// </summary>
  434. /// <param name="XCoefficient"></param>
  435. /// <param name="YCoefficient"></param>
  436. public void GetXYCoefficient(out double XCoefficient, out double YCoefficient)
  437. {
  438. XCoefficient = this.XCoefficient;
  439. YCoefficient = this.YCoefficient;
  440. }
  441. /// <summary>
  442. /// 设置坐标转换X系数
  443. /// </summary>
  444. /// <param name="XCoefficient"></param>
  445. public void SetXCoefficient(double XCoefficient)
  446. {
  447. this.XCoefficient = XCoefficient;
  448. }
  449. /// <summary>
  450. /// 设置坐标转换Y系数
  451. /// </summary>
  452. public void SetYCoefficient(double YCoefficient)
  453. {
  454. this.YCoefficient = YCoefficient;
  455. }
  456. #endregion
  457. #region 私有方法
  458. /// <summary>
  459. /// 对外通知事件
  460. /// </summary>
  461. private void OnWorkCompleted(List<ActiveObjectClass> activeObject)
  462. {
  463. if(activeObject == null) return;
  464. ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject);
  465. // 触发事件
  466. WorkCompleted?.Invoke(this, activeObjectEventArgs);
  467. }
  468. private bool IsPrintLightOnError = false;
  469. /// <summary>
  470. /// 处理单行像素数据
  471. /// 返回值为false的时候无活跃物体转变为历史物体
  472. /// 返回值为true的时候有活跃物体转变为历史物体
  473. /// </summary>
  474. /// <param name="image">当前行像素数组</param>
  475. private bool ProcessLine(IImage imagedata, int RowNo)
  476. {
  477. //Stopwatch stopwatch = Stopwatch.StartNew();
  478. bool result = false;
  479. // 步骤1:检测当前行的有效区域
  480. var currentRegions = FindValidRegions(imagedata, RowNo);
  481. if (currentRegions.Count == 1)
  482. {
  483. if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Width)
  484. {
  485. if (!IsPrintLightOnError)
  486. {
  487. FaultLog.RecordLogMessage("当前行有效区域为整行,检查视野和光源", 5);
  488. IsPrintLightOnError = true;
  489. }
  490. return false;
  491. }
  492. }
  493. IsPrintLightOnError = false;
  494. //stopwatch.Stop();
  495. //if (stopwatch.ElapsedMilliseconds > 1)
  496. //{
  497. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像连通域检测超时,此次识别耗时:{stopwatch.Elapsed}");
  498. //}
  499. //stopwatch.Restart();
  500. foreach (var region in currentRegions)
  501. {
  502. // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
  503. _rwLock.EnterReadLock();
  504. var matcheds = activeObjects.Where(o =>
  505. IsOverlapping(o, region) &&
  506. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList();
  507. _rwLock.ExitReadLock();
  508. //当有多个可合并的活跃物体时,将多个物体合并
  509. if (matcheds.Count >= 2)
  510. {
  511. // 合并有效区域队列
  512. var CopeRowsData = new List<RowStartEndCol>();
  513. matcheds.ForEach(o => CopeRowsData = CopeRowsData.Concat(o.RowsData).ToList());
  514. // 合并有效区域并保存在新的区域中
  515. var MergeMatched = new ActiveObjectClass
  516. {
  517. MinStartCol = matcheds.Min(o => o.MinStartCol),
  518. MaxEndCol = matcheds.Max(o => o.MaxEndCol),
  519. StartLine = matcheds.Min(o => o.StartLine),
  520. LastSeenLine = matcheds.Max(o => o.LastSeenLine),
  521. LastSeenLineStartCol = matcheds.Min(o => o.LastSeenLineStartCol),
  522. LastSeenLineEndCol = matcheds.Max(o => o.LastSeenLineEndCol),
  523. StartCheckTime = matcheds.Min(o => o.StartCheckTime),
  524. EndCheckTime = matcheds.Max(o => o.EndCheckTime),
  525. Area = matcheds.Sum(o => o.Area),
  526. RowsData = CopeRowsData,
  527. ImageWidth = matcheds.FirstOrDefault().ImageWidth,
  528. StateCode = 8
  529. };
  530. _rwLock.EnterWriteLock();
  531. // 从活跃区域中删除被合并的区域
  532. matcheds.ForEach(o => activeObjects.Remove(o));
  533. // 保存新的区域到活跃区域中
  534. activeObjects.Add(MergeMatched);
  535. _rwLock.ExitWriteLock();
  536. }
  537. _rwLock.EnterReadLock();
  538. // 搜获可用且可合并的活跃区域
  539. var matched = activeObjects.FirstOrDefault(o =>
  540. IsOverlapping(o, region) &&
  541. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP);
  542. _rwLock.ExitReadLock();
  543. if (matched != null)
  544. {
  545. // 合并区域:扩展物体边界并更新状态
  546. matched.MinStartCol = Math.Min(matched.MinStartCol, region.Start);
  547. matched.MaxEndCol = Math.Max(matched.MaxEndCol, region.End);
  548. matched.Area += region.End - region.Start + 1;
  549. matched.LastSeenLine = currentLine;
  550. matched.RowsData.Add(new RowStartEndCol
  551. {
  552. StartCol = region.Start,
  553. EndCol = region.End,
  554. RowsCol = currentLine,
  555. });
  556. matched.LastSeenLineStartCol = region.Start;
  557. matched.LastSeenLineEndCol = region.End;
  558. }
  559. else
  560. {
  561. _rwLock.EnterWriteLock();
  562. // 创建新物体(首次出现的区域)
  563. activeObjects.Add(new ActiveObjectClass
  564. {
  565. MinStartCol = region.Start,
  566. MaxEndCol = region.End,
  567. StartLine = currentLine,
  568. LastSeenLine = currentLine,
  569. LastSeenLineStartCol = region.Start,
  570. LastSeenLineEndCol = region.End,
  571. StartCheckTime = DateTime.Now,
  572. Area = region.End - region.Start + 1,
  573. ImageWidth = IdentifyImageWidth,
  574. RowsData = new List<RowStartEndCol> {
  575. new RowStartEndCol {
  576. StartCol = region.Start,
  577. EndCol = region.End,
  578. RowsCol = currentLine,
  579. }
  580. }
  581. });
  582. _rwLock.ExitWriteLock();
  583. }
  584. }
  585. //stopwatch.Stop();
  586. //if (stopwatch.ElapsedMilliseconds > 1)
  587. //{
  588. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}");
  589. //}
  590. currentRegions.Clear();
  591. return result;
  592. }
  593. List<ValidRegionModelClass> regions = new List<ValidRegionModelClass>();
  594. /// <summary>
  595. /// 检测有效物体区域(横向连续黑色像素段)
  596. /// </summary>
  597. /// <param name="line">当前行像素数组</param>
  598. /// <returns>有效区域列表(起始/结束位置)</returns>
  599. private List<ValidRegionModelClass> FindValidRegions(IImage image, int RowNo)
  600. {
  601. regions.Clear();
  602. int start = -1; // 当前区域起始标记
  603. int end = -1;
  604. // 遍历所有像素列
  605. if (shuLiConfig.IsIdentifyRoiOpen)
  606. {
  607. for (int i = (int)image.Width * RowNo + shuLiConfig.IdentifyStartX; i < (int)image.Width * RowNo + shuLiConfig.IdentifyStopX; i++)
  608. {
  609. if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  610. {
  611. if (start == -1) start = i % (int)image.Width; // 开始新区域
  612. }
  613. else if (start != -1) // 遇到白色像素且存在进行中的区域
  614. {
  615. end = (i - 1) % (int)image.Width;
  616. // 检查区域宽度是否达标
  617. if (end - start >= shuLiConfig.NoiseFilter_Threshold)
  618. {
  619. regions.Add(new ValidRegionModelClass()
  620. {
  621. Start = start,
  622. End = end
  623. }); // 记录有效区域
  624. }
  625. start = -1; // 重置区域标记
  626. end = -1;
  627. }
  628. }
  629. }
  630. else
  631. {
  632. for (int i = (int)image.Width * RowNo; i < (int)image.Width * (RowNo + 1); i++)
  633. {
  634. if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  635. {
  636. if (start == -1) start = i % (int)image.Width; // 开始新区域
  637. }
  638. else if (start != -1) // 遇到白色像素且存在进行中的区域
  639. {
  640. end = (i - 1) % (int)image.Width;
  641. // 检查区域宽度是否达标
  642. if (end - start >= shuLiConfig.NoiseFilter_Threshold)
  643. {
  644. regions.Add(new ValidRegionModelClass()
  645. {
  646. Start = start,
  647. End = end
  648. }); // 记录有效区域
  649. }
  650. start = -1; // 重置区域标记
  651. end = -1;
  652. }
  653. }
  654. }
  655. // 处理行尾未闭合的区域
  656. if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold)
  657. {
  658. regions.Add(new ValidRegionModelClass()
  659. {
  660. Start = start,
  661. End = (int)image.Width - 1
  662. });
  663. }
  664. return regions;
  665. }
  666. /// <summary>
  667. /// 判断区域重叠(与活跃物体的横向坐标重叠检测)
  668. /// </summary>
  669. /// <param name="obj">活跃物体</param>
  670. /// <param name="region">当前区域</param>
  671. /// <returns>是否发生重叠</returns>
  672. private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass region)
  673. {
  674. // 判断区域是否不相交的逆条件
  675. return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol);
  676. }
  677. /// <summary>
  678. /// 通道区域判定
  679. /// </summary>
  680. /// <param name="activeObject"></param>
  681. /// <returns></returns>
  682. private int ActiveChannel(ActiveObjectClass activeObject)
  683. {
  684. int result = -1;
  685. int StartChannel = activeObject.MinStartCol / ChannelWidth;
  686. int EndChannel = activeObject.MaxEndCol / ChannelWidth;
  687. if (StartChannel == EndChannel)
  688. {
  689. result = StartChannel;
  690. }
  691. else if (EndChannel - StartChannel > 1)
  692. {
  693. Console.WriteLine("ActiveChannel-Error");
  694. //error
  695. }
  696. else
  697. {
  698. result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel;
  699. }
  700. return result;
  701. }
  702. /// <summary>
  703. /// 获取结果最长长边
  704. /// </summary>
  705. /// <param name="Rows"></param>
  706. /// <returns></returns>
  707. public BoundingRectangleMdoel SizeCalculation(List<RowStartEndCol> Rows)
  708. {
  709. //Stopwatch stopwatch = Stopwatch.StartNew();
  710. ConvexResultClass points = ConvexHullWithConcavityDetection(Rows);
  711. //stopwatch.Stop();
  712. //Console.WriteLine($"凸包计算耗时:{stopwatch.Elapsed}");
  713. //var test = CalculateMinimumBoundingRectangle(points);
  714. var result = CoefficientCalculateMinimumBoundingRectangle(points.convexHull);
  715. result.hasSignificantConcavity = points.hasSignificantConcavity;
  716. result.concavityRatio = points.concavityRatio;
  717. return result;
  718. }
  719. /// <summary>
  720. /// 凸包点集合获取
  721. /// </summary>
  722. /// <param name="Rows"></param>
  723. /// <returns></returns>
  724. public List<Point> ConvexHull(List<RowStartEndCol> Rows)
  725. {
  726. List<Point> points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
  727. points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
  728. points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
  729. var lower = new List<Point>();
  730. foreach (var p in points)
  731. {
  732. while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
  733. lower.RemoveAt(lower.Count - 1);
  734. lower.Add(p);
  735. }
  736. var upper = new List<Point>();
  737. for (int i = points.Count - 1; i >= 0; i--)
  738. {
  739. var p = points[i];
  740. while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
  741. upper.RemoveAt(upper.Count - 1);
  742. upper.Add(p);
  743. }
  744. lower.RemoveAt(lower.Count - 1);
  745. upper.RemoveAt(upper.Count - 1);
  746. lower.AddRange(upper);
  747. return lower;
  748. }
  749. /// <summary>
  750. /// 凸包点集合获取,输出原点集
  751. /// </summary>
  752. /// <param name="Rows"></param>
  753. /// <returns></returns>
  754. public List<Point> ConvexHull(List<RowStartEndCol> Rows,out List<Point> points)
  755. {
  756. points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
  757. points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
  758. points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
  759. var lower = new List<Point>();
  760. foreach (var p in points)
  761. {
  762. while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
  763. lower.RemoveAt(lower.Count - 1);
  764. lower.Add(p);
  765. }
  766. var upper = new List<Point>();
  767. for (int i = points.Count - 1; i >= 0; i--)
  768. {
  769. var p = points[i];
  770. while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
  771. upper.RemoveAt(upper.Count - 1);
  772. upper.Add(p);
  773. }
  774. lower.RemoveAt(lower.Count - 1);
  775. upper.RemoveAt(upper.Count - 1);
  776. lower.AddRange(upper);
  777. return lower;
  778. }
  779. /// <summary>
  780. /// 凸包点集合获取,同时检测较大的内凹
  781. /// </summary>
  782. /// <param name="Rows"></param>
  783. /// <returns>返回凸包点集</returns>
  784. public ConvexResultClass ConvexHullWithConcavityDetection(List<RowStartEndCol> Rows)
  785. {
  786. ConvexResultClass result = null;
  787. // 构建原始点集
  788. var lower = ConvexHull(Rows, out List<Point> originalPoints);
  789. // 检测内凹
  790. ConvexResultClass ConvexResult = DetectConcavity(originalPoints, lower);
  791. return result = new ConvexResultClass()
  792. {
  793. convexHull = lower,
  794. hasSignificantConcavity = ConvexResult.hasSignificantConcavity,
  795. concavityRatio = ConvexResult.concavityRatio,
  796. };
  797. }
  798. /// <summary>
  799. /// 检测是否存在显著的内凹
  800. /// </summary>
  801. /// <param name="originalPoints">原始点集</param>
  802. /// <param name="convexHull">凸包点集</param>
  803. /// <returns>是否存在显著内凹和程度比值</returns>
  804. private ConvexResultClass DetectConcavity(List<Point> originalPoints, List<Point> convexHull)
  805. {
  806. if (convexHull.Count < 3) return new ConvexResultClass()
  807. {
  808. hasSignificantConcavity = false,
  809. concavityRatio = 0,
  810. };
  811. // 检查原始点集中有多少点在凸包内部(即非凸包顶点)
  812. HashSet<Point> hullPoints = new HashSet<Point>(convexHull);
  813. var internalPoints = originalPoints.Where(p => !hullPoints.Contains(p)).ToList();
  814. if (internalPoints.Count == 0)
  815. {
  816. // 所有点都在凸包上,没有内凹
  817. return new ConvexResultClass()
  818. {
  819. hasSignificantConcavity = false,
  820. concavityRatio = 0,
  821. };
  822. }
  823. // 计算凸包面积
  824. double convexHullArea = CalculatePolygonArea(convexHull);
  825. double PointsArea = CalculatePolygonArea(SortPointsByNearestNeighbor(originalPoints));
  826. var concavityMeasure = (convexHullArea - PointsArea) / convexHullArea;
  827. // 判断是否存在显著内凹(可以根据实际需求调整阈值)
  828. bool hasSignificantConcavity = concavityMeasure > 0.3;
  829. return new ConvexResultClass()
  830. {
  831. hasSignificantConcavity = hasSignificantConcavity,
  832. concavityRatio = concavityMeasure,
  833. };
  834. }
  835. /// <summary>
  836. /// 计算封闭多边形的面积(鞋带公式)
  837. /// </summary>
  838. /// <param name="polygon">多边形顶点(必须是封闭的)</param>
  839. /// <returns>面积</returns>
  840. private double CalculatePolygonArea(List<Point> polygon)
  841. {
  842. if (polygon.Count < 3) return 0;
  843. // 确保多边形是封闭的
  844. var closedPolygon = new List<Point>(polygon);
  845. if (closedPolygon.First() != closedPolygon.Last())
  846. {
  847. closedPolygon.Add(closedPolygon[0]);
  848. }
  849. double area = 0;
  850. for (int i = 0; i < closedPolygon.Count - 1; i++)
  851. {
  852. area += (double)closedPolygon[i].X * closedPolygon[i + 1].Y;
  853. area -= (double)closedPolygon[i].Y * closedPolygon[i + 1].X;
  854. }
  855. return Math.Abs(area) / 2.0;
  856. }
  857. /// <summary>
  858. /// 凸包最长边,结果通过系数计算
  859. /// </summary>
  860. /// <param name="hull"></param>
  861. /// <returns></returns>
  862. public MaxLengthModel CoefficientRotatingCalipers(List<Point> hull)
  863. {
  864. //老方法
  865. MaxLengthModel result = new MaxLengthModel();
  866. int n = hull.Count;
  867. if (n == 1)
  868. {
  869. result = null;
  870. return result;
  871. }
  872. if (n == 2)
  873. {
  874. result.MaxLength = Distance(hull[0], hull[1]);
  875. result.Point1Index = 0;
  876. result.Point1 = hull[0];
  877. result.Point2Index = 1;
  878. result.Point2 = hull[1];
  879. return result;
  880. }
  881. double maxDist = 0;
  882. int pointIndex1 = 0;
  883. int pointIndex2 = 0;
  884. for (int i = 0; i < n; i++)
  885. {
  886. for (int j = i + 1; j < n; j++)
  887. {
  888. if (CoefficientDistance(hull[i], hull[j]) > maxDist)
  889. {
  890. maxDist = CoefficientDistance(hull[i], hull[j]);
  891. pointIndex1 = i;
  892. pointIndex2 = j;
  893. }
  894. }
  895. }
  896. result.Point1 = hull[pointIndex1];
  897. result.Point1Index = pointIndex1;
  898. result.Point2 = hull[pointIndex2];
  899. result.Point2Index = pointIndex2;
  900. result.MaxLength = maxDist;
  901. return result;
  902. }
  903. /// <summary>
  904. /// 凸包最长边
  905. /// </summary>
  906. /// <param name="hull"></param>
  907. /// <returns></returns>
  908. public MaxLengthModel RotatingCalipers(List<Point> hull)
  909. {
  910. //老方法
  911. MaxLengthModel result = new MaxLengthModel();
  912. int n = hull.Count;
  913. if (n == 1)
  914. {
  915. result = null;
  916. return result;
  917. }
  918. if (n == 2)
  919. {
  920. result.MaxLength = Distance(hull[0], hull[1]);
  921. result.Point1Index = 0;
  922. result.Point1 = hull[0];
  923. result.Point2Index = 1;
  924. result.Point2 = hull[1];
  925. return result;
  926. }
  927. double maxDist = 0;
  928. int pointIndex1 = 0;
  929. int pointIndex2 = 0;
  930. for (int i = 0; i < n; i++)
  931. {
  932. for (int j = i + 1; j < n; j++)
  933. {
  934. if (Distance(hull[i], hull[j]) > maxDist)
  935. {
  936. maxDist = Distance(hull[i], hull[j]);
  937. pointIndex1 = i;
  938. pointIndex2 = j;
  939. }
  940. }
  941. }
  942. result.Point1 = hull[pointIndex1];
  943. result.Point1Index = pointIndex1;
  944. result.Point2 = hull[pointIndex2];
  945. result.Point2Index = pointIndex2;
  946. result.MaxLength = Distance(result.Point1, result.Point2);
  947. return result;
  948. }
  949. /// <summary>
  950. /// 计算凸包的最小外接矩形
  951. /// </summary>
  952. /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
  953. /// <returns>最小外接矩形的四个顶点</returns>
  954. public BoundingRectangleMdoel CalculateMinimumBoundingRectangle(List<Point> convexHull)
  955. {
  956. BoundingRectangleMdoel result = new BoundingRectangleMdoel();
  957. if (convexHull == null || convexHull.Count < 3)
  958. return null;
  959. double minArea = double.MaxValue;
  960. int n = convexHull.Count;
  961. // 遍历每一条边作为基准边
  962. for (int i = 0; i < n; i++)
  963. {
  964. Point edgeStart = convexHull[i];
  965. Point edgeEnd = convexHull[(i + 1) % n];
  966. // 计算当前边的角度
  967. double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
  968. // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
  969. var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
  970. // 找到包围盒
  971. int minX = rotatedPoints.Min(p => p.X);
  972. int maxX = rotatedPoints.Max(p => p.X);
  973. int minY = rotatedPoints.Min(p => p.Y);
  974. int maxY = rotatedPoints.Max(p => p.Y);
  975. // 计算面积
  976. double area = (maxX - minX) * (maxY - minY);
  977. // 如果面积更小,则更新最小矩形
  978. if (area < minArea)
  979. {
  980. minArea = area;
  981. // 构造矩形的四个顶点并旋转回原来的角度
  982. var rectangle = new List<Point>
  983. {
  984. RotatePoint(new Point(minX, minY), angle),
  985. RotatePoint(new Point(maxX, minY), angle),
  986. RotatePoint(new Point(maxX, maxY), angle),
  987. RotatePoint(new Point(minX, maxY), angle)
  988. };
  989. result.points = rectangle;
  990. result.Angle = angle;
  991. result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
  992. }
  993. }
  994. double l1 = Distance(result.points[0], result.points[1]);
  995. double l2 = Distance(result.points[1], result.points[2]);
  996. if (l1 < l2)
  997. {
  998. result.WidthPoints = new List<Point>
  999. {
  1000. result.points[0],
  1001. result.points[1],
  1002. };
  1003. result.HeightPoints = new List<Point>
  1004. {
  1005. result.points[1],
  1006. result.points[2],
  1007. };
  1008. }
  1009. else
  1010. {
  1011. result.WidthPoints = new List<Point>
  1012. {
  1013. result.points[2],
  1014. result.points[1],
  1015. };
  1016. result.HeightPoints = new List<Point>
  1017. {
  1018. result.points[1],
  1019. result.points[0],
  1020. };
  1021. }
  1022. result.Height = Math.Max(l1, l2);
  1023. result.Width = Math.Min(l1, l2);
  1024. return result;
  1025. }
  1026. /// <summary>
  1027. /// 计算凸包的最小外接矩形
  1028. /// </summary>
  1029. /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
  1030. /// <returns>最小外接矩形的四个顶点</returns>
  1031. public BoundingRectangleMdoel CoefficientCalculateMinimumBoundingRectangle(List<Point> convexHull)
  1032. {
  1033. BoundingRectangleMdoel result = new BoundingRectangleMdoel();
  1034. if (convexHull == null || convexHull.Count < 3)
  1035. return null;
  1036. double minArea = double.MaxValue;
  1037. int n = convexHull.Count;
  1038. // 遍历每一条边作为基准边
  1039. for (int i = 0; i < n; i++)
  1040. {
  1041. Point edgeStart = convexHull[i];
  1042. Point edgeEnd = convexHull[(i + 1) % n];
  1043. // 计算当前边的角度
  1044. double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
  1045. // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
  1046. var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
  1047. // 找到包围盒
  1048. int minX = rotatedPoints.Min(p => p.X);
  1049. int maxX = rotatedPoints.Max(p => p.X);
  1050. int minY = rotatedPoints.Min(p => p.Y);
  1051. int maxY = rotatedPoints.Max(p => p.Y);
  1052. // 计算面积
  1053. double area = (maxX - minX) * (maxY - minY);
  1054. // 如果面积更小,则更新最小矩形
  1055. if (area < minArea)
  1056. {
  1057. minArea = area;
  1058. // 构造矩形的四个顶点并旋转回原来的角度
  1059. var rectangle = new List<Point>
  1060. {
  1061. RotatePoint(new Point(minX, minY), angle),
  1062. RotatePoint(new Point(maxX, minY), angle),
  1063. RotatePoint(new Point(maxX, maxY), angle),
  1064. RotatePoint(new Point(minX, maxY), angle)
  1065. };
  1066. result.points = rectangle;
  1067. result.Angle = angle;
  1068. result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
  1069. }
  1070. }
  1071. double l1 = CoefficientDistance(result.points[0], result.points[1]);
  1072. double l2 = CoefficientDistance(result.points[1], result.points[2]);
  1073. if (l1 < l2)
  1074. {
  1075. result.WidthPoints = new List<Point>
  1076. {
  1077. result.points[0],
  1078. result.points[1],
  1079. };
  1080. result.HeightPoints = new List<Point>
  1081. {
  1082. result.points[1],
  1083. result.points[2],
  1084. };
  1085. }
  1086. else
  1087. {
  1088. result.WidthPoints = new List<Point>
  1089. {
  1090. result.points[2],
  1091. result.points[1],
  1092. };
  1093. result.HeightPoints = new List<Point>
  1094. {
  1095. result.points[1],
  1096. result.points[0],
  1097. };
  1098. }
  1099. result.Height = Math.Max(l1, l2);
  1100. result.Width = Math.Min(l1, l2);
  1101. return result;
  1102. }
  1103. public List<Point> SortPointsByNearestNeighbor(List<Point> points)
  1104. {
  1105. if (points.Count <= 1) return points;
  1106. var sorted = new List<Point>();
  1107. var remaining = new HashSet<Point>(points);
  1108. // 选择起始点(例如最左边的点)
  1109. var current = remaining.OrderBy(p => p.X).First();
  1110. sorted.Add(current);
  1111. remaining.Remove(current);
  1112. while (remaining.Any())
  1113. {
  1114. // 找到最近的点
  1115. var nearest = remaining.OrderBy(p => Distance(current, p)).First();
  1116. sorted.Add(nearest);
  1117. remaining.Remove(nearest);
  1118. current = nearest;
  1119. }
  1120. return sorted;
  1121. }
  1122. /// <summary>
  1123. /// 绕原点旋转点
  1124. /// </summary>
  1125. /// <param name="point">待旋转的点</param>
  1126. /// <param name="angle">旋转角度(弧度)</param>
  1127. /// <returns>旋转后的点</returns>
  1128. private static Point RotatePoint(Point point, double angle)
  1129. {
  1130. double cos = Math.Cos(angle);
  1131. double sin = Math.Sin(angle);
  1132. return new Point(
  1133. (int)(point.X * cos - point.Y * sin),
  1134. (int)(point.X * sin + point.Y * cos)
  1135. );
  1136. }
  1137. /// <summary>
  1138. /// 计算点到线段的距离
  1139. /// </summary>
  1140. /// <param name="a">线段点1</param>
  1141. /// <param name="b">线段点2</param>
  1142. /// <param name="c">点三</param>
  1143. /// <returns></returns>
  1144. private double DistanceToLine(Point a, Point b, Point c)
  1145. {
  1146. double area = Math.Abs(Area2(a, b, c));
  1147. double baseLength = Distance(a, b);
  1148. return area / baseLength;
  1149. }
  1150. // 计算向量叉积
  1151. private int Cross(Point o, Point a, Point b) =>
  1152. (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X);
  1153. // 计算三角形面积的两倍
  1154. private int Area2(Point a, Point b, Point c) =>
  1155. (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
  1156. // 计算两点间距离
  1157. private double Distance(Point a, Point b)
  1158. {
  1159. int dx = a.X - b.X;
  1160. int dy = a.Y - b.Y;
  1161. return Math.Sqrt((dx * dx) + (dy * dy));
  1162. }
  1163. private double CoefficientDistance(Point a,Point b)
  1164. {
  1165. double dx = (a.X - b.X) * XCoefficient;
  1166. double dy = (a.Y - b.Y) * YCoefficient;
  1167. return Math.Sqrt((dx * dx) + (dy * dy));
  1168. }
  1169. private bool TryAdd(List<ActiveObjectClass> list, ActiveObjectClass item, int maxSize)
  1170. {
  1171. list.Add(item);
  1172. if (list.Count > maxSize)
  1173. {
  1174. list[list.Count - maxSize].RowsData.Clear();
  1175. }
  1176. return true;
  1177. }
  1178. #endregion
  1179. #region 线程方法
  1180. /// <summary>
  1181. /// 识别图像线程
  1182. /// </summary>
  1183. private void IdentifyImageProcess()
  1184. {
  1185. Stopwatch stopwatch = Stopwatch.StartNew();
  1186. while (IsIdentify)
  1187. {
  1188. //判断队列中是否有数据
  1189. if (IFrameDatas.Count() > 0)
  1190. {
  1191. stopwatch.Restart();
  1192. if (IFrameDatas.Count() > 5)
  1193. //SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积, $"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}", "DLL:ShuLIClass-IdentifyImageProcess");
  1194. FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
  1195. //else
  1196. //SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
  1197. IFrameDatas.TryDequeue(out IImage IframeData);
  1198. stopwatch.Stop();
  1199. if (stopwatch.ElapsedMilliseconds > 5)
  1200. {
  1201. FaultLog.RecordErrorMessage($"ShuLiClass-IdentifyImageProcess:图像读取超时,此次识别耗时:{stopwatch.Elapsed}");
  1202. }
  1203. stopwatch.Restart();
  1204. //是否成功取得数据
  1205. if (IframeData != null)
  1206. {
  1207. //识别
  1208. ProcessImageSequence(IframeData);
  1209. }
  1210. else
  1211. {
  1212. Console.WriteLine("识别数据为空");
  1213. continue;
  1214. }
  1215. //输出耗时
  1216. stopwatch.Stop();
  1217. if(stopwatch.ElapsedMilliseconds > 5)
  1218. {
  1219. FaultLog.RecordErrorMessage($"ShuLiClass-IdentifyImageProcess:图像识别超时,此次识别耗时:{stopwatch.Elapsed}");
  1220. }
  1221. //Console.WriteLine($"识别线程识别一张图片耗时:{stopwatch.Elapsed},待识别队列剩余数量{IFrameDatas.Count()}");
  1222. }
  1223. else
  1224. {
  1225. Thread.Sleep(1);
  1226. }
  1227. }
  1228. }
  1229. #endregion
  1230. }
  1231. }