ShuLiClass.cs 31 KB

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