using CCDCount.DLL.AlarmTools; using CCDCount.DLL.Tools; using CCDCount.MODEL.ConfigModel; using CCDCount.MODEL.ResultModel; using CCDCount.MODEL.ShuLiModel; using LogClass; using MvCameraControl; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace CCDCount.DLL { public class ShuLiClass { #region 变量 /// /// 当活跃物体转变为历史物体时的回调事件 /// public event EventHandler WorkCompleted; private List activeObjects = new List(); // 当前跟踪中的物体 private List historyActiveObjects = new List(); // 历史物体 private LockFreeRingBuffer RingBuffer_IFrameDatas = new LockFreeRingBuffer(300); private long currentLine = 0; //行数记录 private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件 public List ChannelsRoi { get { return _ChannelsRoi; } } private List _ChannelsRoi = new List(); private int ChannelWidth = 0;//每个区域的宽度 private int IdentifyImageWidth = -1; private int ObjectNum = 0; public int ImageNum { get { return RingBuffer_IFrameDatas.Count; } } private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private double XCoefficient = 0.1; private double YCoefficient = 0.2; private DateTime[] ChannelIntervalTime = new DateTime[8]; private int _HistoryActiveNum = 0; public int HistoryActiveNum { get { return _HistoryActiveNum; } } private int _OkHistoryNum = 0; public int OkHistoryNum { get { return _OkHistoryNum; } } private int _NgHistoryNum = 0; public int NgHistoryNum { get { return _NgHistoryNum; } } #endregion #region 公共方法 /// /// 初始化构造方法 /// public ShuLiClass() { // 加载默认参数 shuLiConfig = new ShuLiConfigClass() { Channel = 8, PandingCode = 2 }; for(int i = 0; i < ChannelIntervalTime.Length; i++) { ChannelIntervalTime[i] = DateTime.Now; } } /// /// 带参数的构造方法 /// public ShuLiClass(ShuLiConfigClass config) { if (config.IsLoadCanfig) { // 加载传出的参数 shuLiConfig = config; XCoefficient = shuLiConfig.ScaleX; YCoefficient = shuLiConfig.ScaleY; } else { // 加载默认参数 shuLiConfig = new ShuLiConfigClass() { Channel = 8, PandingCode = 2 }; } InitChannel(); } public long InitCurrentLine(int line) { currentLine = line; return currentLine; } public int InitNum(int num) { ObjectNum = num; return ObjectNum; } /// /// 处理图像序列的主入口 /// /// 图像像素数据 /// 检测到的物体总数 public bool ProcessImageSequence(IFrameOut image) { bool result = false; ReadOnlySpan spanFromArr = image.Image.PixelData.AsSpan(); for (int i = 0; i < image.Image.Height; i++) { ProcessLine(image, i); currentLine += 1; } // 优化: 减少锁的使用频率,批量处理逻辑 List objectsToProcess = null; _rwLock.EnterReadLock(); try { // 使用ToList()避免在锁内进行复杂查询 objectsToProcess = activeObjects .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height) .ToList(); } finally { _rwLock.ExitReadLock(); } // 优化: 提前返回,避免不必要的处理 if (objectsToProcess.Count == 0) { return result; } // 优化: 预分配容量,减少内存重新分配 var processedObjects = new List(objectsToProcess.Count); // 优化: 将耗时的计算移到循环外 var endTime = DateTime.Now; var pictureEndTime = FromUnixTimestamp((long)image.HostTimeStamp); foreach (var item in objectsToProcess) { // 优化: 将耗时的计算移到循环外 ProcessSingleObject(item, pictureEndTime, endTime, processedObjects); } result = true; // 优化: 批量操作减少锁的持有时间 if (processedObjects.Count > 0) { _rwLock.EnterWriteLock(); try { // 批量转移对象 foreach (var item in processedObjects) { TryAdd(historyActiveObjects, item, 2500); } // 批量移除 foreach (var item in objectsToProcess) { activeObjects.Remove(item); } } finally { _rwLock.ExitWriteLock(); } // 优化: 异步触发事件,避免阻塞主线程 //ThreadPool.QueueUserWorkItem(_ => //{ OnWorkCompleted(processedObjects); //}); } return result; } // 抽取的单个对象处理方法 private void ProcessSingleObject(ActiveObjectClass item, DateTime pictureEndTime, DateTime endTime, List processedObjects) { item.PictureEndReadTime = pictureEndTime; var nowTime = DateTime.Now; var ShiBieLuoHouTime = (nowTime - item.PictureEndReadTime).TotalMilliseconds; //// 优化: 将日志记录改为条件执行 if (ShiBieLuoHouTime > 30) { //LOG.error($"算法识别结果落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}"); Console.WriteLine($"算法识别结果落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}"); } // 快速滤除噪点 if (item.Area < shuLiConfig.NoiseFilter_Threshold) { item.StateCode = 9; return; } // 计算长度 if (item.StartLine == item.LastSeenLine) { item.MaxLength = item.MaxEndCol - item.MinStartCol; item.StateCode = 10; LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num)); return; } else { if ((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height) { var calculationResult = SizeCalculation(item.RowsData); if (calculationResult != null) { item.MaxLength = calculationResult.Height; } } else { item.StateCode = 7; return; } } // 分类处理 ApplyClassificationRules(ref item); // 添加到历史记录 if (item.StateCode != 7 && item.StateCode != 9 && item.StateCode != 10) { item.Num = Interlocked.Increment(ref ObjectNum); item.ChannelNO = ActiveChannel(item); } item.EndCheckTime = endTime; if (item.StateCode != 7 && item.StateCode != 9 && item.StateCode != 10) { processedObjects.Add(item); } } /// /// 分类规则 /// /// private void ApplyClassificationRules(ref ActiveObjectClass item) { if (shuLiConfig.PandingCode == -1) return; if (item.StateCode != -1) { if (item.StateCode == 8) { LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num)); } else if (item.StateCode == 7) { LOG.log("视野被遮挡"); } else if (item.StateCode == 9) { LOG.log(string.Format("噪点", item.Num)); } } else if (item.Area < shuLiConfig.MinArea && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1)) { item.StateCode = 6; LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num)); } else if (item.Area > shuLiConfig.MaxArea && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1)) { item.StateCode = 5; LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num)); } else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 2; LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num)); } else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0)) { item.StateCode = 1; LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num)); } else { item.StateCode = 0; LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num)); } } /// /// 返回最后一个历史物品 /// /// public ActiveObjectClass GetLastActive() { if (historyActiveObjects.Count() == 0) return null; _rwLock.EnterReadLock(); var result = historyActiveObjects.Last(); _rwLock.ExitReadLock(); return result; } /// /// 返回历史物品 /// /// public List GetHistoryActive() { //lock (_lockObj) // 加锁 //{ _rwLock.EnterReadLock(); var result = historyActiveObjects.ToList(); _rwLock.ExitReadLock(); return result; //} } /// /// 清除历史数据 /// /// public bool ClearHistoryActive() { _HistoryActiveNum = 0; _OkHistoryNum = 0; _NgHistoryNum = 0; _rwLock.EnterWriteLock(); historyActiveObjects.Clear(); _rwLock.ExitWriteLock(); return true; //} } /// /// 开启识别-优化版本 /// /// public void StartIdentifyFuntion(int ImaageWidth) { UpdateIdentifyImageWidth(ImaageWidth); InitChannel(); try { CancellationTokenSource = new CancellationTokenSource(); var token = CancellationTokenSource.Token; LOG.log(string.Format("X系数:{0},Y系数:{1}", XCoefficient, YCoefficient), 6); // 启动图像处理线程 ProcessingTask = Task.Factory.StartNew(() => IdentifyImageProcessTask(token), token, TaskCreationOptions.LongRunning, TaskScheduler.Default); SystemAlarm.AlarmCancel(AlarmMessageList.识别线程启动失败); } catch (Exception ex) { SystemAlarm.AlarmAlert(AlarmMessageList.识别线程启动失败, "Start thread failed!, " + ex.Message, "识别线程启动失败, " + ex.Message, "DLL:ShuLiClass-StartIdentifyFuntion"); //FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message); throw; } } public async void StopIdentifyFuntion() { try { // 发送取消信号 CancellationTokenSource.Cancel(); // 等待线程完成 try { if (ProcessingTask != null) await ProcessingTask; } catch (OperationCanceledException) { // 正常取消,忽略异常 } SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败); } catch (Exception ex) { //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message); SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败, "Stop thread failed!, " + ex.Message, "识别线程停止失败, " + ex.Message, "DLL:ShuLiClass-StopIdentifyFuntion"); throw; } } /// /// 向识别队列添加一个数据 /// /// public void SetOnceIdentifyImageData(IFrameOut items) { RingBuffer_IFrameDatas.TryEnqueue(items.Clone() as IFrameOut); } /// /// 保存参数 /// 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() { _ChannelsRoi.Clear(); shuLiConfig.ImageWidth = IdentifyImageWidth == -1 ? 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; } public bool TryAddHis(ActiveObjectClass item, int maxSize) { ThreadPool.QueueUserWorkItem(_ => { _rwLock.EnterWriteLock(); historyActiveObjects.Add(item); if (historyActiveObjects.Count > maxSize) { historyActiveObjects.Remove(historyActiveObjects[historyActiveObjects.Count - maxSize]); } _rwLock.ExitWriteLock(); }); return true; } public int GetConfigImageWidth() { int result = -1; if (shuLiConfig != null) result = shuLiConfig.ImageWidth; return result; } /// /// 获取坐标转换系数 /// /// /// public void GetXYCoefficient(out double XCoefficient, out double YCoefficient) { XCoefficient = this.XCoefficient; YCoefficient = this.YCoefficient; } /// /// 设置坐标转换X系数 /// /// public void SetXCoefficient(double XCoefficient) { this.XCoefficient = XCoefficient; } /// /// 设置坐标转换Y系数 /// public void SetYCoefficient(double YCoefficient) { this.YCoefficient = YCoefficient; } /// /// 获取结果最长长边 /// /// /// public BoundingRectangleMdoel SizeCalculation(List Rows) { //Stopwatch stopwatch = Stopwatch.StartNew(); var points = ConvexHull(Rows); //stopwatch.Stop(); //Console.WriteLine($"凸包计算耗时:{stopwatch.Elapsed}"); //var test = CalculateMinimumBoundingRectangle(points); var result = CoefficientCalculateMinimumBoundingRectangle(points); return result; } public MaxLengthModel SizeCalculation2(List Rows) { var points = ConvexHull(Rows); var result = CoefficientRotatingCalipers(points); return result; } /// /// 凸包点集合获取 /// /// /// public List ConvexHull(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 = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList(); var lower = new List(); foreach (var p in points) { while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0) lower.RemoveAt(lower.Count - 1); lower.Add(p); } var upper = new List(); for (int i = points.Count - 1; i >= 0; i--) { var p = points[i]; while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0) upper.RemoveAt(upper.Count - 1); upper.Add(p); } lower.RemoveAt(lower.Count - 1); upper.RemoveAt(upper.Count - 1); lower.AddRange(upper); return lower; } /// /// 凸包点集合获取,输出原点集 /// /// /// public List ConvexHull(List Rows, out List points) { 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 = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList(); var lower = new List(); foreach (var p in points) { while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0) lower.RemoveAt(lower.Count - 1); lower.Add(p); } var upper = new List(); for (int i = points.Count - 1; i >= 0; i--) { var p = points[i]; while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0) upper.RemoveAt(upper.Count - 1); upper.Add(p); } lower.RemoveAt(lower.Count - 1); upper.RemoveAt(upper.Count - 1); lower.AddRange(upper); return lower; } /// /// 凸包点集合获取,同时检测较大的内凹 /// /// /// 返回凸包点集 public ConvexResultClass ConvexHullWithConcavityDetection(List Rows) { ConvexResultClass result = null; // 构建原始点集 var lower = ConvexHull(Rows, out List originalPoints); // 检测内凹 ConvexResultClass ConvexResult = DetectConcavity(originalPoints, lower); return result = new ConvexResultClass() { convexHull = lower, hasSignificantConcavity = ConvexResult.hasSignificantConcavity, concavityRatio = ConvexResult.concavityRatio, }; } #endregion #region 私有方法 public DateTime FromUnixTimestamp(long timestamp, bool isMilliseconds = false) { try { // 如果未指定单位,尝试自动检测 if (!isMilliseconds) { // 如果时间戳看起来像毫秒级(13位数字) if (timestamp.ToString().Length > 10) { isMilliseconds = true; } } if (isMilliseconds) { // 验证毫秒级时间戳范围 const long minMilliTimestamp = -62135596800000; // 0001-01-01 00:00:00 UTC const long maxMilliTimestamp = 253402300799999; // 9999-12-31 23:59:59 UTC if (timestamp < minMilliTimestamp || timestamp > maxMilliTimestamp) { throw new ArgumentOutOfRangeException(nameof(timestamp), "毫秒级时间戳超出有效范围"); } DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(timestamp); return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local); } else { // 验证秒级时间戳范围 const long minTimestamp = -62135596800; const long maxTimestamp = 253402300799; if (timestamp < minTimestamp || timestamp > maxTimestamp) { throw new ArgumentOutOfRangeException(nameof(timestamp), "秒级时间戳超出有效范围"); } DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp); return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local); } } catch (ArgumentOutOfRangeException) { throw; } catch (Exception ex) { throw new ArgumentException($"无法转换时间戳 {timestamp}: {ex.Message}", ex); } } /// /// 对外通知事件 /// private void OnWorkCompleted(List activeObject) { if(activeObject == null) return; ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject); // 触发事件 WorkCompleted?.Invoke(this, activeObjectEventArgs); } private bool IsPrintLightOnError = false; List currentRegions = null; /// /// 处理单行像素数据 /// 返回值为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 == 0) return result; if (currentRegions.Count == 1) { if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Image.Width) { if (!IsPrintLightOnError) { FaultLog.RecordLogMessage("The current effective area is the entire row. Check the field of view and light source", 6); IsPrintLightOnError = true; } return false; } } IsPrintLightOnError = false; foreach (var region in currentRegions) { if(region.End-region.Start<=3) { continue; } // 查找全部可合并的活跃物体(有重叠+在允许间隔内) _rwLock.EnterReadLock(); var matcheds = activeObjects.Where(o => IsOverlapping(o, region) && (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList(); _rwLock.ExitReadLock(); //当有多个可合并的活跃物体时,将多个物体合并 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), PreSeenLineStartCol = matcheds.Min(o => o.PreSeenLineStartCol), PreSeenLineEndCol = matcheds.Max(o => o.PreSeenLineEndCol), StartCheckTime = matcheds.Min(o => o.StartCheckTime), EndCheckTime = matcheds.Max(o => o.EndCheckTime), Area = matcheds.Sum(o => o.Area), RowsData = CopeRowsData, ImageWidth = matcheds.FirstOrDefault().ImageWidth, //StateCode = 8 }; _rwLock.EnterWriteLock(); // 从活跃区域中删除被合并的区域 matcheds.ForEach(o => activeObjects.Remove(o)); // 保存新的区域到活跃区域中 activeObjects.Add(MergeMatched); _rwLock.ExitWriteLock(); } _rwLock.EnterReadLock(); // 搜获可用且可合并的活跃区域 var matched = activeObjects.FirstOrDefault(o => IsOverlapping(o, region) && (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP); _rwLock.ExitReadLock(); 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, }); if(matched.LastSeenLineEndCol==-1) { matched.LastSeenLineEndCol = region.End; } else { matched.LastSeenLineEndCol = Math.Max(matched.LastSeenLineEndCol, region.End); } if (matched.LastSeenLineStartCol == -1) { matched.LastSeenLineStartCol = region.Start; } else { matched.LastSeenLineStartCol = Math.Min(matched.LastSeenLineStartCol, region.Start); } } else { _rwLock.EnterWriteLock(); // 创建新物体(首次出现的区域) activeObjects.Add(new ActiveObjectClass { MinStartCol = region.Start, MaxEndCol = region.End, StartLine = currentLine, LastSeenLine = currentLine, PreSeenLineStartCol = region.Start, PreSeenLineEndCol = region.End, StartCheckTime = DateTime.Now, PictureStartReadTime = FromUnixTimestamp((long)imagedata.HostTimeStamp), Area = region.End - region.Start + 1, ImageWidth = IdentifyImageWidth, RowsData = new List { new RowStartEndCol { StartCol = region.Start, EndCol = region.End, RowsCol = currentLine, } } }); _rwLock.ExitWriteLock(); } } foreach (var obj in activeObjects) { if(obj.LastSeenLineEndCol!=-1) { obj.PreSeenLineEndCol = obj.LastSeenLineEndCol; obj.LastSeenLineEndCol = -1; } if (obj.LastSeenLineStartCol != -1) { obj.PreSeenLineStartCol = obj.LastSeenLineStartCol; obj.LastSeenLineStartCol = -1; } } //stopwatch.Stop(); //if (stopwatch.ElapsedMilliseconds > 1) //{ // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}"); //} return result; } // 辅助类定义 private enum OperationType { MergeMultiple, Update, Add } private class OperationItem { public OperationType Type { get; set; } public List ObjectsToRemove { get; set; } public ActiveObjectClass ObjectToAdd { get; set; } public ActiveObjectClass ObjectToUpdate { get; set; } public ValidRegionModelClass Region { get; set; } } List regions = new List(); /// /// 检测有效物体区域(横向连续黑色像素段) /// /// 当前行像素数组 /// 有效区域列表(起始/结束位置) private List FindValidRegions(IImage image, int RowNo) { regions.Clear(); int start = -1; // 当前区域起始标记 int end = -1; int imageWidth = (int)image.Width; //var pixelSpan = image.PixelData.AsSpan(); var pixelSpan = image.PixelData; // 遍历所有像素列 if (shuLiConfig.IsIdentifyRoiOpen) { for (int i = imageWidth * RowNo + shuLiConfig.IdentifyStartX; i < imageWidth * RowNo + shuLiConfig.IdentifyStopX; i++) { if (pixelSpan[i] < shuLiConfig.RegionThreshold&& start == -1) // 发现黑色像素 { start = i % imageWidth; // 开始新区域 } else if (start != -1) // 遇到白色像素且存在进行中的区域 { end = (i - 1) % imageWidth; // 检查区域宽度是否达标 regions.Add(new ValidRegionModelClass() { Start = start, End = end }); // 记录有效区域 start = -1; // 重置区域标记 end = -1; } } } else { for (int i = imageWidth * RowNo; i < imageWidth * (RowNo + 1); i++) { if (pixelSpan[i] < shuLiConfig.RegionThreshold) // 发现黑色像素 { if (start == -1) start = i % imageWidth; // 开始新区域 } else if (start != -1) // 遇到白色像素且存在进行中的区域 { end = (i - 1) % imageWidth; regions.Add(new ValidRegionModelClass() { Start = start, End = end }); // 记录有效区域 start = -1; // 重置区域标记 end = -1; } } } // 处理行尾未闭合的区域 if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold) { regions.Add(new ValidRegionModelClass() { Start = start, End = (int)image.Width - 1 }); } return new List(regions); } /// /// 判断区域重叠(与活跃物体的横向坐标重叠检测) /// /// 活跃物体 /// 当前区域 /// 是否发生重叠 private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass region) { // 判断区域是否不相交的逆条件 return !(region.End < obj.PreSeenLineStartCol || region.Start > obj.PreSeenLineEndCol); } /// /// 通道区域判定 /// /// /// 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; } /// /// 检测是否存在显著的内凹 /// /// 原始点集 /// 凸包点集 /// 是否存在显著内凹和程度比值 private ConvexResultClass DetectConcavity(List originalPoints, List convexHull) { if (convexHull.Count < 3) return new ConvexResultClass() { hasSignificantConcavity = false, concavityRatio = 0, }; // 检查原始点集中有多少点在凸包内部(即非凸包顶点) HashSet hullPoints = new HashSet(convexHull); var internalPoints = originalPoints.Where(p => !hullPoints.Contains(p)).ToList(); if (internalPoints.Count == 0) { // 所有点都在凸包上,没有内凹 return new ConvexResultClass() { hasSignificantConcavity = false, concavityRatio = 0, }; } // 计算凸包面积 double convexHullArea = CalculatePolygonArea(SortPointsByNearestNeighbor(convexHull)); double PointsArea = CalculatePolygonArea(SortPointsByNearestNeighbor(originalPoints)); var concavityMeasure = (convexHullArea - PointsArea) / convexHullArea; // 判断是否存在显著内凹(可以根据实际需求调整阈值) bool hasSignificantConcavity = concavityMeasure > 0.3; return new ConvexResultClass() { hasSignificantConcavity = hasSignificantConcavity, concavityRatio = concavityMeasure, }; } /// /// 计算封闭多边形的面积(鞋带公式) /// /// 多边形顶点(必须是封闭的) /// 面积 private double CalculatePolygonArea(List polygon) { if (polygon.Count < 3) return 0; // 确保多边形是封闭的 var closedPolygon = new List(polygon); if (closedPolygon.First() != closedPolygon.Last()) { closedPolygon.Add(closedPolygon[0]); } double area = 0; for (int i = 0; i < closedPolygon.Count - 1; i++) { area += (double)closedPolygon[i].X * closedPolygon[i + 1].Y; area -= (double)closedPolygon[i].Y * closedPolygon[i + 1].X; } return Math.Abs(area) / 2.0; } /// /// 凸包最长边,结果通过系数计算 /// /// /// public MaxLengthModel CoefficientRotatingCalipers(List hull) { //老方法 MaxLengthModel result = new MaxLengthModel(); int n = hull.Count; if (n == 1) { result = null; return result; } if (n == 2) { result.MaxLength = Distance(hull[0], hull[1]); result.Point1Index = 0; result.Point1 = hull[0]; result.Point2Index = 1; result.Point2 = hull[1]; return result; } double maxDist = 0; int pointIndex1 = 0; int pointIndex2 = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (CoefficientDistance(hull[i], hull[j]) > maxDist) { maxDist = CoefficientDistance(hull[i], hull[j]); pointIndex1 = i; pointIndex2 = j; } } } result.Point1 = hull[pointIndex1]; result.Point1Index = pointIndex1; result.Point2 = hull[pointIndex2]; result.Point2Index = pointIndex2; result.MaxLength = maxDist; return result; } /// /// 凸包最长边 /// /// /// public MaxLengthModel RotatingCalipers(List hull) { //老方法 MaxLengthModel result = new MaxLengthModel(); int n = hull.Count; if (n == 1) { result = null; return result; } if (n == 2) { result.MaxLength = Distance(hull[0], hull[1]); result.Point1Index = 0; result.Point1 = hull[0]; result.Point2Index = 1; result.Point2 = hull[1]; return result; } double maxDist = 0; int pointIndex1 = 0; int pointIndex2 = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (Distance(hull[i], hull[j]) > maxDist) { maxDist = Distance(hull[i], hull[j]); pointIndex1 = i; pointIndex2 = j; } } } result.Point1 = hull[pointIndex1]; result.Point1Index = pointIndex1; result.Point2 = hull[pointIndex2]; result.Point2Index = pointIndex2; result.MaxLength = Distance(result.Point1, result.Point2); return result; } /// /// 计算凸包的最小外接矩形 /// /// 凸包顶点列表(按逆时针顺序排列) /// 最小外接矩形的四个顶点 public BoundingRectangleMdoel CalculateMinimumBoundingRectangle(List convexHull) { BoundingRectangleMdoel result = new BoundingRectangleMdoel(); if (convexHull == null || convexHull.Count < 3) return null; double minArea = double.MaxValue; int n = convexHull.Count; // 遍历每一条边作为基准边 for (int i = 0; i < n; i++) { Point edgeStart = convexHull[i]; Point edgeEnd = convexHull[(i + 1) % n]; // 计算当前边的角度 double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X); // 将所有点绕原点旋转-angle角度,使当前边与x轴平行 var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList(); // 找到包围盒 int minX = rotatedPoints.Min(p => p.X); int maxX = rotatedPoints.Max(p => p.X); int minY = rotatedPoints.Min(p => p.Y); int maxY = rotatedPoints.Max(p => p.Y); // 计算面积 double area = (maxX - minX) * (maxY - minY); // 如果面积更小,则更新最小矩形 if (area < minArea) { minArea = area; // 构造矩形的四个顶点并旋转回原来的角度 var rectangle = new List { RotatePoint(new Point(minX, minY), angle), RotatePoint(new Point(maxX, minY), angle), RotatePoint(new Point(maxX, maxY), angle), RotatePoint(new Point(minX, maxY), angle) }; result.points = rectangle; result.Angle = angle; result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle); } } double l1 = Distance(result.points[0], result.points[1]); double l2 = Distance(result.points[1], result.points[2]); if (l1 < l2) { result.WidthPoints = new List { result.points[0], result.points[1], }; result.HeightPoints = new List { result.points[1], result.points[2], }; } else { result.WidthPoints = new List { result.points[2], result.points[1], }; result.HeightPoints = new List { result.points[1], result.points[0], }; } result.Height = Math.Max(l1, l2); result.Width = Math.Min(l1, l2); return result; } /// /// 计算凸包的最小外接矩形 /// /// 凸包顶点列表(按逆时针顺序排列) /// 最小外接矩形的四个顶点 public BoundingRectangleMdoel CoefficientCalculateMinimumBoundingRectangle(List convexHull) { BoundingRectangleMdoel result = new BoundingRectangleMdoel(); if (convexHull == null || convexHull.Count < 3) return null; double minArea = double.MaxValue; int n = convexHull.Count; // 遍历每一条边作为基准边 for (int i = 0; i < n; i++) { Point edgeStart = convexHull[i]; Point edgeEnd = convexHull[(i + 1) % n]; // 计算当前边的角度 double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X); // 将所有点绕原点旋转-angle角度,使当前边与x轴平行 var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList(); // 找到包围盒 int minX = rotatedPoints.Min(p => p.X); int maxX = rotatedPoints.Max(p => p.X); int minY = rotatedPoints.Min(p => p.Y); int maxY = rotatedPoints.Max(p => p.Y); // 计算面积 double area = (maxX - minX) * (maxY - minY); // 如果面积更小,则更新最小矩形 if (area < minArea) { minArea = area; // 构造矩形的四个顶点并旋转回原来的角度 var rectangle = new List { RotatePoint(new Point(minX, minY), angle), RotatePoint(new Point(maxX, minY), angle), RotatePoint(new Point(maxX, maxY), angle), RotatePoint(new Point(minX, maxY), angle) }; result.points = rectangle; result.Angle = angle; result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle); } } double l1 = CoefficientDistance(result.points[0], result.points[1]); double l2 = CoefficientDistance(result.points[1], result.points[2]); if (l1 < l2) { result.WidthPoints = new List { result.points[0], result.points[1], }; result.HeightPoints = new List { result.points[1], result.points[2], }; } else { result.WidthPoints = new List { result.points[2], result.points[1], }; result.HeightPoints = new List { result.points[1], result.points[0], }; } result.Height = Math.Max(l1, l2); result.Width = Math.Min(l1, l2); return result; } public List SortPointsByNearestNeighbor(List points) { if (points.Count <= 1) return points; var sorted = new List(); var remaining = new HashSet(points); // 选择起始点(例如最左边的点) var current = remaining.OrderBy(p => p.X).First(); sorted.Add(current); remaining.Remove(current); while (remaining.Any()) { // 找到最近的点 var nearest = remaining.OrderBy(p => Distance(current, p)).First(); sorted.Add(nearest); remaining.Remove(nearest); current = nearest; } return sorted; } /// /// 绕原点旋转点 /// /// 待旋转的点 /// 旋转角度(弧度) /// 旋转后的点 private static Point RotatePoint(Point point, double angle) { double cos = Math.Cos(angle); double sin = Math.Sin(angle); return new Point( (int)(point.X * cos - point.Y * sin), (int)(point.X * sin + point.Y * cos) ); } /// /// 计算点到线段的距离 /// /// 线段点1 /// 线段点2 /// 点三 /// private double DistanceToLine(Point a, Point b, Point c) { double area = Math.Abs(Area2(a, b, c)); double baseLength = Distance(a, b); return area / baseLength; } // 计算向量叉积 private int Cross(Point o, Point a, Point b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); // 计算三角形面积的两倍 private int Area2(Point a, Point b, Point c) => (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X); // 计算两点间距离 private double Distance(Point a, Point b) { int dx = a.X - b.X; int dy = a.Y - b.Y; return Math.Sqrt((dx * dx) + (dy * dy)); } private double CoefficientDistance(Point a,Point b) { double dx = (a.X - b.X) * XCoefficient; double dy = (a.Y - b.Y) * YCoefficient; return Math.Sqrt((dx * dx) + (dy * dy)); } private bool TryAdd(List list, ActiveObjectClass item, int maxSize) { list.Add(item); if(item.StateCode!=9&& item.StateCode != 10) { Interlocked.Increment(ref _HistoryActiveNum); if (item.StateCode == 0) { Interlocked.Increment(ref _OkHistoryNum); } else { Interlocked.Increment(ref _NgHistoryNum); } } if (list.Count > maxSize) { list.Remove(list[list.Count - maxSize]); } return true; } #endregion #region 线程方法 //信号量 public SemaphoreSlim QueueSemaphore { get; set; } //取消令牌 public CancellationTokenSource CancellationTokenSource { get; set; } //图像处理线程 public Task ProcessingTask { get; set; } /// /// 图像处理线程运行方法 /// /// private async void IdentifyImageProcessTask(CancellationToken token) { const int batchSize = 5; // 每批处理的最大图像数量 var batch = new List(batchSize); // 存储当前批次的图像 while (!token.IsCancellationRequested) { try { // 等待图像数据 await QueueSemaphore.WaitAsync(token); // 收集一批图像(严格按队列顺序) while (batch.Count < batchSize && RingBuffer_IFrameDatas.TryDequeue(out IFrameOut imageData)) { batch.Add(imageData); } // 顺序处理当前批次的所有图像 foreach (var image in batch) { if (image != null) { //识别 ProcessImageSequence(image); image.Dispose(); } else { continue; } } batch.Clear(); } catch(Exception ex) { LOG.error("ShuLiClass-IdentifyImageProcessTask:" + ex.Message); } } LOG.log("图像处理线程运行结束", 6); } #endregion } }