ShuLiClass.cs 80 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082
  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 ConcurrentQueue<IFrameOut> IFrameDatas = new ConcurrentQueue<IFrameOut>(); //图像数据队列
  30. private LockFreeRingBuffer<IFrameOut> RingBuffer_IFrameDatas = new LockFreeRingBuffer<IFrameOut>(300);
  31. private Thread IdentifyImageProcessThread = null; // 识别线程
  32. private bool IsIdentify = false; //线程是否开始识别的标志
  33. private long currentLine = 0; //行数记录
  34. private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件
  35. public List<int> ChannelsRoi { get { return _ChannelsRoi; } }
  36. private List<int> _ChannelsRoi = new List<int>();
  37. private int ChannelWidth = 0;//每个区域的宽度
  38. private int IdentifyImageWidth = -1;
  39. private int ObjectNum = 0;
  40. public int ImageNum { get { return IFrameDatas.Count; } }
  41. private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
  42. private double XCoefficient = 1;
  43. private double YCoefficient = 2;
  44. private DateTime[] ChannelIntervalTime = new DateTime[8];
  45. private int _HistoryActiveNum = 0;
  46. public int HistoryActiveNum { get { return _HistoryActiveNum; } }
  47. private int _OkHistoryNum = 0;
  48. public int OkHistoryNum { get { return _OkHistoryNum; } }
  49. private int _NgHistoryNum = 0;
  50. public int NgHistoryNum { get { return _NgHistoryNum; } }
  51. #endregion
  52. #region 公共方法
  53. /// <summary>
  54. /// 初始化构造方法
  55. /// </summary>
  56. public ShuLiClass()
  57. {
  58. // 加载默认参数
  59. shuLiConfig = new ShuLiConfigClass()
  60. {
  61. Channel = 8,
  62. PandingCode = 2
  63. };
  64. for(int i = 0; i < ChannelIntervalTime.Length; i++)
  65. {
  66. ChannelIntervalTime[i] = DateTime.Now;
  67. }
  68. }
  69. /// <summary>
  70. /// 带参数的构造方法
  71. /// </summary>
  72. public ShuLiClass(ShuLiConfigClass config)
  73. {
  74. if (config.IsLoadCanfig)
  75. {
  76. // 加载传出的参数
  77. shuLiConfig = config;
  78. XCoefficient = shuLiConfig.ScaleX;
  79. YCoefficient = shuLiConfig.ScaleY;
  80. }
  81. else
  82. {
  83. // 加载默认参数
  84. shuLiConfig = new ShuLiConfigClass()
  85. {
  86. Channel = 8,
  87. PandingCode = 2
  88. };
  89. }
  90. InitChannel();
  91. }
  92. public long InitCurrentLine(int line)
  93. {
  94. currentLine = line;
  95. return currentLine;
  96. }
  97. public int InitNum(int num)
  98. {
  99. ObjectNum = num;
  100. return ObjectNum;
  101. }
  102. List<ActiveObjectClass> lostObjects = new List<ActiveObjectClass>();
  103. /// <summary>
  104. /// 处理图像序列的主入口
  105. /// </summary>
  106. /// <param name="image">图像像素数据</param>
  107. /// <param name="ImageWidth">图像宽</param>
  108. /// <param name="currentLine">当前行数</param>
  109. /// <returns>检测到的物体总数</returns>
  110. public bool ProcessImageSequence(IFrameOut image)
  111. {
  112. bool result = false;
  113. ReadOnlySpan<byte> spanFromArr = image.Image.PixelData.AsSpan();
  114. for (int i = 0; i < image.Image.Height; i++)
  115. {
  116. result = ProcessLine2(spanFromArr,image.HostTimeStamp,image.Image.Width, i);
  117. currentLine += 1;
  118. }
  119. //识别到结果并输出
  120. _rwLock.EnterReadLock();
  121. // 清理超时未更新的物体
  122. lostObjects = activeObjects
  123. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  124. .ToList();
  125. _rwLock.ExitReadLock();
  126. _rwLock.EnterWriteLock();
  127. lostObjects.ForEach(o => activeObjects.Remove(o));
  128. _rwLock.ExitWriteLock();
  129. List<ActiveObjectClass> OneActive = new List<ActiveObjectClass>();
  130. // 有物体转变为活跃物体,返回值转为true
  131. if (lostObjects.Count > 0)
  132. {
  133. //Stopwatch stopwatch = Stopwatch.StartNew();
  134. result = true;
  135. //stopwatch.Restart();
  136. foreach (var item in lostObjects)
  137. {
  138. item.PictureEndReadTime = FromUnixTimestamp((long)image.HostTimeStamp);
  139. var QutuYanshiTime = (DateTime.Now - item.PictureEndReadTime).TotalMilliseconds;
  140. if (QutuYanshiTime > 20)
  141. {
  142. //LOG.error($"结果输出延时超过了20ms,耗时{QutuYanshiTime}");
  143. Console.WriteLine($"结果输出延时超过了20ms,耗时{QutuYanshiTime}");
  144. }
  145. //噪点判定
  146. //if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  147. //{
  148. // item.StateCode = 9;
  149. // //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6);
  150. // continue;
  151. //}
  152. //if(item.StartLine == item.LastSeenLine)
  153. //{
  154. // item.MaxLength = item.MaxEndCol - item.MinStartCol;
  155. // item.StateCode = 10;
  156. // LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num));
  157. //}
  158. //else
  159. //{
  160. // if((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height)
  161. // {
  162. // var CalculationResult = SizeCalculation(item.RowsData);
  163. // if(CalculationResult!=null)
  164. // {
  165. // item.MaxLength = CalculationResult.Height;
  166. // }
  167. // //item.hasSignificantConcavity = CalculationResult.hasSignificantConcavity;
  168. // //item.concavityRatio = CalculationResult.concavityRatio;
  169. // //if(CalculationResult.hasSignificantConcavity)
  170. // //{
  171. // // if(CalculationResult.concavityRatio>0.3)
  172. // // {
  173. // // item.StateCode= 11;
  174. // // }
  175. // //}
  176. // }
  177. // else
  178. // {
  179. // item.StateCode = 7;
  180. // }
  181. //}
  182. //if (shuLiConfig.PandingCode != -1)
  183. //{
  184. // if (item.StateCode != -1)
  185. // {
  186. // if (item.StateCode == 8)
  187. // {
  188. // //LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  189. // }
  190. // else if (item.StateCode == 7)
  191. // {
  192. // //LOG.log(string.Format("颗粒编号{0}:视野被遮挡", item.Num));
  193. // }
  194. // }
  195. // else if (item.Area < shuLiConfig.MinArea
  196. // && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  197. // {
  198. // item.StateCode = 6;
  199. // //LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num));
  200. // //Console.WriteLine("颗粒编号{0}:面积过小", item.Num);
  201. // }
  202. // else if (item.Area > shuLiConfig.MaxArea
  203. // && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  204. // {
  205. // item.StateCode = 5;
  206. // //LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  207. // //Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
  208. // }
  209. // else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH
  210. // && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  211. // {
  212. // item.StateCode = 2;
  213. // //LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  214. // //Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
  215. // }
  216. // else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH
  217. // && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  218. // {
  219. // item.StateCode = 1;
  220. // //LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  221. // //Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
  222. // }
  223. // else
  224. // {
  225. // item.StateCode = 0;
  226. // //LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  227. // //Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
  228. // }
  229. //}
  230. if(item.StateCode != 7&&item.StateCode!=9)
  231. {
  232. //转为历史物体,添加缺少的参数
  233. item.Num = Interlocked.Increment(ref ObjectNum);
  234. item.ChannelNO = ActiveChannel(item);
  235. }
  236. item.EndCheckTime = DateTime.Now;
  237. //if(item.StateCode != 9)
  238. OneActive.Add(item);
  239. }
  240. //stopwatch.Stop();
  241. //if (stopwatch.ElapsedMilliseconds > 1)
  242. //{
  243. // Console.WriteLine($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
  244. // //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
  245. //}
  246. //stopwatch.Restart();
  247. if (OneActive.Count > 0)
  248. {
  249. //ThreadPool.QueueUserWorkItem(_ =>
  250. //{
  251. //触发回调事件
  252. OnWorkCompleted(OneActive);
  253. //});
  254. }
  255. // stopwatch.Stop();
  256. //if (stopwatch.ElapsedMilliseconds > 1)
  257. //{
  258. // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果完成回调函数,此次识别耗时:{stopwatch.Elapsed}");
  259. //}
  260. }
  261. else
  262. {
  263. OneActive.Clear();
  264. }
  265. return result;
  266. }
  267. /// <summary>
  268. /// 处理图像序列的主入口
  269. /// </summary>
  270. /// <param name="image">图像像素数据</param>
  271. /// <returns>检测到的物体总数</returns>
  272. public bool ProcessImageSequence2(IFrameOut image)
  273. {
  274. bool result = false;
  275. ReadOnlySpan<byte> spanFromArr = image.Image.PixelData.AsSpan();
  276. for (int i = 0; i < image.Image.Height; i++)
  277. {
  278. //result = ProcessLine2(spanFromArr, image.HostTimeStamp, image.Image.Width, i);
  279. result = ProcessLine(image, i);
  280. currentLine += 1;
  281. }
  282. // 优化: 减少锁的使用频率,批量处理逻辑
  283. List<ActiveObjectClass> objectsToProcess = null;
  284. _rwLock.EnterReadLock();
  285. try
  286. {
  287. // 使用ToList()避免在锁内进行复杂查询
  288. objectsToProcess = activeObjects
  289. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP ||
  290. (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  291. .ToList();
  292. }
  293. finally
  294. {
  295. _rwLock.ExitReadLock();
  296. }
  297. // 优化: 提前返回,避免不必要的处理
  298. if (objectsToProcess.Count == 0)
  299. {
  300. return result;
  301. }
  302. // 优化: 预分配容量,减少内存重新分配
  303. var processedObjects = new List<ActiveObjectClass>(objectsToProcess.Count);
  304. // 优化: 将耗时的计算移到循环外
  305. var endTime = DateTime.Now;
  306. var pictureEndTime = FromUnixTimestamp((long)image.HostTimeStamp);
  307. foreach (var item in objectsToProcess)
  308. {
  309. // 优化: 将耗时的计算移到循环外
  310. ProcessSingleObject(item, pictureEndTime, endTime, processedObjects);
  311. }
  312. result = true;
  313. // 优化: 批量操作减少锁的持有时间
  314. if (processedObjects.Count > 0)
  315. {
  316. _rwLock.EnterWriteLock();
  317. try
  318. {
  319. // 批量转移对象
  320. foreach (var item in processedObjects)
  321. {
  322. TryAdd(historyActiveObjects, item, 2500);
  323. }
  324. // 批量移除
  325. foreach (var item in objectsToProcess)
  326. {
  327. activeObjects.Remove(item);
  328. }
  329. }
  330. finally
  331. {
  332. _rwLock.ExitWriteLock();
  333. }
  334. // 优化: 异步触发事件,避免阻塞主线程
  335. //ThreadPool.QueueUserWorkItem(_ =>
  336. //{
  337. OnWorkCompleted(processedObjects);
  338. //});
  339. }
  340. return result;
  341. }
  342. // 抽取的单个对象处理方法
  343. private void ProcessSingleObject(ActiveObjectClass item, DateTime pictureEndTime,
  344. DateTime endTime, List<ActiveObjectClass> processedObjects)
  345. {
  346. item.PictureEndReadTime = pictureEndTime;
  347. var nowTime = DateTime.Now;
  348. var ShiBieLuoHouTime = (nowTime - item.PictureEndReadTime).TotalMilliseconds;
  349. //// 优化: 将日志记录改为条件执行
  350. if (ShiBieLuoHouTime > 30)
  351. {
  352. //LOG.error($"算法识别结果落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
  353. Console.WriteLine($"算法识别结果落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
  354. }
  355. // 快速滤除噪点
  356. if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  357. {
  358. item.StateCode = 9;
  359. return;
  360. }
  361. // 计算长度
  362. if (item.StartLine == item.LastSeenLine)
  363. {
  364. item.MaxLength = item.MaxEndCol - item.MinStartCol;
  365. item.StateCode = 10;
  366. LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num));
  367. }
  368. else
  369. {
  370. if ((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height)
  371. {
  372. var calculationResult = SizeCalculation(item.RowsData);
  373. if (calculationResult != null)
  374. {
  375. item.MaxLength = calculationResult.Height;
  376. }
  377. }
  378. else
  379. {
  380. item.StateCode = 7;
  381. }
  382. }
  383. // 分类处理
  384. ApplyClassificationRules(item);
  385. // 添加到历史记录
  386. if (item.StateCode != 7 && item.StateCode != 9)
  387. {
  388. item.Num = Interlocked.Increment(ref ObjectNum);
  389. item.ChannelNO = ActiveChannel(item);
  390. }
  391. item.EndCheckTime = endTime;
  392. if (item.StateCode != 9)
  393. {
  394. processedObjects.Add(item);
  395. }
  396. }
  397. // 抽取分类规则,便于维护
  398. private void ApplyClassificationRules(ActiveObjectClass item)
  399. {
  400. if (shuLiConfig.PandingCode == -1) return;
  401. if (item.StateCode != -1)
  402. {
  403. if (item.StateCode == 8)
  404. {
  405. LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  406. }
  407. else if (item.StateCode == 7)
  408. {
  409. LOG.log(string.Format("颗粒编号{0}:视野被遮挡", item.Num));
  410. }
  411. }
  412. else if (item.Area < shuLiConfig.MinArea &&
  413. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  414. {
  415. item.StateCode = 6;
  416. LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num));
  417. }
  418. else if (item.Area > shuLiConfig.MaxArea &&
  419. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  420. {
  421. item.StateCode = 5;
  422. LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  423. }
  424. else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH &&
  425. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  426. {
  427. item.StateCode = 2;
  428. LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  429. }
  430. else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH &&
  431. (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  432. {
  433. item.StateCode = 1;
  434. LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  435. }
  436. else
  437. {
  438. item.StateCode = 0;
  439. LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  440. }
  441. }
  442. /// <summary>
  443. /// 返回最后一个历史物品
  444. /// </summary>
  445. /// <returns></returns>
  446. public ActiveObjectClass GetLastActive()
  447. {
  448. if (historyActiveObjects.Count() == 0)
  449. return null;
  450. _rwLock.EnterReadLock();
  451. var result = historyActiveObjects.Last();
  452. _rwLock.ExitReadLock();
  453. return result;
  454. }
  455. /// <summary>
  456. /// 返回历史物品
  457. /// </summary>
  458. /// <returns></returns>
  459. public List<ActiveObjectClass> GetHistoryActive()
  460. {
  461. //lock (_lockObj) // 加锁
  462. //{
  463. _rwLock.EnterReadLock();
  464. var result = historyActiveObjects.ToList();
  465. _rwLock.ExitReadLock();
  466. return result;
  467. //}
  468. }
  469. /// <summary>
  470. /// 清除历史数据
  471. /// </summary>
  472. /// <returns></returns>
  473. public bool ClearHistoryActive()
  474. {
  475. _HistoryActiveNum = 0;
  476. _OkHistoryNum = 0;
  477. _NgHistoryNum = 0;
  478. _rwLock.EnterWriteLock();
  479. historyActiveObjects.Clear();
  480. _rwLock.ExitWriteLock();
  481. return true;
  482. //}
  483. }
  484. /// <summary>
  485. /// 开启识别
  486. /// </summary>
  487. public void StartIdentifyFuntion(int ImaageWidth)
  488. {
  489. UpdateIdentifyImageWidth(ImaageWidth);
  490. InitChannel();
  491. try
  492. {
  493. // 标志位置位true
  494. IsIdentify = true;
  495. // 打开识别线程
  496. IdentifyImageProcessThread = new Thread(IdentifyImageProcess)
  497. {
  498. Priority = ThreadPriority.AboveNormal
  499. };
  500. IdentifyImageProcessThread.Start();
  501. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程启动失败);
  502. }
  503. catch (Exception ex)
  504. {
  505. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程启动失败,
  506. "Start thread failed!, " + ex.Message,
  507. "识别线程启动失败, " + ex.Message,
  508. "DLL:ShuLiClass-StartIdentifyFuntion");
  509. //FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message);
  510. throw;
  511. }
  512. }
  513. /// <summary>
  514. /// 开启识别-优化版本
  515. /// </summary>
  516. /// <param name="ImaageWidth"></param>
  517. public void StartIdentifyFuntion2(int ImaageWidth)
  518. {
  519. UpdateIdentifyImageWidth(ImaageWidth);
  520. InitChannel();
  521. try
  522. {
  523. CancellationTokenSource = new CancellationTokenSource();
  524. var token = CancellationTokenSource.Token;
  525. // 启动图像处理线程
  526. ProcessingTask = Task.Factory.StartNew(() => IdentifyImageProcessTask2(token),
  527. token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
  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. /// <summary>
  541. /// 关闭识别
  542. /// </summary>
  543. public void StopIdentifyFuntion()
  544. {
  545. try
  546. {
  547. // 标志位设为false
  548. IsIdentify = false;
  549. if (IdentifyImageProcessThread != null && IdentifyImageProcessThread.IsAlive)
  550. IdentifyImageProcessThread.Join();
  551. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败);
  552. }
  553. catch (Exception ex)
  554. {
  555. //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
  556. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败,
  557. "Stop thread failed!, " + ex.Message,
  558. "识别线程停止失败, " + ex.Message,
  559. "DLL:ShuLiClass-StopIdentifyFuntion");
  560. throw;
  561. }
  562. }
  563. public async void StopIdentifyFuntion2()
  564. {
  565. try
  566. {
  567. // 发送取消信号
  568. CancellationTokenSource.Cancel();
  569. // 等待线程完成
  570. try
  571. {
  572. if (ProcessingTask != null)
  573. await ProcessingTask;
  574. }
  575. catch (OperationCanceledException)
  576. {
  577. // 正常取消,忽略异常
  578. }
  579. SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败);
  580. }
  581. catch (Exception ex)
  582. {
  583. //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
  584. SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败,
  585. "Stop thread failed!, " + ex.Message,
  586. "识别线程停止失败, " + ex.Message,
  587. "DLL:ShuLiClass-StopIdentifyFuntion");
  588. throw;
  589. }
  590. }
  591. /// <summary>
  592. /// 向识别队列添加一个数据
  593. /// </summary>
  594. /// <param name="items"></param>
  595. public void SetOnceIdentifyImageData(IFrameOut items)
  596. {
  597. IFrameDatas.Enqueue(items.Clone() as IFrameOut);
  598. //IFrameDatas.Enqueue(items );
  599. }
  600. /// <summary>
  601. /// 向识别队列添加一个数据
  602. /// </summary>
  603. /// <param name="items"></param>
  604. public void SetOnceIdentifyImageData2(IFrameOut items)
  605. {
  606. RingBuffer_IFrameDatas.TryEnqueue(items.Clone() as IFrameOut);
  607. //IFrameDatas.Enqueue(items );
  608. }
  609. /// <summary>
  610. /// 保存参数
  611. /// </summary>
  612. public void SaveConfig()
  613. {
  614. if (!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\");
  615. XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml");
  616. }
  617. /// <summary>
  618. /// 更新检测宽度信息
  619. /// </summary>
  620. /// <param name="Width"></param>
  621. public void UpdateIdentifyImageWidth(int Width)
  622. {
  623. IdentifyImageWidth = Width;
  624. }
  625. /// <summary>
  626. /// 初始化通道划分
  627. /// </summary>
  628. /// <param name="ImageWidth"></param>
  629. public void InitChannel()
  630. {
  631. _ChannelsRoi.Clear();
  632. shuLiConfig.ImageWidth = IdentifyImageWidth == -1 ? shuLiConfig.ImageWidth : IdentifyImageWidth;
  633. if (shuLiConfig.Channel > 0)
  634. {
  635. if (shuLiConfig.IsIdentifyRoiOpen)
  636. {
  637. ChannelWidth = (shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX) / shuLiConfig.Channel;
  638. }
  639. else
  640. {
  641. ChannelWidth = shuLiConfig.ImageWidth / shuLiConfig.Channel;
  642. }
  643. for (int i = 0; i < shuLiConfig.Channel; i++)
  644. {
  645. _ChannelsRoi.Add(ChannelWidth + i * ChannelWidth);
  646. }
  647. }
  648. }
  649. /// <summary>
  650. /// 获取配置信息
  651. /// </summary>
  652. /// <returns></returns>
  653. public ShuLiConfigClass GetConfigValue()
  654. {
  655. ShuLiConfigClass result = shuLiConfig;
  656. return result;
  657. }
  658. public bool TryAddHis(ActiveObjectClass item, int maxSize)
  659. {
  660. ThreadPool.QueueUserWorkItem(_ =>
  661. {
  662. _rwLock.EnterWriteLock();
  663. historyActiveObjects.Add(item);
  664. if (historyActiveObjects.Count > maxSize)
  665. {
  666. historyActiveObjects.Remove(historyActiveObjects[historyActiveObjects.Count - maxSize]);
  667. }
  668. _rwLock.ExitWriteLock();
  669. });
  670. return true;
  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. var pixelSpan = image.PixelData;
  1225. // 遍历所有像素列
  1226. if (shuLiConfig.IsIdentifyRoiOpen)
  1227. {
  1228. for (int i = imageWidth * RowNo + shuLiConfig.IdentifyStartX; i < imageWidth * RowNo + shuLiConfig.IdentifyStopX; i++)
  1229. {
  1230. if (pixelSpan[i] < shuLiConfig.RegionThreshold&& start == -1) // 发现黑色像素
  1231. {
  1232. start = i % imageWidth; // 开始新区域
  1233. }
  1234. else if (start != -1) // 遇到白色像素且存在进行中的区域
  1235. {
  1236. end = (i - 1) % imageWidth;
  1237. // 检查区域宽度是否达标
  1238. regions.Add(new ValidRegionModelClass()
  1239. {
  1240. Start = start,
  1241. End = end
  1242. }); // 记录有效区域
  1243. start = -1; // 重置区域标记
  1244. end = -1;
  1245. }
  1246. }
  1247. }
  1248. else
  1249. {
  1250. for (int i = imageWidth * RowNo; i < imageWidth * (RowNo + 1); i++)
  1251. {
  1252. if (pixelSpan[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  1253. {
  1254. if (start == -1) start = i % imageWidth; // 开始新区域
  1255. }
  1256. else if (start != -1) // 遇到白色像素且存在进行中的区域
  1257. {
  1258. end = (i - 1) % imageWidth;
  1259. regions.Add(new ValidRegionModelClass()
  1260. {
  1261. Start = start,
  1262. End = end
  1263. }); // 记录有效区域
  1264. start = -1; // 重置区域标记
  1265. end = -1;
  1266. }
  1267. }
  1268. }
  1269. // 处理行尾未闭合的区域
  1270. if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold)
  1271. {
  1272. regions.Add(new ValidRegionModelClass()
  1273. {
  1274. Start = start,
  1275. End = (int)image.Width - 1
  1276. });
  1277. }
  1278. return new List<ValidRegionModelClass>(regions);
  1279. }
  1280. private List<ValidRegionModelClass> FindValidRegions(ReadOnlySpan<byte> image,int imageWidth, int RowNo)
  1281. {
  1282. regions.Clear();
  1283. regions.Capacity = 10;
  1284. ReadOnlySpan<byte> rowSpan;
  1285. if (shuLiConfig.IsIdentifyRoiOpen)
  1286. {
  1287. int offset = RowNo * imageWidth + shuLiConfig.IdentifyStartX;
  1288. int length = Math.Min(shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX,
  1289. imageWidth - shuLiConfig.IdentifyStartX);
  1290. rowSpan = image.Slice(offset, length);
  1291. }
  1292. else
  1293. {
  1294. rowSpan = image.Slice(RowNo * imageWidth, imageWidth);
  1295. }
  1296. int start = -1;
  1297. byte threshold = (byte)shuLiConfig.RegionThreshold;
  1298. for (int i = 0; i < rowSpan.Length; i++)
  1299. {
  1300. if (rowSpan[i] < threshold)
  1301. {
  1302. if (start == -1) start = i;
  1303. }
  1304. else if (start != -1)
  1305. {
  1306. regions.Add(new ValidRegionModelClass { Start = start, End = i - 1 });
  1307. start = -1;
  1308. }
  1309. }
  1310. if (start != -1)
  1311. {
  1312. regions.Add(new ValidRegionModelClass { Start = start, End = rowSpan.Length - 1 });
  1313. }
  1314. return new List<ValidRegionModelClass>(regions);
  1315. }
  1316. /// <summary>
  1317. /// 判断区域重叠(与活跃物体的横向坐标重叠检测)
  1318. /// </summary>
  1319. /// <param name="obj">活跃物体</param>
  1320. /// <param name="region">当前区域</param>
  1321. /// <returns>是否发生重叠</returns>
  1322. private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass region)
  1323. {
  1324. // 判断区域是否不相交的逆条件
  1325. return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol);
  1326. }
  1327. /// <summary>
  1328. /// 通道区域判定
  1329. /// </summary>
  1330. /// <param name="activeObject"></param>
  1331. /// <returns></returns>
  1332. private int ActiveChannel(ActiveObjectClass activeObject)
  1333. {
  1334. int result = -1;
  1335. int StartChannel = activeObject.MinStartCol / ChannelWidth;
  1336. int EndChannel = activeObject.MaxEndCol / ChannelWidth;
  1337. if (StartChannel == EndChannel)
  1338. {
  1339. result = StartChannel;
  1340. }
  1341. else if (EndChannel - StartChannel > 1)
  1342. {
  1343. Console.WriteLine("ActiveChannel-Error");
  1344. //error
  1345. }
  1346. else
  1347. {
  1348. result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel;
  1349. }
  1350. return result;
  1351. }
  1352. /// <summary>
  1353. /// 检测是否存在显著的内凹
  1354. /// </summary>
  1355. /// <param name="originalPoints">原始点集</param>
  1356. /// <param name="convexHull">凸包点集</param>
  1357. /// <returns>是否存在显著内凹和程度比值</returns>
  1358. private ConvexResultClass DetectConcavity(List<Point> originalPoints, List<Point> convexHull)
  1359. {
  1360. if (convexHull.Count < 3) return new ConvexResultClass()
  1361. {
  1362. hasSignificantConcavity = false,
  1363. concavityRatio = 0,
  1364. };
  1365. // 检查原始点集中有多少点在凸包内部(即非凸包顶点)
  1366. HashSet<Point> hullPoints = new HashSet<Point>(convexHull);
  1367. var internalPoints = originalPoints.Where(p => !hullPoints.Contains(p)).ToList();
  1368. if (internalPoints.Count == 0)
  1369. {
  1370. // 所有点都在凸包上,没有内凹
  1371. return new ConvexResultClass()
  1372. {
  1373. hasSignificantConcavity = false,
  1374. concavityRatio = 0,
  1375. };
  1376. }
  1377. // 计算凸包面积
  1378. double convexHullArea = CalculatePolygonArea(SortPointsByNearestNeighbor(convexHull));
  1379. double PointsArea = CalculatePolygonArea(SortPointsByNearestNeighbor(originalPoints));
  1380. var concavityMeasure = (convexHullArea - PointsArea) / convexHullArea;
  1381. // 判断是否存在显著内凹(可以根据实际需求调整阈值)
  1382. bool hasSignificantConcavity = concavityMeasure > 0.3;
  1383. return new ConvexResultClass()
  1384. {
  1385. hasSignificantConcavity = hasSignificantConcavity,
  1386. concavityRatio = concavityMeasure,
  1387. };
  1388. }
  1389. /// <summary>
  1390. /// 计算封闭多边形的面积(鞋带公式)
  1391. /// </summary>
  1392. /// <param name="polygon">多边形顶点(必须是封闭的)</param>
  1393. /// <returns>面积</returns>
  1394. private double CalculatePolygonArea(List<Point> polygon)
  1395. {
  1396. if (polygon.Count < 3) return 0;
  1397. // 确保多边形是封闭的
  1398. var closedPolygon = new List<Point>(polygon);
  1399. if (closedPolygon.First() != closedPolygon.Last())
  1400. {
  1401. closedPolygon.Add(closedPolygon[0]);
  1402. }
  1403. double area = 0;
  1404. for (int i = 0; i < closedPolygon.Count - 1; i++)
  1405. {
  1406. area += (double)closedPolygon[i].X * closedPolygon[i + 1].Y;
  1407. area -= (double)closedPolygon[i].Y * closedPolygon[i + 1].X;
  1408. }
  1409. return Math.Abs(area) / 2.0;
  1410. }
  1411. /// <summary>
  1412. /// 凸包最长边,结果通过系数计算
  1413. /// </summary>
  1414. /// <param name="hull"></param>
  1415. /// <returns></returns>
  1416. public MaxLengthModel CoefficientRotatingCalipers(List<Point> hull)
  1417. {
  1418. //老方法
  1419. MaxLengthModel result = new MaxLengthModel();
  1420. int n = hull.Count;
  1421. if (n == 1)
  1422. {
  1423. result = null;
  1424. return result;
  1425. }
  1426. if (n == 2)
  1427. {
  1428. result.MaxLength = Distance(hull[0], hull[1]);
  1429. result.Point1Index = 0;
  1430. result.Point1 = hull[0];
  1431. result.Point2Index = 1;
  1432. result.Point2 = hull[1];
  1433. return result;
  1434. }
  1435. double maxDist = 0;
  1436. int pointIndex1 = 0;
  1437. int pointIndex2 = 0;
  1438. for (int i = 0; i < n; i++)
  1439. {
  1440. for (int j = i + 1; j < n; j++)
  1441. {
  1442. if (CoefficientDistance(hull[i], hull[j]) > maxDist)
  1443. {
  1444. maxDist = CoefficientDistance(hull[i], hull[j]);
  1445. pointIndex1 = i;
  1446. pointIndex2 = j;
  1447. }
  1448. }
  1449. }
  1450. result.Point1 = hull[pointIndex1];
  1451. result.Point1Index = pointIndex1;
  1452. result.Point2 = hull[pointIndex2];
  1453. result.Point2Index = pointIndex2;
  1454. result.MaxLength = maxDist;
  1455. return result;
  1456. }
  1457. /// <summary>
  1458. /// 凸包最长边
  1459. /// </summary>
  1460. /// <param name="hull"></param>
  1461. /// <returns></returns>
  1462. public MaxLengthModel RotatingCalipers(List<Point> hull)
  1463. {
  1464. //老方法
  1465. MaxLengthModel result = new MaxLengthModel();
  1466. int n = hull.Count;
  1467. if (n == 1)
  1468. {
  1469. result = null;
  1470. return result;
  1471. }
  1472. if (n == 2)
  1473. {
  1474. result.MaxLength = Distance(hull[0], hull[1]);
  1475. result.Point1Index = 0;
  1476. result.Point1 = hull[0];
  1477. result.Point2Index = 1;
  1478. result.Point2 = hull[1];
  1479. return result;
  1480. }
  1481. double maxDist = 0;
  1482. int pointIndex1 = 0;
  1483. int pointIndex2 = 0;
  1484. for (int i = 0; i < n; i++)
  1485. {
  1486. for (int j = i + 1; j < n; j++)
  1487. {
  1488. if (Distance(hull[i], hull[j]) > maxDist)
  1489. {
  1490. maxDist = Distance(hull[i], hull[j]);
  1491. pointIndex1 = i;
  1492. pointIndex2 = j;
  1493. }
  1494. }
  1495. }
  1496. result.Point1 = hull[pointIndex1];
  1497. result.Point1Index = pointIndex1;
  1498. result.Point2 = hull[pointIndex2];
  1499. result.Point2Index = pointIndex2;
  1500. result.MaxLength = Distance(result.Point1, result.Point2);
  1501. return result;
  1502. }
  1503. /// <summary>
  1504. /// 计算凸包的最小外接矩形
  1505. /// </summary>
  1506. /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
  1507. /// <returns>最小外接矩形的四个顶点</returns>
  1508. public BoundingRectangleMdoel CalculateMinimumBoundingRectangle(List<Point> convexHull)
  1509. {
  1510. BoundingRectangleMdoel result = new BoundingRectangleMdoel();
  1511. if (convexHull == null || convexHull.Count < 3)
  1512. return null;
  1513. double minArea = double.MaxValue;
  1514. int n = convexHull.Count;
  1515. // 遍历每一条边作为基准边
  1516. for (int i = 0; i < n; i++)
  1517. {
  1518. Point edgeStart = convexHull[i];
  1519. Point edgeEnd = convexHull[(i + 1) % n];
  1520. // 计算当前边的角度
  1521. double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
  1522. // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
  1523. var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
  1524. // 找到包围盒
  1525. int minX = rotatedPoints.Min(p => p.X);
  1526. int maxX = rotatedPoints.Max(p => p.X);
  1527. int minY = rotatedPoints.Min(p => p.Y);
  1528. int maxY = rotatedPoints.Max(p => p.Y);
  1529. // 计算面积
  1530. double area = (maxX - minX) * (maxY - minY);
  1531. // 如果面积更小,则更新最小矩形
  1532. if (area < minArea)
  1533. {
  1534. minArea = area;
  1535. // 构造矩形的四个顶点并旋转回原来的角度
  1536. var rectangle = new List<Point>
  1537. {
  1538. RotatePoint(new Point(minX, minY), angle),
  1539. RotatePoint(new Point(maxX, minY), angle),
  1540. RotatePoint(new Point(maxX, maxY), angle),
  1541. RotatePoint(new Point(minX, maxY), angle)
  1542. };
  1543. result.points = rectangle;
  1544. result.Angle = angle;
  1545. result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
  1546. }
  1547. }
  1548. double l1 = Distance(result.points[0], result.points[1]);
  1549. double l2 = Distance(result.points[1], result.points[2]);
  1550. if (l1 < l2)
  1551. {
  1552. result.WidthPoints = new List<Point>
  1553. {
  1554. result.points[0],
  1555. result.points[1],
  1556. };
  1557. result.HeightPoints = new List<Point>
  1558. {
  1559. result.points[1],
  1560. result.points[2],
  1561. };
  1562. }
  1563. else
  1564. {
  1565. result.WidthPoints = new List<Point>
  1566. {
  1567. result.points[2],
  1568. result.points[1],
  1569. };
  1570. result.HeightPoints = new List<Point>
  1571. {
  1572. result.points[1],
  1573. result.points[0],
  1574. };
  1575. }
  1576. result.Height = Math.Max(l1, l2);
  1577. result.Width = Math.Min(l1, l2);
  1578. return result;
  1579. }
  1580. /// <summary>
  1581. /// 计算凸包的最小外接矩形
  1582. /// </summary>
  1583. /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
  1584. /// <returns>最小外接矩形的四个顶点</returns>
  1585. public BoundingRectangleMdoel CoefficientCalculateMinimumBoundingRectangle(List<Point> convexHull)
  1586. {
  1587. BoundingRectangleMdoel result = new BoundingRectangleMdoel();
  1588. if (convexHull == null || convexHull.Count < 3)
  1589. return null;
  1590. double minArea = double.MaxValue;
  1591. int n = convexHull.Count;
  1592. // 遍历每一条边作为基准边
  1593. for (int i = 0; i < n; i++)
  1594. {
  1595. Point edgeStart = convexHull[i];
  1596. Point edgeEnd = convexHull[(i + 1) % n];
  1597. // 计算当前边的角度
  1598. double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
  1599. // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
  1600. var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
  1601. // 找到包围盒
  1602. int minX = rotatedPoints.Min(p => p.X);
  1603. int maxX = rotatedPoints.Max(p => p.X);
  1604. int minY = rotatedPoints.Min(p => p.Y);
  1605. int maxY = rotatedPoints.Max(p => p.Y);
  1606. // 计算面积
  1607. double area = (maxX - minX) * (maxY - minY);
  1608. // 如果面积更小,则更新最小矩形
  1609. if (area < minArea)
  1610. {
  1611. minArea = area;
  1612. // 构造矩形的四个顶点并旋转回原来的角度
  1613. var rectangle = new List<Point>
  1614. {
  1615. RotatePoint(new Point(minX, minY), angle),
  1616. RotatePoint(new Point(maxX, minY), angle),
  1617. RotatePoint(new Point(maxX, maxY), angle),
  1618. RotatePoint(new Point(minX, maxY), angle)
  1619. };
  1620. result.points = rectangle;
  1621. result.Angle = angle;
  1622. result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
  1623. }
  1624. }
  1625. double l1 = CoefficientDistance(result.points[0], result.points[1]);
  1626. double l2 = CoefficientDistance(result.points[1], result.points[2]);
  1627. if (l1 < l2)
  1628. {
  1629. result.WidthPoints = new List<Point>
  1630. {
  1631. result.points[0],
  1632. result.points[1],
  1633. };
  1634. result.HeightPoints = new List<Point>
  1635. {
  1636. result.points[1],
  1637. result.points[2],
  1638. };
  1639. }
  1640. else
  1641. {
  1642. result.WidthPoints = new List<Point>
  1643. {
  1644. result.points[2],
  1645. result.points[1],
  1646. };
  1647. result.HeightPoints = new List<Point>
  1648. {
  1649. result.points[1],
  1650. result.points[0],
  1651. };
  1652. }
  1653. result.Height = Math.Max(l1, l2);
  1654. result.Width = Math.Min(l1, l2);
  1655. return result;
  1656. }
  1657. public List<Point> SortPointsByNearestNeighbor(List<Point> points)
  1658. {
  1659. if (points.Count <= 1) return points;
  1660. var sorted = new List<Point>();
  1661. var remaining = new HashSet<Point>(points);
  1662. // 选择起始点(例如最左边的点)
  1663. var current = remaining.OrderBy(p => p.X).First();
  1664. sorted.Add(current);
  1665. remaining.Remove(current);
  1666. while (remaining.Any())
  1667. {
  1668. // 找到最近的点
  1669. var nearest = remaining.OrderBy(p => Distance(current, p)).First();
  1670. sorted.Add(nearest);
  1671. remaining.Remove(nearest);
  1672. current = nearest;
  1673. }
  1674. return sorted;
  1675. }
  1676. /// <summary>
  1677. /// 绕原点旋转点
  1678. /// </summary>
  1679. /// <param name="point">待旋转的点</param>
  1680. /// <param name="angle">旋转角度(弧度)</param>
  1681. /// <returns>旋转后的点</returns>
  1682. private static Point RotatePoint(Point point, double angle)
  1683. {
  1684. double cos = Math.Cos(angle);
  1685. double sin = Math.Sin(angle);
  1686. return new Point(
  1687. (int)(point.X * cos - point.Y * sin),
  1688. (int)(point.X * sin + point.Y * cos)
  1689. );
  1690. }
  1691. /// <summary>
  1692. /// 计算点到线段的距离
  1693. /// </summary>
  1694. /// <param name="a">线段点1</param>
  1695. /// <param name="b">线段点2</param>
  1696. /// <param name="c">点三</param>
  1697. /// <returns></returns>
  1698. private double DistanceToLine(Point a, Point b, Point c)
  1699. {
  1700. double area = Math.Abs(Area2(a, b, c));
  1701. double baseLength = Distance(a, b);
  1702. return area / baseLength;
  1703. }
  1704. // 计算向量叉积
  1705. private int Cross(Point o, Point a, Point b) =>
  1706. (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X);
  1707. // 计算三角形面积的两倍
  1708. private int Area2(Point a, Point b, Point c) =>
  1709. (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
  1710. // 计算两点间距离
  1711. private double Distance(Point a, Point b)
  1712. {
  1713. int dx = a.X - b.X;
  1714. int dy = a.Y - b.Y;
  1715. return Math.Sqrt((dx * dx) + (dy * dy));
  1716. }
  1717. private double CoefficientDistance(Point a,Point b)
  1718. {
  1719. double dx = (a.X - b.X) * XCoefficient;
  1720. double dy = (a.Y - b.Y) * YCoefficient;
  1721. return Math.Sqrt((dx * dx) + (dy * dy));
  1722. }
  1723. private bool TryAdd(List<ActiveObjectClass> list, ActiveObjectClass item, int maxSize)
  1724. {
  1725. list.Add(item);
  1726. Interlocked.Increment(ref _HistoryActiveNum);
  1727. if(item.StateCode == 0)
  1728. {
  1729. Interlocked.Increment(ref _OkHistoryNum);
  1730. }
  1731. else
  1732. {
  1733. Interlocked.Increment(ref _NgHistoryNum);
  1734. }
  1735. if (list.Count > maxSize)
  1736. {
  1737. list.Remove(list[list.Count - maxSize]);
  1738. }
  1739. return true;
  1740. }
  1741. #endregion
  1742. #region 线程方法
  1743. //信号量
  1744. public SemaphoreSlim QueueSemaphore { get; set; }
  1745. //取消令牌
  1746. public CancellationTokenSource CancellationTokenSource { get; set; }
  1747. //图像处理线程
  1748. public Task ProcessingTask { get; set; }
  1749. /// <summary>
  1750. /// 识别图像线程
  1751. /// </summary>
  1752. private void IdentifyImageProcess()
  1753. {
  1754. Stopwatch stopwatch = Stopwatch.StartNew();
  1755. while (IsIdentify)
  1756. {
  1757. //判断队列中是否有数据
  1758. if (IFrameDatas.Count() > 0)
  1759. {
  1760. stopwatch.Restart();
  1761. if (IFrameDatas.Count() > 5)
  1762. {
  1763. //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
  1764. 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()}");
  1765. if (IFrameDatas.Count() > 100)
  1766. {
  1767. SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积,
  1768. $"The image data queue is blocked. Please check the configuration and images",
  1769. "待识别队列数据堆积,请检查设置和图像",
  1770. "DLL:ShuLIClass-IdentifyImageProcess");
  1771. }
  1772. else
  1773. {
  1774. SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
  1775. }
  1776. }
  1777. IFrameDatas.TryDequeue(out IFrameOut IframeData);
  1778. stopwatch.Stop();
  1779. //var ShiBieLuoHouTime = (DateTime.Now - FromUnixTimestamp((long)IframeData.HostTimeStamp)).TotalMilliseconds;
  1780. //// 优化: 将日志记录改为条件执行
  1781. //if (ShiBieLuoHouTime > 30)
  1782. //{
  1783. // LOG.error($"算法落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
  1784. //}
  1785. if (stopwatch.ElapsedMilliseconds > 5)
  1786. {
  1787. FaultLog.RecordErrorMessage($"ShuLiClass-IdentifyImageProcess:Image reading timed out, this recognition took time:{stopwatch.Elapsed}");
  1788. }
  1789. if (IframeData != null)
  1790. {
  1791. //识别
  1792. ProcessImageSequence2(IframeData);
  1793. IframeData.Dispose();
  1794. }
  1795. else
  1796. {
  1797. continue;
  1798. }
  1799. }
  1800. else
  1801. {
  1802. Thread.Sleep(1);
  1803. }
  1804. }
  1805. }
  1806. private async void IdentifyImageProcessTask(CancellationToken token)
  1807. {
  1808. Stopwatch stopwatch = Stopwatch.StartNew();
  1809. const int batchSize = 5; // 每批处理的最大图像数量
  1810. var batch = new List<IFrameOut>(batchSize); // 存储当前批次的图像
  1811. while (!token.IsCancellationRequested)
  1812. {
  1813. // 等待图像数据
  1814. await QueueSemaphore.WaitAsync(token);
  1815. //判断队列中是否有数据
  1816. if (IFrameDatas.Count() > 5)
  1817. {
  1818. //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
  1819. 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()}");
  1820. if (IFrameDatas.Count() > 100)
  1821. {
  1822. SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积,
  1823. $"The image data queue is blocked. Please check the configuration and images",
  1824. "待识别队列数据堆积,请检查设置和图像",
  1825. "DLL:ShuLIClass-IdentifyImageProcess");
  1826. }
  1827. else
  1828. {
  1829. SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
  1830. }
  1831. }
  1832. // 收集一批图像(严格按队列顺序)
  1833. while (batch.Count < batchSize && IFrameDatas.TryDequeue(out IFrameOut imageData))
  1834. {
  1835. batch.Add(imageData);
  1836. if (IFrameDatas.Count > 0)
  1837. {
  1838. QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
  1839. }
  1840. }
  1841. // 顺序处理当前批次的所有图像
  1842. foreach (var image in batch)
  1843. {
  1844. if (image != null)
  1845. {
  1846. //识别
  1847. ProcessImageSequence2(image);
  1848. image.Dispose();
  1849. }
  1850. else
  1851. {
  1852. continue;
  1853. }
  1854. }
  1855. batch.Clear();
  1856. }
  1857. }
  1858. private async void IdentifyImageProcessTask2(CancellationToken token)
  1859. {
  1860. const int batchSize = 5; // 每批处理的最大图像数量
  1861. var batch = new List<IFrameOut>(batchSize); // 存储当前批次的图像
  1862. while (!token.IsCancellationRequested)
  1863. {
  1864. try
  1865. {
  1866. // 等待图像数据
  1867. await QueueSemaphore.WaitAsync(token);
  1868. // 收集一批图像(严格按队列顺序)
  1869. while (batch.Count < batchSize && RingBuffer_IFrameDatas.TryDequeue(out IFrameOut imageData))
  1870. {
  1871. batch.Add(imageData);
  1872. if (RingBuffer_IFrameDatas.Count > 0)
  1873. {
  1874. QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
  1875. }
  1876. }
  1877. // 顺序处理当前批次的所有图像
  1878. foreach (var image in batch)
  1879. {
  1880. if (image != null)
  1881. {
  1882. //识别
  1883. ProcessImageSequence2(image);
  1884. image.Dispose();
  1885. }
  1886. else
  1887. {
  1888. continue;
  1889. }
  1890. }
  1891. batch.Clear();
  1892. }
  1893. catch(Exception ex)
  1894. {
  1895. }
  1896. }
  1897. }
  1898. #endregion
  1899. }
  1900. }