ShuLiClass.cs 54 KB

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