ShuLiClass.cs 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030
  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 Org.BouncyCastle.Utilities;
  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.Runtime.InteropServices.WindowsRuntime;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. using System.Windows.Media.Media3D;
  20. using static iTextSharp.text.pdf.AcroFields;
  21. namespace CCDCount.DLL
  22. {
  23. public class ShuLiClass
  24. {
  25. #region 变量
  26. /// <summary>
  27. /// 当活跃物体转变为历史物体时的回调事件
  28. /// </summary>
  29. public event EventHandler<ActiveObjectEventArgsClass> WorkCompleted;
  30. private List<ActiveObjectClass> activeObjects = new List<ActiveObjectClass>(); // 当前跟踪中的物体
  31. private List<ActiveObjectClass> historyActiveObjects = new List<ActiveObjectClass>(); // 历史物体
  32. private ConcurrentQueue<IFrameOut> IFrameDatas = new ConcurrentQueue<IFrameOut>(); //图像数据队列
  33. private Thread IdentifyImageProcessThread = null; // 识别线程
  34. private bool IsIdentify = false; //线程是否开始识别的标志
  35. private long currentLine = 0; //行数记录
  36. private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件
  37. public List<int> ChannelsRoi { get { return _ChannelsRoi; } }
  38. private List<int> _ChannelsRoi = new List<int>();
  39. private int ChannelWidth = 0;//每个区域的宽度
  40. private int IdentifyImageWidth = -1;
  41. private int ObjectNum = 0;
  42. public int ImageNum { get { return IFrameDatas.Count; } }
  43. private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
  44. private double XCoefficient = 1;
  45. private double YCoefficient = 2;
  46. private DateTime[] ChannelIntervalTime = new DateTime[8];
  47. #endregion
  48. #region 公共方法
  49. /// <summary>
  50. /// 初始化构造方法
  51. /// </summary>
  52. public ShuLiClass()
  53. {
  54. // 加载默认参数
  55. shuLiConfig = new ShuLiConfigClass()
  56. {
  57. Channel = 8,
  58. PandingCode = 2
  59. };
  60. for(int i = 0; i < ChannelIntervalTime.Length; i++)
  61. {
  62. ChannelIntervalTime[i] = DateTime.Now;
  63. }
  64. }
  65. /// <summary>
  66. /// 带参数的构造方法
  67. /// </summary>
  68. public ShuLiClass(ShuLiConfigClass config)
  69. {
  70. if (config.IsLoadCanfig)
  71. {
  72. // 加载传出的参数
  73. shuLiConfig = config;
  74. XCoefficient = shuLiConfig.ScaleX;
  75. YCoefficient = shuLiConfig.ScaleY;
  76. }
  77. else
  78. {
  79. // 加载默认参数
  80. shuLiConfig = new ShuLiConfigClass()
  81. {
  82. Channel = 8,
  83. PandingCode = 2
  84. };
  85. }
  86. InitChannel();
  87. }
  88. public long InitCurrentLine(int line)
  89. {
  90. currentLine = line;
  91. return currentLine;
  92. }
  93. public int InitNum(int num)
  94. {
  95. ObjectNum = num;
  96. return ObjectNum;
  97. }
  98. List<ActiveObjectClass> lostObjects = new List<ActiveObjectClass>();
  99. /// <summary>
  100. /// 处理图像序列的主入口
  101. /// </summary>
  102. /// <param name="image">图像像素数据</param>
  103. /// <param name="ImageWidth">图像宽</param>
  104. /// <param name="currentLine">当前行数</param>
  105. /// <returns>检测到的物体总数</returns>
  106. public bool ProcessImageSequence(IFrameOut image)
  107. {
  108. bool result = false;
  109. ReadOnlySpan<byte> spanFromArr = image.Image.PixelData.AsSpan();
  110. for (int i = 0; i < image.Image.Height; i++)
  111. {
  112. result = ProcessLine2(spanFromArr,image.HostTimeStamp,image.Image.Width, i);
  113. currentLine += 1;
  114. }
  115. //识别到结果并输出
  116. _rwLock.EnterReadLock();
  117. // 清理超时未更新的物体
  118. lostObjects = activeObjects
  119. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  120. .ToList();
  121. _rwLock.ExitReadLock();
  122. List<ActiveObjectClass> OneActive = new List<ActiveObjectClass>();
  123. // 有物体转变为活跃物体,返回值转为true
  124. if (lostObjects.Count > 0)
  125. {
  126. Stopwatch stopwatch = Stopwatch.StartNew();
  127. result = true;
  128. //stopwatch.Restart();
  129. foreach (var item in lostObjects)
  130. {
  131. item.PictureEndReadTime = FromUnixTimestamp((long)image.HostTimeStamp);
  132. DateTime NowTime = DateTime.Now;
  133. var AllPictureUseTime = (item.PictureEndReadTime - item.PictureStartReadTime).TotalMilliseconds;
  134. var PictureUsetime = (NowTime - item.PictureStartReadTime).TotalMilliseconds;
  135. if (PictureUsetime - AllPictureUseTime > 20)
  136. LOG.error($"算法耗时超过了20ms,相机取图总耗时:{AllPictureUseTime},从取第一张图到现在的耗时{PictureUsetime}");
  137. //噪点判定
  138. if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  139. {
  140. item.StateCode = 9;
  141. //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6);
  142. continue;
  143. }
  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. if((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height)
  153. {
  154. var CalculationResult = SizeCalculation(item.RowsData);
  155. if(CalculationResult!=null)
  156. {
  157. item.MaxLength = CalculationResult.Height;
  158. }
  159. //item.hasSignificantConcavity = CalculationResult.hasSignificantConcavity;
  160. //item.concavityRatio = CalculationResult.concavityRatio;
  161. //if(CalculationResult.hasSignificantConcavity)
  162. //{
  163. // if(CalculationResult.concavityRatio>0.3)
  164. // {
  165. // item.StateCode= 11;
  166. // }
  167. //}
  168. }
  169. else
  170. {
  171. item.StateCode = 7;
  172. }
  173. }
  174. if (shuLiConfig.PandingCode != -1)
  175. {
  176. if (item.StateCode != -1)
  177. {
  178. if (item.StateCode == 8)
  179. {
  180. //LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  181. }
  182. else if (item.StateCode == 7)
  183. {
  184. //LOG.log(string.Format("颗粒编号{0}:视野被遮挡", item.Num));
  185. }
  186. }
  187. else if (item.Area < shuLiConfig.MinArea
  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.Area > shuLiConfig.MaxArea
  195. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  196. {
  197. item.StateCode = 5;
  198. //LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  199. //Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
  200. }
  201. else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH
  202. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  203. {
  204. item.StateCode = 2;
  205. //LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  206. //Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
  207. }
  208. else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH
  209. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  210. {
  211. item.StateCode = 1;
  212. //LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  213. //Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
  214. }
  215. else
  216. {
  217. item.StateCode = 0;
  218. //LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  219. //Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
  220. }
  221. }
  222. if(item.StateCode != 7&&item.StateCode!=9)
  223. {
  224. //转为历史物体,添加缺少的参数
  225. item.Num = Interlocked.Increment(ref ObjectNum);
  226. item.ChannelNO = ActiveChannel(item);
  227. }
  228. item.EndCheckTime = DateTime.Now;
  229. if(item.StateCode != 9)
  230. OneActive.Add(item);
  231. }
  232. stopwatch.Stop();
  233. if (stopwatch.ElapsedMilliseconds > 1)
  234. {
  235. Console.WriteLine($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
  236. //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
  237. }
  238. //stopwatch.Restart();
  239. if (OneActive.Count > 0)
  240. {
  241. //触发回调事件
  242. OnWorkCompleted(OneActive);
  243. }
  244. // stopwatch.Stop();
  245. //if (stopwatch.ElapsedMilliseconds > 1)
  246. //{
  247. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果完成回调函数,此次识别耗时:{stopwatch.Elapsed}");
  248. //}
  249. }
  250. else
  251. {
  252. OneActive.Clear();
  253. }
  254. _rwLock.EnterWriteLock();
  255. // 累加到总数并从活跃物体转移到历史物体
  256. foreach (var item in OneActive)
  257. {
  258. TryAdd(historyActiveObjects, item, 2500);
  259. }
  260. lostObjects.ForEach(o => activeObjects.Remove(o));
  261. _rwLock.ExitWriteLock();
  262. return result;
  263. }
  264. /// <summary>
  265. /// 处理图像序列的主入口
  266. /// </summary>
  267. /// <param name="image">图像像素数据</param>
  268. /// <returns>检测到的物体总数</returns>
  269. public bool ProcessImageSequence2(IFrameOut image)
  270. {
  271. bool result = false;
  272. ReadOnlySpan<byte> spanFromArr = image.Image.PixelData.AsSpan();
  273. for (int i = 0; i < image.Image.Height; i++)
  274. {
  275. result = ProcessLine2(spanFromArr, image.HostTimeStamp, image.Image.Width, i);
  276. currentLine += 1;
  277. }
  278. // 优化: 减少锁的使用频率,批量处理逻辑
  279. List<ActiveObjectClass> objectsToProcess = null;
  280. _rwLock.EnterReadLock();
  281. try
  282. {
  283. // 使用ToList()避免在锁内进行复杂查询
  284. objectsToProcess = activeObjects
  285. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP ||
  286. (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  287. .ToList();
  288. }
  289. finally
  290. {
  291. _rwLock.ExitReadLock();
  292. }
  293. // 优化: 提前返回,避免不必要的处理
  294. if (objectsToProcess.Count == 0)
  295. {
  296. return result;
  297. }
  298. // 优化: 预分配容量,减少内存重新分配
  299. var processedObjects = new List<ActiveObjectClass>(objectsToProcess.Count);
  300. // 优化: 将耗时的计算移到循环外
  301. var endTime = DateTime.Now;
  302. var pictureEndTime = FromUnixTimestamp((long)image.HostTimeStamp);
  303. foreach (var item in objectsToProcess)
  304. {
  305. // 优化: 将耗时的计算移到循环外
  306. ProcessSingleObject(item, pictureEndTime, endTime, processedObjects);
  307. }
  308. result = true;
  309. // 优化: 批量操作减少锁的持有时间
  310. if (processedObjects.Count > 0)
  311. {
  312. _rwLock.EnterWriteLock();
  313. try
  314. {
  315. // 批量转移对象
  316. foreach (var item in processedObjects)
  317. {
  318. TryAdd(historyActiveObjects, item, 2500);
  319. }
  320. // 批量移除
  321. foreach (var item in objectsToProcess)
  322. {
  323. activeObjects.Remove(item);
  324. }
  325. }
  326. finally
  327. {
  328. _rwLock.ExitWriteLock();
  329. }
  330. // 优化: 异步触发事件,避免阻塞主线程
  331. ThreadPool.QueueUserWorkItem(_ =>
  332. {
  333. OnWorkCompleted(processedObjects);
  334. });
  335. }
  336. return result;
  337. }
  338. // 抽取的单个对象处理方法
  339. private void ProcessSingleObject(ActiveObjectClass item, DateTime pictureEndTime,
  340. DateTime endTime, List<ActiveObjectClass> processedObjects)
  341. {
  342. item.PictureEndReadTime = pictureEndTime;
  343. var nowTime = DateTime.Now;
  344. var ShiBieLuoHouTime = (nowTime - item.PictureEndReadTime).TotalMilliseconds;
  345. // 优化: 将日志记录改为条件执行
  346. if (ShiBieLuoHouTime > 30)
  347. {
  348. LOG.error($"算法落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
  349. }
  350. // 快速滤除噪点
  351. if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  352. {
  353. item.StateCode = 9;
  354. return;
  355. }
  356. // 计算长度
  357. if (item.StartLine == item.LastSeenLine)
  358. {
  359. item.MaxLength = item.MaxEndCol - item.MinStartCol;
  360. item.StateCode = 10;
  361. LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num));
  362. }
  363. else
  364. {
  365. if ((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height)
  366. {
  367. var calculationResult = SizeCalculation(item.RowsData);
  368. if(calculationResult!=null)
  369. {
  370. item.MaxLength = calculationResult.Height;
  371. }
  372. }
  373. else
  374. {
  375. item.StateCode = 7;
  376. }
  377. }
  378. // 分类处理
  379. ApplyClassificationRules(item);
  380. // 添加到历史记录
  381. if (item.StateCode != 7 && item.StateCode != 9)
  382. {
  383. item.Num = Interlocked.Increment(ref ObjectNum);
  384. item.ChannelNO = ActiveChannel(item);
  385. }
  386. item.EndCheckTime = endTime;
  387. if (item.StateCode != 9)
  388. {
  389. processedObjects.Add(item);
  390. }
  391. }
  392. // 抽取分类规则,便于维护
  393. private void ApplyClassificationRules(ActiveObjectClass item)
  394. {
  395. if (shuLiConfig.PandingCode == -1) return;
  396. if (item.StateCode != -1)
  397. {
  398. if (item.StateCode == 8)
  399. {
  400. LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  401. }
  402. else if (item.StateCode == 7)
  403. {
  404. LOG.log(string.Format("颗粒编号{0}:视野被遮挡", item.Num));
  405. }
  406. }
  407. else if (item.Area < shuLiConfig.MinArea &&
  408. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  409. {
  410. item.StateCode = 6;
  411. LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num));
  412. }
  413. else if (item.Area > shuLiConfig.MaxArea &&
  414. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  415. {
  416. item.StateCode = 5;
  417. LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  418. }
  419. else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH &&
  420. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  421. {
  422. item.StateCode = 2;
  423. LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  424. }
  425. else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH &&
  426. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  427. {
  428. item.StateCode = 1;
  429. LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  430. }
  431. else
  432. {
  433. item.StateCode = 0;
  434. LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  435. }
  436. }
  437. /// <summary>
  438. /// 返回最后一个历史物品
  439. /// </summary>
  440. /// <returns></returns>
  441. public ActiveObjectClass GetLastActive()
  442. {
  443. if (historyActiveObjects.Count() == 0)
  444. return null;
  445. return historyActiveObjects.Last();
  446. }
  447. /// <summary>
  448. /// 返回历史物品
  449. /// </summary>
  450. /// <returns></returns>
  451. public List<ActiveObjectClass> GetHistoryActive()
  452. {
  453. //lock (_lockObj) // 加锁
  454. //{
  455. _rwLock.EnterReadLock();
  456. var result = historyActiveObjects.ToList();
  457. _rwLock.ExitReadLock();
  458. return result;
  459. //}
  460. }
  461. /// <summary>
  462. /// 返回缓存在内存的历史物品的总数量
  463. /// </summary>
  464. /// <returns></returns>
  465. public int GetHistoryActiveNum()
  466. {
  467. //lock (_lockObj) // 加锁
  468. _rwLock.EnterReadLock();
  469. var result = historyActiveObjects.Where(o=>o.StateCode!=7&&o.StateCode!=9).ToList().Count;
  470. _rwLock.ExitReadLock();
  471. return result;
  472. }
  473. /// <summary>
  474. /// 获取历史数据中,正常数据数量
  475. /// </summary>
  476. /// <returns></returns>
  477. public int GetOkHistoryNum()
  478. {
  479. //lock (_lockObj)
  480. _rwLock.EnterReadLock();
  481. var result = historyActiveObjects.Where(o => o.StateCode != 7 && o.StateCode != 9&& o.StateCode == 0).ToList().Count;
  482. _rwLock.ExitReadLock();
  483. return result;
  484. }
  485. /// <summary>
  486. /// 获取历史数据中,异常数据数量
  487. /// </summary>
  488. /// <returns></returns>
  489. public int GetNgHistoryNum()
  490. {
  491. //lock (_lockObj)
  492. _rwLock.EnterReadLock();
  493. var result = historyActiveObjects.Where(o => o.StateCode != 7 && o.StateCode != 9 && o.StateCode != 0).ToList().Count;
  494. _rwLock.ExitReadLock();
  495. return result;
  496. }
  497. /// <summary>
  498. /// 清除历史数据
  499. /// </summary>
  500. /// <returns></returns>
  501. public bool ClearHistoryActive()
  502. {
  503. //lock (_lockObj)
  504. //{
  505. _rwLock.EnterWriteLock();
  506. historyActiveObjects.Clear();
  507. _rwLock.ExitWriteLock();
  508. return true;
  509. //}
  510. }
  511. /// <summary>
  512. /// 开启识别
  513. /// </summary>
  514. public void StartIdentifyFuntion(int ImaageWidth)
  515. {
  516. UpdateIdentifyImageWidth(ImaageWidth);
  517. InitChannel();
  518. try
  519. {
  520. // 标志位置位true
  521. IsIdentify = true;
  522. // 打开识别线程
  523. IdentifyImageProcessThread = new Thread(IdentifyImageProcess)
  524. {
  525. Priority = ThreadPriority.AboveNormal
  526. };
  527. IdentifyImageProcessThread.Start();
  528. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程启动失败);
  529. }
  530. catch (Exception ex)
  531. {
  532. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程启动失败,
  533. "Start thread failed!, " + ex.Message,
  534. "识别线程启动失败, " + ex.Message,
  535. "DLL:ShuLiClass-StartIdentifyFuntion");
  536. //FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message);
  537. throw;
  538. }
  539. }
  540. public void StartIdentifyFuntion2(int ImaageWidth)
  541. {
  542. UpdateIdentifyImageWidth(ImaageWidth);
  543. InitChannel();
  544. try
  545. {
  546. CancellationTokenSource = new CancellationTokenSource();
  547. var token = CancellationTokenSource.Token;
  548. // 启动图像处理线程
  549. ProcessingTask = Task.Factory.StartNew(() => IdentifyImageProcessTask(token),
  550. token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
  551. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程启动失败);
  552. }
  553. catch (Exception ex)
  554. {
  555. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程启动失败,
  556. "Start thread failed!, " + ex.Message,
  557. "识别线程启动失败, " + ex.Message,
  558. "DLL:ShuLiClass-StartIdentifyFuntion");
  559. //FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message);
  560. throw;
  561. }
  562. }
  563. /// <summary>
  564. /// 关闭识别
  565. /// </summary>
  566. public void StopIdentifyFuntion()
  567. {
  568. try
  569. {
  570. // 标志位设为false
  571. IsIdentify = false;
  572. if (IdentifyImageProcessThread != null && IdentifyImageProcessThread.IsAlive)
  573. IdentifyImageProcessThread.Join();
  574. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败);
  575. }
  576. catch (Exception ex)
  577. {
  578. //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
  579. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败,
  580. "Stop thread failed!, " + ex.Message,
  581. "识别线程停止失败, " + ex.Message,
  582. "DLL:ShuLiClass-StopIdentifyFuntion");
  583. throw;
  584. }
  585. }
  586. public async void StopIdentifyFuntion2()
  587. {
  588. try
  589. {
  590. // 发送取消信号
  591. CancellationTokenSource.Cancel();
  592. // 等待线程完成
  593. try
  594. {
  595. if (ProcessingTask != null)
  596. await ProcessingTask;
  597. }
  598. catch (OperationCanceledException)
  599. {
  600. // 正常取消,忽略异常
  601. }
  602. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败);
  603. }
  604. catch (Exception ex)
  605. {
  606. //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
  607. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败,
  608. "Stop thread failed!, " + ex.Message,
  609. "识别线程停止失败, " + ex.Message,
  610. "DLL:ShuLiClass-StopIdentifyFuntion");
  611. throw;
  612. }
  613. }
  614. /// <summary>
  615. /// 向识别队列添加一个数据
  616. /// </summary>
  617. /// <param name="items"></param>
  618. public void SetOnceIdentifyImageData(IFrameOut items)
  619. {
  620. IFrameDatas.Enqueue(items.Clone() as IFrameOut);
  621. //IFrameDatas.Enqueue(items );
  622. }
  623. /// <summary>
  624. /// 保存参数
  625. /// </summary>
  626. public void SaveConfig()
  627. {
  628. if (!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\");
  629. XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml");
  630. }
  631. /// <summary>
  632. /// 更新检测宽度信息
  633. /// </summary>
  634. /// <param name="Width"></param>
  635. public void UpdateIdentifyImageWidth(int Width)
  636. {
  637. IdentifyImageWidth = Width;
  638. }
  639. /// <summary>
  640. /// 初始化通道划分
  641. /// </summary>
  642. /// <param name="ImageWidth"></param>
  643. public void InitChannel()
  644. {
  645. _ChannelsRoi.Clear();
  646. shuLiConfig.ImageWidth = IdentifyImageWidth == -1 ? shuLiConfig.ImageWidth : IdentifyImageWidth;
  647. if (shuLiConfig.Channel > 0)
  648. {
  649. if (shuLiConfig.IsIdentifyRoiOpen)
  650. {
  651. ChannelWidth = (shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX) / shuLiConfig.Channel;
  652. }
  653. else
  654. {
  655. ChannelWidth = shuLiConfig.ImageWidth / shuLiConfig.Channel;
  656. }
  657. for (int i = 0; i < shuLiConfig.Channel; i++)
  658. {
  659. _ChannelsRoi.Add(ChannelWidth + i * ChannelWidth);
  660. }
  661. }
  662. }
  663. /// <summary>
  664. /// 获取配置信息
  665. /// </summary>
  666. /// <returns></returns>
  667. public ShuLiConfigClass GetConfigValue()
  668. {
  669. ShuLiConfigClass result = shuLiConfig;
  670. return result;
  671. }
  672. public int GetConfigImageWidth()
  673. {
  674. int result = -1;
  675. if (shuLiConfig != null)
  676. result = shuLiConfig.ImageWidth;
  677. return result;
  678. }
  679. /// <summary>
  680. /// 获取坐标转换系数
  681. /// </summary>
  682. /// <param name="XCoefficient"></param>
  683. /// <param name="YCoefficient"></param>
  684. public void GetXYCoefficient(out double XCoefficient, out double YCoefficient)
  685. {
  686. XCoefficient = this.XCoefficient;
  687. YCoefficient = this.YCoefficient;
  688. }
  689. /// <summary>
  690. /// 设置坐标转换X系数
  691. /// </summary>
  692. /// <param name="XCoefficient"></param>
  693. public void SetXCoefficient(double XCoefficient)
  694. {
  695. this.XCoefficient = XCoefficient;
  696. }
  697. /// <summary>
  698. /// 设置坐标转换Y系数
  699. /// </summary>
  700. public void SetYCoefficient(double YCoefficient)
  701. {
  702. this.YCoefficient = YCoefficient;
  703. }
  704. /// <summary>
  705. /// 获取结果最长长边
  706. /// </summary>
  707. /// <param name="Rows"></param>
  708. /// <returns></returns>
  709. public BoundingRectangleMdoel SizeCalculation(List<RowStartEndCol> Rows)
  710. {
  711. //Stopwatch stopwatch = Stopwatch.StartNew();
  712. var points = ConvexHull(Rows);
  713. //stopwatch.Stop();
  714. //Console.WriteLine($"凸包计算耗时:{stopwatch.Elapsed}");
  715. //var test = CalculateMinimumBoundingRectangle(points);
  716. var result = CoefficientCalculateMinimumBoundingRectangle(points);
  717. return result;
  718. }
  719. public MaxLengthModel SizeCalculation2(List<RowStartEndCol> Rows)
  720. {
  721. var points = ConvexHull(Rows);
  722. var result = CoefficientRotatingCalipers(points);
  723. return result;
  724. }
  725. /// <summary>
  726. /// 凸包点集合获取
  727. /// </summary>
  728. /// <param name="Rows"></param>
  729. /// <returns></returns>
  730. public List<Point> ConvexHull(List<RowStartEndCol> Rows)
  731. {
  732. List<Point> points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
  733. points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
  734. points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
  735. var lower = new List<Point>();
  736. foreach (var p in points)
  737. {
  738. while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
  739. lower.RemoveAt(lower.Count - 1);
  740. lower.Add(p);
  741. }
  742. var upper = new List<Point>();
  743. for (int i = points.Count - 1; i >= 0; i--)
  744. {
  745. var p = points[i];
  746. while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
  747. upper.RemoveAt(upper.Count - 1);
  748. upper.Add(p);
  749. }
  750. lower.RemoveAt(lower.Count - 1);
  751. upper.RemoveAt(upper.Count - 1);
  752. lower.AddRange(upper);
  753. return lower;
  754. }
  755. /// <summary>
  756. /// 凸包点集合获取,输出原点集
  757. /// </summary>
  758. /// <param name="Rows"></param>
  759. /// <returns></returns>
  760. public List<Point> ConvexHull(List<RowStartEndCol> Rows, out List<Point> points)
  761. {
  762. points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
  763. points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
  764. points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
  765. var lower = new List<Point>();
  766. foreach (var p in points)
  767. {
  768. while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
  769. lower.RemoveAt(lower.Count - 1);
  770. lower.Add(p);
  771. }
  772. var upper = new List<Point>();
  773. for (int i = points.Count - 1; i >= 0; i--)
  774. {
  775. var p = points[i];
  776. while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
  777. upper.RemoveAt(upper.Count - 1);
  778. upper.Add(p);
  779. }
  780. lower.RemoveAt(lower.Count - 1);
  781. upper.RemoveAt(upper.Count - 1);
  782. lower.AddRange(upper);
  783. return lower;
  784. }
  785. /// <summary>
  786. /// 凸包点集合获取,同时检测较大的内凹
  787. /// </summary>
  788. /// <param name="Rows"></param>
  789. /// <returns>返回凸包点集</returns>
  790. public ConvexResultClass ConvexHullWithConcavityDetection(List<RowStartEndCol> Rows)
  791. {
  792. ConvexResultClass result = null;
  793. // 构建原始点集
  794. var lower = ConvexHull(Rows, out List<Point> originalPoints);
  795. // 检测内凹
  796. ConvexResultClass ConvexResult = DetectConcavity(originalPoints, lower);
  797. return result = new ConvexResultClass()
  798. {
  799. convexHull = lower,
  800. hasSignificantConcavity = ConvexResult.hasSignificantConcavity,
  801. concavityRatio = ConvexResult.concavityRatio,
  802. };
  803. }
  804. #endregion
  805. #region 私有方法
  806. public DateTime FromUnixTimestamp(long timestamp, bool isMilliseconds = false)
  807. {
  808. try
  809. {
  810. // 如果未指定单位,尝试自动检测
  811. if (!isMilliseconds)
  812. {
  813. // 如果时间戳看起来像毫秒级(13位数字)
  814. if (timestamp.ToString().Length > 10)
  815. {
  816. isMilliseconds = true;
  817. }
  818. }
  819. if (isMilliseconds)
  820. {
  821. // 验证毫秒级时间戳范围
  822. const long minMilliTimestamp = -62135596800000; // 0001-01-01 00:00:00 UTC
  823. const long maxMilliTimestamp = 253402300799999; // 9999-12-31 23:59:59 UTC
  824. if (timestamp < minMilliTimestamp || timestamp > maxMilliTimestamp)
  825. {
  826. throw new ArgumentOutOfRangeException(nameof(timestamp),
  827. "毫秒级时间戳超出有效范围");
  828. }
  829. DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(timestamp);
  830. return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
  831. }
  832. else
  833. {
  834. // 验证秒级时间戳范围
  835. const long minTimestamp = -62135596800;
  836. const long maxTimestamp = 253402300799;
  837. if (timestamp < minTimestamp || timestamp > maxTimestamp)
  838. {
  839. throw new ArgumentOutOfRangeException(nameof(timestamp),
  840. "秒级时间戳超出有效范围");
  841. }
  842. DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp);
  843. return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
  844. }
  845. }
  846. catch (ArgumentOutOfRangeException)
  847. {
  848. throw;
  849. }
  850. catch (Exception ex)
  851. {
  852. throw new ArgumentException($"无法转换时间戳 {timestamp}: {ex.Message}", ex);
  853. }
  854. }
  855. /// <summary>
  856. /// 对外通知事件
  857. /// </summary>
  858. private void OnWorkCompleted(List<ActiveObjectClass> activeObject)
  859. {
  860. if(activeObject == null) return;
  861. ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject);
  862. // 触发事件
  863. WorkCompleted?.Invoke(this, activeObjectEventArgs);
  864. }
  865. private bool IsPrintLightOnError = false;
  866. List<ValidRegionModelClass> currentRegions = null;
  867. /// <summary>
  868. /// 处理单行像素数据
  869. /// 返回值为false的时候无活跃物体转变为历史物体
  870. /// 返回值为true的时候有活跃物体转变为历史物体
  871. /// </summary>
  872. /// <param name="image">当前行像素数组</param>
  873. private bool ProcessLine(IFrameOut imagedata, int RowNo)
  874. {
  875. //Stopwatch stopwatch = Stopwatch.StartNew();
  876. bool result = false;
  877. // 步骤1:检测当前行的有效区域
  878. var currentRegions = FindValidRegions(imagedata.Image, RowNo);
  879. if (currentRegions.Count == 0) return result;
  880. if (currentRegions.Count == 1)
  881. {
  882. if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Image.Width)
  883. {
  884. if (!IsPrintLightOnError)
  885. {
  886. FaultLog.RecordLogMessage("The current effective area is the entire row. Check the field of view and light source", 6);
  887. IsPrintLightOnError = true;
  888. }
  889. return false;
  890. }
  891. }
  892. IsPrintLightOnError = false;
  893. //stopwatch.Stop();
  894. //if (stopwatch.ElapsedMilliseconds > 1)
  895. //{
  896. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像连通域检测超时,此次识别耗时:{stopwatch.Elapsed}");
  897. //}
  898. //stopwatch.Restart();
  899. foreach (var region in currentRegions)
  900. {
  901. // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
  902. _rwLock.EnterReadLock();
  903. var matcheds = activeObjects.Where(o =>
  904. IsOverlapping(o, region) &&
  905. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList();
  906. _rwLock.ExitReadLock();
  907. //当有多个可合并的活跃物体时,将多个物体合并
  908. if (matcheds.Count >= 2)
  909. {
  910. // 合并有效区域队列
  911. var CopeRowsData = new List<RowStartEndCol>();
  912. matcheds.ForEach(o => CopeRowsData = CopeRowsData.Concat(o.RowsData).ToList());
  913. // 合并有效区域并保存在新的区域中
  914. var MergeMatched = new ActiveObjectClass
  915. {
  916. MinStartCol = matcheds.Min(o => o.MinStartCol),
  917. MaxEndCol = matcheds.Max(o => o.MaxEndCol),
  918. StartLine = matcheds.Min(o => o.StartLine),
  919. LastSeenLine = matcheds.Max(o => o.LastSeenLine),
  920. LastSeenLineStartCol = matcheds.Min(o => o.LastSeenLineStartCol),
  921. LastSeenLineEndCol = matcheds.Max(o => o.LastSeenLineEndCol),
  922. StartCheckTime = matcheds.Min(o => o.StartCheckTime),
  923. EndCheckTime = matcheds.Max(o => o.EndCheckTime),
  924. Area = matcheds.Sum(o => o.Area),
  925. RowsData = CopeRowsData,
  926. ImageWidth = matcheds.FirstOrDefault().ImageWidth,
  927. StateCode = 8
  928. };
  929. _rwLock.EnterWriteLock();
  930. // 从活跃区域中删除被合并的区域
  931. matcheds.ForEach(o => activeObjects.Remove(o));
  932. // 保存新的区域到活跃区域中
  933. activeObjects.Add(MergeMatched);
  934. _rwLock.ExitWriteLock();
  935. }
  936. _rwLock.EnterReadLock();
  937. // 搜获可用且可合并的活跃区域
  938. var matched = activeObjects.FirstOrDefault(o =>
  939. IsOverlapping(o, region) &&
  940. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP);
  941. _rwLock.ExitReadLock();
  942. if (matched != null)
  943. {
  944. // 合并区域:扩展物体边界并更新状态
  945. matched.MinStartCol = Math.Min(matched.MinStartCol, region.Start);
  946. matched.MaxEndCol = Math.Max(matched.MaxEndCol, region.End);
  947. matched.Area += region.End - region.Start + 1;
  948. matched.LastSeenLine = currentLine;
  949. matched.RowsData.Add(new RowStartEndCol
  950. {
  951. StartCol = region.Start,
  952. EndCol = region.End,
  953. RowsCol = currentLine,
  954. });
  955. matched.LastSeenLineStartCol = region.Start;
  956. matched.LastSeenLineEndCol = region.End;
  957. }
  958. else
  959. {
  960. _rwLock.EnterWriteLock();
  961. // 创建新物体(首次出现的区域)
  962. activeObjects.Add(new ActiveObjectClass
  963. {
  964. MinStartCol = region.Start,
  965. MaxEndCol = region.End,
  966. StartLine = currentLine,
  967. LastSeenLine = currentLine,
  968. LastSeenLineStartCol = region.Start,
  969. LastSeenLineEndCol = region.End,
  970. StartCheckTime = DateTime.Now,
  971. PictureStartReadTime = FromUnixTimestamp((long)imagedata.HostTimeStamp),
  972. Area = region.End - region.Start + 1,
  973. ImageWidth = IdentifyImageWidth,
  974. RowsData = new List<RowStartEndCol> {
  975. new RowStartEndCol {
  976. StartCol = region.Start,
  977. EndCol = region.End,
  978. RowsCol = currentLine,
  979. }
  980. }
  981. });
  982. _rwLock.ExitWriteLock();
  983. }
  984. }
  985. //stopwatch.Stop();
  986. //if (stopwatch.ElapsedMilliseconds > 1)
  987. //{
  988. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}");
  989. //}
  990. return result;
  991. }
  992. private bool ProcessLine2(ReadOnlySpan<byte> image, ulong ImageHostTime, uint imageWidth, int RowNo)
  993. {
  994. // 缓存配置值
  995. int maxGap = shuLiConfig.MAX_GAP;
  996. long currentTimeLine = currentLine;
  997. // 步骤1:检测当前行的有效区域
  998. currentRegions = FindValidRegions(image, (int)imageWidth, RowNo);
  999. if (currentRegions.Count == 0|| currentRegions == null) return false;
  1000. if (currentRegions.Count == 1)
  1001. {
  1002. if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imageWidth)
  1003. {
  1004. if (!IsPrintLightOnError)
  1005. {
  1006. FaultLog.RecordLogMessage("The current effective area is the entire row. Check the field of view and light source", 6);
  1007. IsPrintLightOnError = true;
  1008. }
  1009. return false;
  1010. }
  1011. }
  1012. IsPrintLightOnError = false;
  1013. // 批量处理操作列表
  1014. var operations = new List<OperationItem>();
  1015. var currentActiveObjects = new List<ActiveObjectClass>();
  1016. // 一次性获取当前活跃物体快照
  1017. _rwLock.EnterReadLock();
  1018. try
  1019. {
  1020. currentActiveObjects = new List<ActiveObjectClass>(activeObjects);
  1021. }
  1022. finally
  1023. {
  1024. _rwLock.ExitReadLock();
  1025. }
  1026. foreach (var region in currentRegions)
  1027. {
  1028. // 查找所有可合并的活跃物体(重叠+在允许间隔内)
  1029. var matcheds = currentActiveObjects.Where(o =>
  1030. IsOverlapping(o, region) &&
  1031. (currentTimeLine - o.LastSeenLine - 1) <= maxGap).ToList();
  1032. // 当有多个可合并的活跃物体时,将多个物体合并
  1033. if (matcheds.Count >= 2)
  1034. {
  1035. // 计算聚合值(单次遍历)
  1036. var aggregateResult = CalculateAggregates(matcheds);
  1037. var mergedObject = new ActiveObjectClass
  1038. {
  1039. MinStartCol = aggregateResult.minStartCol,
  1040. MaxEndCol = aggregateResult.maxEndCol,
  1041. StartLine = aggregateResult.minStartLine,
  1042. LastSeenLine = aggregateResult.maxLastSeenLine,
  1043. LastSeenLineStartCol = aggregateResult.minLastSeenStartCol,
  1044. LastSeenLineEndCol = aggregateResult.maxLastSeenEndCol,
  1045. StartCheckTime = aggregateResult.minStartTime,
  1046. EndCheckTime = aggregateResult.maxEndTime,
  1047. PictureStartReadTime = aggregateResult.pictureMinReadTime,
  1048. Area = aggregateResult.totalArea,
  1049. RowsData = aggregateResult.mergedRowsData,
  1050. ImageWidth = aggregateResult.imageWidth,
  1051. StateCode = 8
  1052. };
  1053. operations.Add(new OperationItem
  1054. {
  1055. Type = OperationType.MergeMultiple,
  1056. ObjectsToRemove = matcheds,
  1057. ObjectToAdd = mergedObject
  1058. });
  1059. // 从currentActiveObjects中移除已处理的物体,避免重复处理
  1060. foreach (var obj in matcheds)
  1061. {
  1062. currentActiveObjects.Remove(obj);
  1063. }
  1064. }
  1065. else
  1066. {
  1067. // 查找单一匹配物体
  1068. var matched = matcheds.FirstOrDefault();
  1069. if (matched != null)
  1070. {
  1071. operations.Add(new OperationItem
  1072. {
  1073. Type = OperationType.Update,
  1074. ObjectToUpdate = matched,
  1075. Region = region
  1076. });
  1077. }
  1078. else
  1079. {
  1080. // 创建新物体
  1081. var newObject = new ActiveObjectClass
  1082. {
  1083. MinStartCol = region.Start,
  1084. MaxEndCol = region.End,
  1085. StartLine = currentTimeLine,
  1086. LastSeenLine = currentTimeLine,
  1087. LastSeenLineStartCol = region.Start,
  1088. LastSeenLineEndCol = region.End,
  1089. StartCheckTime = DateTime.Now,
  1090. PictureStartReadTime = FromUnixTimestamp((long)ImageHostTime),
  1091. Area = region.End - region.Start + 1,
  1092. ImageWidth = IdentifyImageWidth,
  1093. RowsData = new List<RowStartEndCol> {
  1094. new RowStartEndCol {
  1095. StartCol = region.Start,
  1096. EndCol = region.End,
  1097. RowsCol = currentTimeLine,
  1098. }
  1099. }
  1100. };
  1101. operations.Add(new OperationItem
  1102. {
  1103. Type = OperationType.Add,
  1104. ObjectToAdd = newObject
  1105. });
  1106. }
  1107. }
  1108. }
  1109. currentRegions.Clear();
  1110. // 执行所有操作(最小化锁持有时间)
  1111. bool hasChanges = false;
  1112. if (operations.Count > 0)
  1113. {
  1114. _rwLock.EnterWriteLock();
  1115. try
  1116. {
  1117. foreach (var op in operations)
  1118. {
  1119. switch (op.Type)
  1120. {
  1121. case OperationType.MergeMultiple:
  1122. // 移除被合并的物体
  1123. foreach (var objItem in op.ObjectsToRemove)
  1124. {
  1125. activeObjects.Remove(objItem);
  1126. }
  1127. // 添加合并后的物体
  1128. activeObjects.Add(op.ObjectToAdd);
  1129. break;
  1130. case OperationType.Update:
  1131. // 更新现有物体
  1132. var obj = op.ObjectToUpdate;
  1133. obj.MinStartCol = Math.Min(obj.MinStartCol, op.Region.Start);
  1134. obj.MaxEndCol = Math.Max(obj.MaxEndCol, op.Region.End);
  1135. obj.Area += op.Region.End - op.Region.Start + 1;
  1136. obj.LastSeenLine = currentTimeLine;
  1137. obj.RowsData.Add(new RowStartEndCol
  1138. {
  1139. StartCol = op.Region.Start,
  1140. EndCol = op.Region.End,
  1141. RowsCol = currentTimeLine,
  1142. });
  1143. obj.LastSeenLineStartCol = op.Region.Start;
  1144. obj.LastSeenLineEndCol = op.Region.End;
  1145. break;
  1146. case OperationType.Add:
  1147. activeObjects.Add(op.ObjectToAdd);
  1148. break;
  1149. }
  1150. }
  1151. hasChanges = true;
  1152. }
  1153. finally
  1154. {
  1155. _rwLock.ExitWriteLock();
  1156. }
  1157. }
  1158. return hasChanges;
  1159. }
  1160. // 辅助类定义
  1161. private enum OperationType
  1162. {
  1163. MergeMultiple,
  1164. Update,
  1165. Add
  1166. }
  1167. private class OperationItem
  1168. {
  1169. public OperationType Type { get; set; }
  1170. public List<ActiveObjectClass> ObjectsToRemove { get; set; }
  1171. public ActiveObjectClass ObjectToAdd { get; set; }
  1172. public ActiveObjectClass ObjectToUpdate { get; set; }
  1173. public ValidRegionModelClass Region { get; set; }
  1174. }
  1175. // 计算聚合值的方法
  1176. private (int minStartCol, int maxEndCol, long minStartLine, long maxLastSeenLine,
  1177. int minLastSeenStartCol, int maxLastSeenEndCol, DateTime minStartTime,
  1178. DateTime maxEndTime,DateTime pictureMinReadTime,int totalArea, List<RowStartEndCol> mergedRowsData, int imageWidth)
  1179. CalculateAggregates(List<ActiveObjectClass> objects)
  1180. {
  1181. int minStartCol = int.MaxValue;
  1182. int maxEndCol = int.MinValue;
  1183. long minStartLine = int.MaxValue;
  1184. long maxLastSeenLine = int.MinValue;
  1185. int minLastSeenStartCol = int.MaxValue;
  1186. int maxLastSeenEndCol = int.MinValue;
  1187. DateTime minStartTime = DateTime.MaxValue;
  1188. DateTime maxEndTime = DateTime.MinValue;
  1189. DateTime pictureMinReadTime = DateTime.MaxValue;
  1190. int totalArea = 0;
  1191. int imageWidth = objects.FirstOrDefault()?.ImageWidth ?? IdentifyImageWidth;
  1192. var mergedRowsData = new List<RowStartEndCol>();
  1193. foreach (var obj in objects)
  1194. {
  1195. minStartCol = Math.Min(minStartCol, obj.MinStartCol);
  1196. maxEndCol = Math.Max(maxEndCol, obj.MaxEndCol);
  1197. minStartLine = Math.Min(minStartLine, obj.StartLine);
  1198. maxLastSeenLine = Math.Max(maxLastSeenLine, obj.LastSeenLine);
  1199. minLastSeenStartCol = Math.Min(minLastSeenStartCol, obj.LastSeenLineStartCol);
  1200. maxLastSeenEndCol = Math.Max(maxLastSeenEndCol, obj.LastSeenLineEndCol);
  1201. if (obj.StartCheckTime < minStartTime) minStartTime = obj.StartCheckTime;
  1202. if (obj.EndCheckTime > maxEndTime) maxEndTime = obj.EndCheckTime;
  1203. if (obj.PictureStartReadTime<pictureMinReadTime) pictureMinReadTime = obj.PictureStartReadTime;
  1204. totalArea += obj.Area;
  1205. mergedRowsData.AddRange(obj.RowsData);
  1206. }
  1207. return (minStartCol, maxEndCol, minStartLine, maxLastSeenLine,
  1208. minLastSeenStartCol, maxLastSeenEndCol, minStartTime,
  1209. maxEndTime,pictureMinReadTime,totalArea, mergedRowsData, imageWidth);
  1210. }
  1211. List<ValidRegionModelClass> regions = new List<ValidRegionModelClass>();
  1212. /// <summary>
  1213. /// 检测有效物体区域(横向连续黑色像素段)
  1214. /// </summary>
  1215. /// <param name="line">当前行像素数组</param>
  1216. /// <returns>有效区域列表(起始/结束位置)</returns>
  1217. private List<ValidRegionModelClass> FindValidRegions(IImage image, int RowNo)
  1218. {
  1219. regions.Clear();
  1220. int start = -1; // 当前区域起始标记
  1221. int end = -1;
  1222. int imageWidth = (int)image.Width;
  1223. var pixelSpan = image.PixelData.AsSpan();
  1224. // 遍历所有像素列
  1225. if (shuLiConfig.IsIdentifyRoiOpen)
  1226. {
  1227. for (int i = imageWidth * RowNo + shuLiConfig.IdentifyStartX; i < imageWidth * RowNo + shuLiConfig.IdentifyStopX; i++)
  1228. {
  1229. if (pixelSpan[i] < shuLiConfig.RegionThreshold&& start == -1) // 发现黑色像素
  1230. {
  1231. start = i % imageWidth; // 开始新区域
  1232. }
  1233. else if (start != -1) // 遇到白色像素且存在进行中的区域
  1234. {
  1235. end = (i - 1) % imageWidth;
  1236. // 检查区域宽度是否达标
  1237. regions.Add(new ValidRegionModelClass()
  1238. {
  1239. Start = start,
  1240. End = end
  1241. }); // 记录有效区域
  1242. start = -1; // 重置区域标记
  1243. end = -1;
  1244. }
  1245. }
  1246. }
  1247. else
  1248. {
  1249. for (int i = imageWidth * RowNo; i < imageWidth * (RowNo + 1); i++)
  1250. {
  1251. if (pixelSpan[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  1252. {
  1253. if (start == -1) start = i % imageWidth; // 开始新区域
  1254. }
  1255. else if (start != -1) // 遇到白色像素且存在进行中的区域
  1256. {
  1257. end = (i - 1) % imageWidth;
  1258. regions.Add(new ValidRegionModelClass()
  1259. {
  1260. Start = start,
  1261. End = end
  1262. }); // 记录有效区域
  1263. start = -1; // 重置区域标记
  1264. end = -1;
  1265. }
  1266. }
  1267. }
  1268. // 处理行尾未闭合的区域
  1269. if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold)
  1270. {
  1271. regions.Add(new ValidRegionModelClass()
  1272. {
  1273. Start = start,
  1274. End = (int)image.Width - 1
  1275. });
  1276. }
  1277. return new List<ValidRegionModelClass>(regions);
  1278. }
  1279. private List<ValidRegionModelClass> FindValidRegions(ReadOnlySpan<byte> image,int imageWidth, int RowNo)
  1280. {
  1281. regions.Clear();
  1282. regions.Capacity = 10;
  1283. ReadOnlySpan<byte> rowSpan;
  1284. if (shuLiConfig.IsIdentifyRoiOpen)
  1285. {
  1286. int offset = RowNo * imageWidth + shuLiConfig.IdentifyStartX;
  1287. int length = Math.Min(shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX,
  1288. imageWidth - shuLiConfig.IdentifyStartX);
  1289. rowSpan = image.Slice(offset, length);
  1290. }
  1291. else
  1292. {
  1293. rowSpan = image.Slice(RowNo * imageWidth, imageWidth);
  1294. }
  1295. int start = -1;
  1296. byte threshold = (byte)shuLiConfig.RegionThreshold;
  1297. for (int i = 0; i < rowSpan.Length; i++)
  1298. {
  1299. if (rowSpan[i] < threshold)
  1300. {
  1301. if (start == -1) start = i;
  1302. }
  1303. else if (start != -1)
  1304. {
  1305. regions.Add(new ValidRegionModelClass { Start = start, End = i - 1 });
  1306. start = -1;
  1307. }
  1308. }
  1309. if (start != -1)
  1310. {
  1311. regions.Add(new ValidRegionModelClass { Start = start, End = rowSpan.Length - 1 });
  1312. }
  1313. return new List<ValidRegionModelClass>(regions);
  1314. }
  1315. /// <summary>
  1316. /// 判断区域重叠(与活跃物体的横向坐标重叠检测)
  1317. /// </summary>
  1318. /// <param name="obj">活跃物体</param>
  1319. /// <param name="region">当前区域</param>
  1320. /// <returns>是否发生重叠</returns>
  1321. private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass region)
  1322. {
  1323. // 判断区域是否不相交的逆条件
  1324. return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol);
  1325. }
  1326. /// <summary>
  1327. /// 通道区域判定
  1328. /// </summary>
  1329. /// <param name="activeObject"></param>
  1330. /// <returns></returns>
  1331. private int ActiveChannel(ActiveObjectClass activeObject)
  1332. {
  1333. int result = -1;
  1334. int StartChannel = activeObject.MinStartCol / ChannelWidth;
  1335. int EndChannel = activeObject.MaxEndCol / ChannelWidth;
  1336. if (StartChannel == EndChannel)
  1337. {
  1338. result = StartChannel;
  1339. }
  1340. else if (EndChannel - StartChannel > 1)
  1341. {
  1342. Console.WriteLine("ActiveChannel-Error");
  1343. //error
  1344. }
  1345. else
  1346. {
  1347. result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel;
  1348. }
  1349. return result;
  1350. }
  1351. /// <summary>
  1352. /// 检测是否存在显著的内凹
  1353. /// </summary>
  1354. /// <param name="originalPoints">原始点集</param>
  1355. /// <param name="convexHull">凸包点集</param>
  1356. /// <returns>是否存在显著内凹和程度比值</returns>
  1357. private ConvexResultClass DetectConcavity(List<Point> originalPoints, List<Point> convexHull)
  1358. {
  1359. if (convexHull.Count < 3) return new ConvexResultClass()
  1360. {
  1361. hasSignificantConcavity = false,
  1362. concavityRatio = 0,
  1363. };
  1364. // 检查原始点集中有多少点在凸包内部(即非凸包顶点)
  1365. HashSet<Point> hullPoints = new HashSet<Point>(convexHull);
  1366. var internalPoints = originalPoints.Where(p => !hullPoints.Contains(p)).ToList();
  1367. if (internalPoints.Count == 0)
  1368. {
  1369. // 所有点都在凸包上,没有内凹
  1370. return new ConvexResultClass()
  1371. {
  1372. hasSignificantConcavity = false,
  1373. concavityRatio = 0,
  1374. };
  1375. }
  1376. // 计算凸包面积
  1377. double convexHullArea = CalculatePolygonArea(SortPointsByNearestNeighbor(convexHull));
  1378. double PointsArea = CalculatePolygonArea(SortPointsByNearestNeighbor(originalPoints));
  1379. var concavityMeasure = (convexHullArea - PointsArea) / convexHullArea;
  1380. // 判断是否存在显著内凹(可以根据实际需求调整阈值)
  1381. bool hasSignificantConcavity = concavityMeasure > 0.3;
  1382. return new ConvexResultClass()
  1383. {
  1384. hasSignificantConcavity = hasSignificantConcavity,
  1385. concavityRatio = concavityMeasure,
  1386. };
  1387. }
  1388. /// <summary>
  1389. /// 计算封闭多边形的面积(鞋带公式)
  1390. /// </summary>
  1391. /// <param name="polygon">多边形顶点(必须是封闭的)</param>
  1392. /// <returns>面积</returns>
  1393. private double CalculatePolygonArea(List<Point> polygon)
  1394. {
  1395. if (polygon.Count < 3) return 0;
  1396. // 确保多边形是封闭的
  1397. var closedPolygon = new List<Point>(polygon);
  1398. if (closedPolygon.First() != closedPolygon.Last())
  1399. {
  1400. closedPolygon.Add(closedPolygon[0]);
  1401. }
  1402. double area = 0;
  1403. for (int i = 0; i < closedPolygon.Count - 1; i++)
  1404. {
  1405. area += (double)closedPolygon[i].X * closedPolygon[i + 1].Y;
  1406. area -= (double)closedPolygon[i].Y * closedPolygon[i + 1].X;
  1407. }
  1408. return Math.Abs(area) / 2.0;
  1409. }
  1410. /// <summary>
  1411. /// 凸包最长边,结果通过系数计算
  1412. /// </summary>
  1413. /// <param name="hull"></param>
  1414. /// <returns></returns>
  1415. public MaxLengthModel CoefficientRotatingCalipers(List<Point> hull)
  1416. {
  1417. //老方法
  1418. MaxLengthModel result = new MaxLengthModel();
  1419. int n = hull.Count;
  1420. if (n == 1)
  1421. {
  1422. result = null;
  1423. return result;
  1424. }
  1425. if (n == 2)
  1426. {
  1427. result.MaxLength = Distance(hull[0], hull[1]);
  1428. result.Point1Index = 0;
  1429. result.Point1 = hull[0];
  1430. result.Point2Index = 1;
  1431. result.Point2 = hull[1];
  1432. return result;
  1433. }
  1434. double maxDist = 0;
  1435. int pointIndex1 = 0;
  1436. int pointIndex2 = 0;
  1437. for (int i = 0; i < n; i++)
  1438. {
  1439. for (int j = i + 1; j < n; j++)
  1440. {
  1441. if (CoefficientDistance(hull[i], hull[j]) > maxDist)
  1442. {
  1443. maxDist = CoefficientDistance(hull[i], hull[j]);
  1444. pointIndex1 = i;
  1445. pointIndex2 = j;
  1446. }
  1447. }
  1448. }
  1449. result.Point1 = hull[pointIndex1];
  1450. result.Point1Index = pointIndex1;
  1451. result.Point2 = hull[pointIndex2];
  1452. result.Point2Index = pointIndex2;
  1453. result.MaxLength = maxDist;
  1454. return result;
  1455. }
  1456. /// <summary>
  1457. /// 凸包最长边
  1458. /// </summary>
  1459. /// <param name="hull"></param>
  1460. /// <returns></returns>
  1461. public MaxLengthModel RotatingCalipers(List<Point> hull)
  1462. {
  1463. //老方法
  1464. MaxLengthModel result = new MaxLengthModel();
  1465. int n = hull.Count;
  1466. if (n == 1)
  1467. {
  1468. result = null;
  1469. return result;
  1470. }
  1471. if (n == 2)
  1472. {
  1473. result.MaxLength = Distance(hull[0], hull[1]);
  1474. result.Point1Index = 0;
  1475. result.Point1 = hull[0];
  1476. result.Point2Index = 1;
  1477. result.Point2 = hull[1];
  1478. return result;
  1479. }
  1480. double maxDist = 0;
  1481. int pointIndex1 = 0;
  1482. int pointIndex2 = 0;
  1483. for (int i = 0; i < n; i++)
  1484. {
  1485. for (int j = i + 1; j < n; j++)
  1486. {
  1487. if (Distance(hull[i], hull[j]) > maxDist)
  1488. {
  1489. maxDist = Distance(hull[i], hull[j]);
  1490. pointIndex1 = i;
  1491. pointIndex2 = j;
  1492. }
  1493. }
  1494. }
  1495. result.Point1 = hull[pointIndex1];
  1496. result.Point1Index = pointIndex1;
  1497. result.Point2 = hull[pointIndex2];
  1498. result.Point2Index = pointIndex2;
  1499. result.MaxLength = Distance(result.Point1, result.Point2);
  1500. return result;
  1501. }
  1502. /// <summary>
  1503. /// 计算凸包的最小外接矩形
  1504. /// </summary>
  1505. /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
  1506. /// <returns>最小外接矩形的四个顶点</returns>
  1507. public BoundingRectangleMdoel CalculateMinimumBoundingRectangle(List<Point> convexHull)
  1508. {
  1509. BoundingRectangleMdoel result = new BoundingRectangleMdoel();
  1510. if (convexHull == null || convexHull.Count < 3)
  1511. return null;
  1512. double minArea = double.MaxValue;
  1513. int n = convexHull.Count;
  1514. // 遍历每一条边作为基准边
  1515. for (int i = 0; i < n; i++)
  1516. {
  1517. Point edgeStart = convexHull[i];
  1518. Point edgeEnd = convexHull[(i + 1) % n];
  1519. // 计算当前边的角度
  1520. double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
  1521. // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
  1522. var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
  1523. // 找到包围盒
  1524. int minX = rotatedPoints.Min(p => p.X);
  1525. int maxX = rotatedPoints.Max(p => p.X);
  1526. int minY = rotatedPoints.Min(p => p.Y);
  1527. int maxY = rotatedPoints.Max(p => p.Y);
  1528. // 计算面积
  1529. double area = (maxX - minX) * (maxY - minY);
  1530. // 如果面积更小,则更新最小矩形
  1531. if (area < minArea)
  1532. {
  1533. minArea = area;
  1534. // 构造矩形的四个顶点并旋转回原来的角度
  1535. var rectangle = new List<Point>
  1536. {
  1537. RotatePoint(new Point(minX, minY), angle),
  1538. RotatePoint(new Point(maxX, minY), angle),
  1539. RotatePoint(new Point(maxX, maxY), angle),
  1540. RotatePoint(new Point(minX, maxY), angle)
  1541. };
  1542. result.points = rectangle;
  1543. result.Angle = angle;
  1544. result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
  1545. }
  1546. }
  1547. double l1 = Distance(result.points[0], result.points[1]);
  1548. double l2 = Distance(result.points[1], result.points[2]);
  1549. if (l1 < l2)
  1550. {
  1551. result.WidthPoints = new List<Point>
  1552. {
  1553. result.points[0],
  1554. result.points[1],
  1555. };
  1556. result.HeightPoints = new List<Point>
  1557. {
  1558. result.points[1],
  1559. result.points[2],
  1560. };
  1561. }
  1562. else
  1563. {
  1564. result.WidthPoints = new List<Point>
  1565. {
  1566. result.points[2],
  1567. result.points[1],
  1568. };
  1569. result.HeightPoints = new List<Point>
  1570. {
  1571. result.points[1],
  1572. result.points[0],
  1573. };
  1574. }
  1575. result.Height = Math.Max(l1, l2);
  1576. result.Width = Math.Min(l1, l2);
  1577. return result;
  1578. }
  1579. /// <summary>
  1580. /// 计算凸包的最小外接矩形
  1581. /// </summary>
  1582. /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
  1583. /// <returns>最小外接矩形的四个顶点</returns>
  1584. public BoundingRectangleMdoel CoefficientCalculateMinimumBoundingRectangle(List<Point> convexHull)
  1585. {
  1586. BoundingRectangleMdoel result = new BoundingRectangleMdoel();
  1587. if (convexHull == null || convexHull.Count < 3)
  1588. return null;
  1589. double minArea = double.MaxValue;
  1590. int n = convexHull.Count;
  1591. // 遍历每一条边作为基准边
  1592. for (int i = 0; i < n; i++)
  1593. {
  1594. Point edgeStart = convexHull[i];
  1595. Point edgeEnd = convexHull[(i + 1) % n];
  1596. // 计算当前边的角度
  1597. double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
  1598. // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
  1599. var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
  1600. // 找到包围盒
  1601. int minX = rotatedPoints.Min(p => p.X);
  1602. int maxX = rotatedPoints.Max(p => p.X);
  1603. int minY = rotatedPoints.Min(p => p.Y);
  1604. int maxY = rotatedPoints.Max(p => p.Y);
  1605. // 计算面积
  1606. double area = (maxX - minX) * (maxY - minY);
  1607. // 如果面积更小,则更新最小矩形
  1608. if (area < minArea)
  1609. {
  1610. minArea = area;
  1611. // 构造矩形的四个顶点并旋转回原来的角度
  1612. var rectangle = new List<Point>
  1613. {
  1614. RotatePoint(new Point(minX, minY), angle),
  1615. RotatePoint(new Point(maxX, minY), angle),
  1616. RotatePoint(new Point(maxX, maxY), angle),
  1617. RotatePoint(new Point(minX, maxY), angle)
  1618. };
  1619. result.points = rectangle;
  1620. result.Angle = angle;
  1621. result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
  1622. }
  1623. }
  1624. double l1 = CoefficientDistance(result.points[0], result.points[1]);
  1625. double l2 = CoefficientDistance(result.points[1], result.points[2]);
  1626. if (l1 < l2)
  1627. {
  1628. result.WidthPoints = new List<Point>
  1629. {
  1630. result.points[0],
  1631. result.points[1],
  1632. };
  1633. result.HeightPoints = new List<Point>
  1634. {
  1635. result.points[1],
  1636. result.points[2],
  1637. };
  1638. }
  1639. else
  1640. {
  1641. result.WidthPoints = new List<Point>
  1642. {
  1643. result.points[2],
  1644. result.points[1],
  1645. };
  1646. result.HeightPoints = new List<Point>
  1647. {
  1648. result.points[1],
  1649. result.points[0],
  1650. };
  1651. }
  1652. result.Height = Math.Max(l1, l2);
  1653. result.Width = Math.Min(l1, l2);
  1654. return result;
  1655. }
  1656. public List<Point> SortPointsByNearestNeighbor(List<Point> points)
  1657. {
  1658. if (points.Count <= 1) return points;
  1659. var sorted = new List<Point>();
  1660. var remaining = new HashSet<Point>(points);
  1661. // 选择起始点(例如最左边的点)
  1662. var current = remaining.OrderBy(p => p.X).First();
  1663. sorted.Add(current);
  1664. remaining.Remove(current);
  1665. while (remaining.Any())
  1666. {
  1667. // 找到最近的点
  1668. var nearest = remaining.OrderBy(p => Distance(current, p)).First();
  1669. sorted.Add(nearest);
  1670. remaining.Remove(nearest);
  1671. current = nearest;
  1672. }
  1673. return sorted;
  1674. }
  1675. /// <summary>
  1676. /// 绕原点旋转点
  1677. /// </summary>
  1678. /// <param name="point">待旋转的点</param>
  1679. /// <param name="angle">旋转角度(弧度)</param>
  1680. /// <returns>旋转后的点</returns>
  1681. private static Point RotatePoint(Point point, double angle)
  1682. {
  1683. double cos = Math.Cos(angle);
  1684. double sin = Math.Sin(angle);
  1685. return new Point(
  1686. (int)(point.X * cos - point.Y * sin),
  1687. (int)(point.X * sin + point.Y * cos)
  1688. );
  1689. }
  1690. /// <summary>
  1691. /// 计算点到线段的距离
  1692. /// </summary>
  1693. /// <param name="a">线段点1</param>
  1694. /// <param name="b">线段点2</param>
  1695. /// <param name="c">点三</param>
  1696. /// <returns></returns>
  1697. private double DistanceToLine(Point a, Point b, Point c)
  1698. {
  1699. double area = Math.Abs(Area2(a, b, c));
  1700. double baseLength = Distance(a, b);
  1701. return area / baseLength;
  1702. }
  1703. // 计算向量叉积
  1704. private int Cross(Point o, Point a, Point b) =>
  1705. (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X);
  1706. // 计算三角形面积的两倍
  1707. private int Area2(Point a, Point b, Point c) =>
  1708. (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
  1709. // 计算两点间距离
  1710. private double Distance(Point a, Point b)
  1711. {
  1712. int dx = a.X - b.X;
  1713. int dy = a.Y - b.Y;
  1714. return Math.Sqrt((dx * dx) + (dy * dy));
  1715. }
  1716. private double CoefficientDistance(Point a,Point b)
  1717. {
  1718. double dx = (a.X - b.X) * XCoefficient;
  1719. double dy = (a.Y - b.Y) * YCoefficient;
  1720. return Math.Sqrt((dx * dx) + (dy * dy));
  1721. }
  1722. private bool TryAdd(List<ActiveObjectClass> list, ActiveObjectClass item, int maxSize)
  1723. {
  1724. list.Add(item);
  1725. if (list.Count > maxSize)
  1726. {
  1727. list[list.Count - maxSize].RowsData.Clear();
  1728. }
  1729. return true;
  1730. }
  1731. #endregion
  1732. #region 线程方法
  1733. //信号量
  1734. public SemaphoreSlim QueueSemaphore { get; set; }
  1735. //取消令牌
  1736. public CancellationTokenSource CancellationTokenSource { get; set; }
  1737. //图像处理线程
  1738. public Task ProcessingTask { get; set; }
  1739. /// <summary>
  1740. /// 识别图像线程
  1741. /// </summary>
  1742. private void IdentifyImageProcess()
  1743. {
  1744. Stopwatch stopwatch = Stopwatch.StartNew();
  1745. while (IsIdentify)
  1746. {
  1747. //判断队列中是否有数据
  1748. if (IFrameDatas.Count() > 0)
  1749. {
  1750. stopwatch.Restart();
  1751. if (IFrameDatas.Count() > 5)
  1752. {
  1753. //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
  1754. FaultLog.RecordErrorMessage($"There is too much data in the image data queue. Please handle it in a timely manner! The current data quantity is:{IFrameDatas.Count()}");
  1755. if (IFrameDatas.Count() > 100)
  1756. {
  1757. SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积,
  1758. $"The image data queue is blocked. Please check the configuration and images",
  1759. "待识别队列数据堆积,请检查设置和图像",
  1760. "DLL:ShuLIClass-IdentifyImageProcess");
  1761. }
  1762. else
  1763. {
  1764. SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
  1765. }
  1766. }
  1767. IFrameDatas.TryDequeue(out IFrameOut IframeData);
  1768. stopwatch.Stop();
  1769. var ShiBieLuoHouTime = (DateTime.Now - FromUnixTimestamp((long)IframeData.HostTimeStamp)).TotalMilliseconds;
  1770. // 优化: 将日志记录改为条件执行
  1771. if (ShiBieLuoHouTime > 30)
  1772. {
  1773. LOG.error($"算法落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
  1774. }
  1775. if (stopwatch.ElapsedMilliseconds > 5)
  1776. {
  1777. FaultLog.RecordErrorMessage($"ShuLiClass-IdentifyImageProcess:Image reading timed out, this recognition took time:{stopwatch.Elapsed}");
  1778. }
  1779. if (IframeData != null)
  1780. {
  1781. //识别
  1782. ProcessImageSequence2(IframeData);
  1783. IframeData.Dispose();
  1784. }
  1785. else
  1786. {
  1787. continue;
  1788. }
  1789. }
  1790. else
  1791. {
  1792. Thread.Sleep(1);
  1793. }
  1794. }
  1795. }
  1796. private async void IdentifyImageProcessTask(CancellationToken token)
  1797. {
  1798. Stopwatch stopwatch = Stopwatch.StartNew();
  1799. const int batchSize = 5; // 每批处理的最大图像数量
  1800. var batch = new List<IFrameOut>(batchSize); // 存储当前批次的图像
  1801. while (!token.IsCancellationRequested)
  1802. {
  1803. // 等待图像数据
  1804. await QueueSemaphore.WaitAsync(token);
  1805. //判断队列中是否有数据
  1806. if (IFrameDatas.Count() > 5)
  1807. {
  1808. //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
  1809. FaultLog.RecordErrorMessage($"There is too much data in the image data queue. Please handle it in a timely manner! The current data quantity is:{IFrameDatas.Count()}");
  1810. if (IFrameDatas.Count() > 100)
  1811. {
  1812. SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积,
  1813. $"The image data queue is blocked. Please check the configuration and images",
  1814. "待识别队列数据堆积,请检查设置和图像",
  1815. "DLL:ShuLIClass-IdentifyImageProcess");
  1816. }
  1817. else
  1818. {
  1819. SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
  1820. }
  1821. }
  1822. // 收集一批图像(严格按队列顺序)
  1823. while (batch.Count < batchSize && IFrameDatas.TryDequeue(out IFrameOut imageData))
  1824. {
  1825. batch.Add(imageData);
  1826. if (IFrameDatas.Count > 0)
  1827. {
  1828. QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
  1829. }
  1830. }
  1831. // 顺序处理当前批次的所有图像
  1832. foreach (var image in batch)
  1833. {
  1834. if (image != null)
  1835. {
  1836. //识别
  1837. ProcessImageSequence2(image);
  1838. image.Dispose();
  1839. }
  1840. else
  1841. {
  1842. continue;
  1843. }
  1844. }
  1845. batch.Clear();
  1846. }
  1847. }
  1848. #endregion
  1849. }
  1850. }