ProcessingAlgorithm_CCDShuLi.cs 20 KB

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