using MvCameraControl; using MvvmScaffoldFrame48.DLL.LogTools; using MvvmScaffoldFrame48.DLL.SystemTools; using MvvmScaffoldFrame48.DLL.ThreadManager; using MvvmScaffoldFrame48.Model.StorageModel.ImageAlgorithm.ShuLI; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MvvmScaffoldFrame48.DLL.ImageAlgorithm { public class ProcessingAlgorithm_CCDShuLi : IImageProcessingAlgorithmHikVision { private List activeObjects = new List(); // 当前跟踪中的物体 private List lostObjects = new List(); private List historyActiveObjects = new List(); // 历史物体 BoundRectangleClass BoundRectangle = new BoundRectangleClass(); private long currentLine = 0; //行数记录 private int ObjectNum = 0; private int ChannelWidth = 0;//每个区域的宽度 public int HistoryNum { get { return _HistoryNum; } } private int _HistoryNum = 0; public int HistoryOkNum { get { return _HistoryOkNum; } } private int _HistoryOkNum = 0; public int HistoryNgNum { get { return _HistoryNgNum; } } private int _HistoryNgNum = 0; private ShuLiConfigClassModel shuLiConfig = new ShuLiConfigClassModel(); public List ChannelsRoi { get { return _ChannelsRoi; } } private List _ChannelsRoi = new List(); private bool IsPrintLightOnError = false; public string AlgorithmName => "ProcessingAlgorithm_CCDShuLi"; public void Configure(string parameters) { throw new NotImplementedException(); } public object GetParameters() { throw new NotImplementedException(); } public string GetSaveJson() { throw new NotImplementedException(); } public object ProcessImage(IFrameOut imageData, int cameraId) { bool result = ProcessImageSequence(imageData, out List resultValue); if (result) { return resultValue; } else { return null; } } /// /// 处理图像序列的主入口 /// /// 图像像素数据 /// 图像宽 /// 当前行数 /// 检测到的物体总数 public bool ProcessImageSequence(IFrameOut image,out List resultValue) { bool result = false; for (int i = 0; i < image.Image.Height; i++) { ProcessLine(image, i); currentLine += 1; } //识别到结果并输出 // 清理超时未更新的物体 lostObjects = activeObjects .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height) .ToList(); lostObjects.ForEach(o => activeObjects.Remove(o)); resultValue = new List(); // 有物体转变为活跃物体,返回值转为true if (lostObjects.Count > 0) { foreach (var item in lostObjects) { //噪点判定 if (item.Area < shuLiConfig.NoiseFilter_Threshold) { item.StateCode = 9; //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6); continue; } //转为历史物体,添加缺少的参数 item.Num = Interlocked.Increment(ref ObjectNum); //item.ChannelNO = ActiveChannel(item); item.EndCheckTime = DateTime.Now; item.MaxLength = GetActionMaxLength(item.RowsData); item.PictureEndReadTime = TimeStampTools.FromUnixTimestamp((long)image.HostTimeStamp); var QutuYanshiTime = (DateTime.Now - item.PictureEndReadTime).TotalMilliseconds; if (QutuYanshiTime > 30) { Console.WriteLine($"结果输出延时超过了30ms,耗时{QutuYanshiTime}"); } if ((item.LastSeenLine - item.StartLine) > shuLiConfig.MAX_Idetify_Height) { item.StateCode = 7; //FaultLog.RecordLogMessage("ShuLiClass-ProcessLine:非颗粒,视野异常", 3); //TxtLog.log(string.Format("ShuLiClass-ProcessLine:非颗粒,视野异常"), 6); //Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常"); } else if (shuLiConfig.PandingCode != -1) { if (item.StateCode != -1) { if (item.StateCode == 8) { //TxtLog.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num)); } } else if (item.Area < shuLiConfig.MinArea && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1)) { item.StateCode = 5; //TxtLog.log(string.Format("颗粒编号{0}:面积过小", item.Num)); //Console.WriteLine("颗粒编号{0}:面积过小", item.Num); } else if (item.Area > shuLiConfig.MaxArea && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1)) { item.StateCode = 6; //TxtLog.log(string.Format("颗粒编号{0}:面积过大", item.Num)); //Console.WriteLine("颗粒编号{0}:面积过大", item.Num); } else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 2; //TxtLog.log(string.Format("颗粒编号{0}:超短粒", item.Num)); //Console.WriteLine("颗粒编号{0}:超短粒", item.Num); } else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 1; //TxtLog.log(string.Format("颗粒编号{0}:超长粒", item.Num)); //Console.WriteLine("颗粒编号{0}:超长粒", item.Num); } else { item.StateCode = 0; //TxtLog.log(string.Format("颗粒编号{0}:正常粒", item.Num)); //Console.WriteLine("颗粒编号{0}:正常粒", item.Num); } } resultValue.Add(item); } if (resultValue.Count > 0) { result = true; //LOG.log(string.Format("识别完成,首个颗粒编号:{0},颗粒数量:{1}", OneActive[0].Num, OneActive.Count), 6); //触发回调事件 //Task.Run(() => //{ // OnWorkCompleted(OneActive); //}); //ThreadPool.QueueUserWorkItem(_ => OnWorkCompleted(OneActive)); } } else { resultValue.Clear(); } // 累加到总数并从活跃物体转移到历史物体 resultValue.ForEach(o => TryAdd(historyActiveObjects, o, 2500)); return result; } /// /// 处理单行像素数据 /// 返回值为false的时候无活跃物体转变为历史物体 /// 返回值为true的时候有活跃物体转变为历史物体 /// /// 当前行像素数组 private bool ProcessLine(IFrameOut imagedata, int RowNo) { Stopwatch stopwatch = Stopwatch.StartNew(); bool result = false; // 步骤1:检测当前行的有效区域 var currentRegions = FindValidRegions(imagedata.Image, RowNo); if (currentRegions.Count == 1) { if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Image.Width) { if (!IsPrintLightOnError) { //FaultLog.RecordLogMessage("当前行有效区域为整行,检查视野和光源", 5); IsPrintLightOnError = true; } return false; } } IsPrintLightOnError = false; stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 1) { //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像连通域检测超时,此次识别耗时:{stopwatch.Elapsed}"); } stopwatch.Restart(); //lock (_lockObj) //{ foreach (var region in currentRegions) { // 查找全部可合并的活跃物体(有重叠+在允许间隔内) var matcheds = activeObjects.Where(o => IsOverlapping(o, region) && (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList(); //当有多个可合并的活跃物体时,将多个物体合并 if (matcheds.Count >= 2) { // 合并有效区域队列 var CopeRowsData = new List(); matcheds.ForEach(o => CopeRowsData = CopeRowsData.Concat(o.RowsData).ToList()); // 合并有效区域并保存在新的区域中 var MergeMatched = new ActiveObjectClassModel { MinStartCol = matcheds.Min(o => o.MinStartCol), MaxEndCol = matcheds.Max(o => o.MaxEndCol), StartLine = matcheds.Min(o => o.StartLine), LastSeenLine = matcheds.Max(o => o.LastSeenLine), LastSeenLineStartCol = matcheds.Min(o => o.LastSeenLineStartCol), LastSeenLineEndCol = matcheds.Max(o => o.LastSeenLineEndCol), StartCheckTime = matcheds.Min(o => o.StartCheckTime), EndCheckTime = matcheds.Max(o => o.EndCheckTime), PictureStartReadTime = matcheds.Min(o=>o.PictureStartReadTime), Area = matcheds.Sum(o => o.Area), RowsData = CopeRowsData, ImageWidth = matcheds.FirstOrDefault().ImageWidth, //StateCode = 8 }; // 从活跃区域中删除被合并的区域 matcheds.ForEach(o => activeObjects.Remove(o)); // 保存新的区域到活跃区域中 activeObjects.Add(MergeMatched); } // 搜获可用且可合并的活跃区域 var matched = activeObjects.FirstOrDefault(o => IsOverlapping(o, region) && (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP); if (matched != null) { // 合并区域:扩展物体边界并更新状态 matched.MinStartCol = Math.Min(matched.MinStartCol, region.Start); matched.MaxEndCol = Math.Max(matched.MaxEndCol, region.End); matched.Area += region.End - region.Start + 1; matched.LastSeenLine = currentLine; matched.RowsData.Add(new RowStartEndColModel { StartCol = region.Start, EndCol = region.End, RowsCol = currentLine, }); matched.LastSeenLineStartCol = region.Start; matched.LastSeenLineEndCol = region.End; } else { // 创建新物体(首次出现的区域) activeObjects.Add(new ActiveObjectClassModel { MinStartCol = region.Start, MaxEndCol = region.End, StartLine = currentLine, LastSeenLine = currentLine, LastSeenLineStartCol = region.Start, LastSeenLineEndCol = region.End, StartCheckTime = DateTime.Now, PictureStartReadTime = TimeStampTools.FromUnixTimestamp((long)imagedata.HostTimeStamp), Area = region.End - region.Start + 1, ImageWidth = shuLiConfig.ImageWidth, RowsData = new List { new RowStartEndColModel { StartCol = region.Start, EndCol = region.End, RowsCol = currentLine, } } }); } } //} stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 1) { //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}"); } currentRegions.Clear(); return result; } List regions = new List(); /// /// 检测有效物体区域(横向连续黑色像素段) /// /// 当前行像素数组 /// 有效区域列表(起始/结束位置) private List FindValidRegions(IImage image, int RowNo) { regions.Clear(); int start = -1; // 当前区域起始标记 int end = -1; // 遍历所有像素列 if (shuLiConfig.IsIdentifyRoiOpen) { for (int i = (int)image.Width * RowNo + shuLiConfig.IdentifyStartX; i < (int)image.Width * RowNo + shuLiConfig.IdentifyStopX; i++) { if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素 { if (start == -1) start = i % (int)image.Width; // 开始新区域 } else if (start != -1) // 遇到白色像素且存在进行中的区域 { end = (i - 1) % (int)image.Width; // 检查区域宽度是否达标 if (end - start >= shuLiConfig.NoiseFilter_Threshold) { regions.Add(new ValidRegionModel() { Start = start, End = end }); // 记录有效区域 } start = -1; // 重置区域标记 end = -1; } } } else { for (int i = (int)image.Width * RowNo; i < (int)image.Width * (RowNo + 1); i++) { if (image.PixelData[i] < shuLiConfig.RegionThreshold) // 发现黑色像素 { if (start == -1) start = i % (int)image.Width; // 开始新区域 } else if (start != -1) // 遇到白色像素且存在进行中的区域 { end = (i - 1) % (int)image.Width; // 检查区域宽度是否达标 if (end - start >= shuLiConfig.NoiseFilter_Threshold) { regions.Add(new ValidRegionModel() { Start = start, End = end }); // 记录有效区域 } start = -1; // 重置区域标记 end = -1; } } } // 处理行尾未闭合的区域 if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold) { regions.Add(new ValidRegionModel() { Start = start, End = (int)image.Width - 1 }); } return regions; } /// /// 判断区域重叠(与活跃物体的横向坐标重叠检测) /// /// 活跃物体 /// 当前区域 /// 是否发生重叠 private bool IsOverlapping(ActiveObjectClassModel obj, ValidRegionModel region) { // 判断区域是否不相交的逆条件 return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol); } /// /// 历史数据队列添加数据 /// /// 历史数据队列 /// 添加的数据 /// 队列最大数据量 /// private bool TryAdd(List list, ActiveObjectClassModel item, int maxSize) { list.Add(item); Interlocked.Increment(ref _HistoryNum); if(item.StateCode == 0) { Interlocked.Increment(ref _HistoryOkNum); } else { Interlocked.Increment(ref _HistoryNgNum); } if (list.Count > maxSize) { list.Remove(list[list.Count - maxSize]); } return true; } /// /// 获取结果最长长边 /// /// /// private double GetActionMaxLength(List Rows) { //外轮廓点集 List points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList(); points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList()); //获取凸包点集 points = BoundRectangle.ConvexHull(points); //获取凸包长度最长的外接矩形 var result = BoundRectangle.RotatingCalipers(points); if (result != null) { return result.Height; } else { return 0; } } /// /// 通道区域判定 /// /// /// private int ActiveChannel(ActiveObjectClassModel activeObject) { int result = -1; int StartChannel = activeObject.MinStartCol / ChannelWidth; int EndChannel = activeObject.MaxEndCol / ChannelWidth; if (StartChannel == EndChannel) { result = StartChannel; } else if (EndChannel - StartChannel > 1) { Console.WriteLine("ActiveChannel-Error"); //error } else { result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel; } return result; } } }