ShuLiClass.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. using CCDCount.MODEL.ConfigModel;
  2. using CCDCount.MODEL.ShuLiModel;
  3. using LogClass;
  4. using MvCameraControl;
  5. using System;
  6. using System.Collections.Concurrent;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.Drawing;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Runtime.InteropServices.WindowsRuntime;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. namespace CCDCount.DLL
  16. {
  17. public class ShuLiClass
  18. {
  19. #region 变量
  20. /// <summary>
  21. /// 当活跃物体转变为历史物体时的回调事件
  22. /// </summary>
  23. public event EventHandler<ActiveObjectEventArgsClass> WorkCompleted;
  24. private List<ActiveObjectClass> activeObjects = new List<ActiveObjectClass>(); // 当前跟踪中的物体
  25. private List<ActiveObjectClass> historyActiveObjects = new List<ActiveObjectClass>(); // 历史物体
  26. private ConcurrentQueue<IImage> IFrameDatas = new ConcurrentQueue<IImage>(); //图像数据队列
  27. private Thread IdentifyImageProcessThread = null; // 识别线程
  28. private bool IsIdentify = false; //线程是否开始识别的标志
  29. private long currentLine = 0; //行数记录
  30. private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件
  31. public List<int> ChannelsRoi { get { return _ChannelsRoi; } }
  32. private List<int> _ChannelsRoi = new List<int>();
  33. private int ChannelWidth = 0;//每个区域的宽度
  34. private int IdentifyImageWidth = -1;
  35. private static readonly object _lockObj = new object(); // 专用锁对象\
  36. private static readonly object taskLock = new object(); // 异步专用锁
  37. private int ObjectNum = 0;
  38. public int ImageNum { get { return IFrameDatas.Count; } }
  39. #endregion
  40. #region 公共方法
  41. /// <summary>
  42. /// 初始化构造方法
  43. /// </summary>
  44. public ShuLiClass()
  45. {
  46. // 加载默认参数
  47. shuLiConfig = new ShuLiConfigClass()
  48. {
  49. Channel = 8,
  50. PandingCode = 2
  51. };
  52. }
  53. public ShuLiClass(ShuLiConfigClass config)
  54. {
  55. if (config.IsLoadCanfig)
  56. {
  57. // 加载传出的参数
  58. shuLiConfig = config;
  59. }
  60. else
  61. {
  62. // 加载默认参数
  63. shuLiConfig = new ShuLiConfigClass()
  64. {
  65. Channel = 8,
  66. PandingCode = 2
  67. };
  68. }
  69. InitChannel();
  70. }
  71. public long InitCurrentLine(int line)
  72. {
  73. currentLine = line;
  74. return currentLine;
  75. }
  76. public int InitNum(int num)
  77. {
  78. ObjectNum = num;
  79. return ObjectNum;
  80. }
  81. /// <summary>
  82. /// 处理图像序列的主入口
  83. /// </summary>
  84. /// <param name="image">图像像素数据</param>
  85. /// <param name="ImageWidth">图像宽</param>
  86. /// <param name="currentLine">当前行数</param>
  87. /// <returns>检测到的物体总数</returns>
  88. public bool ProcessImageSequence(IImage image)
  89. {
  90. bool result = false;
  91. for (int i = 0; i < image.Height; i++)
  92. {
  93. result = ProcessLine(image, i);
  94. currentLine += 1;
  95. }
  96. //识别到结果并输出
  97. lock (_lockObj)
  98. {
  99. // 清理超时未更新的物体
  100. var lostObjects = activeObjects
  101. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  102. .ToList();
  103. List<ActiveObjectClass> OneActive = new List<ActiveObjectClass>();
  104. // 有物体转变为活跃物体,返回值转为true
  105. if (lostObjects.Count() > 0)
  106. {
  107. result = true;
  108. foreach (var item in lostObjects)
  109. {
  110. //噪点判定
  111. if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  112. {
  113. item.StateCode = 9;
  114. //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6);
  115. continue;
  116. }
  117. //转为历史物体,添加缺少的参数
  118. item.Num = ObjectNum += 1;
  119. item.ChannelNO = ActiveChannel(item);
  120. item.EndCheckTime = DateTime.Now;
  121. OneActive.Add(item);
  122. item.MaxLength = GetActionMaxLength(item.RowsData);
  123. if ((item.LastSeenLine - item.StartLine) > shuLiConfig.MAX_Idetify_Height)
  124. {
  125. item.StateCode = 7;
  126. FaultLog.RecordLogMessage("ShuLiClass-ProcessLine:非颗粒,视野异常", 3);
  127. LOG.log(string.Format("ShuLiClass-ProcessLine:非颗粒,视野异常"), 6);
  128. Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常");
  129. }
  130. else if (shuLiConfig.PandingCode != -1)
  131. {
  132. if (item.StateCode != -1)
  133. {
  134. if (item.StateCode == 8)
  135. {
  136. LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  137. }
  138. }
  139. else if (item.Area < shuLiConfig.MinArea
  140. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  141. {
  142. item.StateCode = 5;
  143. LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num));
  144. Console.WriteLine("颗粒编号{0}:面积过小", item.Num);
  145. }
  146. else if (item.Area > shuLiConfig.MaxArea
  147. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  148. {
  149. item.StateCode = 6;
  150. LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  151. Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
  152. }
  153. else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH
  154. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  155. {
  156. item.StateCode = 2;
  157. LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  158. Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
  159. }
  160. else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH
  161. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  162. {
  163. item.StateCode = 1;
  164. LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  165. Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
  166. }
  167. else
  168. {
  169. item.StateCode = 0;
  170. LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  171. Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
  172. }
  173. }
  174. }
  175. if (OneActive.Count > 0)
  176. {
  177. LOG.log(string.Format("识别完成,首个颗粒编号:{0},颗粒数量:{1}", OneActive[0].Num, OneActive.Count), 6);
  178. //触发回调事件
  179. Task.Run(() =>
  180. {
  181. OnWorkCompleted(OneActive);
  182. });
  183. }
  184. }
  185. else
  186. {
  187. OneActive = null;
  188. }
  189. // 累加到总数并从活跃物体转移到历史物体
  190. lostObjects.Where(o => o.Area >= shuLiConfig.NoiseFilter_Threshold && o.StateCode != 7 && o.StateCode != 9).ToList().ForEach(o => TryAdd(historyActiveObjects, o, 2500));
  191. lostObjects.ForEach(o => activeObjects.Remove(o));
  192. }
  193. return result;
  194. }
  195. /// <summary>
  196. /// 返回最后一个历史物品
  197. /// </summary>
  198. /// <returns></returns>
  199. public ActiveObjectClass GetLastActive()
  200. {
  201. if (historyActiveObjects.Count() == 0)
  202. return null;
  203. return historyActiveObjects.Last();
  204. }
  205. /// <summary>
  206. /// 返回历史物品
  207. /// </summary>
  208. /// <returns></returns>
  209. public List<ActiveObjectClass> GetHistoryActive()
  210. {
  211. lock (_lockObj) // 加锁
  212. {
  213. return historyActiveObjects.ToList();
  214. }
  215. }
  216. /// <summary>
  217. /// 返回缓存在内存的历史物品的总数量
  218. /// </summary>
  219. /// <returns></returns>
  220. public int GetHistoryActiveNum()
  221. {
  222. lock (_lockObj) // 加锁
  223. return historyActiveObjects.Count();
  224. }
  225. /// <summary>
  226. /// 获取历史数据中,正常数据数量
  227. /// </summary>
  228. /// <returns></returns>
  229. public int GetOkHistoryNum()
  230. {
  231. lock (_lockObj)
  232. return historyActiveObjects.Where(o => o.StateCode == 0).Count();
  233. }
  234. /// <summary>
  235. /// 获取历史数据中,异常数据数量
  236. /// </summary>
  237. /// <returns></returns>
  238. public int GetNgHistoryNum()
  239. {
  240. lock (_lockObj)
  241. return historyActiveObjects.Where(o => o.StateCode != 0).Count();
  242. }
  243. /// <summary>
  244. /// 清除历史数据
  245. /// </summary>
  246. /// <returns></returns>
  247. public bool ClearHistoryActive()
  248. {
  249. lock (_lockObj)
  250. {
  251. historyActiveObjects.Clear();
  252. return true;
  253. }
  254. }
  255. /// <summary>
  256. /// 开启识别
  257. /// </summary>
  258. public void StartIdentifyFuntion(int ImaageWidth)
  259. {
  260. UpdateIdentifyImageWidth(ImaageWidth);
  261. InitChannel();
  262. try
  263. {
  264. // 标志位置位true
  265. IsIdentify = true;
  266. // 打开识别线程
  267. IdentifyImageProcessThread = new Thread(IdentifyImageProcess)
  268. {
  269. Priority = ThreadPriority.Highest
  270. };
  271. IdentifyImageProcessThread.Start();
  272. }
  273. catch (Exception ex)
  274. {
  275. FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message);
  276. throw;
  277. }
  278. }
  279. /// <summary>
  280. /// 关闭识别
  281. /// </summary>
  282. public void StopIdentifyFuntion()
  283. {
  284. try
  285. {
  286. // 标志位设为false
  287. IsIdentify = false;
  288. if (IdentifyImageProcessThread != null && IdentifyImageProcessThread.IsAlive)
  289. IdentifyImageProcessThread.Join();
  290. }
  291. catch (Exception ex)
  292. {
  293. FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
  294. throw;
  295. }
  296. }
  297. /// <summary>
  298. /// 向识别队列添加一个数据
  299. /// </summary>
  300. /// <param name="items"></param>
  301. public void SetOnceIdentifyImageData(IImage items)
  302. {
  303. IFrameDatas.Enqueue(items.Clone() as IImage);
  304. }
  305. /// <summary>
  306. /// 保存参数
  307. /// </summary>
  308. public void SaveConfig()
  309. {
  310. if (!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\");
  311. XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml");
  312. }
  313. /// <summary>
  314. /// 更新检测宽度信息
  315. /// </summary>
  316. /// <param name="Width"></param>
  317. public void UpdateIdentifyImageWidth(int Width)
  318. {
  319. IdentifyImageWidth = Width;
  320. }
  321. /// <summary>
  322. /// 初始化通道划分
  323. /// </summary>
  324. /// <param name="ImageWidth"></param>
  325. public void InitChannel()
  326. {
  327. _ChannelsRoi.Clear();
  328. shuLiConfig.ImageWidth = IdentifyImageWidth == -1 ? shuLiConfig.ImageWidth : IdentifyImageWidth;
  329. if (shuLiConfig.Channel > 0)
  330. {
  331. if (shuLiConfig.IsIdentifyRoiOpen)
  332. {
  333. ChannelWidth = (shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX) / shuLiConfig.Channel;
  334. }
  335. else
  336. {
  337. ChannelWidth = shuLiConfig.ImageWidth / shuLiConfig.Channel;
  338. }
  339. for (int i = 0; i < shuLiConfig.Channel; i++)
  340. {
  341. _ChannelsRoi.Add(ChannelWidth + i * ChannelWidth);
  342. }
  343. }
  344. }
  345. /// <summary>
  346. /// 获取配置信息
  347. /// </summary>
  348. /// <returns></returns>
  349. public ShuLiConfigClass GetConfigValue()
  350. {
  351. ShuLiConfigClass result = shuLiConfig;
  352. return result;
  353. }
  354. public int GetConfigImageWidth()
  355. {
  356. int result = -1;
  357. if (shuLiConfig != null)
  358. result = shuLiConfig.ImageWidth;
  359. return result;
  360. }
  361. #endregion
  362. #region 私有方法
  363. /// <summary>
  364. /// 对外通知事件
  365. /// </summary>
  366. private void OnWorkCompleted(List<ActiveObjectClass> activeObject)
  367. {
  368. ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject);
  369. // 触发事件
  370. WorkCompleted?.Invoke(this, activeObjectEventArgs);
  371. }
  372. private bool IsPrintLightOnError = false;
  373. /// <summary>
  374. /// 处理单行像素数据
  375. /// 返回值为false的时候无活跃物体转变为历史物体
  376. /// 返回值为true的时候有活跃物体转变为历史物体
  377. /// </summary>
  378. /// <param name="image">当前行像素数组</param>
  379. private bool ProcessLine(IImage imagedata, int RowNo)
  380. {
  381. bool result = false;
  382. // 步骤1:检测当前行的有效区域
  383. var currentRegions = FindValidRegions(imagedata, RowNo);
  384. if (currentRegions.Count == 1)
  385. {
  386. if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Width)
  387. {
  388. if (!IsPrintLightOnError)
  389. {
  390. FaultLog.RecordLogMessage("当前行有效区域为整行,检查视野和光源", 5);
  391. IsPrintLightOnError = true;
  392. }
  393. return false;
  394. }
  395. IsPrintLightOnError = false;
  396. }
  397. MergeCurrentRegions(currentRegions);
  398. currentRegions.Clear();
  399. // 更新有效物体的最后一行的起始点
  400. activeObjects.Where(o => o.LastSeenLine == currentLine).ToList().ForEach(o => o.LastSeenLineStartCol = o.RowsData.Where(p => p.RowsCol == currentLine).Min(p => p.StartCol));
  401. activeObjects.Where(o => o.LastSeenLine == currentLine).ToList().ForEach(o => o.LastSeenLineEndCol = o.RowsData.Where(p => p.RowsCol == currentLine).Max(p => p.EndCol));
  402. return result;
  403. }
  404. /// <summary>
  405. /// 检测有效物体区域(横向连续黑色像素段)
  406. /// </summary>
  407. /// <param name="line">当前行像素数组</param>
  408. /// <returns>有效区域列表(起始/结束位置)</returns>
  409. private List<ValidRegionModelClass> FindValidRegions(IImage image, int RowNo)
  410. {
  411. List<ValidRegionModelClass> regions = new List<ValidRegionModelClass>();
  412. int start = -1; // 当前区域起始标记
  413. // 遍历所有像素列
  414. if (shuLiConfig.IsIdentifyRoiOpen)
  415. {
  416. for (int i = (int)image.Width * RowNo + shuLiConfig.IdentifyStartX; i < (int)image.Width * RowNo + shuLiConfig.IdentifyStopX; i++)
  417. {
  418. if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  419. {
  420. if (start == -1) start = i % (int)image.Width; // 开始新区域
  421. }
  422. else if (start != -1) // 遇到白色像素且存在进行中的区域
  423. {
  424. // 检查区域宽度是否达标
  425. if (i - start >= shuLiConfig.MIN_OBJECT_WIDTH)
  426. {
  427. regions.Add(new ValidRegionModelClass()
  428. {
  429. Start = start,
  430. End = (i - 1) % (int)image.Width
  431. }); // 记录有效区域
  432. }
  433. start = -1; // 重置区域标记
  434. }
  435. }
  436. }
  437. else
  438. {
  439. for (int i = (int)image.Width * RowNo; i < (int)image.Width * (RowNo + 1); i++)
  440. {
  441. if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  442. {
  443. if (start == -1) start = i % (int)image.Width; // 开始新区域
  444. }
  445. else if (start != -1) // 遇到白色像素且存在进行中的区域
  446. {
  447. // 检查区域宽度是否达标
  448. if (i - start >= shuLiConfig.MIN_OBJECT_WIDTH)
  449. {
  450. regions.Add(new ValidRegionModelClass()
  451. {
  452. Start = start,
  453. End = (i - 1) % (int)image.Width
  454. }); // 记录有效区域
  455. }
  456. start = -1; // 重置区域标记
  457. }
  458. }
  459. }
  460. // 处理行尾未闭合的区域
  461. if (start != -1 && image.Width - start >= shuLiConfig.MIN_OBJECT_WIDTH)
  462. {
  463. regions.Add(new ValidRegionModelClass() {
  464. Start = start,
  465. End = (int)image.Width - 1
  466. });
  467. }
  468. return regions;
  469. }
  470. /// <summary>
  471. /// 判断区域重叠(与活跃物体的横向坐标重叠检测)
  472. /// </summary>
  473. /// <param name="obj">活跃物体</param>
  474. /// <param name="region">当前区域</param>
  475. /// <returns>是否发生重叠</returns>
  476. private bool IsOverlapping(ActiveObjectClass obj,ValidRegionModelClass region)
  477. {
  478. // 判断区域是否不相交的逆条件
  479. return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol);
  480. }
  481. /// <summary>
  482. /// 通道区域判定
  483. /// </summary>
  484. /// <param name="activeObject"></param>
  485. /// <returns></returns>
  486. private int ActiveChannel(ActiveObjectClass activeObject)
  487. {
  488. int result = -1;
  489. int StartChannel = activeObject.MinStartCol / ChannelWidth;
  490. int EndChannel = activeObject.MaxEndCol / ChannelWidth;
  491. if (StartChannel == EndChannel)
  492. {
  493. result = StartChannel;
  494. }
  495. else if (EndChannel - StartChannel > 1)
  496. {
  497. Console.WriteLine("ActiveChannel-Error");
  498. //error
  499. }
  500. else
  501. {
  502. result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel;
  503. }
  504. return result;
  505. }
  506. /// <summary>
  507. /// 获取结果最长长边
  508. /// </summary>
  509. /// <param name="Rows"></param>
  510. /// <returns></returns>
  511. private double GetActionMaxLength(List<RowStartEndCol> Rows)
  512. {
  513. List<Point> points = ConvexHull(Rows);
  514. return RotatingCalipers(points);
  515. }
  516. /// <summary>
  517. /// 凸包点集合获取
  518. /// </summary>
  519. /// <param name="Rows"></param>
  520. /// <returns></returns>
  521. private List<Point> ConvexHull(List<RowStartEndCol> Rows)
  522. {
  523. List<Point> points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
  524. points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
  525. points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
  526. var lower = new List<Point>();
  527. foreach (var p in points)
  528. {
  529. while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
  530. lower.RemoveAt(lower.Count - 1);
  531. lower.Add(p);
  532. }
  533. var upper = new List<Point>();
  534. for (int i = points.Count - 1; i >= 0; i--)
  535. {
  536. var p = points[i];
  537. while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
  538. upper.RemoveAt(upper.Count - 1);
  539. upper.Add(p);
  540. }
  541. lower.RemoveAt(lower.Count - 1);
  542. upper.RemoveAt(upper.Count - 1);
  543. lower.AddRange(upper);
  544. return lower;
  545. }
  546. /// <summary>
  547. /// 凸包最长边
  548. /// </summary>
  549. /// <param name="hull"></param>
  550. /// <returns></returns>
  551. private double RotatingCalipers(List<Point> hull)
  552. {
  553. int n = hull.Count;
  554. if (n == 1) return 0;
  555. if (n == 2) return Distance(hull[0], hull[1]);
  556. int k = 1;
  557. double maxDist = 0;
  558. for (int i = 0; i < n; i++)
  559. {
  560. while (Area2(hull[i], hull[(i + 1) % n], hull[(k + 1) % n]) >
  561. Area2(hull[i], hull[(i + 1) % n], hull[k]))
  562. {
  563. k = (k + 1) % n;
  564. }
  565. double currentDist = DistanceToLine(hull[i], hull[(i + 1) % n], hull[k]);
  566. maxDist = Math.Max(maxDist, currentDist);
  567. }
  568. return maxDist;
  569. }
  570. private double DistanceToLine(Point a, Point b, Point c)
  571. {
  572. double area = Math.Abs(Area2(a, b, c));
  573. double baseLength = Distance(a, b);
  574. return area / baseLength;
  575. }
  576. // 计算向量叉积
  577. private int Cross(Point o, Point a, Point b) =>
  578. (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X);
  579. // 计算三角形面积的两倍
  580. private int Area2(Point a, Point b, Point c) =>
  581. (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
  582. // 计算两点间距离
  583. private double Distance(Point a, Point b)
  584. {
  585. int dx = a.X - b.X;
  586. int dy = a.Y - b.Y;
  587. return Math.Sqrt(dx * dx + dy * dy);
  588. }
  589. private bool TryAdd(List<ActiveObjectClass> list, ActiveObjectClass item, int maxSize)
  590. {
  591. list.Add(item);
  592. if (list.Count > maxSize)
  593. {
  594. list[list.Count - maxSize].RowsData.Clear();
  595. }
  596. return true;
  597. }
  598. private bool MergeCurrentRegions(List<ValidRegionModelClass> validRegions)
  599. {
  600. bool Result = false;
  601. ExecuteAsyncOperations(validRegions).GetAwaiter().GetResult();
  602. return Result;
  603. }
  604. private async Task ExecuteAsyncOperations(List<ValidRegionModelClass> validRegions)
  605. {
  606. // 创建异步操作任务列表
  607. var tasks = new List<Task<string>>();
  608. for (int i = 0; i < validRegions.Count; i++)
  609. {
  610. tasks.Add(AsyncOperation(validRegions[i],i));
  611. }
  612. try
  613. {
  614. // 等待所有异步操作完成
  615. var results = await Task.WhenAll(tasks);
  616. Console.WriteLine($"All operations completed: [{string.Join(", ", results)}]");
  617. }
  618. catch (Exception ex)
  619. {
  620. Console.WriteLine($"One or more operations failed: {ex.Message}");
  621. }
  622. }
  623. private async Task<string> AsyncOperation(ValidRegionModelClass region,int i)
  624. {
  625. // 模拟异步操作,比如网络请求或I/O操作
  626. await Task.Run(() => {
  627. lock(taskLock)
  628. {
  629. // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
  630. var matcheds = activeObjects.Where(o =>
  631. IsOverlapping(o, region) &&
  632. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList();
  633. //当有多个可合并的活跃物体时,将多个物体合并
  634. if (matcheds.Count >= 2)
  635. {
  636. // 合并有效区域队列
  637. var CopeRowsData = new List<RowStartEndCol>();
  638. matcheds.ForEach(o => CopeRowsData = CopeRowsData.Concat(o.RowsData).ToList());
  639. // 合并有效区域并保存在新的区域中
  640. var MergeMatched = new ActiveObjectClass
  641. {
  642. MinStartCol = matcheds.Min(o => o.MinStartCol),
  643. MaxEndCol = matcheds.Max(o => o.MaxEndCol),
  644. StartLine = matcheds.Min(o => o.StartLine),
  645. LastSeenLine = matcheds.Max(o => o.LastSeenLine),
  646. LastSeenLineStartCol = matcheds.Min(o => o.LastSeenLineStartCol),
  647. LastSeenLineEndCol = matcheds.Max(o => o.LastSeenLineEndCol),
  648. StartCheckTime = matcheds.Min(o => o.StartCheckTime),
  649. EndCheckTime = matcheds.Max(o => o.EndCheckTime),
  650. Area = matcheds.Sum(o => o.Area),
  651. RowsData = CopeRowsData,
  652. ImageWidth = matcheds.FirstOrDefault().ImageWidth,
  653. //StateCode = 8
  654. };
  655. // 从活跃区域中删除被合并的区域
  656. matcheds.ForEach(o => activeObjects.Remove(o));
  657. // 保存新的区域到活跃区域中
  658. activeObjects.Add(MergeMatched);
  659. }
  660. // 搜获可用且可合并的活跃区域
  661. var matched = activeObjects.FirstOrDefault(o =>
  662. IsOverlapping(o, region) &&
  663. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP);
  664. if (matched != null)
  665. {
  666. // 合并区域:扩展物体边界并更新状态
  667. matched.MinStartCol = Math.Min(matched.MinStartCol, region.Start);
  668. matched.MaxEndCol = Math.Max(matched.MaxEndCol, region.End);
  669. matched.Area += region.End - region.Start + 1;
  670. matched.LastSeenLine = currentLine;
  671. matched.RowsData.Add(new RowStartEndCol
  672. {
  673. StartCol = region.Start,
  674. EndCol = region.End,
  675. RowsCol = currentLine,
  676. });
  677. }
  678. else
  679. {
  680. // 创建新物体(首次出现的区域)
  681. activeObjects.Add(new ActiveObjectClass
  682. {
  683. MinStartCol = region.Start,
  684. MaxEndCol = region.End,
  685. StartLine = currentLine,
  686. LastSeenLine = currentLine,
  687. LastSeenLineStartCol = region.Start,
  688. LastSeenLineEndCol = region.End,
  689. StartCheckTime = DateTime.Now,
  690. Area = region.End - region.Start + 1,
  691. ImageWidth = IdentifyImageWidth,
  692. RowsData = new List<RowStartEndCol> {
  693. new RowStartEndCol {
  694. StartCol = region.Start,
  695. EndCol = region.End,
  696. RowsCol = currentLine,
  697. }
  698. }
  699. });
  700. }
  701. }
  702. }); // 延迟
  703. Console.WriteLine($"Operation {i} completed");
  704. return $"Result {i}";
  705. }
  706. #endregion
  707. #region 线程方法
  708. /// <summary>
  709. /// 识别图像线程
  710. /// </summary>
  711. private void IdentifyImageProcess()
  712. {
  713. Stopwatch stopwatch = Stopwatch.StartNew();
  714. while (IsIdentify)
  715. {
  716. //判断队列中是否有数据
  717. if (IFrameDatas.Count() > 0)
  718. {
  719. stopwatch.Restart();
  720. if(IFrameDatas.Count()>50)
  721. FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
  722. IFrameDatas.TryDequeue(out IImage IframeData);
  723. //是否成功取得数据
  724. if (IframeData != null)
  725. {
  726. //识别
  727. ProcessImageSequence(IframeData);
  728. }
  729. else
  730. {
  731. Console.WriteLine("识别数据为空");
  732. continue;
  733. }
  734. //输出耗时
  735. stopwatch.Stop();
  736. Console.WriteLine($"识别线程识别一张图片耗时:{stopwatch.Elapsed},待识别队列剩余数量{IFrameDatas.Count()}");
  737. }
  738. else
  739. {
  740. Thread.Sleep(1);
  741. }
  742. }
  743. }
  744. #endregion
  745. }
  746. }