using CCDCount.MODEL.ConfigModel; using CCDCount.MODEL.ShuLiClass; using LogClass; using MvCameraControl; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; namespace CCDCount.DLL { public class ShuLiClass { #region 变量 /// /// 当活跃物体转变为历史物体时的回调事件 /// public event EventHandler WorkCompleted; private List activeObjects = new List(); // 当前跟踪中的物体 private List historyActiveObjects = new List(); // 历史物体 private ConcurrentQueue ImageBytes = new ConcurrentQueue(); //图像数据队列 private ConcurrentQueue IFrameDatas = new ConcurrentQueue(); //图像数据队列 private Thread IdentifyImageProcessThread = null; // 识别线程 private bool IsIdentify = false; //线程是否开始识别的标志 private long currentLine = 0; //行数记录 private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件 private List ChannelsRoi = new List(); private int ChannelWidth = 0;//每个区域的宽度 private int IdentifyImageWidth = -1; private static readonly object _lockObj = new object(); // 专用锁对象\ private int ObjectNum = 0; public int ImageNum { get { return IFrameDatas.Count; } } #endregion #region 公共方法 /// /// 初始化构造方法 /// public ShuLiClass() { // 加载默认参数 shuLiConfig = new ShuLiConfigClass() { Channel = 8, PandingCode = 2 }; } public ShuLiClass(ShuLiConfigClass config) { if(config.IsLoadCanfig) { // 加载传出的参数 shuLiConfig = config; } else { // 加载默认参数 shuLiConfig = new ShuLiConfigClass() { Channel = 8, PandingCode = 2 }; } } /// /// 处理图像序列的主入口 /// /// 图像像素数据 /// 图像宽 /// 当前行数 /// 检测到的物体总数 public bool ProcessImageSequence(IImage image) { bool result = false; for (int i = 0;i /// 返回最后一个历史物品 /// /// public ActiveObjectClass GetLastActive() { if (historyActiveObjects.Count() == 0) return null; return historyActiveObjects.Last(); } /// /// 返回历史物品 /// /// public List GetHistoryActive() { lock (_lockObj) // 加锁 { return historyActiveObjects.ToList(); } } /// /// 返回缓存在内存的历史物品的总数量 /// /// public int GetHistoryActiveNum() { lock (_lockObj) // 加锁 return historyActiveObjects.Count(); } /// /// 清除缓存的内存的中的历史数据 /// public void ClearHistoryActive() { historyActiveObjects.Clear(); } /// /// 获取历史数据中,正常数据数量 /// /// public int GetOkHistoryNum() { lock (_lockObj) return historyActiveObjects.Where(o=>o.StateCode == 0).Count(); } /// /// 获取历史数据中,异常数据数量 /// /// public int GetNgHistoryNum() { lock (_lockObj) return historyActiveObjects.Where(o=>o.StateCode != 0).Count(); } /// /// 开启识别 /// public void StartIdentifyFuntion(int ImaageWidth) { UpdateIdentifyImageWidth(ImaageWidth); InitChannel(); try { // 标志位置位true IsIdentify = true; // 打开识别线程 IdentifyImageProcessThread = new Thread(IdentifyImageProcess) { Priority = ThreadPriority.Highest }; IdentifyImageProcessThread.Start(); } catch (Exception ex) { LOG.error("Start thread failed!, " + ex.Message); throw; } } /// /// 关闭识别 /// public void StopIdentifyFuntion() { try { // 标志位设为false IsIdentify = false; if(IdentifyImageProcessThread!=null&& IdentifyImageProcessThread.IsAlive) IdentifyImageProcessThread.Join(); } catch (Exception ex) { LOG.error("Stop thread failed!, " + ex.Message); throw; } } /// /// 向识别队列添加一个数据 /// /// public void SetOnceIdentifyImageData(byte[] items) { ImageBytes.Enqueue(items.Clone() as byte[]); } /// /// 向识别队列添加一个数据 /// /// public void SetOnceIdentifyImageData(IImage items) { IFrameDatas.Enqueue(items.Clone() as IImage); } /// /// 保存参数 /// public void SaveConfig() { if(!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\"); XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml"); } /// /// 更新检测宽度信息 /// /// public void UpdateIdentifyImageWidth(int Width) { IdentifyImageWidth = Width; } /// /// 初始化通道划分 /// /// public void InitChannel() { shuLiConfig.ImageWidth = IdentifyImageWidth; if (shuLiConfig.Channel > 0) { if (shuLiConfig.IsIdentifyRoiOpen) { ChannelWidth = (shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX) / shuLiConfig.Channel; } else { ChannelWidth = shuLiConfig.ImageWidth / shuLiConfig.Channel; } for (int i = 0; i < shuLiConfig.Channel; i++) { ChannelsRoi.Add(ChannelWidth + i * ChannelWidth); } } } /// /// 获取配置信息 /// /// public ShuLiConfigClass GetConfigValue() { ShuLiConfigClass result = shuLiConfig; return result; } #endregion #region 私有方法 /// /// 对外通知事件 /// private void OnWorkCompleted(List activeObject) { ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject); // 触发事件 WorkCompleted?.Invoke(this, activeObjectEventArgs); } /// /// 处理单行像素数据 /// 返回值为false的时候无活跃物体转变为历史物体 /// 返回值为true的时候有活跃物体转变为历史物体 /// /// 当前行像素数组 private bool ProcessLine(IImage imagedata,int RowNo) { bool result = false; // 步骤1:检测当前行的有效区域 var currentRegions = FindValidRegions(imagedata,RowNo); if (currentRegions.Count == 1) { if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Width) { LOG.error("当前行有效区域为整行,检查视野和光源"); return false; } } // 步骤2:处理当前行每个区域 for (int i = 0; i < currentRegions.Count; i++) { var region = currentRegions[i]; // 查找全部可合并的活跃物体(有重叠+在允许间隔内) 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 ActiveObjectClass { 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), Area = matcheds.Sum(o => o.Area), RowsData = CopeRowsData, ImageWidth = matcheds.FirstOrDefault().ImageWidth, }; // 从活跃区域中删除被合并的区域 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 RowStartEndCol { StartCol = region.Start, EndCol = region.End, RowsCol = currentLine, }); } else { // 创建新物体(首次出现的区域) activeObjects.Add(new ActiveObjectClass { MinStartCol = region.Start, MaxEndCol = region.End, StartLine = currentLine, LastSeenLine = currentLine, LastSeenLineStartCol = region.Start, LastSeenLineEndCol = region.End, StartCheckTime = DateTime.Now, Area = region.End - region.Start + 1, ImageWidth = IdentifyImageWidth, RowsData = new List { new RowStartEndCol { StartCol = region.Start, EndCol = region.End, RowsCol = currentLine, } } }); } } currentRegions.Clear(); // 更新有效物体的最后一行的起始点 activeObjects.Where(o => o.LastSeenLine == currentLine).ToList().ForEach(o => o.LastSeenLineStartCol = o.RowsData.Where(p => p.RowsCol == currentLine).Min(p => p.StartCol)); activeObjects.Where(o => o.LastSeenLine == currentLine).ToList().ForEach(o => o.LastSeenLineEndCol = o.RowsData.Where(p => p.RowsCol == currentLine).Max(p => p.EndCol)); // 步骤3:清理超时未更新的物体 var lostObjects = activeObjects .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height) .ToList(); List OneActive = new List(); // 有物体转变为活跃物体,返回值转为true if (lostObjects.Count() > 0) { result = true; foreach (var item in lostObjects) { //噪点判定 if (item.LastSeenLine - item.StartLine < shuLiConfig.NoiseFilter_Threshold || item.RowsData.Max(o => o.EndCol - o.StartCol) < shuLiConfig.NoiseFilter_Threshold) continue; //转为历史物体,添加缺少的参数 item.Num = ObjectNum += 1; item.ChannelNO = ActiveChannel(item); item.EndCheckTime = DateTime.Now; OneActive.Add(item); if ((item.LastSeenLine - item.StartLine) > shuLiConfig.MAX_Idetify_Height) { item.StateCode = 7; LOG.error("ShuLiClass-ProcessLine:非颗粒,视野异常"); Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常"); } else if (shuLiConfig.PandingCode != -1) { if (item.Area < shuLiConfig.MinArea && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1)) { item.StateCode = 5; LOG.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; LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num)); Console.WriteLine("颗粒编号{0}:面积过大", item.Num); } else if (item.LastSeenLine - item.StartLine < shuLiConfig.MIN_OBJECT_HEIGHT && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 2; LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num)); Console.WriteLine("颗粒编号{0}:超短粒", item.Num); } else if (item.LastSeenLine - item.StartLine > shuLiConfig.MAX_OBJECT_HEIGHT && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 1; LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num)); Console.WriteLine("颗粒编号{0}:超长粒", item.Num); } else if (item.RowsData.Max(o => o.EndCol - o.StartCol) > shuLiConfig.MAX_OBJECT_WIDTH && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 3; LOG.log(string.Format("颗粒编号{0}:超宽粒", item.Num)); Console.WriteLine("颗粒编号{0}:超宽粒", item.Num); } else if (item.RowsData.Max(o => o.EndCol - o.StartCol) < shuLiConfig.MIN_OBJECT_WIDTH && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 4; LOG.log(string.Format("颗粒编号{0}:超窄粒", item.Num)); Console.WriteLine("颗粒编号{0}:超窄粒", item.Num); } else { item.StateCode = 0; LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num)); Console.WriteLine("颗粒编号{0}:正常粒", item.Num); } } } if (OneActive.Count > 0) //触发回调事件 OnWorkCompleted(OneActive); } else { OneActive = null; } lock (_lockObj) { // 累加到总数并从活跃物体转移到历史物体 lostObjects.Where(o => o.LastSeenLine - o.StartLine >= shuLiConfig.NoiseFilter_Threshold && o.StateCode != 7).ToList().ForEach(o => historyActiveObjects.Add(o)); lostObjects.ForEach(o => activeObjects.Remove(o)); lostObjects.ForEach(o => historyActiveObjects.Where(P => P.Num == o.Num - 100).ToList().ForEach(P => P.RowsData.Clear())); } return result; } /// /// 检测有效物体区域(横向连续黑色像素段) /// /// 当前行像素数组 /// 有效区域列表(起始/结束位置) private List<(int Start, int End)> FindValidRegions(IImage image,int RowNo) { List<(int Start, int End)> regions = new List<(int Start, int End)>(); int start = -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) // 遇到白色像素且存在进行中的区域 { // 检查区域宽度是否达标 if (i - start >= shuLiConfig.MIN_OBJECT_WIDTH) { regions.Add((start, (i - 1)% (int)image.Width)); // 记录有效区域 } start = -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) // 遇到白色像素且存在进行中的区域 { // 检查区域宽度是否达标 if (i - start >= shuLiConfig.MIN_OBJECT_WIDTH) { regions.Add((start, (i - 1) % (int)image.Width)); // 记录有效区域 } start = -1; // 重置区域标记 } } } // 处理行尾未闭合的区域 if (start != -1 && image.Width - start >= shuLiConfig.MIN_OBJECT_WIDTH) { regions.Add((start, (int)image.Width - 1)); } return regions; } /// /// 判断区域重叠(与活跃物体的横向坐标重叠检测) /// /// 活跃物体 /// 当前区域 /// 是否发生重叠 private bool IsOverlapping(ActiveObjectClass obj, (int Start, int End) region) { // 判断区域是否不相交的逆条件 return !(region.End < obj.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol); } /// /// 通道区域判定 /// /// /// private int ActiveChannel(ActiveObjectClass 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; } #endregion #region 线程方法 /// /// 识别图像线程 /// private void IdentifyImageProcess() { //Stopwatch stopwatch = Stopwatch.StartNew(); IImage IframeData = null; while (IsIdentify) { //判断队列中是否有数据 if (IFrameDatas.Count() > 0) { //stopwatch.Restart(); IFrameDatas.TryDequeue(out IframeData); //是否成功取得数据 if (IframeData != null) { //识别 ProcessImageSequence(IframeData); } else { Console.WriteLine("识别数据为空"); continue; } //输出耗时 //stopwatch.Stop(); ///Console.WriteLine("识别线程单次运行耗时:" + stopwatch.Elapsed.ToString()); } else { Thread.Sleep(1); } } } #endregion } }