ShuLiClass.cs 39 KB

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