using CCDCount.DLL.AlarmTools; using CCDCount.MODEL.ConfigModel; using CCDCount.MODEL.ResultModel; using CCDCount.MODEL.ShuLiModel; using LogClass; using MvCameraControl; using Org.BouncyCastle.Utilities; 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.Runtime.InteropServices.WindowsRuntime; using System.Threading; using System.Threading.Tasks; using System.Windows.Media.Media3D; using static iTextSharp.text.pdf.AcroFields; namespace CCDCount.DLL { public class ShuLiClass { #region 变量 /// /// 当活跃物体转变为历史物体时的回调事件 /// public event EventHandler WorkCompleted; private List activeObjects = new List(); // 当前跟踪中的物体 private List historyActiveObjects = new List(); // 历史物体 private ConcurrentQueue IFrameDatas = new ConcurrentQueue(); //图像数据队列 private Thread IdentifyImageProcessThread = null; // 识别线程 private bool IsIdentify = false; //线程是否开始识别的标志 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 IFrameDatas.Count; } } private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private double XCoefficient = 1; private double YCoefficient = 2; private DateTime[] ChannelIntervalTime = new DateTime[8]; #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; } List lostObjects = new List(); /// /// 处理图像序列的主入口 /// /// 图像像素数据 /// 图像宽 /// 当前行数 /// 检测到的物体总数 public bool ProcessImageSequence(IFrameOut image) { bool result = false; ReadOnlySpan spanFromArr = image.Image.PixelData.AsSpan(); for (int i = 0; i < image.Image.Height; i++) { result = ProcessLine2(spanFromArr,image.HostTimeStamp,image.Image.Width, i); currentLine += 1; } //识别到结果并输出 _rwLock.EnterReadLock(); // 清理超时未更新的物体 lostObjects = activeObjects .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height) .ToList(); _rwLock.ExitReadLock(); List OneActive = new List(); // 有物体转变为活跃物体,返回值转为true if (lostObjects.Count > 0) { Stopwatch stopwatch = Stopwatch.StartNew(); result = true; //stopwatch.Restart(); foreach (var item in lostObjects) { item.PictureEndReadTime = FromUnixTimestamp((long)image.HostTimeStamp); DateTime NowTime = DateTime.Now; var AllPictureUseTime = (item.PictureEndReadTime - item.PictureStartReadTime).TotalMilliseconds; var PictureUsetime = (NowTime - item.PictureStartReadTime).TotalMilliseconds; if (PictureUsetime - AllPictureUseTime > 20) LOG.error($"算法耗时超过了20ms,相机取图总耗时:{AllPictureUseTime},从取第一张图到现在的耗时{PictureUsetime}"); //噪点判定 if (item.Area < shuLiConfig.NoiseFilter_Threshold) { item.StateCode = 9; //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6); continue; } if(item.StartLine == item.LastSeenLine) { item.MaxLength = item.MaxEndCol - item.MinStartCol; item.StateCode = 10; LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num)); } else { if((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height) { var CalculationResult = SizeCalculation(item.RowsData); if(CalculationResult!=null) { item.MaxLength = CalculationResult.Height; } //item.hasSignificantConcavity = CalculationResult.hasSignificantConcavity; //item.concavityRatio = CalculationResult.concavityRatio; //if(CalculationResult.hasSignificantConcavity) //{ // if(CalculationResult.concavityRatio>0.3) // { // item.StateCode= 11; // } //} } else { item.StateCode = 7; } } if (shuLiConfig.PandingCode != -1) { if (item.StateCode != -1) { if (item.StateCode == 8) { //LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num)); } else if (item.StateCode == 7) { //LOG.log(string.Format("颗粒编号{0}:视野被遮挡", item.Num)); } } else if (item.Area < shuLiConfig.MinArea && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1)) { item.StateCode = 6; //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 = 5; //LOG.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; //LOG.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; //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(item.StateCode != 7&&item.StateCode!=9) { //转为历史物体,添加缺少的参数 item.Num = Interlocked.Increment(ref ObjectNum); item.ChannelNO = ActiveChannel(item); } item.EndCheckTime = DateTime.Now; if(item.StateCode != 9) OneActive.Add(item); } stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 1) { Console.WriteLine($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}"); //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}"); } //stopwatch.Restart(); if (OneActive.Count > 0) { //触发回调事件 OnWorkCompleted(OneActive); } // stopwatch.Stop(); //if (stopwatch.ElapsedMilliseconds > 1) //{ // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果完成回调函数,此次识别耗时:{stopwatch.Elapsed}"); //} } else { OneActive.Clear(); } _rwLock.EnterWriteLock(); // 累加到总数并从活跃物体转移到历史物体 foreach (var item in OneActive) { TryAdd(historyActiveObjects, item, 2500); } lostObjects.ForEach(o => activeObjects.Remove(o)); _rwLock.ExitWriteLock(); return result; } /// /// 处理图像序列的主入口 /// /// 图像像素数据 /// 检测到的物体总数 public bool ProcessImageSequence2(IFrameOut image) { bool result = false; ReadOnlySpan spanFromArr = image.Image.PixelData.AsSpan(); for (int i = 0; i < image.Image.Height; i++) { result = ProcessLine2(spanFromArr, image.HostTimeStamp, image.Image.Width, 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}"); } // 快速滤除噪点 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)); } 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; } } // 分类处理 ApplyClassificationRules(item); // 添加到历史记录 if (item.StateCode != 7 && item.StateCode != 9) { item.Num = Interlocked.Increment(ref ObjectNum); item.ChannelNO = ActiveChannel(item); } item.EndCheckTime = endTime; if (item.StateCode != 9) { processedObjects.Add(item); } } // 抽取分类规则,便于维护 private void ApplyClassificationRules(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(string.Format("颗粒编号{0}:视野被遮挡", 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; return historyActiveObjects.Last(); } /// /// 返回历史物品 /// /// public List GetHistoryActive() { //lock (_lockObj) // 加锁 //{ _rwLock.EnterReadLock(); var result = historyActiveObjects.ToList(); _rwLock.ExitReadLock(); return result; //} } /// /// 返回缓存在内存的历史物品的总数量 /// /// public int GetHistoryActiveNum() { //lock (_lockObj) // 加锁 _rwLock.EnterReadLock(); var result = historyActiveObjects.Where(o=>o.StateCode!=7&&o.StateCode!=9).ToList().Count; _rwLock.ExitReadLock(); return result; } /// /// 获取历史数据中,正常数据数量 /// /// public int GetOkHistoryNum() { //lock (_lockObj) _rwLock.EnterReadLock(); var result = historyActiveObjects.Where(o => o.StateCode != 7 && o.StateCode != 9&& o.StateCode == 0).ToList().Count; _rwLock.ExitReadLock(); return result; } /// /// 获取历史数据中,异常数据数量 /// /// public int GetNgHistoryNum() { //lock (_lockObj) _rwLock.EnterReadLock(); var result = historyActiveObjects.Where(o => o.StateCode != 7 && o.StateCode != 9 && o.StateCode != 0).ToList().Count; _rwLock.ExitReadLock(); return result; } /// /// 清除历史数据 /// /// public bool ClearHistoryActive() { //lock (_lockObj) //{ _rwLock.EnterWriteLock(); historyActiveObjects.Clear(); _rwLock.ExitWriteLock(); return true; //} } /// /// 开启识别 /// public void StartIdentifyFuntion(int ImaageWidth) { UpdateIdentifyImageWidth(ImaageWidth); InitChannel(); try { // 标志位置位true IsIdentify = true; // 打开识别线程 IdentifyImageProcessThread = new Thread(IdentifyImageProcess) { Priority = ThreadPriority.AboveNormal }; IdentifyImageProcessThread.Start(); 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 void StartIdentifyFuntion2(int ImaageWidth) { UpdateIdentifyImageWidth(ImaageWidth); InitChannel(); try { CancellationTokenSource = new CancellationTokenSource(); var token = CancellationTokenSource.Token; // 启动图像处理线程 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 void StopIdentifyFuntion() { try { // 标志位设为false IsIdentify = false; if (IdentifyImageProcessThread != null && IdentifyImageProcessThread.IsAlive) IdentifyImageProcessThread.Join(); 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 async void StopIdentifyFuntion2() { 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) { IFrameDatas.Enqueue(items.Clone() as IFrameOut); //IFrameDatas.Enqueue(items ); } /// /// 保存参数 /// 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 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; //stopwatch.Stop(); //if (stopwatch.ElapsedMilliseconds > 1) //{ // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像连通域检测超时,此次识别耗时:{stopwatch.Elapsed}"); //} //stopwatch.Restart(); foreach (var region in currentRegions) { // 查找全部可合并的活跃物体(有重叠+在允许间隔内) _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), 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, 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, }); matched.LastSeenLineStartCol = region.Start; matched.LastSeenLineEndCol = region.End; } else { _rwLock.EnterWriteLock(); // 创建新物体(首次出现的区域) activeObjects.Add(new ActiveObjectClass { MinStartCol = region.Start, MaxEndCol = region.End, StartLine = currentLine, LastSeenLine = currentLine, LastSeenLineStartCol = region.Start, LastSeenLineEndCol = 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(); } } //stopwatch.Stop(); //if (stopwatch.ElapsedMilliseconds > 1) //{ // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}"); //} return result; } private bool ProcessLine2(ReadOnlySpan image, ulong ImageHostTime, uint imageWidth, int RowNo) { // 缓存配置值 int maxGap = shuLiConfig.MAX_GAP; long currentTimeLine = currentLine; // 步骤1:检测当前行的有效区域 currentRegions = FindValidRegions(image, (int)imageWidth, RowNo); if (currentRegions.Count == 0|| currentRegions == null) return false; if (currentRegions.Count == 1) { if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imageWidth) { 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; // 批量处理操作列表 var operations = new List(); var currentActiveObjects = new List(); // 一次性获取当前活跃物体快照 _rwLock.EnterReadLock(); try { currentActiveObjects = new List(activeObjects); } finally { _rwLock.ExitReadLock(); } foreach (var region in currentRegions) { // 查找所有可合并的活跃物体(重叠+在允许间隔内) var matcheds = currentActiveObjects.Where(o => IsOverlapping(o, region) && (currentTimeLine - o.LastSeenLine - 1) <= maxGap).ToList(); // 当有多个可合并的活跃物体时,将多个物体合并 if (matcheds.Count >= 2) { // 计算聚合值(单次遍历) var aggregateResult = CalculateAggregates(matcheds); var mergedObject = new ActiveObjectClass { MinStartCol = aggregateResult.minStartCol, MaxEndCol = aggregateResult.maxEndCol, StartLine = aggregateResult.minStartLine, LastSeenLine = aggregateResult.maxLastSeenLine, LastSeenLineStartCol = aggregateResult.minLastSeenStartCol, LastSeenLineEndCol = aggregateResult.maxLastSeenEndCol, StartCheckTime = aggregateResult.minStartTime, EndCheckTime = aggregateResult.maxEndTime, PictureStartReadTime = aggregateResult.pictureMinReadTime, Area = aggregateResult.totalArea, RowsData = aggregateResult.mergedRowsData, ImageWidth = aggregateResult.imageWidth, StateCode = 8 }; operations.Add(new OperationItem { Type = OperationType.MergeMultiple, ObjectsToRemove = matcheds, ObjectToAdd = mergedObject }); // 从currentActiveObjects中移除已处理的物体,避免重复处理 foreach (var obj in matcheds) { currentActiveObjects.Remove(obj); } } else { // 查找单一匹配物体 var matched = matcheds.FirstOrDefault(); if (matched != null) { operations.Add(new OperationItem { Type = OperationType.Update, ObjectToUpdate = matched, Region = region }); } else { // 创建新物体 var newObject = new ActiveObjectClass { MinStartCol = region.Start, MaxEndCol = region.End, StartLine = currentTimeLine, LastSeenLine = currentTimeLine, LastSeenLineStartCol = region.Start, LastSeenLineEndCol = region.End, StartCheckTime = DateTime.Now, PictureStartReadTime = FromUnixTimestamp((long)ImageHostTime), Area = region.End - region.Start + 1, ImageWidth = IdentifyImageWidth, RowsData = new List { new RowStartEndCol { StartCol = region.Start, EndCol = region.End, RowsCol = currentTimeLine, } } }; operations.Add(new OperationItem { Type = OperationType.Add, ObjectToAdd = newObject }); } } } currentRegions.Clear(); // 执行所有操作(最小化锁持有时间) bool hasChanges = false; if (operations.Count > 0) { _rwLock.EnterWriteLock(); try { foreach (var op in operations) { switch (op.Type) { case OperationType.MergeMultiple: // 移除被合并的物体 foreach (var objItem in op.ObjectsToRemove) { activeObjects.Remove(objItem); } // 添加合并后的物体 activeObjects.Add(op.ObjectToAdd); break; case OperationType.Update: // 更新现有物体 var obj = op.ObjectToUpdate; obj.MinStartCol = Math.Min(obj.MinStartCol, op.Region.Start); obj.MaxEndCol = Math.Max(obj.MaxEndCol, op.Region.End); obj.Area += op.Region.End - op.Region.Start + 1; obj.LastSeenLine = currentTimeLine; obj.RowsData.Add(new RowStartEndCol { StartCol = op.Region.Start, EndCol = op.Region.End, RowsCol = currentTimeLine, }); obj.LastSeenLineStartCol = op.Region.Start; obj.LastSeenLineEndCol = op.Region.End; break; case OperationType.Add: activeObjects.Add(op.ObjectToAdd); break; } } hasChanges = true; } finally { _rwLock.ExitWriteLock(); } } return hasChanges; } // 辅助类定义 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; } } // 计算聚合值的方法 private (int minStartCol, int maxEndCol, long minStartLine, long maxLastSeenLine, int minLastSeenStartCol, int maxLastSeenEndCol, DateTime minStartTime, DateTime maxEndTime,DateTime pictureMinReadTime,int totalArea, List mergedRowsData, int imageWidth) CalculateAggregates(List objects) { int minStartCol = int.MaxValue; int maxEndCol = int.MinValue; long minStartLine = int.MaxValue; long maxLastSeenLine = int.MinValue; int minLastSeenStartCol = int.MaxValue; int maxLastSeenEndCol = int.MinValue; DateTime minStartTime = DateTime.MaxValue; DateTime maxEndTime = DateTime.MinValue; DateTime pictureMinReadTime = DateTime.MaxValue; int totalArea = 0; int imageWidth = objects.FirstOrDefault()?.ImageWidth ?? IdentifyImageWidth; var mergedRowsData = new List(); foreach (var obj in objects) { minStartCol = Math.Min(minStartCol, obj.MinStartCol); maxEndCol = Math.Max(maxEndCol, obj.MaxEndCol); minStartLine = Math.Min(minStartLine, obj.StartLine); maxLastSeenLine = Math.Max(maxLastSeenLine, obj.LastSeenLine); minLastSeenStartCol = Math.Min(minLastSeenStartCol, obj.LastSeenLineStartCol); maxLastSeenEndCol = Math.Max(maxLastSeenEndCol, obj.LastSeenLineEndCol); if (obj.StartCheckTime < minStartTime) minStartTime = obj.StartCheckTime; if (obj.EndCheckTime > maxEndTime) maxEndTime = obj.EndCheckTime; if (obj.PictureStartReadTime 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(); // 遍历所有像素列 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 List FindValidRegions(ReadOnlySpan image,int imageWidth, int RowNo) { regions.Clear(); regions.Capacity = 10; ReadOnlySpan rowSpan; if (shuLiConfig.IsIdentifyRoiOpen) { int offset = RowNo * imageWidth + shuLiConfig.IdentifyStartX; int length = Math.Min(shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX, imageWidth - shuLiConfig.IdentifyStartX); rowSpan = image.Slice(offset, length); } else { rowSpan = image.Slice(RowNo * imageWidth, imageWidth); } int start = -1; byte threshold = (byte)shuLiConfig.RegionThreshold; for (int i = 0; i < rowSpan.Length; i++) { if (rowSpan[i] < threshold) { if (start == -1) start = i; } else if (start != -1) { regions.Add(new ValidRegionModelClass { Start = start, End = i - 1 }); start = -1; } } if (start != -1) { regions.Add(new ValidRegionModelClass { Start = start, End = rowSpan.Length - 1 }); } return new List(regions); } /// /// 判断区域重叠(与活跃物体的横向坐标重叠检测) /// /// 活跃物体 /// 当前区域 /// 是否发生重叠 private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass 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; } /// /// 检测是否存在显著的内凹 /// /// 原始点集 /// 凸包点集 /// 是否存在显著内凹和程度比值 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 (list.Count > maxSize) { list[list.Count - maxSize].RowsData.Clear(); } return true; } #endregion #region 线程方法 //信号量 public SemaphoreSlim QueueSemaphore { get; set; } //取消令牌 public CancellationTokenSource CancellationTokenSource { get; set; } //图像处理线程 public Task ProcessingTask { get; set; } /// /// 识别图像线程 /// private void IdentifyImageProcess() { Stopwatch stopwatch = Stopwatch.StartNew(); while (IsIdentify) { //判断队列中是否有数据 if (IFrameDatas.Count() > 0) { stopwatch.Restart(); if (IFrameDatas.Count() > 5) { //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}"); FaultLog.RecordErrorMessage($"There is too much data in the image data queue. Please handle it in a timely manner! The current data quantity is:{IFrameDatas.Count()}"); if (IFrameDatas.Count() > 100) { SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积, $"The image data queue is blocked. Please check the configuration and images", "待识别队列数据堆积,请检查设置和图像", "DLL:ShuLIClass-IdentifyImageProcess"); } else { SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积); } } IFrameDatas.TryDequeue(out IFrameOut IframeData); stopwatch.Stop(); var ShiBieLuoHouTime = (DateTime.Now - FromUnixTimestamp((long)IframeData.HostTimeStamp)).TotalMilliseconds; // 优化: 将日志记录改为条件执行 if (ShiBieLuoHouTime > 30) { LOG.error($"算法落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}"); } if (stopwatch.ElapsedMilliseconds > 5) { FaultLog.RecordErrorMessage($"ShuLiClass-IdentifyImageProcess:Image reading timed out, this recognition took time:{stopwatch.Elapsed}"); } if (IframeData != null) { //识别 ProcessImageSequence2(IframeData); IframeData.Dispose(); } else { continue; } } else { Thread.Sleep(1); } } } private async void IdentifyImageProcessTask(CancellationToken token) { Stopwatch stopwatch = Stopwatch.StartNew(); const int batchSize = 5; // 每批处理的最大图像数量 var batch = new List(batchSize); // 存储当前批次的图像 while (!token.IsCancellationRequested) { // 等待图像数据 await QueueSemaphore.WaitAsync(token); //判断队列中是否有数据 if (IFrameDatas.Count() > 5) { //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}"); FaultLog.RecordErrorMessage($"There is too much data in the image data queue. Please handle it in a timely manner! The current data quantity is:{IFrameDatas.Count()}"); if (IFrameDatas.Count() > 100) { SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积, $"The image data queue is blocked. Please check the configuration and images", "待识别队列数据堆积,请检查设置和图像", "DLL:ShuLIClass-IdentifyImageProcess"); } else { SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积); } } // 收集一批图像(严格按队列顺序) while (batch.Count < batchSize && IFrameDatas.TryDequeue(out IFrameOut imageData)) { batch.Add(imageData); if (IFrameDatas.Count > 0) { QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程 } } // 顺序处理当前批次的所有图像 foreach (var image in batch) { if (image != null) { //识别 ProcessImageSequence2(image); image.Dispose(); } else { continue; } } batch.Clear(); } } #endregion } }