ProcessingAlgorithm_CCDShuLi.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. using MvCameraControl;
  2. using MvvmScaffoldFrame48.DLL.LogTools;
  3. using MvvmScaffoldFrame48.DLL.ThreadManager;
  4. using MvvmScaffoldFrame48.Model.StorageModel.ImageAlgorithm.ShuLI;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Drawing;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. namespace MvvmScaffoldFrame48.DLL.ImageAlgorithm
  14. {
  15. public class ProcessingAlgorithm_CCDShuLi : IImageProcessingAlgorithmHikVision
  16. {
  17. private List<ActiveObjectClassModel> activeObjects = new List<ActiveObjectClassModel>(); // 当前跟踪中的物体
  18. private List<ActiveObjectClassModel> lostObjects = new List<ActiveObjectClassModel>();
  19. private List<ActiveObjectClassModel> historyActiveObjects = new List<ActiveObjectClassModel>(); // 历史物体
  20. BoundRectangleClass BoundRectangle = new BoundRectangleClass();
  21. private long currentLine = 0; //行数记录
  22. private int ObjectNum = 0;
  23. private int ChannelWidth = 0;//每个区域的宽度
  24. private ShuLiConfigClassModel shuLiConfig = new ShuLiConfigClassModel();
  25. public List<int> ChannelsRoi { get { return _ChannelsRoi; } }
  26. private List<int> _ChannelsRoi = new List<int>();
  27. private bool IsPrintLightOnError = false;
  28. public string AlgorithmName => "ProcessingAlgorithm_CCDShuLi";
  29. public void Configure(string parameters)
  30. {
  31. throw new NotImplementedException();
  32. }
  33. public object GetParameters()
  34. {
  35. throw new NotImplementedException();
  36. }
  37. public string GetSaveJson()
  38. {
  39. throw new NotImplementedException();
  40. }
  41. public object ProcessImage(IImage imageData, int cameraId)
  42. {
  43. bool result = ProcessImageSequence(imageData, out List<ActiveObjectClassModel> resultValue);
  44. if (result)
  45. {
  46. return resultValue;
  47. }
  48. else
  49. {
  50. return null;
  51. }
  52. }
  53. /// <summary>
  54. /// 处理图像序列的主入口
  55. /// </summary>
  56. /// <param name="image">图像像素数据</param>
  57. /// <param name="ImageWidth">图像宽</param>
  58. /// <param name="currentLine">当前行数</param>
  59. /// <returns>检测到的物体总数</returns>
  60. public bool ProcessImageSequence(IImage image,out List<ActiveObjectClassModel> resultValue)
  61. {
  62. bool result = false;
  63. for (int i = 0; i < image.Height; i++)
  64. {
  65. ProcessLine(image, i);
  66. currentLine += 1;
  67. }
  68. //识别到结果并输出
  69. // 清理超时未更新的物体
  70. lostObjects = activeObjects
  71. .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
  72. .ToList();
  73. resultValue = new List<ActiveObjectClassModel>();
  74. // 有物体转变为活跃物体,返回值转为true
  75. if (lostObjects.Count > 0)
  76. {
  77. foreach (var item in lostObjects)
  78. {
  79. //噪点判定
  80. if (item.Area < shuLiConfig.NoiseFilter_Threshold)
  81. {
  82. item.StateCode = 9;
  83. //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6);
  84. continue;
  85. }
  86. //转为历史物体,添加缺少的参数
  87. item.Num = Interlocked.Increment(ref ObjectNum) + 1;
  88. //item.ChannelNO = ActiveChannel(item);
  89. item.EndCheckTime = DateTime.Now;
  90. item.MaxLength = GetActionMaxLength(item.RowsData);
  91. if ((item.LastSeenLine - item.StartLine) > shuLiConfig.MAX_Idetify_Height)
  92. {
  93. item.StateCode = 7;
  94. //FaultLog.RecordLogMessage("ShuLiClass-ProcessLine:非颗粒,视野异常", 3);
  95. //TxtLog.log(string.Format("ShuLiClass-ProcessLine:非颗粒,视野异常"), 6);
  96. //Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常");
  97. }
  98. else if (shuLiConfig.PandingCode != -1)
  99. {
  100. if (item.StateCode != -1)
  101. {
  102. if (item.StateCode == 8)
  103. {
  104. //TxtLog.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
  105. }
  106. }
  107. else if (item.Area < shuLiConfig.MinArea
  108. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  109. {
  110. item.StateCode = 5;
  111. //TxtLog.log(string.Format("颗粒编号{0}:面积过小", item.Num));
  112. //Console.WriteLine("颗粒编号{0}:面积过小", item.Num);
  113. }
  114. else if (item.Area > shuLiConfig.MaxArea
  115. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
  116. {
  117. item.StateCode = 6;
  118. //TxtLog.log(string.Format("颗粒编号{0}:面积过大", item.Num));
  119. //Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
  120. }
  121. else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH
  122. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  123. {
  124. item.StateCode = 2;
  125. //TxtLog.log(string.Format("颗粒编号{0}:超短粒", item.Num));
  126. //Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
  127. }
  128. else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH
  129. && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
  130. {
  131. item.StateCode = 1;
  132. //TxtLog.log(string.Format("颗粒编号{0}:超长粒", item.Num));
  133. //Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
  134. }
  135. else
  136. {
  137. item.StateCode = 0;
  138. //TxtLog.log(string.Format("颗粒编号{0}:正常粒", item.Num));
  139. //Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
  140. }
  141. }
  142. resultValue.Add(item);
  143. }
  144. if (resultValue.Count > 0)
  145. {
  146. result = true;
  147. //LOG.log(string.Format("识别完成,首个颗粒编号:{0},颗粒数量:{1}", OneActive[0].Num, OneActive.Count), 6);
  148. //触发回调事件
  149. //Task.Run(() =>
  150. //{
  151. // OnWorkCompleted(OneActive);
  152. //});
  153. //ThreadPool.QueueUserWorkItem(_ => OnWorkCompleted(OneActive));
  154. }
  155. }
  156. else
  157. {
  158. resultValue.Clear();
  159. }
  160. // 累加到总数并从活跃物体转移到历史物体
  161. resultValue.ForEach(o => TryAdd(historyActiveObjects, o, 2500));
  162. //lostObjects.Where(o => o.StateCode != 7 && o.StateCode != 9).ToList().ForEach(o => TryAdd(historyActiveObjects, o, 2500));
  163. lostObjects.ForEach(o => activeObjects.Remove(o));
  164. return result;
  165. }
  166. /// <summary>
  167. /// 处理单行像素数据
  168. /// 返回值为false的时候无活跃物体转变为历史物体
  169. /// 返回值为true的时候有活跃物体转变为历史物体
  170. /// </summary>
  171. /// <param name="image">当前行像素数组</param>
  172. private bool ProcessLine(IImage imagedata, int RowNo)
  173. {
  174. Stopwatch stopwatch = Stopwatch.StartNew();
  175. bool result = false;
  176. // 步骤1:检测当前行的有效区域
  177. var currentRegions = FindValidRegions(imagedata, RowNo);
  178. if (currentRegions.Count == 1)
  179. {
  180. if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Width)
  181. {
  182. if (!IsPrintLightOnError)
  183. {
  184. //FaultLog.RecordLogMessage("当前行有效区域为整行,检查视野和光源", 5);
  185. IsPrintLightOnError = true;
  186. }
  187. return false;
  188. }
  189. }
  190. IsPrintLightOnError = false;
  191. stopwatch.Stop();
  192. if (stopwatch.ElapsedMilliseconds > 1)
  193. {
  194. //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像连通域检测超时,此次识别耗时:{stopwatch.Elapsed}");
  195. }
  196. stopwatch.Restart();
  197. //lock (_lockObj)
  198. //{
  199. foreach (var region in currentRegions)
  200. {
  201. // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
  202. var matcheds = activeObjects.Where(o =>
  203. IsOverlapping(o, region) &&
  204. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList();
  205. //当有多个可合并的活跃物体时,将多个物体合并
  206. if (matcheds.Count >= 2)
  207. {
  208. // 合并有效区域队列
  209. var CopeRowsData = new List<RowStartEndColModel>();
  210. matcheds.ForEach(o => CopeRowsData = CopeRowsData.Concat(o.RowsData).ToList());
  211. // 合并有效区域并保存在新的区域中
  212. var MergeMatched = new ActiveObjectClassModel
  213. {
  214. MinStartCol = matcheds.Min(o => o.MinStartCol),
  215. MaxEndCol = matcheds.Max(o => o.MaxEndCol),
  216. StartLine = matcheds.Min(o => o.StartLine),
  217. LastSeenLine = matcheds.Max(o => o.LastSeenLine),
  218. LastSeenLineStartCol = matcheds.Min(o => o.LastSeenLineStartCol),
  219. LastSeenLineEndCol = matcheds.Max(o => o.LastSeenLineEndCol),
  220. StartCheckTime = matcheds.Min(o => o.StartCheckTime),
  221. EndCheckTime = matcheds.Max(o => o.EndCheckTime),
  222. Area = matcheds.Sum(o => o.Area),
  223. RowsData = CopeRowsData,
  224. ImageWidth = matcheds.FirstOrDefault().ImageWidth,
  225. //StateCode = 8
  226. };
  227. // 从活跃区域中删除被合并的区域
  228. matcheds.ForEach(o => activeObjects.Remove(o));
  229. // 保存新的区域到活跃区域中
  230. activeObjects.Add(MergeMatched);
  231. }
  232. // 搜获可用且可合并的活跃区域
  233. var matched = activeObjects.FirstOrDefault(o =>
  234. IsOverlapping(o, region) &&
  235. (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP);
  236. if (matched != null)
  237. {
  238. // 合并区域:扩展物体边界并更新状态
  239. matched.MinStartCol = Math.Min(matched.MinStartCol, region.Start);
  240. matched.MaxEndCol = Math.Max(matched.MaxEndCol, region.End);
  241. matched.Area += region.End - region.Start + 1;
  242. matched.LastSeenLine = currentLine;
  243. matched.RowsData.Add(new RowStartEndColModel
  244. {
  245. StartCol = region.Start,
  246. EndCol = region.End,
  247. RowsCol = currentLine,
  248. });
  249. matched.LastSeenLineStartCol = region.Start;
  250. matched.LastSeenLineEndCol = region.End;
  251. }
  252. else
  253. {
  254. // 创建新物体(首次出现的区域)
  255. activeObjects.Add(new ActiveObjectClassModel
  256. {
  257. MinStartCol = region.Start,
  258. MaxEndCol = region.End,
  259. StartLine = currentLine,
  260. LastSeenLine = currentLine,
  261. LastSeenLineStartCol = region.Start,
  262. LastSeenLineEndCol = region.End,
  263. StartCheckTime = DateTime.Now,
  264. Area = region.End - region.Start + 1,
  265. ImageWidth = shuLiConfig.ImageWidth,
  266. RowsData = new List<RowStartEndColModel> {
  267. new RowStartEndColModel {
  268. StartCol = region.Start,
  269. EndCol = region.End,
  270. RowsCol = currentLine,
  271. }
  272. }
  273. });
  274. }
  275. }
  276. //}
  277. stopwatch.Stop();
  278. if (stopwatch.ElapsedMilliseconds > 1)
  279. {
  280. //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}");
  281. }
  282. currentRegions.Clear();
  283. return result;
  284. }
  285. List<ValidRegionModel> regions = new List<ValidRegionModel>();
  286. /// <summary>
  287. /// 检测有效物体区域(横向连续黑色像素段)
  288. /// </summary>
  289. /// <param name="line">当前行像素数组</param>
  290. /// <returns>有效区域列表(起始/结束位置)</returns>
  291. private List<ValidRegionModel> FindValidRegions(IImage image, int RowNo)
  292. {
  293. regions.Clear();
  294. int start = -1; // 当前区域起始标记
  295. int end = -1;
  296. // 遍历所有像素列
  297. if (shuLiConfig.IsIdentifyRoiOpen)
  298. {
  299. for (int i = (int)image.Width * RowNo + shuLiConfig.IdentifyStartX; i < (int)image.Width * RowNo + shuLiConfig.IdentifyStopX; i++)
  300. {
  301. if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  302. {
  303. if (start == -1) start = i % (int)image.Width; // 开始新区域
  304. }
  305. else if (start != -1) // 遇到白色像素且存在进行中的区域
  306. {
  307. end = (i - 1) % (int)image.Width;
  308. // 检查区域宽度是否达标
  309. if (end - start >= shuLiConfig.NoiseFilter_Threshold)
  310. {
  311. regions.Add(new ValidRegionModel()
  312. {
  313. Start = start,
  314. End = end
  315. }); // 记录有效区域
  316. }
  317. start = -1; // 重置区域标记
  318. end = -1;
  319. }
  320. }
  321. }
  322. else
  323. {
  324. for (int i = (int)image.Width * RowNo; i < (int)image.Width * (RowNo + 1); i++)
  325. {
  326. if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
  327. {
  328. if (start == -1) start = i % (int)image.Width; // 开始新区域
  329. }
  330. else if (start != -1) // 遇到白色像素且存在进行中的区域
  331. {
  332. end = (i - 1) % (int)image.Width;
  333. // 检查区域宽度是否达标
  334. if (end - start >= shuLiConfig.NoiseFilter_Threshold)
  335. {
  336. regions.Add(new ValidRegionModel()
  337. {
  338. Start = start,
  339. End = end
  340. }); // 记录有效区域
  341. }
  342. start = -1; // 重置区域标记
  343. end = -1;
  344. }
  345. }
  346. }
  347. // 处理行尾未闭合的区域
  348. if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold)
  349. {
  350. regions.Add(new ValidRegionModel()
  351. {
  352. Start = start,
  353. End = (int)image.Width - 1
  354. });
  355. }
  356. return regions;
  357. }
  358. /// <summary>
  359. /// 判断区域重叠(与活跃物体的横向坐标重叠检测)
  360. /// </summary>
  361. /// <param name="obj">活跃物体</param>
  362. /// <param name="region">当前区域</param>
  363. /// <returns>是否发生重叠</returns>
  364. private bool IsOverlapping(ActiveObjectClassModel obj, ValidRegionModel region)
  365. {
  366. // 判断区域是否不相交的逆条件
  367. return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol);
  368. }
  369. private bool TryAdd(List<ActiveObjectClassModel> list, ActiveObjectClassModel item, int maxSize)
  370. {
  371. list.Add(item);
  372. if (list.Count > maxSize)
  373. {
  374. list[list.Count - maxSize].RowsData.Clear();
  375. }
  376. return true;
  377. }
  378. /// <summary>
  379. /// 获取结果最长长边
  380. /// </summary>
  381. /// <param name="Rows"></param>
  382. /// <returns></returns>
  383. private double GetActionMaxLength(List<RowStartEndColModel> Rows)
  384. {
  385. //外轮廓点集
  386. List<Point> points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
  387. points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
  388. //获取凸包点集
  389. points = BoundRectangle.ConvexHull(points);
  390. //获取凸包长度最长的外接矩形
  391. var result = BoundRectangle.RotatingCalipers(points);
  392. if (result != null)
  393. {
  394. return result.Height;
  395. }
  396. else
  397. {
  398. return 0;
  399. }
  400. }
  401. /// <summary>
  402. /// 通道区域判定
  403. /// </summary>
  404. /// <param name="activeObject"></param>
  405. /// <returns></returns>
  406. private int ActiveChannel(ActiveObjectClassModel activeObject)
  407. {
  408. int result = -1;
  409. int StartChannel = activeObject.MinStartCol / ChannelWidth;
  410. int EndChannel = activeObject.MaxEndCol / ChannelWidth;
  411. if (StartChannel == EndChannel)
  412. {
  413. result = StartChannel;
  414. }
  415. else if (EndChannel - StartChannel > 1)
  416. {
  417. Console.WriteLine("ActiveChannel-Error");
  418. //error
  419. }
  420. else
  421. {
  422. result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel;
  423. }
  424. return result;
  425. }
  426. }
  427. }